๐Ÿ“„ 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