📄 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