Skip to content

Runtime values ​

A container expects every value to be buildable from its bindings. Some values only exist later, like the HTTP request being handled, or the user who sent it.

Building, checking and wiring a new container for every request is wasteful. Instead, create a container factory from a module with those dependencies still unbound. Check and wire it once, then bind the missing values for each request and create a container:

ts
import { 
bind
,
createContainer
,
createContainerFactory
,
createModule
,
identifier
} from 'haywire';
interface IncomingRequest {
url
: string;
} interface User {
id
: string;
} const
requestId
=
identifier
<IncomingRequest>();
const
userId
=
identifier
<User>();
class
RequestHandler
{
constructor( private readonly
request
: IncomingRequest,
private readonly
user
: User
) {}
handle
() {
return `${this.
user
.
id
} requested ${this.
request
.
url
}`;
} } const
factory
=
createContainerFactory
(
createModule
(
bind
(
RequestHandler
).
withDependencies
([
requestId
,
userId
]).
withConstructorProvider
()
) ); // Once, at startup or in a test
factory
.
check
();
factory
.
wire
();
const
handleRequest
= (
request
: IncomingRequest,
user
: User) => {
const
container
=
createContainer
(
factory
.
bindInstance
(
requestId
,
request
).
bindInstance
(
userId
,
user
)
); return
container
.
get
(
RequestHandler
).
handle
();
};

Each container shares the factory's checking and wiring, so it is cheap to create, but has its own singletons. createContainer(factory) and factory.toContainer() are equivalent.

See Web frameworks for this pattern in Hono, Fastify and Next.js.

Container factories are immutable: bindInstance() returns a new factory, so one factory can serve many requests at once.

Checks ​

Binding instances is checked like adding bindings to a module. Creating a container before every dependency is bound is a compile error:

ts
createContainer
(factory.bindInstance(requestId, request));
No overload matches this call. Overload 1 of 2, '(this: void, factory: ContainerFactory<CombineListOutputs<[NonExtendable<RequestHandler, typeof RequestHandler, null, false, false, false>] | [NonExtendable<RequestHandler, ... 4 more ..., true>] | [...] | [...], never>, [...], false, InstanceBinding<...>> & { ...; }): any', gave the following error. Argument of type 'ContainerFactory<CombineListOutputs<[NonExtendable<RequestHandler, typeof RequestHandler, null, false, false, false>] | [NonExtendable<RequestHandler, ... 4 more ..., true>] | [...] | [...], never>, [...], false, InstanceBinding<...>>' is not assignable to parameter of type 'ContainerFactory<CombineListOutputs<[NonExtendable<RequestHandler, typeof RequestHandler, null, false, false, false>] | [NonExtendable<RequestHandler, ... 4 more ..., true>] | [...] | [...], never>, [...], false, InstanceBinding<...>> & { ...; }'. Property 'error' is missing in type 'ContainerFactory<CombineListOutputs<[NonExtendable<RequestHandler, typeof RequestHandler, null, false, false, false>] | [NonExtendable<RequestHandler, ... 4 more ..., true>] | [...] | [...], never>, [...], false, InstanceBinding<...>>' but required in type '{ error: "Error: some dependencies have no binding. Add bindings for them before creating a container."; }'. Overload 2 of 2, '(this: void, mod: GenericModule, options?: ContainerOptions | undefined): SyncContainer<any> | Container<any>', gave the following error. Argument of type 'ContainerFactory<CombineListOutputs<[NonExtendable<RequestHandler, typeof RequestHandler, null, false, false, false>] | [NonExtendable<RequestHandler, ... 4 more ..., true>] | [...] | [...], never>, [...], false, InstanceBinding<...>>' is not assignable to parameter of type 'GenericModule'. Property '#private' in type 'ContainerFactory' refers to a different member that cannot be accessed from within type 'Module'.

So is binding an id twice, or binding a value laxer than its dependents need, such as null where they need a value:

ts
factory
.
bindInstance
(userId.nullable(), null);
Argument of type 'HaywireId<User, null, null, false, true, false, false, false>' is not assignable to parameter of type 'HaywireId<User, null, null, false, true, false, false, false> & { error: "Error: the output is laxer than what depends on it, e.g. it is nullable but a non-null value is needed."; }'. Property 'error' is missing in type 'HaywireId<User, null, null, false, true, false, false, false>' but required in type '{ error: "Error: the output is laxer than what depends on it, e.g. it is nullable but a non-null value is needed."; }'.

A factory can bind ids that nothing depends on yet, but only instances: bindings with providers and dependencies belong in the module.

factory.check() assumes the values bound later are synchronous singletons with no dependencies.