๐ src/models/patchContext.ts
D-OPEN SOVEREIGN
Documentation & Insights
IDB proxy for the `instanceRemaps` store (ยง3.3 Remap Persistence).Returns the realIndex for (objectId, langId, creatorUsername, originalIndex), or undefined if no remap exists.Persists a remap produced by resolveAddInstanceConflict.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 */
15// packages/models_v2/src/patchContext.ts
16
17import { ModelTypes } from './generated-src/index.ts';
18import {
19 PatchOperation,
20 type EditPropertyNumberObj,
21 type AnyPatch,
22 type CreateRecordObj,
23 type AddInstanceObj,
24 LANG_ID_DECODE,
25} from './binary.ts';
26import { Orm, Config } from './orm.ts';
27import { getRecord, hasRecord, pinRecord, unpinRecord, addRecord } from './db.ts';
28import type { LocalRecordTable } from './localRecordTable.ts';
29import type { Lang } from './interfaces.ts';
30import { Registry } from './registry.ts';
31
32/** IDB proxy for the `instanceRemaps` store (ยง3.3 Remap Persistence). */
33export interface InstanceRemapStore {
34 /** Returns the realIndex for (objectId, langId, creatorUsername, originalIndex), or undefined if no remap exists. */
35 get(objectId: number, langId: number, creatorUsername: string, originalIndex: number): Promise<number | undefined>;
36 /** Persists a remap produced by resolveAddInstanceConflict. */
37 put(objectId: number, langId: number, creatorUsername: string, originalIndex: number, realIndex: number): Promise<void>;
38}
39
40export interface PatchContext {
41 localRecordTable: LocalRecordTable;
42 creatorUsername: string; // known from subscription context; "mine" for the current user's own records
43 blockTimestamp: number; // Unix ms from the block; used as createdAt on new-device recovery
44 batchRemainder: AnyPatch[];
45 instanceRemapStore: InstanceRemapStore;
46}
47
48// Generic equality check โ driven by ModelDefinition, no model-specific code.
49function areInstancesEqual(
50 objectType: number,
51 existing: any, // actually the instance data
52 incoming: Map<number, string | number>,
53): boolean {
54 const schema = Registry.get(objectType);
55 if (!schema) return false;
56 const propIdByName = new Map<string, number>(
57 schema.instanceFields?.map(p => [p.name, p.propId as number]) ?? []
58 );
59
60 // 1. Try each standard pk (schema.standardPks: Record<standardId, propName>).
61 for (const propName of Object.values(schema.standardPks ?? {}) as string[]) {
62 const propId = propIdByName.get(propName);
63 if (propId === undefined) continue;
64 const av = existing[propName]; // the data is keyed by name
65 const bv = incoming.get(propId);
66 if (av !== undefined && bv !== undefined) return av === bv;
67 }
68
69 // 2. Native fallback: downloadTxId โ stable content address, reliable equality signal.
70 const dlPropId = propIdByName.get('downloadTxId');
71 if (dlPropId !== undefined) {
72 const av = existing['downloadTxId'];
73 const bv = incoming.get(dlPropId);
74 if (av !== undefined && bv !== undefined) return av === bv;
75 }
76
77 // 3. No reliable identifier found on either instance โ treat as different, preserve both.
78 return false;
79}
80
81async function resolveAddInstanceConflict(
82 record: Orm,
83 incoming: AddInstanceObj,
84 batchRemainder: AnyPatch[],
85 lang: Lang,
86 ctx: PatchContext,
87): Promise<void> {
88 // Collect the incoming instance's field values from the batch remainder.
89 const incomingFields = new Map<number, string | number>();
90 for (const p of batchRemainder) {
91 if (
92 (p.operation === PatchOperation.EditInstanceFieldString ||
93 p.operation === PatchOperation.EditInstanceFieldNumber) &&
94 p.objectType === incoming.objectType &&
95 p.objectId === incoming.objectId &&
96 p.langId === incoming.langId &&
97 p.instanceIndex === incoming.instanceIndex
98 ) {
99 incomingFields.set(p.propertyId, p.value);
100 }
101 }
102
103 // Scan all existing slots for a match
104 const instances = record.getInstancesData(lang);
105 let matchedIndex = -1;
106 for (let i = 0; i < instances.length; i++) {
107 if (areInstancesEqual(incoming.objectType, instances[i], incomingFields)) {
108 matchedIndex = i;
109 break;
110 }
111 }
112
113 if (matchedIndex === incoming.instanceIndex) {
114 // Same manifestation at the original slot โ LWW per field; no remapping. Proceed normally.
115 return;
116 }
117
118 let targetIndex = matchedIndex;
119 if (matchedIndex === -1) {
120 // Different manifestation from all existing slots โ create a new slot at the end.
121 targetIndex = instances.length;
122 record.growInstances(lang, targetIndex);
123 }
124
125 await ctx.instanceRemapStore.put(incoming.objectId, incoming.langId, ctx.creatorUsername, incoming.instanceIndex, targetIndex);
126
127 for (const p of batchRemainder) {
128 if (
129 (p.operation === PatchOperation.EditInstanceFieldString ||
130 p.operation === PatchOperation.EditInstanceFieldNumber) &&
131 p.objectType === incoming.objectType &&
132 p.objectId === incoming.objectId &&
133 p.langId === incoming.langId &&
134 p.instanceIndex === incoming.instanceIndex
135 ) {
136 const fieldPatch = p as AnyPatch & { instanceIndex: number };
137 fieldPatch.instanceIndex = targetIndex; // mutate in-place
138 }
139 }
140}
141
142// applyPatchWithContext โ used ONLY by the replay worker, never by ORM setters
143export async function applyPatchWithContext(patch: AnyPatch, ctx: PatchContext): Promise<void> {
144
145 if (patch.operation === PatchOperation.CreateRecord) {
146 const p = patch as CreateRecordObj;
147 // Materialize the record in LRU using the type-dispatch registry.
148 // The generated constructor calls addRecord internally โ do not call it again here.
149 const modelClass = ModelTypes[p.objectType];
150 if (!modelClass) throw new Error(`[applyPatchWithContext] Unknown objectType ${p.objectType}`);
151 const record = new modelClass(Orm.createNewRequest(p.objectId), ctx.creatorUsername);
152 addRecord(p.objectType, record, ctx.creatorUsername);
153
154 // Pin before any await so LRU eviction cannot race between construction and the IndexedDB write.
155 // pinRecord() is the same function createNew() calls โ one pin implementation.
156 pinRecord(p.objectType, p.objectId, ctx.creatorUsername);
157 // putIfAbsent preserves the original createdAt written by createNew() on first creation;
158 // on a new device recovering from chain, blockTimestamp is used as the initial value.
159 await ctx.localRecordTable.putIfAbsent({
160 wireId: p.objectId,
161 creatorUsername: ctx.creatorUsername,
162 objectType: p.objectType,
163 localId: p.objectId & 0x7FFFFFFF,
164 createdAt: ctx.blockTimestamp,
165 status: 'active',
166 });
167 return;
168 // Note: no orm.patch() call โ CreateRecord has no in-memory field state to apply.
169 }
170
171 if (patch.operation === PatchOperation.AddInstance) {
172 const p = patch as AddInstanceObj;
173 // The pre-flight load pass (ยง3.4) ensures the target is in the LRU if it exists.
174 if (!hasRecord(p.objectType, p.objectId, ctx.creatorUsername)) {
175 console.warn(`[applyPatchWithContext] AddInstance target absent; skipping`);
176 return;
177 }
178 const record = getRecord(p.objectType, p.objectId, ctx.creatorUsername);
179 const lang = LANG_ID_DECODE[p.langId];
180 if (!lang) return;
181
182 const slotExists = record.getInstancesData(lang).length > p.instanceIndex;
183 if (slotExists) {
184 await resolveAddInstanceConflict(record, p, ctx.batchRemainder, lang, ctx);
185 } else {
186 record.patch(p); // normal path: grow the array to instanceIndex + 1
187 }
188 return;
189 }
190
191 // All other operations: caller must ensure the record is already in LRU before calling here.
192
193 // For EditInstanceField* patches: check for a persisted instanceIndex remap (ยง3.3).
194 // If Device B's AddInstance at index N was remapped to M in a prior batch, Device B's
195 // subsequent transactions still encode index N on the wire. Redirect to M before dispatching.
196 const p = patch as AnyPatch & { objectType?: number; objectId?: number; langId?: number; instanceIndex?: number };
197
198 if (p.operation === PatchOperation.EditInstanceFieldString || p.operation === PatchOperation.EditInstanceFieldNumber) {
199 const realIndex = await ctx.instanceRemapStore.get(p.objectId!, p.langId!, ctx.creatorUsername, p.instanceIndex!);
200 if (realIndex !== undefined) p.instanceIndex = realIndex;
201 }
202
203 // The pre-flight load pass ensures the target is in the LRU if it exists.
204 if (p.objectType !== undefined && p.objectId !== undefined) {
205 if (!hasRecord(p.objectType, p.objectId, ctx.creatorUsername)) {
206 console.warn(`[applyPatchWithContext] Edit target absent; skipping`);
207 return;
208 }
209 const record = getRecord(p.objectType, p.objectId, ctx.creatorUsername);
210 record.patch(patch);
211 // Restore provenance lost across page reloads: provenance has no patch op of
212 // its own, so replay re-derives it from Config.defaultProvenance (set at boot).
213 if (
214 patch.operation === PatchOperation.EditPropertyString ||
215 patch.operation === PatchOperation.EditPropertyNumber
216 ) {
217 const prov = Config.defaultProvenance;
218 if (prov !== undefined) {
219 const propPatch = patch as { propertyId: number; langId: number };
220 const lang = LANG_ID_DECODE[propPatch.langId] ?? 'eng';
221 record.setProvenanceRaw(lang, propPatch.propertyId, prov);
222 }
223 }
224 }
225
226
227}
228