Documents injecute 1.x · Migrating from 0.x
Skip to content

Class: DIContainer<S> ​

Defined in: src/container.ts:130

A dependency injection container: registers services with explicit dependency keys and resolves them with full types. The container is the composition root: it can also add middlewares, listen to events, fork scopes and dispose what it created. Hand narrower views to the rest of the code: ServiceRegistry to modules, ServiceProvider to consumers.

Example ​

ts
import { DIContainer } from 'injecute';

class UserRepository {
  constructor(readonly dbUrl: string) {}
}

const app = new DIContainer()
  .addInstance('dbUrl', 'postgres://localhost/app')
  .addSingleton('users', UserRepository, ['dbUrl']);

app.get('users'); // UserRepository

Type Parameters ​

S ​

S extends object = { }

Implements ​

Constructors ​

Constructor ​

new DIContainer<S>(): DIContainer<S>

Returns ​

DIContainer<S>

Accessors ​

keys ​

Get Signature ​

get keys(): readonly ServiceKey[]

Defined in: src/container.ts:269

Keys visible from this container, including its parents' (each once).

Returns ​

readonly ServiceKey[]

Keys visible from this container, including its parents'.

Implementation of ​

ServiceRegistry.keys


ownKeys ​

Get Signature ​

get ownKeys(): readonly ServiceKey[]

Defined in: src/container.ts:276

Keys registered in this container.

Returns ​

readonly ServiceKey[]

Keys registered in this container.

Implementation of ​

ServiceRegistry.ownKeys

Methods ​

[asyncDispose]() ​

[asyncDispose](): Promise<void>

Defined in: src/container.ts:731

Same as dispose; enables await using container = ….

Returns ​

Promise<void>


addAlias() ​

addAlias<K, T, R>(key, target, options?): DIContainer<Registered<S, { [P in ServiceKey]: ResolveDependency<T, {}, S> }, R>>

Defined in: src/container.ts:404

Makes key resolve to the service registered under target. Pass replace: true to replace an existing registration of key, here or in a parent.

Type Parameters ​

K ​

K extends ServiceKey

T ​

T extends string | number | symbol

R ​

R extends boolean = false

Parameters ​

key ​

K

target ​

T

options? ​

AliasOptions<R>

Returns ​

DIContainer<Registered<S, { [P in ServiceKey]: ResolveDependency<T, {}, S> }, R>>

Throws ​

INJECUTE_ALREADY_REGISTERED (see addSingleton).

Example ​

ts
app.addAlias('logger', config.production ? 'jsonLogger' : 'prettyLogger');
app.fork().addAlias('logger', 'prettyLogger', { replace: true }); // in this fork only

Implementation of ​

ServiceRegistry.addAlias


addEventListener() ​

addEventListener<E>(event, handler): this

Defined in: src/container.ts:603

Subscribes to container events: add, replace, reset, get, produce, dispose.

Type Parameters ​

E ​

E extends keyof ContainerEvents

Parameters ​

event ​

E

handler ​

(event) => void

Returns ​

this

Example ​

ts
app.addEventListener('produce', ({ key }) => console.debug(`created ${String(key)}`));

addInstance() ​

addInstance<K, T, R>(key, value, options?): DIContainer<Registered<S, { [P in ServiceKey]: T }, R>>

Defined in: src/container.ts:373

Registers an existing value. The container does not dispose it unless dispose is set.

Type Parameters ​

K ​

K extends ServiceKey

T ​

T extends unknown

R ​

R extends boolean = false

Parameters ​

key ​

K

value ​

T

options? ​

InstanceOptions<Awaited<T>, R>

Returns ​

DIContainer<Registered<S, { [P in ServiceKey]: T }, R>>

Example ​

ts
app.addInstance('config', loadConfig());

Implementation of ​

ServiceRegistry.addInstance


addSingleton() ​

addSingleton<K, F, D, R>(key, factory, dependencies?): DIContainer<Registered<S, { [P in ServiceKey]: Produced<F> }, R>>

Defined in: src/container.ts:324

Registers a service that is created once, on first use, and then cached.

factory is a function or a class. It receives the resolved dependencies in order; a class is called with new. A dependency on key itself receives the previous definition (decoration).

