import { Authorization, CaseSurface, CommandContext, CorrectiveCaseRunnerError, MainMutationCommand, MainMutationResult, SurfaceReceipt, TargetKind, runDesktopCase, runWasmCase, } from "./corrective-case-runner"; export const CORRECTIVE_COMMAND_REGISTRY_SCHEMA = 1 as const; export type CommandExecutor = (command: MainMutationCommand) => MainMutationResult | Promise; export interface RegisteredCommand { commandType: string; targetKinds: readonly TargetKind[]; authorizations: readonly Exclude[]; execute: CommandExecutor; } export class CommandRegistryError extends Error { readonly code: "REGISTRY_INVALID" | "REGISTRY_DUPLICATE"; constructor(code: "REGISTRY_INVALID" | "REGISTRY_DUPLICATE", message: string) { super(`${code}: ${message}`); this.name = "CommandRegistryError"; this.code = code; } } export interface DispatchOptions { command: unknown; mainRevision: number; } const COMMAND_TYPE = /^[a-z][A-Za-z0-9]{0,127}$/u; const TARGET_KINDS = new Set(["ASSET_LIBRARY", "DATABLOCK", "MESH", "MODIFIER", "SCENE"]); const AUTHORIZATIONS = new Set>(["USER_EDIT", "SYSTEM"]); function unique(values: readonly T[]): boolean { return new Set(values).size === values.length; } function validateDefinition(definition: RegisteredCommand): void { if (!definition || typeof definition !== "object" || !COMMAND_TYPE.test(definition.commandType)) { throw new CommandRegistryError("REGISTRY_INVALID", "commandType is invalid"); } if (!Array.isArray(definition.targetKinds) || definition.targetKinds.length === 0 || !unique(definition.targetKinds) || definition.targetKinds.some((kind) => !TARGET_KINDS.has(kind))) { throw new CommandRegistryError("REGISTRY_INVALID", "targetKinds must contain unique supported targets"); } if (!Array.isArray(definition.authorizations) || definition.authorizations.length === 0 || !unique(definition.authorizations) || definition.authorizations.some((authorization) => !AUTHORIZATIONS.has(authorization))) { throw new CommandRegistryError("REGISTRY_INVALID", "authorizations must contain unique mutating authorities"); } if (typeof definition.execute !== "function") { throw new CommandRegistryError("REGISTRY_INVALID", "execute must be a function"); } } function contextError(context: CommandContext, message: string): CorrectiveCaseRunnerError { return new CorrectiveCaseRunnerError("INVALID_CONTEXT", `${context.targetKind}: ${message}`); } export class CorrectiveCommandRegistry { readonly schemaVersion = CORRECTIVE_COMMAND_REGISTRY_SCHEMA; private readonly commands = new Map(); register(definition: RegisteredCommand): this { validateDefinition(definition); if (this.commands.has(definition.commandType)) { throw new CommandRegistryError("REGISTRY_DUPLICATE", `commandType ${definition.commandType} is already registered`); } this.commands.set(definition.commandType, { commandType: definition.commandType, targetKinds: [...definition.targetKinds], authorizations: [...definition.authorizations], execute: definition.execute, }); return this; } has(commandType: string): boolean { return this.commands.has(commandType); } list(): string[] { return [...this.commands.keys()].sort(); } async dispatch(surface: CaseSurface, options: DispatchOptions): Promise { const run = surface === "DESKTOP" ? runDesktopCase : runWasmCase; return run({ command: options.command, mainRevision: options.mainRevision, execute: async (command) => { const definition = this.commands.get(command.commandType); if (!definition) { throw new CorrectiveCaseRunnerError("COMMAND_UNSUPPORTED", `commandType ${command.commandType} is not registered`); } if (!definition.targetKinds.includes(command.context.targetKind)) { throw contextError(command.context, "target is not authorized for this command"); } if (!definition.authorizations.includes(command.context.authorization as Exclude)) { throw contextError(command.context, "authorization is not allowed for this command"); } return definition.execute(command); }, }); } }