102 lines
3.3 KiB
TypeScript
102 lines
3.3 KiB
TypeScript
|
|
import type { Command } from './types.js';
|
||
|
|
import type { CommandRunner, CommandRunnerContext as RunnerContext } from './command-runner.js';
|
||
|
|
import { parseCommand } from './command-parse.js';
|
||
|
|
import { applyCommandSchema } from './command-apply.js';
|
||
|
|
|
||
|
|
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);
|
||
|
|
}
|
||
|
|
|
||
|
|
async function executeWithRunnerContext<TContext>(
|
||
|
|
registry: CommandRegistry<TContext>,
|
||
|
|
context: TContext,
|
||
|
|
runner: CommandRunner<TContext, unknown>,
|
||
|
|
command: Command
|
||
|
|
): Promise<{ success: true; result: unknown } | { success: false; error: string }> {
|
||
|
|
const runnerCtx: RunnerContext<TContext> = {
|
||
|
|
context,
|
||
|
|
run: (input: string) => runCommand(registry, context, input),
|
||
|
|
runParsed: (cmd: Command) => runCommandParsed(registry, context, cmd),
|
||
|
|
};
|
||
|
|
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 command = parseCommand(input);
|
||
|
|
return await runCommandParsed(registry, context, command);
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function runCommandParsed<TContext>(
|
||
|
|
registry: CommandRegistry<TContext>,
|
||
|
|
context: TContext,
|
||
|
|
command: Command
|
||
|
|
): 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(registry, context, runner, validationResult.command);
|
||
|
|
}
|
||
|
|
|
||
|
|
export type CommandRunnerContext<TContext> = RunnerContext<TContext> & {
|
||
|
|
registry: CommandRegistry<TContext>;
|
||
|
|
};
|
||
|
|
|
||
|
|
export function createCommandRunnerContext<TContext>(
|
||
|
|
registry: CommandRegistry<TContext>,
|
||
|
|
context: TContext
|
||
|
|
): CommandRunnerContext<TContext> {
|
||
|
|
return {
|
||
|
|
registry,
|
||
|
|
context,
|
||
|
|
run: (input: string) => runCommand(registry, context, input),
|
||
|
|
runParsed: (command: Command) => runCommandParsed(registry, context, command),
|
||
|
|
};
|
||
|
|
}
|