---
url: https://masyaka.github.io/injecute/guide/lifecycle.md
description: >-
  Release connections and other resources with dispose() and await using, in
  reverse creation order.
---

# 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](../playground?example=lifecycle)

## 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`](../errors/invalid-option.md).

## 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`](../errors/dispose-failed.md) (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](./startup-shutdown.md).

## `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.
