85 lines
7.6 KiB
TypeScript
85 lines
7.6 KiB
TypeScript
import { FacadeHandlerError, FacadeRequest, FacadeResponse, FacadeSerialQueue, createRequest, isFacadeRequest } from "./facade-envelope.js";
|
|
|
|
export const SPREADSHEET_PYTHON_FAMILY_SCHEMA = "freecad-web-spreadsheet/python-family-batch/v1" as const;
|
|
export const SPREADSHEET_PYTHON_FAMILY_COMMAND = "spreadsheet.pythonFamilyBatch" as const;
|
|
/** Native-backed Spreadsheet desktop commands exposed through the generic family dispatcher. */
|
|
export const SPREADSHEET_DESKTOP_COMMANDS = [
|
|
"Spreadsheet_AlignTop",
|
|
"Spreadsheet_AlignVCenter",
|
|
"Spreadsheet_AlignBottom",
|
|
"Spreadsheet_AlignCenter",
|
|
"Spreadsheet_AlignLeft",
|
|
"Spreadsheet_AlignRight",
|
|
"Spreadsheet_CreateSheet",
|
|
"Spreadsheet_Export",
|
|
"Spreadsheet_Import",
|
|
"Spreadsheet_MergeCells",
|
|
"Spreadsheet_SetAlias",
|
|
"Spreadsheet_SplitCell",
|
|
"Spreadsheet_StyleBold",
|
|
"Spreadsheet_StyleItalic",
|
|
"Spreadsheet_StyleUnderline",
|
|
] as const;
|
|
export type SpreadsheetDesktopCommand = typeof SPREADSHEET_DESKTOP_COMMANDS[number];
|
|
export type SpreadsheetPythonFamilyTier = "tier-1-readonly" | "tier-2-pure" | "object-contract";
|
|
export type SpreadsheetPythonFamilyStatus = "exposed" | "restricted" | "nonportable" | "not_implemented";
|
|
export interface SpreadsheetPythonFamilyRecord { apiId: string; name: string; family: string; sourcePath?: string; disposition?: string; args?: Record<string, unknown>; }
|
|
export interface SpreadsheetPythonFamilyPayload { schema: typeof SPREADSHEET_PYTHON_FAMILY_SCHEMA; module: "Spreadsheet"; tier: SpreadsheetPythonFamilyTier; records: SpreadsheetPythonFamilyRecord[]; }
|
|
export interface SpreadsheetPythonFamilyNativeRecord extends SpreadsheetPythonFamilyRecord { status: SpreadsheetPythonFamilyStatus; code: string; message: string; native: true; resourcesReleased: true; sourceEntry: string; sourceRevision: string; parameterSchema?: string; returnSchema?: string; replay?: string; }
|
|
export interface SpreadsheetPythonFamilyResult { schema: typeof SPREADSHEET_PYTHON_FAMILY_SCHEMA; module: "Spreadsheet"; tier: SpreadsheetPythonFamilyTier; source: "freecad-source"; records: SpreadsheetPythonFamilyNativeRecord[]; count: number; resourcesReleased: true; }
|
|
export type SpreadsheetPythonFamilyRequest = FacadeRequest<SpreadsheetPythonFamilyPayload>;
|
|
export type SpreadsheetPythonFamilyResponse = FacadeResponse<SpreadsheetPythonFamilyResult>;
|
|
export interface SpreadsheetPythonFamilyKernel { ccall(name: string, returnType: "number" | "string", argTypes?: string[], args?: unknown[]): number | string; }
|
|
|
|
export function createSpreadsheetPythonFamilyRequest(requestId: string, records: SpreadsheetPythonFamilyRecord[], tier: SpreadsheetPythonFamilyTier = "tier-1-readonly"): SpreadsheetPythonFamilyRequest {
|
|
return createRequest(requestId, SPREADSHEET_PYTHON_FAMILY_COMMAND, { schema: SPREADSHEET_PYTHON_FAMILY_SCHEMA, module: "Spreadsheet", tier, records }, "spreadsheet-python-family");
|
|
}
|
|
|
|
/** Build a React-visible command record without introducing a transport alias or fixture payload. */
|
|
export function createSpreadsheetDesktopCommandRecord(command: SpreadsheetDesktopCommand, args: Record<string, unknown> = {}): SpreadsheetPythonFamilyRecord {
|
|
return { apiId: `Spreadsheet:desktop:${command}`, name: command, family: "desktop-command", sourcePath: "freecad-source/src/Mod/Spreadsheet/Gui/Command.cpp", args };
|
|
}
|
|
|
|
export function isSpreadsheetPythonFamilyRequest(value: unknown): value is SpreadsheetPythonFamilyRequest {
|
|
if (!isFacadeRequest(value)) return false;
|
|
const request = value as SpreadsheetPythonFamilyRequest;
|
|
const payload = request.payload;
|
|
return request.command === SPREADSHEET_PYTHON_FAMILY_COMMAND
|
|
&& payload?.schema === SPREADSHEET_PYTHON_FAMILY_SCHEMA
|
|
&& payload?.module === "Spreadsheet"
|
|
&& (payload?.tier === "tier-1-readonly" || payload?.tier === "tier-2-pure" || 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" && typeof record.sourcePath === "string");
|
|
}
|
|
|
|
export async function handleSpreadsheetPythonFamily(payload: SpreadsheetPythonFamilyPayload, signal: AbortSignal, source: SpreadsheetPythonFamilyKernel | Promise<SpreadsheetPythonFamilyKernel>): Promise<SpreadsheetPythonFamilyResult> {
|
|
if (signal.aborted) throw new FacadeHandlerError("cancelled", "request was cancelled");
|
|
if (!isSpreadsheetPythonFamilyRequest(createSpreadsheetPythonFamilyRequest("validation", payload.records, payload.tier))) throw new FacadeHandlerError("invalid_fixture", "Spreadsheet Python family payload is invalid");
|
|
const kernel = await source;
|
|
if (signal.aborted) throw new FacadeHandlerError("cancelled", "request was cancelled");
|
|
let raw: string;
|
|
try { raw = String(kernel.ccall("fc_web_dispatch_json", "string", ["string"], [JSON.stringify({ cmd: SPREADSHEET_PYTHON_FAMILY_COMMAND, args: { tier: payload.tier, records: payload.records } })])); }
|
|
catch (error) { throw new FacadeHandlerError("native_dispatch_failed", error instanceof Error ? error.message : "Spreadsheet native dispatch failed"); }
|
|
let reply: unknown;
|
|
try { reply = JSON.parse(raw); } catch { throw new FacadeHandlerError("native_protocol_error", "Spreadsheet native dispatcher returned invalid JSON"); }
|
|
if (!reply || typeof reply !== "object") throw new FacadeHandlerError("native_protocol_error", "Spreadsheet 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 || "Spreadsheet Python family dispatch failed"));
|
|
if (result.schema !== SPREADSHEET_PYTHON_FAMILY_SCHEMA || result.module !== "Spreadsheet" || result.tier !== payload.tier || result.source !== "freecad-source" || result.count !== payload.records.length || result.resourcesReleased !== true || !Array.isArray(result.records) || result.records.length !== payload.records.length) throw new FacadeHandlerError("native_protocol_error", "Spreadsheet native dispatcher returned an invalid batch schema");
|
|
const terminal = new Set<SpreadsheetPythonFamilyStatus>(["exposed", "restricted", "nonportable", "not_implemented"]);
|
|
for (let index = 0; index < payload.records.length; index += 1) {
|
|
const requested = payload.records[index];
|
|
const native = result.records[index];
|
|
if (!native || typeof native !== "object") throw new FacadeHandlerError("native_protocol_error", "Spreadsheet native dispatcher returned a non-object record", { index });
|
|
const record = native as Record<string, unknown>;
|
|
if (record.apiId !== requested.apiId || record.name !== requested.name || record.family !== requested.family || record.sourceEntry !== requested.sourcePath) throw new FacadeHandlerError("native_protocol_error", "Spreadsheet native dispatcher returned a record identity mismatch", { index, expectedApiId: requested.apiId, actualApiId: record.apiId });
|
|
if (!terminal.has(String(record.status) as SpreadsheetPythonFamilyStatus) || record.native !== true || record.resourcesReleased !== true || typeof record.sourceRevision !== "string") throw new FacadeHandlerError("native_protocol_error", "Spreadsheet native dispatcher returned an invalid record status", { index, apiId: requested.apiId });
|
|
}
|
|
return result as unknown as SpreadsheetPythonFamilyResult;
|
|
}
|
|
|
|
export function dispatchSpreadsheetPythonFamily(queue: FacadeSerialQueue, request: SpreadsheetPythonFamilyRequest, source: SpreadsheetPythonFamilyKernel | Promise<SpreadsheetPythonFamilyKernel>): Promise<SpreadsheetPythonFamilyResponse> {
|
|
return queue.dispatch(request, (payload, signal) => handleSpreadsheetPythonFamily(payload, signal, source));
|
|
}
|