62 lines
9.0 KiB
TypeScript
62 lines
9.0 KiB
TypeScript
import { FacadeHandlerError, FacadeRequest, FacadeResponse, FacadeSerialQueue, createRequest, isFacadeRequest } from "./facade-envelope.js";
|
|
|
|
export const START_SCHEMA = "freecad-web-start/module/v1" as const;
|
|
export const START_COMMAND = "start.moduleImport" as const;
|
|
export const START_DESKTOP_COMMAND = "Start:desktop:Start_Start" as const;
|
|
export const START_IMPORT_SVG_COMMAND = "start.importSvg" as const;
|
|
export interface StartImportSvgPayload { schema: "freecad-web-start/import-svg/v1"; module: "Start"; action: "import"; path: string; content?: string }
|
|
export interface StartImportSvgResult { schema: "freecad-web-start/import-svg/v1"; module: "Start"; command: "Start_ImportSVG"; status: "imported"; document: string; object: string; label: string; recomputed: true; objectCount: number; objects: Array<{ object: string; length: number; valid: true; isNull: false }> }
|
|
export interface StartDesktopPayload { schema: "freecad-web-start/desktop/v1"; module: "Start"; action: "Start_Start"; args: Record<string, unknown> }
|
|
export interface StartDesktopResult { schema: "freecad-web-start/desktop/v1"; module: "Start"; command: "Start_Start"; action: "show"; status: "exposed"; view: "StartView"; active: true; documentCount: number; activeDocument: string | null; documents: Array<{ name: string; label: string }>; args: Record<string, unknown> }
|
|
export const START_FIXTURE = { schema: START_SCHEMA, module: "Start", action: "module-import", sourceEntry: "src/Mod/Start/App/AppStart.cpp" } as const;
|
|
export interface StartPayload { schema: typeof START_SCHEMA; module: "Start"; action: "module-import"; sourceEntry: string }
|
|
export interface StartResult { schema: typeof START_SCHEMA; module: "Start"; action: "module-import"; sourceEntry: typeof START_FIXTURE.sourceEntry; moduleAvailable: true; nativeTypeCount: 0; pythonApiCount: 0; resourcesReleased: true }
|
|
export interface StartKernel { ccall(name: string, type: "number" | "string", argTypes?: string[], args?: unknown[]): number | string }
|
|
export type StartRequest = FacadeRequest<StartPayload>;
|
|
export type StartResponse = FacadeResponse<StartResult>;
|
|
export function createStartRequest(id: string, payload: StartPayload = START_FIXTURE): StartRequest { return createRequest(id, START_COMMAND, payload, "start"); }
|
|
export function isStartRequest(value: unknown): value is StartRequest { if (!isFacadeRequest(value)) return false; const request = value as StartRequest; const payload = request.payload; return request.command === START_COMMAND && payload?.schema === START_SCHEMA && payload?.module === "Start" && payload?.action === "module-import" && typeof payload?.sourceEntry === "string"; }
|
|
export async function handleStart(payload: StartPayload, signal: AbortSignal, source: StartKernel | Promise<StartKernel>): Promise<StartResult> {
|
|
if (signal.aborted) throw new FacadeHandlerError("cancelled", "request was cancelled");
|
|
if (!isStartRequest(createStartRequest("validation", payload)) || payload.sourceEntry !== START_FIXTURE.sourceEntry) throw new FacadeHandlerError("revision_mismatch", "Start source entry is not locked", { field: "sourceEntry" });
|
|
const kernel = await source;
|
|
if (signal.aborted) throw new FacadeHandlerError("cancelled", "request was cancelled");
|
|
if (kernel.ccall("fc_web_start_initialize", "number") !== 1) throw new FacadeHandlerError("start_failed", String(kernel.ccall("fc_web_start_error", "string")));
|
|
const result = { schema: START_SCHEMA, module: "Start", action: "module-import", sourceEntry: START_FIXTURE.sourceEntry, moduleAvailable: Number(kernel.ccall("fc_web_start_module_available", "number")) === 1, nativeTypeCount: Number(kernel.ccall("fc_web_start_type_count", "number")), pythonApiCount: Number(kernel.ccall("fc_web_start_python_api_count", "number")), resourcesReleased: Number(kernel.ccall("fc_web_start_resources_released", "number")) === 1 } as StartResult;
|
|
if (!result.moduleAvailable || result.nativeTypeCount !== 0 || result.pythonApiCount !== 0 || !result.resourcesReleased) throw new FacadeHandlerError("result_mismatch", "Start module result invalid");
|
|
return result;
|
|
}
|
|
export function dispatchStart(queue: FacadeSerialQueue, request: StartRequest, source: StartKernel | Promise<StartKernel>): Promise<StartResponse> { return queue.dispatch(request, (payload, signal) => handleStart(payload, signal, source)); }
|
|
export type StartDesktopRequest = FacadeRequest<StartDesktopPayload>;
|
|
export function createStartDesktopRequest(id: string, args: Record<string, unknown> = {}): StartDesktopRequest { return createRequest(id, START_DESKTOP_COMMAND, { schema: "freecad-web-start/desktop/v1", module: "Start", action: "Start_Start", args }, "start"); }
|
|
export function isStartDesktopRequest(value: unknown): value is StartDesktopRequest { return isFacadeRequest(value) && value.command === START_DESKTOP_COMMAND; }
|
|
export async function handleStartDesktop(payload: StartDesktopPayload, signal: AbortSignal, source: StartKernel | Promise<StartKernel>): Promise<StartDesktopResult> { if (signal.aborted) throw new FacadeHandlerError("cancelled", "request was cancelled"); const kernel = await source; const raw = String(kernel.ccall("fc_web_dispatch_json", "string", ["string"], [JSON.stringify({ cmd: "Start_Start", args: payload.args || {} })])); let result: StartDesktopResult; try { result = JSON.parse(raw) as StartDesktopResult; } catch { throw new FacadeHandlerError("start_failed", raw); } if (!result.active || result.status !== "exposed" || result.view !== "StartView" || !Array.isArray(result.documents) || result.documentCount !== result.documents.length) throw new FacadeHandlerError("start_failed", "Start view model is invalid"); return { ...result, schema: "freecad-web-start/desktop/v1", module: "Start", command: "Start_Start", args: payload.args || {} }; }
|
|
export function dispatchStartDesktop(queue: FacadeSerialQueue, request: StartDesktopRequest, source: StartKernel | Promise<StartKernel>): Promise<FacadeResponse<StartDesktopResult>> { return queue.dispatch(request, (payload, signal) => handleStartDesktop(payload, signal, source)); }
|
|
export type StartImportSvgRequest = FacadeRequest<StartImportSvgPayload>;
|
|
export function createStartImportSvgRequest(id: string, path: string, content = ""): StartImportSvgRequest { return createRequest(id, START_IMPORT_SVG_COMMAND, { schema: "freecad-web-start/import-svg/v1", module: "Start", action: "import", path, content }, "start"); }
|
|
export function isStartImportSvgRequest(value: unknown): value is StartImportSvgRequest {
|
|
if (!isFacadeRequest(value) || value.command !== START_IMPORT_SVG_COMMAND) return false;
|
|
const payload = (value as StartImportSvgRequest).payload;
|
|
return payload?.schema === "freecad-web-start/import-svg/v1" && payload.module === "Start" && payload.action === "import"
|
|
&& typeof payload.path === "string" && (payload.content === undefined || typeof payload.content === "string");
|
|
}
|
|
export async function handleStartImportSvg(payload: StartImportSvgPayload, signal: AbortSignal, source: StartKernel | Promise<StartKernel>): Promise<StartImportSvgResult> {
|
|
if (signal.aborted) throw new FacadeHandlerError("cancelled", "request was cancelled");
|
|
if (!isStartImportSvgRequest(createRequest("validation", START_IMPORT_SVG_COMMAND, payload))) throw new FacadeHandlerError("invalid_request", "SVG input requires a path and optional text content");
|
|
if (!payload.path.toLowerCase().endsWith(".svg")) throw new FacadeHandlerError("invalid_path", "Start SVG import requires a .svg path", { path: payload.path });
|
|
const kernel = await source;
|
|
if (signal.aborted) throw new FacadeHandlerError("cancelled", "request was cancelled");
|
|
const raw = String(kernel.ccall("fc_web_dispatch_json", "string", ["string"], [JSON.stringify({ cmd: "Start_ImportSVG", args: { path: payload.path, content: payload.content ?? "" } })]));
|
|
let native: Omit<StartImportSvgResult, "schema" | "module">;
|
|
try { native = JSON.parse(raw); } catch { throw new FacadeHandlerError("import_failed", raw); }
|
|
if (!native || native.command !== "Start_ImportSVG" || native.status !== "imported" || typeof native.document !== "string" || !native.document
|
|
|| typeof native.object !== "string" || !native.object || native.recomputed !== true || !Number.isSafeInteger(native.objectCount)
|
|
|| !Array.isArray(native.objects) || !native.objects.length || native.objectCount < native.objects.length
|
|
|| native.objects.some(object => !object || typeof object.object !== "string" || !object.object || object.valid !== true || object.isNull !== false
|
|
|| !Number.isFinite(object.length) || object.length <= 0) || !native.objects.some(object => object.object === native.object)
|
|
|| new Set(native.objects.map(object => object.object)).size !== native.objects.length)
|
|
throw new FacadeHandlerError("import_failed", "Start SVG import result is invalid");
|
|
return { ...native, schema: "freecad-web-start/import-svg/v1", module: "Start" };
|
|
}
|
|
export function dispatchStartImportSvg(queue: FacadeSerialQueue, request: StartImportSvgRequest, source: StartKernel | Promise<StartKernel>): Promise<FacadeResponse<StartImportSvgResult>> { return queue.dispatch(request, (payload, signal) => handleStartImportSvg(payload, signal, source)); }
|