Files
FreeCADWasmDev/web/src/command-runtime.ts
wangdequan 9a7c4f0335 Save FreeCAD Web workspace sources and user validation evidence
IFC native binding remains incomplete; preserve accurate test results.
2026-09-15 22:01:58 +08:00

54 lines
2.7 KiB
TypeScript

import type { FacadeResponse } from "./facade-envelope.js";
import { WEB_MODULE_REGISTRY, getWebModuleWorkerForCommand } from "./module-registry.generated.js";
import { WebModuleRouter } from "./module-router.js";
import { FEM_NATIVE_BOUNDARY } from "./fem-boundary.js";
import { nativeSourceDecision } from "./native-source-policy.js";
export interface WebCommandDescriptor {
id: string;
module: string;
command: string;
worker: string | null;
category: "create" | "inspect" | "python" | "persistence" | "format" | "boundary";
runnable: boolean;
reason: string;
}
function category(command: string): WebCommandDescriptor["category"] {
if (command.includes("save") || command.includes("reopen")) return "persistence";
if (command.includes("inspect")) return "inspect";
if (command.includes("python")) return "python";
if (command.includes("import") || command.includes("export")) return "format";
if (command.includes("boundary") || command.includes("server")) return "boundary";
return "create";
}
export function listWebCommands(query = ""): WebCommandDescriptor[] {
const normalized = query.trim().toLowerCase();
return WEB_MODULE_REGISTRY.flatMap((route) => route.commands.map((command) => {
const worker = getWebModuleWorkerForCommand(route.module, command) ?? route.primaryWorker;
const boundary = route.guiDependency && category(command) === "boundary";
const sourceDecision = nativeSourceDecision(route.module, command);
const nativeBlocked = route.module === "Fem";
return {
id: `${route.module}:${command}`,
module: route.module,
command,
worker: worker ?? null,
category: category(command),
runnable: !boundary && !sourceDecision,
reason: sourceDecision?.message ?? (nativeBlocked ? FEM_NATIVE_BOUNDARY.message : boundary ? "GUI/external capability boundary" : "registered Worker route"),
};
})).filter((descriptor) => !normalized || `${descriptor.module} ${descriptor.command} ${descriptor.category}`.toLowerCase().includes(normalized));
}
export class WebCommandRuntime {
readonly commands = listWebCommands();
constructor(private readonly router: WebModuleRouter) {}
async execute(descriptor: WebCommandDescriptor, payload: unknown, documentId = "workbench-document", signal?: AbortSignal): Promise<FacadeResponse> {
if (!descriptor.runnable) return { schema: "freecad-web-facade/v1", version: 1, kind: "error", requestId: `command-${descriptor.id}`, error: { code: "restricted", message: descriptor.reason, diagnostic: { module: descriptor.module, command: descriptor.command } } };
return this.router.request(descriptor.module, descriptor.command, payload, documentId, signal);
}
}