A factory may return a promise: the promise is cached, a rejected one is dropped (the next resolution tries again), and dispose() releases the resolved value.

Type Parameters ​

K ​

K extends ServiceKey

F ​

F extends Factory<D, { }, S, ExpectedService<K>>

D ​

D extends Dependency<{ }, S>[] = []

R ​

R extends boolean = false

Parameters ​

key ​

K

factory ​

F

dependencies? ​

[...D[]] | SingletonFactoryOptions<[...D[]], F, R>

Returns ​

DIContainer<Registered<S, { [P in ServiceKey]: Produced<F> }, R>>

Remarks ​

Classes compiled to ES5 and bound classes cannot be detected; register them with construct.

Throws ​

INJECUTE_ALREADY_REGISTERED when key is registered in this container or a parent (pass replace: true; in a plain fork the override reaches only services resolved in the fork), INJECUTE_CIRCULAR_DEPENDENCY when the dependencies form a cycle.

Example ​

ts
app
  .addSingleton('db', () => createPool(process.env.DATABASE_URL))
  .addSingleton('cache', async () => connectRedis()) // Promise<Redis>, disposed once resolved
  .addSingleton('users', UserRepository, ['db'])
  .addSingleton('mailer', createMailer, { dependencies: ['config'], dispose: (m) => m.close() });

Implementation of ​

ServiceRegistry.addSingleton


addTransient() ​

addTransient<K, F, D, R>(key, factory, dependencies?): DIContainer<Registered<S, { [P in ServiceKey]: Produced<F> }, R>>

Defined in: src/container.ts:351

Registers a service that is created again on every resolution. The container does not keep (or dispose) transient instances.

Type Parameters ​

K ​

K extends ServiceKey

F ​

F extends Factory<D, { }, S, ExpectedService<K>>

D ​

D extends Dependency<{ }, S>[] = []

R ​

R extends boolean = false

Parameters ​

key ​

K

factory ​

F

dependencies? ​

[...D[]] | RegistrationOptions<[...D[]], R>

Returns ​

DIContainer<Registered<S, { [P in ServiceKey]: Produced<F> }, R>>

Remarks ​

Classes compiled to ES5 and bound classes cannot be detected; register them with construct.

Throws ​

INJECUTE_ALREADY_REGISTERED, INJECUTE_CIRCULAR_DEPENDENCY.

Example ​

ts
app.addTransient('requestId', () => crypto.randomUUID());

Implementation of ​

ServiceRegistry.addTransient


bind() ​

bind<D, R>(dependencies, factory): () => R

Defined in: src/container.ts:551

Returns a function that runs factory with freshly resolved dependencies on every call.

Type Parameters ​

D ​

D extends Dependency<{ }, S>[]

R ​

R

Parameters ​

dependencies ​

[...D[]]

factory ​

((...args) => R) | (new (...args) => R)

Returns ​

() => R

Example ​

ts
const sendReport = app.bind(['mailer', 'reports'], (mailer, reports) => mailer.send(reports.weekly()));
sendReport();

Implementation of ​

ServiceRegistry.bind


call() ​

call<K>(key, args, thisArg?): ReturnType<Extract<ServiceType<S, K>, (...args) => any>>

Defined in: src/container.ts:253

Resolves a function service and calls it.

Type Parameters ​

K ​

K extends string | number | symbol

Parameters ​

key ​

K | TaggedKey<any, K>

args ​

Parameters<Extract<ServiceType<S, K>, (...args) => any>>

thisArg? ​

unknown = undefined

Returns ​

ReturnType<Extract<ServiceType<S, K>, (...args) => any>>

Example ​

ts
app.addInstance('greet', (name: string) => `Hello ${name}`);
app.call('greet', ['Ada']); // "Hello Ada"

Implementation of ​

ServiceRegistry.call


createResolver() ​

createResolver<K>(key): () => ServiceType<S, K>

Defined in: src/container.ts:238

Returns a function that resolves key when called.

Type Parameters ​

K ​

K extends string | number | symbol

Parameters ​

key ​

K | TaggedKey<any, K>

Returns ​

() => ServiceType<S, K>

Implementation of ​

ServiceRegistry.createResolver


dispose() ​

dispose(): Promise<void>

Defined in: src/container.ts:725

