Lifecycle and dispose
await container.dispose() releases every instance the container owns, dependents first, then marks the container as disposed.
const app = new DIContainer()
.addInstance('url', 'postgres://localhost/app')
.addSingleton('pool', Pool, ['url']) // disposed with [Symbol.asyncDispose]
.addSingleton('cache', Cache) // disposed with [Symbol.dispose]
.addSingleton('mailer', () => ({ close: () => log.push('close mailer') }), {
dependencies: [],
dispose: (mailer) => mailer.close(), // custom disposer
});
export async function shutdown(): Promise<void> {
app.get('pool');
app.get('cache');
app.get('mailer');
// dependents first: reverse creation order
await app.dispose();
}What the container owns
- Singletons it created. Each is disposed with its registration's
disposefunction, or with[Symbol.asyncDispose]/[Symbol.dispose]when the instance has one.dispose: falseopts out. When a factory returns a promise, the resolved value is disposed (a singleton still being created is awaited first); one whose promise rejected is skipped. addInstancevalues registered with{ dispose: true }(or a function). Values you add are yours by default.- Namespace containers, with their services.
Transients are never tracked: the caller owns them. A dispose option on a transient throws INJECUTE_INVALID_OPTION.
Order and failures
Instances are disposed in reverse creation order across the container and its namespaces, so a service is released before the services it depends on. If some disposers fail, the others still run, and dispose() rejects with INJECUTE_DISPOSE_FAILED (the failures are in cause, an AggregateError). Calling dispose() again returns the same promise.
Scopes with await using
Containers implement [Symbol.asyncDispose], so a fork can be disposed at the end of a block:
export async function handleRequest(requestId: string): Promise<string> {
await using scope = app.fork().addSingleton('trace', () => ({
id: requestId,
[Symbol.dispose]: () => log.push(`end ${requestId}`),
}));
return scope.get('trace').id;
// `trace` is disposed here; the app's singletons stay alive
}A parent does not dispose its forks: dispose each fork you create (await using does that for you).
Stopping a running app
To stop servers, consumers and schedulers before the connections they use are released, start the app with lifecycle hooks and stop it with running.stop(): it undoes the hooks in reverse, then calls dispose(). See Startup and shutdown.
reset() vs dispose()
reset() clears cached instances so they are created again on the next resolution. It does not dispose them; dispose() still releases every instance the container created, including ones reset() or replace dropped.