Files
workinf_Blender_Wasm/web/protocol/library-linked-mutation.ts
mes123456 380cbed4ff
Some checks failed
M6 deployable RC / quick (push) Has been cancelled
M6 deployable RC / chromium (push) Has been cancelled
M6 deployable RC / release (push) Has been cancelled
Checkpoint web parity through Chromium input tasks
2026-08-19 10:39:03 -04:00

92 lines
4.1 KiB
TypeScript

import { blockedGate, capabilityIssue, type CapabilityGateResult } from "./capability-gates";
import type { ErrorCode } from "./error";
export const LIBRARY_LINKED_MUTATION_SCHEMA = 1 as const;
export const LINKED_DATA_WRITER_OPERATIONS = [
"OBJECT_TRANSFORM",
"MESH_GEOMETRY",
"MESH_MATERIAL_SLOT",
"MATERIAL_PROPERTIES",
"MATERIAL_IMAGE_NODE",
"IMAGE_PACKED_DATA",
] as const;
export type LinkedDataWriterOperation = typeof LINKED_DATA_WRITER_OPERATIONS[number];
export interface LinkedDataMutationIR {
schemaVersion: typeof LIBRARY_LINKED_MUTATION_SCHEMA;
operation: LinkedDataWriterOperation;
dataBlockId: string;
baseRevision: number;
owner: "SOURCE_LIBRARY";
linkedLibrary: true;
readOnly: true;
}
export class LinkedDataMutationError extends Error {
readonly code: ErrorCode;
readonly path?: string;
constructor(code: ErrorCode, message: string, path?: string) {
super(message);
this.name = "LinkedDataMutationError";
this.code = code;
this.path = path;
}
}
function record(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function exactKeys(value: Record<string, unknown>): void {
const expected = ["schemaVersion", "operation", "dataBlockId", "baseRevision", "owner", "linkedLibrary", "readOnly"];
const actual = Object.keys(value).sort();
if (actual.length !== expected.length || actual.some((key, index) => key !== expected.slice().sort()[index])) {
throw new LinkedDataMutationError("TASK_VALIDATION_FAILED", "linked data mutation contains unsupported fields");
}
}
export function parseLinkedDataMutation(value: unknown): LinkedDataMutationIR {
if (!record(value) || value.schemaVersion !== LIBRARY_LINKED_MUTATION_SCHEMA) {
throw new LinkedDataMutationError("PROTOCOL_MISMATCH", "Unsupported linked data mutation schema");
}
exactKeys(value);
if (!LINKED_DATA_WRITER_OPERATIONS.includes(value.operation as LinkedDataWriterOperation)) {
throw new LinkedDataMutationError("TASK_VALIDATION_FAILED", "linked data mutation operation is unsupported", "operation");
}
if (typeof value.dataBlockId !== "string" || value.dataBlockId.length === 0 || value.dataBlockId.length > 256) {
throw new LinkedDataMutationError("TASK_VALIDATION_FAILED", "dataBlockId is invalid", "dataBlockId");
}
if (typeof value.baseRevision !== "number" || !Number.isSafeInteger(value.baseRevision) || value.baseRevision < 0) {
throw new LinkedDataMutationError("TASK_VALIDATION_FAILED", "baseRevision is invalid", "baseRevision");
}
if (value.owner !== "SOURCE_LIBRARY" || value.linkedLibrary !== true || value.readOnly !== true) {
throw new LinkedDataMutationError("LINKED_DATA_MUTATION_BLOCKED", "linked data must remain SOURCE_LIBRARY/readOnly", "ownership");
}
return {
schemaVersion: LIBRARY_LINKED_MUTATION_SCHEMA,
operation: value.operation as LinkedDataWriterOperation,
dataBlockId: value.dataBlockId,
baseRevision: value.baseRevision,
owner: "SOURCE_LIBRARY",
linkedLibrary: true,
readOnly: true,
};
}
export function gateLinkedDataMutation(value: unknown, currentRevision: number): CapabilityGateResult {
let request: LinkedDataMutationIR;
try {
request = parseLinkedDataMutation(value);
}
catch (error) {
const code = error instanceof LinkedDataMutationError ? error.code : "TASK_VALIDATION_FAILED";
const message = error instanceof Error ? error.message : "linked data mutation is invalid";
return blockedGate("N-023", "LINKED_DATA_WRITER", [capabilityIssue(code, message, error instanceof LinkedDataMutationError ? error.path : undefined, false)]);
}
if (!Number.isSafeInteger(currentRevision) || currentRevision < 0 || request.baseRevision !== currentRevision) {
return blockedGate("N-023", `LINKED_${request.operation}`, [capabilityIssue("REVISION_CONFLICT", "linked data mutation revision is stale", "baseRevision", false)]);
}
return blockedGate("N-023", `LINKED_${request.operation}`, [capabilityIssue("LINKED_DATA_MUTATION_BLOCKED", "linked-library data is read-only", "readOnly", false)]);
}