Disposes every instance this container owns, dependents first (reverse creation order), then marks the container as disposed: later get() and registrations throw.

Owned instances are the singletons this container created, and addInstance values registered with dispose. Each is disposed with its registration's dispose function, or with [Symbol.asyncDispose] / [Symbol.dispose] when it has one. Namespace containers are disposed too; forks are not (dispose each fork you create, e.g. with await using).

Calling it again returns the same promise. If some disposers fail, the others still run and the promise rejects with an AggregateError.

Returns ​

Promise<void>

Example ​

ts
await using scope = app.fork().addSingleton('tx', (db) => db.begin(), ['db']);
// ... scope and its singletons (the transaction) are disposed at the end of the block

extend() ​

extend<EA, Req>(extension): DIContainer<S & { [K in string | number | symbol]: EA[K] }>

Defined in: src/container.ts:499

Applies a module: a function that registers services and returns the registry it was given (or a fork of it). A module can declare only the services it needs; extend checks that they exist.

Type Parameters ​

EA ​

EA extends object

Req ​

Req extends object = S

Parameters ​

extension ​

[S] extends [Req] ? (registry) => ServiceRegistry<any, EA> : object

Returns ​

DIContainer<S & { [K in string | number | symbol]: EA[K] }>

Example ​

ts
const addBilling = (c: ServiceRegistry<{ db: Database }>) =>
  c.addSingleton('invoices', InvoiceRepository, ['db']);

const app = new DIContainer().addSingleton('db', createDb).extend(addBilling);

Implementation of ​

ServiceRegistry.extend


fork() ​

fork(options?): DIContainer<S>

Defined in: src/container.ts:674

Creates a child container. The child sees every service of this container (and later additions to it); services added to the child stay in the child.

  • By default a service registered in this container runs here and is shared by all forks, so overriding one of its dependencies in a fork does not affect it.
  • With isolated: true the fork runs and caches every service it resolves, including ones registered here. Overrides in the fork then reach the whole graph, and nothing leaks back. Use it for tests and per-tenant variants.

Services registered here can't depend on what a fork adds. A fork for one unit of work (a session, a request, a transaction, a job) is a scope: register its services in it with a scope module, and dispose it with await using. The "Scoped state" guide compares scopes with patterns that keep every service here (an argument, a factory with using, a context accessor).

Parameters ​

options? ​

ForkOptions

Returns ​

DIContainer<S>

Example ​

ts
await using requestScope = app.fork().addSingleton('tx', (db) => db.begin(), ['db']);

const testContainer = app.fork({ isolated: true }).addInstance('db', fakeDb, { replace: true });
testContainer.get('users'); // built with fakeDb; app is untouched

get() ​

get<K, O>(key, options?): O extends object ? ServiceType<S, K> | undefined : ServiceType<S, K>

Defined in: src/container.ts:220

Resolves a service: returns the cached singleton, creates it, or creates a new transient. Looks in parent containers when the key is not registered here. Throws when nothing is registered under key, unless { optional: true } is passed.

Type Parameters ​

K ​

K extends string | number | symbol

O ​

O extends GetOptions = { }

Parameters ​

key ​

K | TaggedKey<any, K>

options? ​

O

Returns ​

O extends object ? ServiceType<S, K> | undefined : ServiceType<S, K>

Throws ​

INJECUTE_NOT_REGISTERED (with "did you mean" suggestions), INJECUTE_RESOLUTION_FAILED when a factory throws (the original error is cause), INJECUTE_CLASS_NOT_CONSTRUCTED, INJECUTE_DISPOSED.

Example ​

ts
const users = app.get('users');
const cache = app.get('cache', { optional: true }); // Cache | undefined

Implementation of ​

ServiceRegistry.get


getParent() ​

getParent(): ServiceProvider<{ }> | undefined

Defined in: src/container.ts:281

The parent container as a read-only view, if any.

Returns ​

ServiceProvider<{ }> | undefined

Implementation of ​

ServiceRegistry.getParent


getRegistration() ​

getRegistration(key): RegistrationInfo | undefined

Defined in: src/container.ts:286

Read-only metadata of the registration visible under key, or undefined.

Parameters ​

key ​

ServiceKey

Returns ​

RegistrationInfo | undefined

