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; export type CloudPythonFamilyResponse = FacadeResponse; 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; 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).status)) || (record as Record).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): Promise { 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): Promise { return queue.dispatch(request, (payload, signal) => handleCloudPythonFamily(payload, signal, source)); }