# DevtoolsExtensionRegistry

Source: /tuil/docs/reference/packages/devtools/api/devtools-extension-registry
Locale: en

class exported by @mwillbanks/tuil-devtools.



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

## class [#class]

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

```ts
export class DevtoolsExtensionRegistry {
  readonly #extensions = new Map<string, DevtoolsExtension>();
  readonly #capabilities: ReadonlySet<DevtoolsCapability>;
  readonly #history: DevtoolsActionRecord[] = [];
  readonly #historyLimit: number;
  #historyDropped = 0;
  readonly #development: boolean;
  readonly #transport?: ProtocolTransport;
  readonly #redact: (value: unknown) => unknown;

  constructor(
    options: {
      readonly capabilities?: ReadonlySet<DevtoolsCapability>;
      readonly development?: boolean;
      readonly transport?: ProtocolTransport;
      readonly redact?: (value: unknown) => unknown;
      readonly actionHistoryLimit?: number;
    } = {},
  ) {
    this.#capabilities =
      options.capabilities ??
      new Set<DevtoolsCapability>([
        "runtime",
        "services",
        "commands",
        "focus",
        "semantics",
        "layout",
        "pointer",
        "frames",
        "scroll",
        "operations",
        "workflows",
        "routes",
        "plugins",
        "themes",
        "editors",
        "logs",
        "performance",
        "errors",
      ]);
    this.#development = options.development ?? false;
    this.#historyLimit = options.actionHistoryLimit ?? 200;
    if (!Number.isSafeInteger(this.#historyLimit) || this.#historyLimit <= 0) {
      throw new Error(
        "Devtools actionHistoryLimit must be a positive safe integer",
      );
    }
    this.#transport = options.transport;
    this.#redact = options.redact
      ? (value) =>
          redactDevtoolsValue(options.redact?.(redactDevtoolsValue(value)))
      : redactDevtoolsValue;
  }

  register(extension: DevtoolsExtension): () => void {
    validateDevtoolsExtension(extension, this.#extensions, this.#capabilities);
    if (extension.activation && !extension.activation()) {
      return () => {};
    }
    this.#extensions.set(extension.id, extension);
    void Promise.resolve(
      this.#transport?.send(
        createProtocolMessage("contribution", {
          id: extension.id,
          kind: extension.kind,
        }),
      ),
    ).catch(() => undefined);
    return () => {
      if (this.#extensions.get(extension.id) !== extension) return;
      this.#extensions.delete(extension.id);
      extension.dispose?.();
    };
  }

  list<TKind extends DevtoolsExtension["kind"]>(
    kind?: TKind,
  ): readonly Extract<DevtoolsExtension, { readonly kind: TKind }>[] {
    return Object.freeze(
      [...this.#extensions.values()].filter(
        (
          extension,
        ): extension is Extract<DevtoolsExtension, { readonly kind: TKind }> =>
          !kind || extension.kind === kind,
      ),
    );
  }

  inspect(id: string): unknown {
    const panel = this.#extensions.get(id);
    if (panel?.kind !== "panel") {
      throw new Error(`Devtools panel "${id}" is unavailable`);
    }
    return this.#redact(panel.inspect());
  }

  search(query: string): readonly DevtoolsPanelContribution[] {
    const needle = query.toLowerCase();
    return Object.freeze(
      this.list("panel").filter((panel) =>
        `${panel.title} ${panel.searchText?.() ?? ""}`
          .toLowerCase()
          .includes(needle),
      ),
    );
  }

  async execute(id: string, input?: unknown): Promise<unknown> {
    const extension = this.#extensions.get(id);
    if (extension?.kind !== "action") {
      throw new Error(`Devtools action "${id}" is unavailable`);
    }
    if (!extension.permissions.has("write")) {
      throw new Error(`Devtools action "${id}" lacks write permission`);
    }
    if (!this.#development) {
      throw new Error(`Devtools action "${id}" requires development mode`);
    }
    let result: unknown;
    try {
      result = await extension.run(input, {
        development: true,
        record: (entry) => {
          this.#record(entry);
        },
      });
    } catch (error) {
      const message = redactDevtoolsValue(
        error instanceof Error ? error.message : String(error),
      ) as string;
      this.#record({
        id,
        timestamp: Date.now(),
        input,
        ok: false,
        error: message,
      });
      throw new Error(message);
    }
    const entry = this.#record({
      id,
      timestamp: Date.now(),
      input,
      ok: true,
    });
    try {
      await this.#transport?.send(createProtocolMessage("command", entry));
    } catch {
      // Delivery is observational. The completed action must not become a
      // retryable failure when a devtools transport is unavailable.
    }
    return result;
  }

  #record(entry: DevtoolsActionRecord): DevtoolsActionRecord {
    const redacted = Object.freeze({
      ...entry,
      input: this.#redact(entry.input),
      error:
        entry.error === undefined
          ? undefined
          : (this.#redact(entry.error) as string),
    });
    this.#history.push(redacted);
    const overflow = this.#history.length - this.#historyLimit;
    if (overflow > 0) {
      this.#history.splice(0, overflow);
      this.#historyDropped += overflow;
    }
    return redacted;
  }

  actionHistory(): readonly DevtoolsActionRecord[] {
    return Object.freeze([...this.#history]);
  }

  actionHistorySnapshot(): DevtoolsActionHistorySnapshot {
    return Object.freeze({
      records: this.actionHistory(),
      limit: this.#historyLimit,
      dropped: this.#historyDropped,
      truncated: this.#historyDropped > 0,
    });
  }

  async query(id: string, input: string): Promise<unknown> {
    const extension = this.#extensions.get(id);
    if (extension?.kind !== "query") {
      throw new Error(`Devtools query "${id}" is unavailable`);
    }
    return extension.query(input);
  }

  async observe(id: string, input?: unknown): Promise<unknown> {
    const extension = this.#extensions.get(id);
    if (
      !extension ||
      extension.kind === "panel" ||
      extension.kind === "action" ||
      extension.kind === "query"
    ) {
      throw new Error(`Devtools observer "${id}" is unavailable`);
    }
    return extension.observe(input);
  }

  diagnosticsBundle(): string {
    const panels = Object.fromEntries(
      this.list("panel").map((panel) => [panel.id, panel.inspect()]),
    );
    return JSON.stringify(
      this.#redact({
        version: 1,
        generatedAt: new Date(0).toISOString(),
        panels,
        actions: this.#history,
        actionHistory: {
          limit: this.#historyLimit,
          dropped: this.#historyDropped,
          truncated: this.#historyDropped > 0,
        },
      }),
      null,
      2,
    );
  }

  dispose(): void {
    for (const extension of this.#extensions.values()) {
      extension.dispose?.();
    }
    this.#extensions.clear();
  }
}
```

