Files
FreeCADWasmDev/web/src/cloud-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

84 lines
5.1 KiB
TypeScript

import { FacadeHandlerError, FacadeRequest, FacadeResponse, FacadeSerialQueue, createRequest, isFacadeRequest } from "./facade-envelope.js";
import type { CloudKernelModule } from "./cloud-facade.js";
export const CLOUD_PYTHON_FAMILY_SCHEMA = "freecad-web-cloud/python-family-batch/v1" as const;
export const CLOUD_PYTHON_FAMILY_COMMAND = "cloud.pythonFamilyBatch" as const;
export const CLOUD_PYTHON_FAMILY_TIER = "tier-1-readonly" as const;
export interface CloudPythonFamilyRecord { apiId: string; name: string; family: string; }
export interface CloudPythonFamilyPayload {
schema: typeof CLOUD_PYTHON_FAMILY_SCHEMA;
module: "Cloud";
tier: typeof CLOUD_PYTHON_FAMILY_TIER;
records: CloudPythonFamilyRecord[];
}
export interface CloudPythonFamilyNativeRecord extends CloudPythonFamilyRecord {
status: "exposed" | "restricted" | "nonportable" | "not_implemented";
code: string;
message: string;
native: true;
sourceEntry?: string;
sourceRevision?: string;
}
export interface CloudPythonFamilyResult {
schema: typeof CLOUD_PYTHON_FAMILY_SCHEMA;
module: "Cloud";
tier: typeof CLOUD_PYTHON_FAMILY_TIER;
source: "freecad-source";
records: CloudPythonFamilyNativeRecord[];
count: number;
resourcesReleased: true;
}
export type CloudPythonFamilyRequest = FacadeRequest<CloudPythonFamilyPayload>;
export type CloudPythonFamilyResponse = FacadeResponse<CloudPythonFamilyResult>;
export function createCloudPythonFamilyRequest(requestId: string, records: CloudPythonFamilyRecord[]): CloudPythonFamilyRequest {
return createRequest(requestId, CLOUD_PYTHON_FAMILY_COMMAND, {
schema: CLOUD_PYTHON_FAMILY_SCHEMA,
module: "Cloud",
tier: CLOUD_PYTHON_FAMILY_TIER,
records,
}, "cloud-python-family");
}
export function isCloudPythonFamilyRequest(value: unknown): value is CloudPythonFamilyRequest {
if (!isFacadeRequest(value)) return false;
const request = value as CloudPythonFamilyRequest;
const payload = request.payload;
return request.command === CLOUD_PYTHON_FAMILY_COMMAND
&& payload?.schema === CLOUD_PYTHON_FAMILY_SCHEMA
&& payload?.module === "Cloud"
&& payload?.tier === CLOUD_PYTHON_FAMILY_TIER
&& 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");
}
function dispatchJson(kernel: CloudKernelModule, payload: CloudPythonFamilyPayload): CloudPythonFamilyResult {
const raw = String(kernel.ccall("fc_web_dispatch_json", "string", ["string"], [JSON.stringify({
cmd: CLOUD_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", "Cloud native dispatcher returned invalid JSON"); }
if (!reply || typeof reply !== "object") throw new FacadeHandlerError("native_protocol_error", "Cloud 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 || "Cloud Python family dispatch failed"));
if (result.schema !== CLOUD_PYTHON_FAMILY_SCHEMA || result.module !== "Cloud" || result.tier !== CLOUD_PYTHON_FAMILY_TIER || result.source !== "freecad-source") throw new FacadeHandlerError("native_protocol_error", "Cloud 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", "Cloud 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", "Cloud native dispatcher returned an invalid record status");
return result as unknown as CloudPythonFamilyResult;
}
export async function handleCloudPythonFamily(payload: CloudPythonFamilyPayload, signal: AbortSignal, source: CloudKernelModule | Promise<CloudKernelModule>): Promise<CloudPythonFamilyResult> {
if (signal.aborted) throw new FacadeHandlerError("cancelled", "request was cancelled");
if (!isCloudPythonFamilyRequest(createCloudPythonFamilyRequest("validation", payload.records))) throw new FacadeHandlerError("invalid_fixture", "Cloud 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 dispatchCloudPythonFamily(queue: FacadeSerialQueue, request: CloudPythonFamilyRequest, source: CloudKernelModule | Promise<CloudKernelModule>): Promise<CloudPythonFamilyResponse> {
return queue.dispatch(request, (payload, signal) => handleCloudPythonFamily(payload, signal, source));
}