# ServiceContainer

Source: /tuil/docs/reference/packages/core/api/service-container
Locale: en

class exported by @mwillbanks/tuil-core.



{/* Generated by tooling/docs/generate-reference.ts. */}

## class [#class]

Public class exported by `@mwillbanks/tuil-core`.

```ts
export class ServiceContainer implements Disposable {
  readonly #records: Map<string, ServiceRecord>;
  readonly #order: string[];
  readonly #controller: AbortController;
  #disposed: boolean;

  constructor() {
    this.#records = new Map();
    this.#order = [];
    this.#controller = new AbortController();
    this.#disposed = false;
  }

  register<TId extends string, TValue>(
    definition: ServiceDefinition<TId, TValue>,
  ): Disposable;
  register<TId extends string, TValue>(id: TId, value: TValue): Disposable;
  register<TId extends string, TValue>(
    definitionOrId: ServiceDefinition<TId, TValue> | TId,
    value?: TValue,
  ): Disposable {
    this.#assertActive();
    const id =
      typeof definitionOrId === "string" ? definitionOrId : definitionOrId.id;
    if (this.#records.has(id)) {
      throw new Error(`Service "${id}" is already registered`);
    }
    this.#records.set(
      id,
      typeof definitionOrId === "string"
        ? { value, status: "ready" }
        : {
            definition: {
              create: definitionOrId.create,
              dispose: definitionOrId.dispose as
                | ((created: unknown) => void | Promise<void>)
                | undefined,
            },
            status: "registered",
          },
    );
    this.#order.push(id);
    return toDisposable(() => {
      const record = this.#records.get(id);
      if (record?.status === "initializing") {
        throw new Error(
          `Cannot unregister service "${id}" while it initializes`,
        );
      }
      this.#records.delete(id);
      const index = this.#order.indexOf(id);
      if (index >= 0) {
        this.#order.splice(index, 1);
      }
    });
  }

  has(id: string): boolean {
    return this.#records.has(id);
  }

  get<TValue>(id: string): TValue {
    this.#assertActive();
    const record = this.#records.get(id);
    if (!record) {
      throw new Error(`Service "${id}" is not registered`);
    }
    if (record.status === "failed") {
      throw record.error;
    }
    if (record.status !== "ready") {
      throw new Error(`Service "${id}" has not been initialized`);
    }
    return record.value as TValue;
  }

  async resolve<TValue>(id: string): Promise<TValue> {
    this.#assertActive();
    const record = this.#records.get(id);
    if (!record) {
      throw new Error(`Service "${id}" is not registered`);
    }
    if (record.status === "ready") {
      return record.value as TValue;
    }
    if (record.status === "failed") {
      throw record.error;
    }
    if (record.status === "initializing") {
      throw new Error(
        `Circular or concurrent initialization detected for service "${id}"`,
      );
    }
    record.status = "initializing";
    try {
      record.value = await record.definition?.create({
        services: this,
        signal: this.#controller.signal,
      });
      record.status = "ready";
      return record.value as TValue;
    } catch (error) {
      record.status = "failed";
      record.error = error;
      throw error;
    }
  }

  async initialize(): Promise<void> {
    for (const id of this.#order) {
      await this.resolve(id);
    }
  }

  entries(): readonly [string, unknown][] {
    return this.#order
      .filter((id) => this.#records.get(id)?.status === "ready")
      .map((id) => [id, this.#records.get(id)?.value] as const);
  }

  async dispose(): Promise<void> {
    if (this.#disposed) {
      return;
    }
    this.#disposed = true;
    this.#controller.abort(new Error("Service container disposed"));
    const errors: unknown[] = [];
    for (const id of [...this.#order].reverse()) {
      const record = this.#records.get(id);
      if (
        record?.status === "ready" &&
        record.definition?.dispose &&
        record.value !== undefined
      ) {
        try {
          await record.definition.dispose(record.value);
        } catch (error) {
          errors.push(error);
        }
      }
    }
    this.#records.clear();
    this.#order.length = 0;
    if (errors.length > 0) {
      throw new AggregateError(errors, "Failed to dispose runtime services");
    }
  }

  #assertActive(): void {
    if (this.#disposed) {
      throw new Error("Service container is disposed");
    }
  }
}
```