Implementation of ​

ServiceRegistry.getRegistration


has() ​

has(key, options?): boolean

Defined in: src/container.ts:232

true when a service is registered under key, here or (unless local: true) in a parent.

Parameters ​

key ​

ServiceKey

options? ​

HasOptions

Returns ​

boolean

Implementation of ​

ServiceRegistry.has


injecute() ​

injecute<D, R>(factory, dependencies): R

Defined in: src/container.ts:531

Runs a function (or constructs a class) with resolved dependencies, without registering it.

Type Parameters ​

D ​

D extends Dependency<{ }, S>[]

R ​

R

Parameters ​

factory ​

((...args) => R) | (new (...args) => R)

dependencies ​

[...D[]]

Returns ​

R

Example ​

ts
const handler = app.injecute((users, logger) => createHandler(users, logger), ['users', 'logger']);

Implementation of ​

ServiceRegistry.injecute


namespace() ​

namespace<N, NA, Req>(name, extension): DIContainer<S & { [K in string | number | symbol]: NamespacedServices<N, NA>[K] }>

Defined in: src/container.ts:445

Registers the services added by extension under a prefix: name.key for each of them, and name for a read-only provider of the namespace. The extension receives a fork of this container, so it can use every service registered here; its own registrations stay in the namespace.

In the service map, name is a Namespace marker; get(name) is typed as a ServiceProvider of the namespace's services.

Type Parameters ​

N ​

N extends string

NA ​

NA extends object

Req ​

Req extends object = S

Parameters ​

name ​

N

extension ​

[S] extends [Req] ? (registry) => ServiceRegistry<any, NA> : object

Returns ​

DIContainer<S & { [K in string | number | symbol]: NamespacedServices<N, NA>[K] }>

Example ​

ts
app.namespace('Billing', (billing) =>
  billing.addSingleton('invoices', InvoiceRepository, ['db']),
);
app.get('Billing.invoices');

Implementation of ​

ServiceRegistry.namespace


removeEventListener() ​

removeEventListener<E>(event, handler): this

Defined in: src/container.ts:613

Removes a listener added with addEventListener.

Type Parameters ​

E ​

E extends keyof ContainerEvents

Parameters ​

event ​

E

handler ​

(event) => void

Returns ​

this


reset() ​

reset(options?): this

Defined in: src/container.ts:686

Clears cached instances (all, or keys), so the next resolution creates them again. Instances are not disposed here; dispose() still releases them later.

Parameters ​

options? ​

ResetOptions<S> = {}

Returns ​

this


seal() ​

seal(): SealedDIContainer<{ [K in string | number | symbol]: S[K] }>

Defined in: src/container.ts:645

Seals the container: no more services can be registered in it (add*, namespace and extend throw INJECUTE_SEALED); resolving, middlewares, events, reset() and dispose() keep working, and forks of a sealed container are open for registrations. Call it once the composition root is complete, then fork it per request or test.

The returned type has no registration methods, and its service map is one object type instead of an intersection of every registration, so code built on it (forks, request scopes) typechecks faster and shows readable types.

Returns ​

SealedDIContainer<{ [K in string | number | symbol]: S[K] }>

Throws ​

INJECUTE_DISPOSED.

Example ​

ts
export const app = new DIContainer()
  .addSingleton('db', createDb)
  .addSingleton('users', UserRepository, ['db'])
  .seal();

app.get('users');
const scope = app.fork().addInstance('requestId', id); // forks stay open

unuse() ​

unuse(middleware): this

Defined in: src/container.ts:586

Removes a middleware added with use.

Parameters ​

middleware ​

Middleware

Returns ​

this


use() ​

use(middleware): this

Defined in: src/container.ts:579

Adds a middleware around every resolution started from this container and its forks.

A middleware receives the key, a next function that continues the resolution (optionally with another key) and a context with the resolution path. The last added middleware runs first. Middlewares added to a parent later still apply to existing forks.

Parameters ​

middleware ​

Middleware

Returns ​

this

Example ​

ts
app.use((key, next, { depth }) => {
  const start = performance.now();
  const value = next();
  console.debug(`${'  '.repeat(depth)}${String(key)}: ${performance.now() - start}ms`);
  return value;
});

Released under the MIT License.