Files
workinf_Blender_Wasm/web/protocol/library-override-writer.ts
mes123456 380cbed4ff
Some checks are pending
M6 deployable RC / quick (push) Waiting to run
M6 deployable RC / chromium (push) Blocked by required conditions
M6 deployable RC / release (push) Blocked by required conditions
Checkpoint web parity through Chromium input tasks
2026-08-19 10:39:03 -04:00

157 lines
7.4 KiB
TypeScript

import type { ErrorCode } from "./error";
export const LIBRARY_OVERRIDE_WRITER_SCHEMA = 1 as const;
export const LIBRARY_OVERRIDE_WRITER_OPERATION = "SET_M12_OVERRIDE_VALUE" as const;
export const LIBRARY_OVERRIDE_PROPERTY_PATH = '["m12_override_value"]' as const;
export interface OverrideWriterStateIR {
schemaVersion: typeof LIBRARY_OVERRIDE_WRITER_SCHEMA;
revision: number;
localDataBlockId: string;
referenceSourceDataBlockId: string;
hierarchyRootDataBlockId: string;
owner: "LOCAL_OVERRIDE";
readOnly: false;
referenceReadOnly: true;
propertyPath: typeof LIBRARY_OVERRIDE_PROPERTY_PATH;
value: number;
}
export interface OverrideWriterRequestIR {
schemaVersion: typeof LIBRARY_OVERRIDE_WRITER_SCHEMA;
operation: typeof LIBRARY_OVERRIDE_WRITER_OPERATION;
baseRevision: number;
localDataBlockId: string;
referenceSourceDataBlockId: string;
hierarchyRootDataBlockId: string;
owner: "LOCAL_OVERRIDE";
readOnly: false;
referenceReadOnly: true;
propertyPath: typeof LIBRARY_OVERRIDE_PROPERTY_PATH;
value: number;
}
export interface OverrideWriterDecisionIR {
status: "APPLIED" | "BLOCKED";
code: ErrorCode | null;
state: OverrideWriterStateIR;
}
export class OverrideWriterValidationError extends Error {
readonly code: ErrorCode;
readonly path?: string;
constructor(code: ErrorCode, message: string, path?: string) {
super(`${code}: ${message}`);
this.name = "OverrideWriterValidationError";
this.code = code;
this.path = path;
}
}
const DATA_BLOCK_ID = /^[A-Za-z0-9][A-Za-z0-9:._/ -]{0,255}$/;
const MAX_VALUE = 1_000_000;
function record(value: unknown, path: string): Record<string, unknown> {
if (typeof value !== "object" || value === null || Array.isArray(value)) {
throw new OverrideWriterValidationError("TASK_VALIDATION_FAILED", `${path} must be an object`, path);
}
return value as Record<string, unknown>;
}
function exactKeys(value: Record<string, unknown>, expected: readonly string[], path: string): void {
const actual = Object.keys(value).sort();
const allowed = [...expected].sort();
if (actual.length !== allowed.length || actual.some((key, index) => key !== allowed[index])) {
throw new OverrideWriterValidationError("TASK_VALIDATION_FAILED", `${path} contains undeclared fields`, path);
}
}
function integer(value: unknown, path: string): number {
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
throw new OverrideWriterValidationError("TASK_VALIDATION_FAILED", `${path} must be a safe integer >= 0`, path);
}
return value;
}
function dataBlockId(value: unknown, path: string): string {
if (typeof value !== "string" || !DATA_BLOCK_ID.test(value)) {
throw new OverrideWriterValidationError("TASK_VALIDATION_FAILED", `${path} is invalid`, path);
}
return value;
}
function valueNumber(value: unknown, path: string): number {
if (typeof value !== "number" || !Number.isFinite(value) || Math.abs(value) > MAX_VALUE) {
throw new OverrideWriterValidationError("TASK_VALIDATION_FAILED", `${path} is outside the bounded float range`, path);
}
return value;
}
export function parseOverrideWriterState(value: unknown): OverrideWriterStateIR {
const state = record(value, "state");
exactKeys(state, ["schemaVersion", "revision", "localDataBlockId", "referenceSourceDataBlockId", "hierarchyRootDataBlockId", "owner", "readOnly", "referenceReadOnly", "propertyPath", "value"], "state");
if (state.schemaVersion !== LIBRARY_OVERRIDE_WRITER_SCHEMA) throw new OverrideWriterValidationError("PROTOCOL_MISMATCH", "Unsupported override writer state schema", "schemaVersion");
if (state.owner !== "LOCAL_OVERRIDE" || state.readOnly !== false || state.referenceReadOnly !== true) throw new OverrideWriterValidationError("TASK_VALIDATION_FAILED", "state ownership semantics are invalid", "owner");
if (state.propertyPath !== LIBRARY_OVERRIDE_PROPERTY_PATH) throw new OverrideWriterValidationError("EDITOR_WRITER_UNAVAILABLE", "state property path is not the verified writer path", "propertyPath");
return {
schemaVersion: LIBRARY_OVERRIDE_WRITER_SCHEMA,
revision: integer(state.revision, "revision"),
localDataBlockId: dataBlockId(state.localDataBlockId, "localDataBlockId"),
referenceSourceDataBlockId: dataBlockId(state.referenceSourceDataBlockId, "referenceSourceDataBlockId"),
hierarchyRootDataBlockId: dataBlockId(state.hierarchyRootDataBlockId, "hierarchyRootDataBlockId"),
owner: "LOCAL_OVERRIDE",
readOnly: false,
referenceReadOnly: true,
propertyPath: LIBRARY_OVERRIDE_PROPERTY_PATH,
value: valueNumber(state.value, "value"),
};
}
export function parseOverrideWriterRequest(value: unknown): OverrideWriterRequestIR {
const request = record(value, "request");
exactKeys(request, ["schemaVersion", "operation", "baseRevision", "localDataBlockId", "referenceSourceDataBlockId", "hierarchyRootDataBlockId", "owner", "readOnly", "referenceReadOnly", "propertyPath", "value"], "request");
if (request.schemaVersion !== LIBRARY_OVERRIDE_WRITER_SCHEMA) throw new OverrideWriterValidationError("PROTOCOL_MISMATCH", "Unsupported override writer request schema", "schemaVersion");
if (request.operation !== LIBRARY_OVERRIDE_WRITER_OPERATION) throw new OverrideWriterValidationError("TASK_VALIDATION_FAILED", "override writer operation is invalid", "operation");
if (request.owner !== "LOCAL_OVERRIDE" || request.readOnly !== false || request.referenceReadOnly !== true) throw new OverrideWriterValidationError("LINKED_DATA_MUTATION_BLOCKED", "override writer must retain local override ownership", "owner");
if (request.propertyPath !== LIBRARY_OVERRIDE_PROPERTY_PATH) throw new OverrideWriterValidationError("EDITOR_WRITER_UNAVAILABLE", "only the verified override property is writable", "propertyPath");
return {
schemaVersion: LIBRARY_OVERRIDE_WRITER_SCHEMA,
operation: LIBRARY_OVERRIDE_WRITER_OPERATION,
baseRevision: integer(request.baseRevision, "baseRevision"),
localDataBlockId: dataBlockId(request.localDataBlockId, "localDataBlockId"),
referenceSourceDataBlockId: dataBlockId(request.referenceSourceDataBlockId, "referenceSourceDataBlockId"),
hierarchyRootDataBlockId: dataBlockId(request.hierarchyRootDataBlockId, "hierarchyRootDataBlockId"),
owner: "LOCAL_OVERRIDE",
readOnly: false,
referenceReadOnly: true,
propertyPath: LIBRARY_OVERRIDE_PROPERTY_PATH,
value: valueNumber(request.value, "value"),
};
}
function blocked(state: OverrideWriterStateIR, code: ErrorCode): OverrideWriterDecisionIR {
return { status: "BLOCKED", code, state: { ...state } };
}
export function applyOverrideWriter(stateValue: unknown, requestValue: unknown): OverrideWriterDecisionIR {
const state = parseOverrideWriterState(stateValue);
let request: OverrideWriterRequestIR;
try {
request = parseOverrideWriterRequest(requestValue);
}
catch (error) {
const code = error instanceof OverrideWriterValidationError ? error.code : "TASK_VALIDATION_FAILED";
return blocked(state, code);
}
if (request.baseRevision !== state.revision) return blocked(state, "REVISION_CONFLICT");
if (request.localDataBlockId !== state.localDataBlockId || request.referenceSourceDataBlockId !== state.referenceSourceDataBlockId || request.hierarchyRootDataBlockId !== state.hierarchyRootDataBlockId) {
return blocked(state, "ASSET_SOURCE_HASH_MISMATCH");
}
return {
status: "APPLIED",
code: null,
state: { ...state, revision: state.revision + 1, value: request.value },
};
}