CommandRegistry
class exported by @mwillbanks/tuil-core.
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
| Member | Type | Required | Description | Related types |
|---|---|---|---|---|
#commands | Map<string, Command<unknown>> | Yes | The #commands member uses the Map<string, Command<unknown>> contract. | Command |
#observers | Set<(execution: CommandExecution) => void> | Yes | The #observers member uses the Set<(execution: CommandExecution) => void> contract. | CommandExecution |
#registryObservers | Set<(change: CommandRegistryChange) => void> | Yes | The #registryObservers member uses the Set<(change: CommandRegistryChange) => void> contract. | CommandRegistryChange |
__constructor | any | Yes | The __constructor member uses the any contract. | — |
register | (command: Command) => Disposable | Yes | The register member uses the (command: Command) => Disposable contract. | Command, Disposable |
get | (id: string) => Command | undefined | Yes | The get member uses the (id: string) => Command | undefined contract. | Command |
list | () => readonly Command[] | Yes | The list member uses the () => readonly Command[] contract. | Command |
observe | (observer: (execution: CommandExecution) => void) => Disposable | Yes | The observe member uses the (observer: (execution: CommandExecution) => void) => Disposable contract. | CommandExecution, Disposable |
observeRegistry | (observer: (change: CommandRegistryChange) => void) => Disposable | Yes | The observeRegistry member uses the (observer: (change: CommandRegistryChange) => void) => Disposable contract. | CommandRegistryChange, Disposable |
execute | <TResult = unknown>(id: string, options?: { signal?: AbortSignal; source?: string; metadata?: Readonly<Record<string, unknown>>; }) => Promise<TResult | undefined> | Yes | The execute member uses the <TResult = unknown>(id: string, options?: { signal?: AbortSignal; source?: string; metadata?: Readonly<Record<string, unknown>>; }) => Promise<TResult | undefined> contract. | — |
#notify | (execution: CommandExecution) => void | Yes | The #notify member uses the (execution: CommandExecution) => void contract. | CommandExecution |
#notifyRegistry | (change: CommandRegistryChange) => void | Yes | The #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.
Related types
Source
View the secondary source reference