Files
FreeCADWasmDev/web/src/import-python-family-facade.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

43 lines
5.3 KiB
TypeScript

import { FacadeHandlerError, FacadeRequest, FacadeResponse, FacadeSerialQueue, createRequest, isFacadeRequest } from "./facade-envelope.js";
export const IMPORT_PYTHON_FAMILY_SCHEMA = "freecad-web-import/python-family-batch/v1" as const;
export const IMPORT_PYTHON_FAMILY_COMMAND = "import.pythonFamilyBatch" as const;
export interface ImportPythonFamilyRecord { apiId: string; name: string; family: string; sourcePath?: string; disposition?: "exposed" | "restricted" | "nonportable" | "planned" | "not_implemented"; }
export type ImportPythonFamilyTier = "tier-1-readonly" | "tier-2-pure" | "tier-3-stateful" | "object-contract";
export interface ImportPythonFamilyPayload { schema: typeof IMPORT_PYTHON_FAMILY_SCHEMA; module: "Import"; tier: ImportPythonFamilyTier; records: ImportPythonFamilyRecord[]; }
export interface ImportPythonFamilyNativeRecord extends ImportPythonFamilyRecord { status: "exposed" | "restricted" | "nonportable" | "not_implemented"; code: string; message: string; native: true; sourceEntry?: string; sourceRevision?: string; }
export interface ImportPythonFamilyResult { schema: typeof IMPORT_PYTHON_FAMILY_SCHEMA; module: "Import"; tier: ImportPythonFamilyTier; source: "freecad-source"; records: ImportPythonFamilyNativeRecord[]; count: number; resourcesReleased: true; }
export type ImportPythonFamilyRequest = FacadeRequest<ImportPythonFamilyPayload>;
export type ImportPythonFamilyResponse = FacadeResponse<ImportPythonFamilyResult>;
export function createImportPythonFamilyRequest(requestId: string, records: ImportPythonFamilyRecord[], tier: ImportPythonFamilyTier = "tier-1-readonly"): ImportPythonFamilyRequest {
return createRequest(requestId, IMPORT_PYTHON_FAMILY_COMMAND, { schema: IMPORT_PYTHON_FAMILY_SCHEMA, module: "Import", tier, records }, "import-python-family");
}
export function isImportPythonFamilyRequest(value: unknown): value is ImportPythonFamilyRequest {
if (!isFacadeRequest(value)) return false;
const request = value as ImportPythonFamilyRequest;
const payload = request.payload;
return request.command === IMPORT_PYTHON_FAMILY_COMMAND && payload?.schema === IMPORT_PYTHON_FAMILY_SCHEMA && payload?.module === "Import" && (payload?.tier === "tier-1-readonly" || payload?.tier === "tier-2-pure" || payload?.tier === "tier-3-stateful" || payload?.tier === "object-contract") && Array.isArray(payload.records) && payload.records.length > 0 && payload.records.every((record) => record && typeof record.apiId === "string" && typeof record.name === "string" && typeof record.family === "string");
}
type ImportKernel = { ccall(name: string, returnType: "number" | "string", argTypes?: string[], args?: unknown[]): number | string };
function dispatchJson(kernel: ImportKernel, payload: ImportPythonFamilyPayload): ImportPythonFamilyResult {
const raw = String(kernel.ccall("fc_web_dispatch_json", "string", ["string"], [JSON.stringify({ cmd: IMPORT_PYTHON_FAMILY_COMMAND, args: { tier: payload.tier, records: payload.records } })]));
let reply: unknown;
try { reply = JSON.parse(raw); } catch { throw new FacadeHandlerError("native_protocol_error", "Import native dispatcher returned invalid JSON"); }
if (!reply || typeof reply !== "object") throw new FacadeHandlerError("native_protocol_error", "Import native dispatcher returned a non-object result");
const result = reply as Record<string, unknown>;
if (result.ok !== true) throw new FacadeHandlerError(String(result.code || "native_error"), String(result.error || "Import Python family dispatch failed"));
if (result.schema !== IMPORT_PYTHON_FAMILY_SCHEMA || result.module !== "Import" || result.tier !== payload.tier || result.source !== "freecad-source") throw new FacadeHandlerError("native_protocol_error", "Import native dispatcher returned an invalid batch schema");
if (!Array.isArray(result.records) || result.records.length !== payload.records.length || result.count !== payload.records.length || result.resourcesReleased !== true) throw new FacadeHandlerError("native_protocol_error", "Import native dispatcher returned an incomplete record set");
if (result.records.some((record) => !record || typeof record !== "object" || !["exposed", "restricted", "nonportable", "not_implemented"].includes(String((record as Record<string, unknown>).status)) || (record as Record<string, unknown>).native !== true)) throw new FacadeHandlerError("native_protocol_error", "Import native dispatcher returned an invalid record status");
return result as unknown as ImportPythonFamilyResult;
}
export async function handleImportPythonFamily(payload: ImportPythonFamilyPayload, signal: AbortSignal, source: ImportKernel | Promise<ImportKernel>): Promise<ImportPythonFamilyResult> {
if (signal.aborted) throw new FacadeHandlerError("cancelled", "request was cancelled");
if (!isImportPythonFamilyRequest(createImportPythonFamilyRequest("validation", payload.records))) throw new FacadeHandlerError("invalid_fixture", "Import Python family payload is invalid");
const kernel = await source;
if (signal.aborted) throw new FacadeHandlerError("cancelled", "request was cancelled");
return dispatchJson(kernel, payload);
}
export function dispatchImportPythonFamily(queue: FacadeSerialQueue, request: ImportPythonFamilyRequest, source: ImportKernel | Promise<ImportKernel>): Promise<ImportPythonFamilyResponse> { return queue.dispatch(request, (payload, signal) => handleImportPythonFamily(payload, signal, source)); }