Skip to main content
zerotal

Documentation


Documentation / zerotal / index / Container

Class: Container

Defined in: packages/core/src/container/Container.ts:56

Registers and resolves bindings, with auto-wiring, scopes, and contextual overrides.

The container is the framework's IoC core. Register how a token is built with bind (transient), singleton, scoped, or value, then resolve it with make. Classes with declared dependencies (@inject()) are auto-wired without an explicit binding.

Remarks

Resolution is async-first: prefer make everywhere and reserve makeSync for Facade accessors where the singleton has already been pre-resolved. Scoped bindings only resolve inside a request scope opened by runScoped; resolving one elsewhere throws ScopedOutsideRequestError.

Example

class Config {}
class Db {}

const container = Container.createEmpty();

container.bind(Db, () => new Db());                 // new instance each make()
container.singleton(Config, () => new Config());    // built once, cached
container.value("appName", "zerotal");               // pre-built value

const db = await container.make(Db);
const config = await container.make(Config);

Constructors

Constructor

new Container(): Container

Returns

Container

Binding

bind()

bind<T>(token, factory): this

Defined in: packages/core/src/container/Container.ts:84

Register a transient binding — the factory runs on every resolution.

Type Parameters

T

T

Parameters

token

BindingToken<T>

factory

Factory<T>

Returns

this

Example

container.bind(Uuid, () => new Uuid(crypto.randomUUID()));
const a = await container.make(Uuid);
const b = await container.make(Uuid); // a !== b

singleton()

singleton<T>(token, factory): this

Defined in: packages/core/src/container/Container.ts:104

Register a singleton binding — resolved once and cached for the process lifetime.

Type Parameters

T

T

Parameters

token

BindingToken<T>

factory

Factory<T>

Returns

this

Remarks

The factory receives the container so it can resolve its own dependencies. Concurrent first resolutions share a single in-flight promise, so the factory runs exactly once even under parallel make() calls.

Example

container.singleton("config", (c) => new ConfigManager());
const config = await container.make("config"); // same instance every time

scoped()

scoped<T>(token, factory): this

Defined in: packages/core/src/container/Container.ts:123

Register a scoped binding — resolved once per request scope.

Type Parameters

T

T

Parameters

token

BindingToken<T>

factory

Factory<T>

Returns

this

Remarks

Resolving a scoped token outside a request scope (see runScoped) throws ScopedOutsideRequestError.


value()

value<T>(token, instance): this

Defined in: packages/core/src/container/Container.ts:137

Register an already-constructed value under token.

Type Parameters

T

T

Parameters

token

BindingToken<T>

instance

T

Returns

this

Remarks

Value bindings are the only kind (besides an already-resolved singleton) that makeSync can return.


alias()

alias(from, to): this

Defined in: packages/core/src/container/Container.ts:153

Make resolving from resolve to instead. Alias chains are followed transitively.

Parameters

from

unknown

to

unknown

Returns

this

Example

container.singleton(FileLogger, () => new FileLogger());
container.alias("log", FileLogger);
const log = await container.make("log"); // resolves FileLogger

for()

for<C>(consumer): ContextualBindingBuilder<C>

Defined in: packages/core/src/container/Container.ts:190

Begin a contextual binding so consumer receives a tailored dependency.

Type Parameters

C

C

Parameters

consumer

BindingToken<C>

Returns

ContextualBindingBuilder<C>

Example

// ReportService gets a FixedClock; everyone else gets the default Clock.
container.singleton(Clock, () => new SystemClock());
container.for(ReportService).give(Clock, () => new FixedClock());

defer()

defer(token, provider): this

Defined in: packages/core/src/container/Container.ts:203

Defer a provider so it boots lazily the first time token is resolved.

Parameters

token

unknown

provider

(app) => unknown

Returns

this

Remarks

Booting the provider (its onRegister/onBooting/onBooted hooks) only runs when Container._app is set, i.e. inside a real application boot.


createEmpty()

static createEmpty(): Container

Defined in: packages/core/src/container/Container.ts:603

Create a fresh container with no bindings registered.

Returns

Container

Lifecycle hooks

resolving()

resolving<T>(token, hook): this

Defined in: packages/core/src/container/Container.ts:172

Register a hook fired with each freshly constructed instance of token.

Type Parameters

T

T

Parameters

token

BindingToken<T>

hook

(instance) => void

Returns

this

Remarks

The hook runs after construction on every fresh instance — so once per process for a singleton, and on each resolution for a transient. It does not fire for an already-cached singleton or value.

