boardgame-core/src/utils/command/command-registry.ts

142 lines
4.7 KiB
TypeScript
Raw Normal View History

2026-04-02 08:58:11 +08:00
import type { Command } from './types.js';
import type { CommandRunner, CommandRunnerContext, PromptEvent } from './command-runner.js';
2026-04-02 08:58:11 +08:00
import { parseCommand } from './command-parse.js';
import { applyCommandSchema } from './command-apply.js';
import { parseCommandSchema } from './schema-parse.js';
2026-04-02 08:58:11 +08:00
export type CommandRegistry<TContext> = Map<string, CommandRunner<TContext, unknown>>;
export function createCommandRegistry<TContext>(): CommandRegistry<TContext> {
return new Map();
}
export function registerCommand<TContext, TResult>(
registry: CommandRegistry<TContext>,
runner: CommandRunner<TContext, TResult>
): void {
registry.set(runner.schema.name, runner as CommandRunner<TContext, unknown>);
}
export function unregisterCommand<TContext>(
registry: CommandRegistry<TContext>,
name: string
): void {
registry.delete(name);
}
export function hasCommand<TContext>(
registry: CommandRegistry<TContext>,
name: string
): boolean {
return registry.has(name);
}
export function getCommand<TContext>(
registry: CommandRegistry<TContext>,
name: string
): CommandRunner<TContext, unknown> | undefined {
return registry.get(name);
}
type Listener = (e: PromptEvent) => void;
export type CommandRunnerContextExport<TContext> = CommandRunnerContext<TContext> & {
registry: CommandRegistry<TContext>;
};
export function createCommandRunnerContext<TContext>(
2026-04-02 08:58:11 +08:00
registry: CommandRegistry<TContext>,
context: TContext
): CommandRunnerContextExport<TContext> {
const listeners = new Set<Listener>();
const on = (_event: 'prompt', listener: Listener) => {
listeners.add(listener);
};
const off = (_event: 'prompt', listener: Listener) => {
listeners.delete(listener);
};
const prompt = (schema: Parameters<CommandRunnerContext<TContext>['prompt']>[0]): Promise<Command> => {
const resolvedSchema = typeof schema === 'string' ? parseCommandSchema(schema) : schema;
return new Promise((resolve, reject) => {
const event: PromptEvent = { schema: resolvedSchema, resolve, reject };
for (const listener of listeners) {
listener(event);
}
});
};
const runnerCtx: CommandRunnerContextExport<TContext> = {
registry,
context,
run: (input: string) => runCommandWithContext(registry, runnerCtx, input),
runParsed: (command: Command) => runCommandParsedWithContext(registry, runnerCtx, command),
prompt,
on,
off,
};
return runnerCtx;
}
async function executeWithRunnerContext<TContext>(
runnerCtx: CommandRunnerContextExport<TContext>,
2026-04-02 08:58:11 +08:00
runner: CommandRunner<TContext, unknown>,
command: Command
): Promise<{ success: true; result: unknown } | { success: false; error: string }> {
try {
const result = await runner.run.call(runnerCtx, command);
return { success: true, result };
} catch (e) {
const error = e as Error;
return { success: false, error: error.message };
}
}
export async function runCommand<TContext>(
registry: CommandRegistry<TContext>,
context: TContext,
input: string
): Promise<{ success: true; result: unknown } | { success: false; error: string }> {
const runnerCtx = createCommandRunnerContext(registry, context);
return await runCommandWithContext(registry, runnerCtx, input);
}
async function runCommandWithContext<TContext>(
registry: CommandRegistry<TContext>,
runnerCtx: CommandRunnerContextExport<TContext>,
input: string
2026-04-02 08:58:11 +08:00
): Promise<{ success: true; result: unknown } | { success: false; error: string }> {
const command = parseCommand(input);
return await runCommandParsedWithContext(registry, runnerCtx, command);
2026-04-02 08:58:11 +08:00
}
export async function runCommandParsed<TContext>(
registry: CommandRegistry<TContext>,
context: TContext,
command: Command
): Promise<{ success: true; result: unknown } | { success: false; error: string }> {
const runnerCtx = createCommandRunnerContext(registry, context);
return await runCommandParsedWithContext(registry, runnerCtx, command);
}
async function runCommandParsedWithContext<TContext>(
registry: CommandRegistry<TContext>,
runnerCtx: CommandRunnerContextExport<TContext>,
command: Command
2026-04-02 08:58:11 +08:00
): Promise<{ success: true; result: unknown } | { success: false; error: string }> {
const runner = registry.get(command.name);
if (!runner) {
return { success: false, error: `Unknown command: ${command.name}` };
}
const validationResult = applyCommandSchema(command, runner.schema);
if (!validationResult.valid) {
return { success: false, error: validationResult.errors.join('; ') };
}
return await executeWithRunnerContext(runnerCtx, runner, validationResult.command);
2026-04-02 08:58:11 +08:00
}