📄 src/models/replay.ts
D-OPEN SOVEREIGN
Documentation & Insights
Applies a decoded batch of patches to the **in-memory ORM** — the v2
replacement for v1's `deserializeOp` + `SyncRestoredPatches`.
The two v1 functions collapse in v2:
- **decode** is `evmToPatch(payload, versionsFile)` (whole batch, version-driven);
- **persist** is `deserializePatches()` (writes to IndexedDB);
- this function is the missing **routing + apply** step only.
Each EVM edit op is routed to its target record by `(objectType, objectId)`
and applied via `record.patch(op)`, which mutates in-memory state, fires
reactivity, and propagates inverse relations. Records not currently
materialized in the LRU are **skipped** (their ops remain in IndexedDB and
are folded in when the record is next hydrated — the Direction-B path).
Local analytics ops carry no object edit and are ignored here (they feed the
stats store on boot).
`setReplay(true)` brackets the loop so no apply is mistaken for user
authoring (defensive: `record.patch()` does not itself queue, but inverse
propagation and future code paths might).
Idempotent: re-applying the same ops converges to the same state
(`patch()` is a last-writer-wins assignment / Set add-remove).
@returns counts of ops applied to live records vs. skipped (not materialized).1/*
2 * Copyright (c) 2026 DataPond D-Library Pty Ltd. ("The Sanctuary")
3 * Non-Profit Public Library — The World Library
4 *
5 * D-SAFE Certification issued by POND Enterprise.
6 * Published under the D-Open Code Sovereign Licence v1.0 framework
7 * DataPond D-Library All exclusive Right reserved on modifiying the code - CC Attribution to data pond, Non modifiable, Non Commercial.
8 * https://registry.world.bibliotech.com/licence
9 * Source code donated to DataPond D-Library by it's author: data pond.
10 *
11 * Technical Guardian ("The Shield"): Pond Enterprise Pty Ltd. (ACN 694 747 987)
12 * All liability claims about D-SAFE certification issues coming from the published D-Safe direction must be addressed with Pond Enterprise Pty Ltd.
13 * Code Modification automatically voids the certified liability protection provided by Pond Enterprise.
14 */
15import { ModelTypes } from './generated-src/index.ts';
16import { Patches } from "./patcher.ts";
17import { getRecord, hasRecord } from "./db.ts";
18import { isEvmPatch } from "./interfaces.ts";
19import type { QueueItem } from "./interfaces.ts";
20import { PatchOperation, type AnyPatch } from "./binary.ts";
21import { applyPatchWithContext, type PatchContext } from "./patchContext.ts";
22import { localRecordTable } from "./localRecordTable.ts";
23
24/**
25 * Applies a decoded batch of patches to the **in-memory ORM** — the v2
26 * replacement for v1's `deserializeOp` + `SyncRestoredPatches`.
27 *
28 * The two v1 functions collapse in v2:
29 * - **decode** is `evmToPatch(payload, versionsFile)` (whole batch, version-driven);
30 * - **persist** is `deserializePatches()` (writes to IndexedDB);
31 * - this function is the missing **routing + apply** step only.
32 *
33 * Each EVM edit op is routed to its target record by `(objectType, objectId)`
34 * and applied via `record.patch(op)`, which mutates in-memory state, fires
35 * reactivity, and propagates inverse relations. Records not currently
36 * materialized in the LRU are **skipped** (their ops remain in IndexedDB and
37 * are folded in when the record is next hydrated — the Direction-B path).
38 * Local analytics ops carry no object edit and are ignored here (they feed the
39 * stats store on boot).
40 *
41 * `setReplay(true)` brackets the loop so no apply is mistaken for user
42 * authoring (defensive: `record.patch()` does not itself queue, but inverse
43 * propagation and future code paths might).
44 *
45 * Idempotent: re-applying the same ops converges to the same state
46 * (`patch()` is a last-writer-wins assignment / Set add-remove).
47 *
48 * @returns counts of ops applied to live records vs. skipped (not materialized).
49 */
50export const applyRestoredPatches = async (
51 ops: Array<AnyPatch>,
52 creatorUsername = 'mine',
53): Promise<{ applied: number; skipped: number }> => {
54 let applied = 0;
55 let skipped = 0;
56
57 Patches.setReplay(true);
58 try {
59 // Last-writer-wins consolidation before apply (collapses redundant edits).
60 const cleaned = Patches.cleanData(ops as Array<QueueItem>);
61
62 const createdInBatch = new Set<string>();
63 for (const op of cleaned) {
64 if ((op as any).operation === PatchOperation.CreateRecord) {
65 createdInBatch.add(`${(op as any).objectType}:${(op as any).objectId}`);
66 }
67 }
68
69 const loadPromises: Promise<any>[] = [];
70 for (const op of cleaned) {
71 if (!isEvmPatch(op)) continue;
72 const objectType = (op as any).objectType;
73 const objectId = (op as any).objectId ?? (op as any).fromUid;
74 if (objectType !== undefined && objectId !== undefined) {
75 if (createdInBatch.has(`${objectType}:${objectId}`)) continue;
76 if (!hasRecord(objectType, objectId, creatorUsername)) {
77 const modelClass = ModelTypes[objectType];
78 if (modelClass) {
79 loadPromises.push(modelClass.LoadAsync(objectId, creatorUsername));
80 }
81 }
82 }
83 }
84 await Promise.all(loadPromises);
85
86 for (let i = 0; i < cleaned.length; i++) {
87 const op = cleaned[i];
88 if (!isEvmPatch(op)) continue; // analytics ops → stats store, not ORM
89
90 const objectType = (op as any).objectType;
91 // SetAlias keys on `fromUid`; all other EVM ops key on `objectId`.
92 const objectId = (op as any).objectId ?? (op as any).fromUid;
93 if (typeof objectId === "undefined") {
94 skipped++;
95 continue;
96 }
97
98 const ctx: PatchContext = {
99 localRecordTable,
100 creatorUsername,
101 blockTimestamp: Date.now(), // Fallback if restoring
102 get batchRemainder() { return cleaned.slice(i + 1) as AnyPatch[]; },
103 instanceRemapStore: localRecordTable.instanceRemaps,
104 };
105
106 await applyPatchWithContext(op as AnyPatch, ctx);
107 applied++;
108 }
109 } finally {
110 Patches.setReplay(false);
111 }
112
113 return { applied, skipped };
114};