📄 src/models/localRecordTable.ts
D-OPEN SOVEREIGN

Documentation & Insights

Called once at app startup, before any createNew() call — mirrors PatchInstance.initThread().
Module-level singleton — import and call directly from generated model files.
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 */
15export interface LocalRecordEntry {
16  wireId:         number;
17  creatorUsername: string;    // known from subscription context; use "mine" for current user's own records
18  objectType:     number;
19  localId:        number;    // = wireId & 0x7FFFFFFF; device-local counter value
20  createdAt:      number;    // Unix ms
21  status:         'active' | 'submitted' | 'community_pending' | 'canonical';
22  canonicalId?:   number;    // set on Canonical transition; triggers SetAlias broadcast
23}
24
25import { localRecordsStoreName, instanceRemapsStoreName } from './indexdb/common.ts';
26
27export class LocalRecordTable {
28  private db: IDBDatabase | null = null;
29  public instanceRemaps: IDBInstanceRemapStore;
30
31  constructor() {
32    this.instanceRemaps = new IDBInstanceRemapStore(this);
33  }
34
35  /** Called once at app startup, before any createNew() call — mirrors PatchInstance.initThread(). */
36  init(db: IDBDatabase): void { this.db = db; }
37
38  public get idb(): IDBDatabase {
39    if (!this.db) throw new Error('[models_v2] LocalRecordTable not initialised — call localRecordTable.init(db) at startup');
40    return this.db;
41  }
42
43  async put(entry: LocalRecordEntry): Promise<void> {
44    return new Promise((resolve, reject) => {
45      const tx = this.idb.transaction('localRecords', 'readwrite');
46      const store = tx.objectStore('localRecords');
47      const req = store.put(entry);
48      req.onsuccess = () => resolve();
49      req.onerror = () => reject(req.error);
50    });
51  }
52
53  async putIfAbsent(entry: LocalRecordEntry): Promise<void> {
54    return new Promise((resolve, reject) => {
55      const tx = this.idb.transaction('localRecords', 'readwrite');
56      tx.onabort = () => reject(tx.error || new Error('Transaction aborted'));
57      tx.onerror = () => reject(tx.error);
58      const store = tx.objectStore('localRecords');
59      const getReq = store.get([entry.creatorUsername, entry.wireId]);
60      getReq.onsuccess = () => {
61        if (!getReq.result) {
62          const addReq = store.add(entry);
63          addReq.onsuccess = () => resolve();
64          addReq.onerror = () => reject(addReq.error);
65        } else {
66          resolve();
67        }
68      };
69      getReq.onerror = () => reject(getReq.error);
70    });
71  }
72
73  async get(creatorUsername: string, wireId: number): Promise<LocalRecordEntry | undefined> {
74    return new Promise((resolve, reject) => {
75      const tx = this.idb.transaction('localRecords', 'readonly');
76      const store = tx.objectStore('localRecords');
77      const req = store.get([creatorUsername, wireId]);
78      req.onsuccess = () => resolve(req.result);
79      req.onerror = () => reject(req.error);
80    });
81  }
82
83  async getByCreator(creatorUsername: string): Promise<LocalRecordEntry[]> {
84    return new Promise((resolve, reject) => {
85      const tx = this.idb.transaction('localRecords', 'readonly');
86      const store = tx.objectStore('localRecords');
87      const index = store.index('byCreator');
88      const req = index.getAll(creatorUsername);
89      req.onsuccess = () => resolve(req.result);
90      req.onerror = () => reject(req.error);
91    });
92  }
93
94  async setStatus(
95    creatorUsername: string,
96    wireId: number,
97    status: LocalRecordEntry['status'],
98  ): Promise<void> {
99    return new Promise((resolve, reject) => {
100      const tx = this.idb.transaction('localRecords', 'readwrite');
101      const store = tx.objectStore('localRecords');
102      const getReq = store.get([creatorUsername, wireId]);
103      getReq.onsuccess = () => {
104        if (!getReq.result) {
105          reject(new Error(`LocalRecordEntry not found: ${creatorUsername}:${wireId}`));
106          return;
107        }
108        const entry = getReq.result as LocalRecordEntry;
109        entry.status = status;
110        const putReq = store.put(entry);
111        putReq.onsuccess = () => resolve();
112        putReq.onerror = () => reject(putReq.error);
113      };
114      getReq.onerror = () => reject(getReq.error);
115    });
116  }
117
118  async setCanonicalId(
119    creatorUsername: string,
120    wireId: number,
121    canonicalId: number,
122  ): Promise<void> {
123    return new Promise((resolve, reject) => {
124      const tx = this.idb.transaction('localRecords', 'readwrite');
125      const store = tx.objectStore('localRecords');
126      const getReq = store.get([creatorUsername, wireId]);
127      getReq.onsuccess = () => {
128        if (!getReq.result) {
129          reject(new Error(`LocalRecordEntry not found: ${creatorUsername}:${wireId}`));
130          return;
131        }
132        const entry = getReq.result as LocalRecordEntry;
133        entry.canonicalId = canonicalId;
134        const putReq = store.put(entry);
135        putReq.onsuccess = () => resolve();
136        putReq.onerror = () => reject(putReq.error);
137      };
138      getReq.onerror = () => reject(getReq.error);
139    });
140  }
141}
142
143export class IDBInstanceRemapStore {
144  constructor(private table: LocalRecordTable) {}
145
146  async get(objectId: number, langId: number, creatorUsername: string, originalIndex: number): Promise<number | undefined> {
147    return new Promise((resolve, reject) => {
148      const db = this.table.idb;
149      const tx = db.transaction(instanceRemapsStoreName, 'readonly');
150      const store = tx.objectStore(instanceRemapsStoreName);
151      const req = store.get([creatorUsername, objectId, langId, originalIndex]);
152      req.onsuccess = () => resolve(req.result?.realIndex);
153      req.onerror = () => reject(req.error);
154    });
155  }
156
157  async put(objectId: number, langId: number, creatorUsername: string, originalIndex: number, realIndex: number): Promise<void> {
158    return new Promise((resolve, reject) => {
159      const db = this.table.idb;
160      const tx = db.transaction(instanceRemapsStoreName, 'readwrite');
161      const store = tx.objectStore(instanceRemapsStoreName);
162      const req = store.put({ creatorUsername, objectId, langId, originalIndex, realIndex });
163      req.onsuccess = () => resolve();
164      req.onerror = () => reject(req.error);
165    });
166  }
167}
168
169/** Module-level singleton — import and call directly from generated model files. */
170export const localRecordTable = new LocalRecordTable();
171