import { CorrectiveCaseRunnerError, MainMutationCommand, MainMutationResult, } from "./corrective-case-runner"; export interface MainSnapshot { value: T; revision: number; dirty: boolean; } export interface AtomicMutationOutcome { delta: Record; persistence?: Record | null; } export type DraftMutation = (draft: T, command: MainMutationCommand) => AtomicMutationOutcome | Promise; export class InMemoryMainStore { 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 { 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): 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; 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( store: InMemoryMainStore, command: MainMutationCommand, mutate: DraftMutation, ): Promise { 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; } }