Example

container.resolving(Mailer, (mailer) => mailer.setFrom("noreply@example.com"));

Other

registry

registry: Map<unknown, Binding<unknown>>

Defined in: packages/core/src/container/Container.ts:58

Resolution

make()

make<T>(token, consumer?): Promise<T>

Defined in: packages/core/src/container/Container.ts:229

Resolve a binding asynchronously. This is the primary resolution method. Always use this over makeSync() unless you have pre-resolved the singleton during onBooting().

Type Parameters

T

T

Parameters

token

BindingToken<T>

The class, abstract class, or registry key to resolve.

consumer?

unknown

Optional resolving consumer, used to apply a contextual override registered via for.

Returns

Promise<T>

The resolved instance.

Throws

When no binding exists and the token cannot be auto-wired.

Throws

When resolving token re-enters itself through its dependency chain.

Throws

When token is a scoped binding resolved outside a request scope.

Example

const users = await container.make(UserService);
const config = await container.make("config");

makeSync()

makeSync<T>(token): T

Defined in: packages/core/src/container/Container.ts:243

Resolve a binding synchronously. Only works for: value bindings, and singleton bindings already resolved. Throws SyncResolutionError for everything else. Use this exclusively in Facade accessors — after onBooting() has run.

Type Parameters

T

T

Parameters

token

BindingToken<T>

Returns

T

Throws

When no binding is registered for the token.

Throws

When the binding is a not-yet-resolved singleton, or is transient/scoped (which cannot resolve synchronously).


build()

build<T>(ctor): Promise<T>

Defined in: packages/core/src/container/Container.ts:279

Construct a class by auto-wiring its declared dependencies (@inject()), bypassing any registered binding for that token.

Used by the app/services convention to build a fresh instance inside a singleton/scoped factory without the factory shadowing auto-wiring.

Type Parameters

T

T

Parameters

ctor

(...args) => T

Returns

Promise<T>

Throws

When the class's dependency graph contains a cycle.


forget()

forget(token): boolean

Defined in: packages/core/src/container/Container.ts:289

Remove a binding. Returns true if a binding existed for the token. Follows the alias chain so forget(alias) removes the canonical binding.

Parameters

token

BindingToken

Returns

boolean


bound()

bound(token): boolean

Defined in: packages/core/src/container/Container.ts:302

Report whether a binding — or a deferred provider — exists for token, without constructing anything. Follows the alias chain.

Used by the boot-time doctor to verify that every token a provider names in static provides is actually wired.

Parameters

token

BindingToken

Returns

boolean


isDeferred()

isDeferred(token): boolean

Defined in: packages/core/src/container/Container.ts:314

Report whether token resolves through a deferred provider (booted lazily on first make()), as opposed to an eagerly-registered binding. Follows the alias chain.

Parameters

token

BindingToken

Returns

boolean


tryMake()

tryMake<K>(token): ContainerBindings[K] | undefined

Defined in: packages/core/src/container/Container.ts:587

Attempt to resolve a binding synchronously. Returns undefined instead of throwing if the key is not registered. Used by providers to check whether CommandRunner exists (it only does in console mode, not web mode).

Type Parameters

K

K extends keyof ContainerBindings

Parameters

token

K

Returns

ContainerBindings[K] | undefined

Scopes

runScoped()

runScoped<T>(callback): Promise<T>

Defined in: packages/core/src/container/Container.ts:541

Run callback inside a fresh request scope.

A new ScopedResolver is created for the duration of the call and stored in AsyncLocalStorage so every container.make() for a scoped binding resolves against this scope — even across await boundaries and even when multiple requests are in flight concurrently. The resolver is flushed (cache cleared) in a finally block, and the ALS entry is garbage-collected automatically when the async context exits. There is no shared mutable state on the container and therefore no possibility of cross-request leaks.

Type Parameters

T

T

Parameters

callback

(scoped) => Promise<T>

Receives the resolver so the caller can pass it to HttpContext (and therefore to afterResponse() hooks).

Returns

Promise<T>

Example

await container.runScoped(async (scoped) => {
  // scoped bindings resolve to per-request instances inside here
  const session = await container.make(RequestSession);
  return handle(session);
});

createScopedResolver()

createScopedResolver(): ScopedResolver

Defined in: packages/core/src/container/Container.ts:553

Create a bare ScopedResolver without entering an ALS context. Useful in unit tests that call scoped.resolve() directly and do not need container.make() to route through the ALS store.

Returns

ScopedResolver