nupp.services
Typed service definitions and named implementations within one Lua state.
A declaration module owns one invariant Service
local services = require("nupp.services")
interface Codec
readonly encode: function(value: string): string
end
local service: services.Service<Codec> = 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
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 for packaging and setup module examples.
Module contents
Types
| Type | Kind | Description |
|---|---|---|
Member | interface | Runtime field-shape entry derived from a canonical provider interface. |
Service | record | A named, versioned contract with checked lazy loaders and cached values. |
Functions
| Function | Kind | Description |
|---|---|---|
define | function | Defines the unique handle owned by a service's declaration module. |
dialect | function | Returns the artifact's fixed target dialect. |
setupWorkers | function | Registers an ordinary setup module for every child worker state. |
workerConfiguration | function | Captures child initialization as loadable setup and catalog selections. |
Types#
Memberinterface#
Runtime field-shape entry derived from a canonical provider interface. Kinds use the reflected type vocabulary and may include nil for optional fields.
Fields
kinds#
kinds: {string}Servicerecord#
record Service<T>
readonly id: string
readonly api: integer
function register(self: Service<T>, name: string, loader: function(): T): nil end
function select(self: Service<T>, name: string): nil end
function list(self: Service<T>): {string} end
function lookup(self: Service<T>, name: string?): T? end
function assemble(self: Service<T>, defaultLoader: function(): T, checkImplementation: (function(T): nil)?): T end
function assembleOptional(
self: Service<T>,
defaultLoader: function(): T?,
checkImplementation: (function(T): nil)?
): T? end
function require(self: Service<T>, name: string?): T end
endA 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#
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<T> | |
name | string | |
loader | function(): T |
Returns
| Type | Description |
|---|---|
nil |
Raises
when the name is empty or already registered
select#
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<T> | |
name | string |
Returns
| Type | Description |
|---|---|
nil |
Raises
when the service is already resolving or resolved
list#
list: function list(self: Service<T>): {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<T> |
Returns
| Type | Description |
|---|---|
{string} |
lookup#
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<T> | |
name | string? |
Returns
| Type | Description |
|---|---|
T? |
Raises
when a selected provider is absent, cyclic, or fails to load
assemble#
assemble: function assemble(self: Service<T>, defaultLoader: function(): T, checkImplementation: (function(T): nil)?): TResolves 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<T> | |
defaultLoader | function(): T | |
checkImplementation | (function(T): nil)? |
Returns
| Type | Description |
|---|---|
T |
Raises
when the service cannot resolve an implementation
assembleOptional#
assembleOptional: function assembleOptional(self: Service<T>, 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<T> | |
defaultLoader | function(): T? | |
checkImplementation | (function(T): nil)? |
Returns
| Type | Description |
|---|---|
T? |
require#
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<T> | |
name | string? |
Returns
| Type | Description |
|---|---|
T |
Raises
when no implementation is available or its loader fails
Fields
Functions#
definefunction#
function define<T>(id: string, api: integer, defaultLoader: (function(): T?)?, members: {Member}?): Service<T>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
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<T> |
Raises
when the identity or API version is invalid or already defined
dialectfunction#
function dialect(): stringReturns 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 |
setupWorkersfunction#
function setupWorkers(moduleName: string): nilRegisters 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
workerConfigurationfunction#
function workerConfiguration(): stringCaptures 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