tuil
ReferencePackages@mwillbanks/tuil-coreAPI

CommandRegistry

class exported by @mwillbanks/tuil-core.

View rawEdit

class

Public class exported by @mwillbanks/tuil-core.

export class CommandRegistry {
  readonly #commands = new Map<string, Command>();
  readonly #observers = new Set<(execution: CommandExecution) => void>();
  readonly #registryObservers = new Set<
    (change: CommandRegistryChange) => void
  >();

  constructor(readonly services: ServiceContainer) {}

  register(command: Command): Disposable {
    if (this.#commands.has(command.id)) {
      throw new Error(`Command "${command.id}" is already registered`);
    }
    this.#commands.set(command.id, command);
    this.#notifyRegistry({ type: "registered", command });
    let disposed = false;
    return toDisposable(() => {
      if (disposed) return;
      disposed = true;
      this.#commands.delete(command.id);
      this.#notifyRegistry({ type: "unregistered", command });
    });
  }

  get(id: string): Command | undefined {
    return this.#commands.get(id);
  }

  list(): readonly Command[] {
    return [...this.#commands.values()];
  }

  observe(observer: (execution: CommandExecution) => void): Disposable {
    this.#observers.add(observer);
    return toDisposable(() => {
      this.#observers.delete(observer);
    });
  }

  observeRegistry(
    observer: (change: CommandRegistryChange) => void,
  ): Disposable {
    this.#registryObservers.add(observer);
    return toDisposable(() => {
      this.#registryObservers.delete(observer);
    });
  }

  async execute<TResult = unknown>(
    id: string,
    options: {
      signal?: AbortSignal;
      source?: string;
      metadata?: Readonly<Record<string, unknown>>;
    } = {},
  ): Promise<TResult | undefined> {
    const command = this.#commands.get(id);
    if (!command) {
      throw new Error(`Command "${id}" is not registered`);
    }
    const startedAt = Date.now();
    const signal = options.signal ?? new AbortController().signal;
    const context: CommandContext = {
      services: this.services,
      signal,
      source: options.source,
      metadata: options.metadata ?? {},
    };
    if (signal.aborted) {
      this.#notify({
        commandId: id,
        startedAt,
        completedAt: Date.now(),
        status: "cancelled",
        error: signal.reason,
      });
      throw signal.reason;
    }
    if (command.enabled && !(await command.enabled(context))) {
      this.#notify({
        commandId: id,
        startedAt,
        completedAt: Date.now(),
        status: "disabled",
      });
      return undefined;
    }
    try {
      const result = (await command.execute(context)) as TResult;
      this.#notify({
        commandId: id,
        startedAt,
        completedAt: Date.now(),
        status: "succeeded",
      });
      return result;
    } catch (error) {
      const status = signal.aborted ? "cancelled" : "failed";
      this.#notify({
        commandId: id,
        startedAt,
        completedAt: Date.now(),
        status,
        error,
      });
      throw error;
    }
  }

  #notify(execution: CommandExecution): void {
    for (const observer of this.#observers) {
      observer(execution);
    }
  }

  #notifyRegistry(change: CommandRegistryChange): void {
    for (const observer of this.#registryObservers) {
      observer(change);
    }
  }
}

Members

MemberTypeRequiredDescriptionRelated types
#commandsMap<string, Command<unknown>>YesThe #commands member uses the Map<string, Command<unknown>> contract.Command
#observersSet<(execution: CommandExecution) => void>YesThe #observers member uses the Set<(execution: CommandExecution) => void> contract.CommandExecution
#registryObserversSet<(change: CommandRegistryChange) => void>YesThe #registryObservers member uses the Set<(change: CommandRegistryChange) => void> contract.CommandRegistryChange
__constructoranyYesThe __constructor member uses the any contract.
register(command: Command) => DisposableYesThe register member uses the (command: Command) => Disposable contract.Command, Disposable
get(id: string) => Command | undefinedYesThe get member uses the (id: string) => Command | undefined contract.Command
list() => readonly Command[]YesThe list member uses the () => readonly Command[] contract.Command
observe(observer: (execution: CommandExecution) => void) => DisposableYesThe observe member uses the (observer: (execution: CommandExecution) => void) => Disposable contract.CommandExecution, Disposable
observeRegistry(observer: (change: CommandRegistryChange) => void) => DisposableYesThe observeRegistry member uses the (observer: (change: CommandRegistryChange) => void) => Disposable contract.CommandRegistryChange, Disposable
execute<TResult = unknown>(id: string, options?: &#123; signal?: AbortSignal; source?: string; metadata?: Readonly<Record<string, unknown>>; &#125;) => Promise<TResult | undefined>YesThe execute member uses the <TResult = unknown>(id: string, options?: &#123; signal?: AbortSignal; source?: string; metadata?: Readonly<Record<string, unknown>>; &#125;) => Promise<TResult | undefined> contract.
#notify(execution: CommandExecution) => voidYesThe #notify member uses the (execution: CommandExecution) => void contract.CommandExecution
#notifyRegistry(change: CommandRegistryChange) => voidYesThe #notifyRegistry member uses the (change: CommandRegistryChange) => void contract.CommandRegistryChange

Parameters

This declaration has no public members.

Returns

This declaration does not return a value.

Throws

No thrown errors are documented for this declaration.

Source

View the secondary source reference

Package

@mwillbanks/tuil-core

On this page