Files
workinf_Blender_Wasm/web/protocol/corrective-main-transaction.ts
mes123456 a081c68c87
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
Reorganize Blender Web execution around capabilities
2026-08-24 18:37:46 -04:00

93 lines
3.1 KiB
TypeScript

import {
CorrectiveCaseRunnerError,
MainMutationCommand,
MainMutationResult,
} from "./corrective-case-runner";
export interface MainSnapshot<T> {
value: T;
revision: number;
dirty: boolean;
}
export interface AtomicMutationOutcome {
delta: Record<string, unknown>;
persistence?: Record<string, unknown> | null;
}
export type DraftMutation<T> = (draft: T, command: MainMutationCommand) => AtomicMutationOutcome | Promise<AtomicMutationOutcome>;
export class InMemoryMainStore<T> {
private value: T;
private currentRevision: number;
private currentDirty: boolean;
private readonly clone: (value: T) => T;
constructor(value: T, revision: number, dirty: boolean, clone: (value: T) => T) {
if (!Number.isSafeInteger(revision) || revision < 0) throw new Error("revision must be a non-negative integer");
this.clone = clone;
this.value = clone(value);
this.currentRevision = revision;
this.currentDirty = dirty;
}
snapshot(): MainSnapshot<T> {
return { value: this.clone(this.value), revision: this.currentRevision, dirty: this.currentDirty };
}
commit(value: T, revision: number, dirty: boolean): void {
if (!Number.isSafeInteger(revision) || revision < 0) throw new Error("revision must be a non-negative integer");
this.value = this.clone(value);
this.currentRevision = revision;
this.currentDirty = dirty;
}
restore(snapshot: MainSnapshot<T>): void {
this.commit(snapshot.value, snapshot.revision, snapshot.dirty);
}
}
function validOutcome(value: unknown): value is AtomicMutationOutcome {
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
const candidate = value as Record<string, unknown>;
if (!candidate.delta || typeof candidate.delta !== "object" || Array.isArray(candidate.delta)) return false;
return candidate.persistence === undefined || candidate.persistence === null ||
(typeof candidate.persistence === "object" && !Array.isArray(candidate.persistence));
}
export async function runAtomicMutation<T>(
store: InMemoryMainStore<T>,
command: MainMutationCommand,
mutate: DraftMutation<T>,
): Promise<MainMutationResult> {
const before = store.snapshot();
if (command.baseRevision !== before.revision) {
throw new CorrectiveCaseRunnerError("REVISION_CONFLICT", "base revision does not match current Main");
}
try {
const draft = store.snapshot().value;
const outcome = await mutate(draft, command);
if (!validOutcome(outcome)) {
throw new CorrectiveCaseRunnerError("COMMAND_MALFORMED", "atomic mutation outcome is invalid");
}
store.commit(draft, before.revision + 1, true);
return {
schemaVersion: 1,
requestId: command.requestId,
status: "FINISHED",
code: "OK",
baseRevision: command.baseRevision,
mainRevisionBefore: before.revision,
mainRevisionAfter: before.revision + 1,
mainMutation: "CHANGED",
delta: { ...outcome.delta },
persistence: outcome.persistence === undefined ? { dirty: true } : outcome.persistence,
error: null,
};
} catch (error) {
store.restore(before);
throw error;
}
}