# `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