# `nupp.services` Typed service definitions and named implementations within one Lua state. A declaration module owns one invariant Service handle. Register checked loaders or discover them through static package metadata; neither action executes a provider. Setup selects names before requiring consuming facades. Each facade assembles its implementation during top-level initialization and retains its actual methods for direct calls. ```nupp local services = require("nupp.services") interface Codec readonly encode: function(value: string): string end local service: services.Service = services.define("example.codec", 1) service:register("plain", function(): Codec return {encode = function(value: string): string return value end} end) service:select("plain") local codec = service:require() assert(codec.encode("hello") == "hello") ``` The variable annotation supplies generic inference. Service is invariant: register accepts T while lookup returns T, so a handle cannot be reassigned to a different provider interface. Import canonical handles rather than redefining an existing ID. Provider exports may have extra members, but their required members, generics, ownership, and suspension behavior must satisfy the declared contract. Successful named loads are cached by name. Unnamed resolution fixes the default selection, including optional absence. Failed loads are not cached as successful values; diagnostics include provider identity and dependency cycles. Named lookup does not itself select or freeze the default. All state is local to one Lua state. Use assembly only while requiring a facade, then call retained operations. Applications must not perform service lookups in hot calls or resource cleanup. Worker configuration carries catalog names and setup modules, never provider instances or captured closures. See [Service Providers](../../../learn/projects/service-providers/index.html) for packaging and setup module examples. ## Types ### `Member` _interface_ ```nupp interface Member name: string kinds: {string} end ``` Runtime field-shape entry derived from a canonical provider interface. Kinds use the reflected type vocabulary and may include nil for optional fields. #### Fields ##### `name` ```nupp name: string ``` ##### `kinds` ```nupp kinds: {string} ``` ### `Service` _record_ ```nupp record Service readonly id: string readonly api: integer function register(self: Service, name: string, loader: function(): T): nil end function select(self: Service, name: string): nil end function list(self: Service): {string} end function lookup(self: Service, name: string?): T? end function assemble(self: Service, defaultLoader: function(): T, checkImplementation: (function(T): nil)?): T end function assembleOptional( self: Service, defaultLoader: function(): T?, checkImplementation: (function(T): nil)? ): T? end function require(self: Service, name: string?): T end end ``` A named, versioned contract with checked lazy loaders and cached values. Define this once in a declaration module. Registration and selection are setup operations; resolve during a facade's require-time initialization. The default freezes after successful resolution, while named implementations retain their independent cached identities. #### Type parameters | Name | Description | | --- | --- | | `T` | | #### Methods ##### `register` ```nupp register: function register(self: Service, name: string, loader: function(): T): nil ``` Registers a checked loader without executing it. Names are unique within this handle. Registration does not select a default. A successful named load runs the loader once and caches its result; a failed load may be retried. Additional registration cannot change a frozen default. ###### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `Service\` | | | `name` | `string` | | | `loader` | `function(): T` | | ###### Returns | Type | Description | | --- | --- | | `nil` | | ###### Raises - when the name is empty or already registered ##### `select` ```nupp select: function select(self: Service, name: string): nil ``` Chooses the name used by unnamed resolution. Selection may precede registration. An absent selected name fails when the facade resolves; it does not fall back to a built-in default. Before resolution, another select may replace the choice. Once resolution starts, reselection is rejected, including from a provider's own loader. ###### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `Service\` | | | `name` | `string` | | ###### Returns | Type | Description | | --- | --- | | `nil` | | ###### Raises - when the service is already resolving or resolved ##### `list` ```nupp list: function list(self: Service): {string} ``` Returns a fresh, sorted list of registered provider names. Listing executes no loader and does not select or freeze the default. The result includes catalog registrations even if none has been loaded. ###### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `Service\` | | ###### Returns | Type | Description | | --- | --- | | `{string}` | | ##### `lookup` ```nupp lookup: function lookup(self: Service, name: string?): T? ``` Loads a named implementation or resolves the unnamed default. A missing explicit name returns nil without freezing the default. An unnamed call uses the selected name or facade default, validates the value, and freezes that outcome, including nil. A selected name that is absent is an error. Cached successful values retain identity on subsequent lookups. Load and validation failures clear the in-progress state and may be retried. Runtime shape validation complements static typing; it cannot prove a Lua function's ownership, suspension, or behavioral guarantees. ###### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `Service\` | | | `name` | `string?` | | ###### Returns | Type | Description | | --- | --- | | `T?` | | ###### Raises - when a selected provider is absent, cyclic, or fails to load ##### `assemble` ```nupp assemble: function assemble(self: Service, defaultLoader: function(): T, checkImplementation: (function(T): nil)?): T ``` Resolves a required facade with explicit selection taking precedence. Call once in module top-level code and retain the result. defaultLoader performs ordinary host checks and method assembly. checkImplementation validates representation or other semantic requirements before publication. A previously resolved value is checked against a newly supplied validator. ###### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `Service\` | | | `defaultLoader` | `function(): T` | | | `checkImplementation` | `(function(T): nil)?` | | ###### Returns | Type | Description | | --- | --- | | `T` | | ###### Raises - when the service cannot resolve an implementation ##### `assembleOptional` ```nupp assembleOptional: function assembleOptional(self: Service, defaultLoader: function(): T?, checkImplementation: (function(T): nil)?): T? ``` Resolves an optional facade and freezes absence as well as presence. defaultLoader may return nil when this host has no implementation. Explicit selection still requires that named provider to exist. Later registration does not turn a successfully resolved absence into an implementation. ###### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `Service\` | | | `defaultLoader` | `function(): T?` | | | `checkImplementation` | `(function(T): nil)?` | | ###### Returns | Type | Description | | --- | --- | | `T?` | | ##### `require` ```nupp require: function require(self: Service, name: string?): T ``` Returns a cached or newly loaded implementation that must be present. Uses lookup's named/default rules, then rejects nil. Named require does not choose the default. Retain the result at module load time for direct calls. ###### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `Service\` | | | `name` | `string?` | | ###### Returns | Type | Description | | --- | --- | | `T` | | ###### Raises - when no implementation is available or its loader fails #### Fields ##### `id` ```nupp id: string ``` Stable service identity shared with catalog declarations. ##### `api` ```nupp api: integer ``` Positive contract API version required of every registered catalog entry. ## Functions ### `define` _function_ ```nupp function define(id: string, api: integer, defaultLoader: (function(): T?)?, members: {Member}?): Service ``` Defines the unique handle owned by a service's declaration module. id is nonempty and api is a positive integer. T is inferred from the receiving Service annotation or loader. defaultLoader is optional; a facade can supply assembly later. members describes runtime field kinds derived from the canonical interface. Extra provider members are permitted. Matching catalog entries register loaders after checking service API and target compatibility. Loading one verifies that its contract module exports this exact handle. Definition itself never requires an implementation module. #### Type parameters | Name | Description | | --- | --- | | `T` | | #### Arguments | Name | Type | Description | | --- | --- | --- | | `id` | `string` | | | `api` | `integer` | | | `defaultLoader` | `(function(): T?)?` | | | `members` | `{Member}?` | | #### Returns | Type | Description | | --- | --- | | `Service\` | | #### Raises - when the identity or API version is invalid or already defined ### `dialect` _function_ ```nupp function dialect(): string ``` Returns the artifact's fixed target dialect. Facades may use this for ordinary default assembly and representation checks. Changing provider names cannot change the dialect or generated calling convention. #### Returns | Type | Description | | --- | --- | | `string` | | ### `setupWorkers` _function_ ```nupp function setupWorkers(moduleName: string): nil ``` Registers an ordinary setup module for every child worker state. Include the module in the artifact's entry modules. It must register/select providers before requiring their consumers. Repeated names are deduplicated in registration order. Call before loading the workers facade, which captures and freezes initialization. Runtime-only registrations must be recreated by setup. #### Arguments | Name | Type | Description | | --- | --- | --- | | `moduleName` | `string` | | #### Returns | Type | Description | | --- | --- | | `nil` | | #### Raises - when the module name is invalid or worker initialization is fixed ### `workerConfiguration` _function_ ```nupp function workerConfiguration(): string ``` Captures child initialization as loadable setup and catalog selections. This is the worker-provider integration point. The first call fixes the setup list, then emits setup requires followed by catalog-backed named selections in service-ID order. Each destination runs it before loading consumer modules. Repeated calls return the same configuration; no provider objects or closures cross the lane boundary. An explicit setup module must recreate runtime-only selections that the artifact catalog cannot name. #### Returns | Type | Description | | --- | --- | | `string` | | #### Raises - when a runtime-only selection has no explicit worker setup