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

Lifecycle and dispose ​

await container.dispose() releases every instance the container owns, dependents first, then marks the container as disposed.

ts
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();
}

Open in the playground

What the container owns ​

  • Singletons it created. Each is disposed with its registration's dispose function, or with [Symbol.asyncDispose] / [Symbol.dispose] when the instance has one. dispose: false opts 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.
  • addInstance values 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:

ts
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.

Released under the MIT License.