## Members [#members]

| Member          | Type                                                                                                                                                                    | Required | Description                                                                                                                                                                                                      | Related types                                                                                                                             |
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `#records`      | `Map<string, ServiceRecord>`                                                                                                                                            | Yes      | The `#records` member uses the `Map<string, ServiceRecord>` contract.                                                                                                                                            | —                                                                                                                                         |
| `#order`        | `string[]`                                                                                                                                                              | Yes      | The `#order` member uses the `string[]` contract.                                                                                                                                                                | —                                                                                                                                         |
| `#controller`   | `AbortController`                                                                                                                                                       | Yes      | The `#controller` member uses the `AbortController` contract.                                                                                                                                                    | —                                                                                                                                         |
| `#disposed`     | `boolean`                                                                                                                                                               | Yes      | The `#disposed` member uses the `boolean` contract.                                                                                                                                                              | —                                                                                                                                         |
| `__constructor` | `any`                                                                                                                                                                   | Yes      | The `__constructor` member uses the `any` contract.                                                                                                                                                              | —                                                                                                                                         |
| `register`      | `&#123; <TId extends string, TValue>(definition: ServiceDefinition<TId, TValue>): Disposable; <TId extends string, TValue>(id: TId, value: TValue): Disposable; &#125;` | Yes      | The `register` member uses the `&#123; <TId extends string, TValue>(definition: ServiceDefinition<TId, TValue>): Disposable; <TId extends string, TValue>(id: TId, value: TValue): Disposable; &#125;` contract. | [`Disposable`](/tuil/docs/reference/packages/core/api/disposable), [`ServiceDefinition`](/tuil/docs/reference/packages/core/api/service-definition) |
| `has`           | `(id: string) => boolean`                                                                                                                                               | Yes      | The `has` member uses the `(id: string) => boolean` contract.                                                                                                                                                    | —                                                                                                                                         |
| `get`           | `<TValue>(id: string) => TValue`                                                                                                                                        | Yes      | The `get` member uses the `<TValue>(id: string) => TValue` contract.                                                                                                                                             | —                                                                                                                                         |
| `resolve`       | `<TValue>(id: string) => Promise<TValue>`                                                                                                                               | Yes      | The `resolve` member uses the `<TValue>(id: string) => Promise<TValue>` contract.                                                                                                                                | —                                                                                                                                         |
| `initialize`    | `() => Promise<void>`                                                                                                                                                   | Yes      | The `initialize` member uses the `() => Promise<void>` contract.                                                                                                                                                 | —                                                                                                                                         |
| `entries`       | `() => readonly [string, unknown][]`                                                                                                                                    | Yes      | The `entries` member uses the `() => readonly [string, unknown][]` contract.                                                                                                                                     | —                                                                                                                                         |
| `dispose`       | `() => Promise<void>`                                                                                                                                                   | Yes      | The `dispose` member uses the `() => Promise<void>` contract.                                                                                                                                                    | —                                                                                                                                         |
| `#assertActive` | `() => void`                                                                                                                                                            | Yes      | The `#assertActive` member uses the `() => void` contract.                                                                                                                                                       | —                                                                                                                                         |

## Parameters [#parameters]

This declaration has no public members.

## Returns [#returns]

This declaration does not return a value.

## Throws [#throws]

No thrown errors are documented for this declaration.

## Related types [#related-types]

* [`Disposable`](/tuil/docs/reference/packages/core/api/disposable)
* [`ServiceDefinition`](/tuil/docs/reference/packages/core/api/service-definition)

## Source [#source]

[View the secondary source reference](https://github.com/mwillbanks/tuil/blob/main/packages/core/src/services.ts)

## Package [#package]

[@mwillbanks/tuil-core](/tuil/docs/reference/packages/core)