## Members [#members]

| Member                  | Type                                                                                                                                    | Required | Description                                                                                                                                                                  | Related types                                                                                             |
| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| `#extensions`           | `Map<string, DevtoolsExtension>`                                                                                                        | Yes      | The `#extensions` member uses the `Map<string, DevtoolsExtension>` contract.                                                                                                 | [`DevtoolsExtension`](/tuil/docs/reference/packages/devtools/api/devtools-extension)                           |
| `#capabilities`         | `ReadonlySet<DevtoolsCapability>`                                                                                                       | Yes      | The `#capabilities` member uses the `ReadonlySet<DevtoolsCapability>` contract.                                                                                              | [`DevtoolsCapability`](/tuil/docs/reference/packages/devtools/api/devtools-capability)                         |
| `#history`              | `DevtoolsActionRecord[]`                                                                                                                | Yes      | The `#history` member uses the `DevtoolsActionRecord[]` contract.                                                                                                            | [`DevtoolsActionRecord`](/tuil/docs/reference/packages/devtools/api/devtools-action-record)                    |
| `#historyLimit`         | `number`                                                                                                                                | Yes      | The `#historyLimit` member uses the `number` contract.                                                                                                                       | —                                                                                                         |
| `#historyDropped`       | `number`                                                                                                                                | Yes      | The `#historyDropped` member uses the `number` contract.                                                                                                                     | —                                                                                                         |
| `#development`          | `boolean`                                                                                                                               | Yes      | The `#development` member uses the `boolean` contract.                                                                                                                       | —                                                                                                         |
| `#transport`            | `ProtocolTransport \| undefined`                                                                                                        | No       | The `#transport` member uses the `ProtocolTransport \| undefined` contract.                                                                                                  | —                                                                                                         |
| `#redact`               | `(value: unknown) => unknown`                                                                                                           | Yes      | The `#redact` member uses the `(value: unknown) => unknown` contract.                                                                                                        | —                                                                                                         |
| `__constructor`         | `any`                                                                                                                                   | Yes      | The `__constructor` member uses the `any` contract.                                                                                                                          | —                                                                                                         |
| `register`              | `(extension: DevtoolsExtension) => () => void`                                                                                          | Yes      | The `register` member uses the `(extension: DevtoolsExtension) => () => void` contract.                                                                                      | [`DevtoolsExtension`](/tuil/docs/reference/packages/devtools/api/devtools-extension)                           |
| `list`                  | `<TKind extends DevtoolsExtension["kind"]>(kind?: TKind) => readonly Extract<DevtoolsExtension, &#123; readonly kind: TKind; &#125;>[]` | Yes      | The `list` member uses the `<TKind extends DevtoolsExtension["kind"]>(kind?: TKind) => readonly Extract<DevtoolsExtension, &#123; readonly kind: TKind; &#125;>[]` contract. | [`DevtoolsExtension`](/tuil/docs/reference/packages/devtools/api/devtools-extension)                           |
| `inspect`               | `(id: string) => unknown`                                                                                                               | Yes      | The `inspect` member uses the `(id: string) => unknown` contract.                                                                                                            | —                                                                                                         |
| `search`                | `(query: string) => readonly DevtoolsPanelContribution[]`                                                                               | Yes      | The `search` member uses the `(query: string) => readonly DevtoolsPanelContribution[]` contract.                                                                             | [`DevtoolsPanelContribution`](/tuil/docs/reference/packages/devtools/api/devtools-panel-contribution)          |
| `execute`               | `(id: string, input?: unknown) => Promise<unknown>`                                                                                     | Yes      | The `execute` member uses the `(id: string, input?: unknown) => Promise<unknown>` contract.                                                                                  | —                                                                                                         |
| `#record`               | `(entry: DevtoolsActionRecord) => DevtoolsActionRecord`                                                                                 | Yes      | The `#record` member uses the `(entry: DevtoolsActionRecord) => DevtoolsActionRecord` contract.                                                                              | [`DevtoolsActionRecord`](/tuil/docs/reference/packages/devtools/api/devtools-action-record)                    |
| `actionHistory`         | `() => readonly DevtoolsActionRecord[]`                                                                                                 | Yes      | The `actionHistory` member uses the `() => readonly DevtoolsActionRecord[]` contract.                                                                                        | [`DevtoolsActionRecord`](/tuil/docs/reference/packages/devtools/api/devtools-action-record)                    |
| `actionHistorySnapshot` | `() => DevtoolsActionHistorySnapshot`                                                                                                   | Yes      | The `actionHistorySnapshot` member uses the `() => DevtoolsActionHistorySnapshot` contract.                                                                                  | [`DevtoolsActionHistorySnapshot`](/tuil/docs/reference/packages/devtools/api/devtools-action-history-snapshot) |
| `query`                 | `(id: string, input: string) => Promise<unknown>`                                                                                       | Yes      | The `query` member uses the `(id: string, input: string) => Promise<unknown>` contract.                                                                                      | —                                                                                                         |
| `observe`               | `(id: string, input?: unknown) => Promise<unknown>`                                                                                     | Yes      | The `observe` member uses the `(id: string, input?: unknown) => Promise<unknown>` contract.                                                                                  | —                                                                                                         |
| `diagnosticsBundle`     | `() => string`                                                                                                                          | Yes      | The `diagnosticsBundle` member uses the `() => string` contract.                                                                                                             | —                                                                                                         |
| `dispose`               | `() => void`                                                                                                                            | Yes      | The `dispose` 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]

* [`DevtoolsActionHistorySnapshot`](/tuil/docs/reference/packages/devtools/api/devtools-action-history-snapshot)
* [`DevtoolsActionRecord`](/tuil/docs/reference/packages/devtools/api/devtools-action-record)
* [`DevtoolsCapability`](/tuil/docs/reference/packages/devtools/api/devtools-capability)
* [`DevtoolsExtension`](/tuil/docs/reference/packages/devtools/api/devtools-extension)
* [`DevtoolsPanelContribution`](/tuil/docs/reference/packages/devtools/api/devtools-panel-contribution)

## Source [#source]

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

## Package [#package]

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