holosphere 2.0.0-alpha2 → 2.0.0-alpha4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/2019-D2OG2idw.js +6680 -0
- package/dist/2019-D2OG2idw.js.map +1 -0
- package/dist/2019-EION3wKo.cjs +8 -0
- package/dist/2019-EION3wKo.cjs.map +1 -0
- package/dist/_commonjsHelpers-C37NGDzP.cjs +2 -0
- package/dist/_commonjsHelpers-C37NGDzP.cjs.map +1 -0
- package/dist/_commonjsHelpers-CUmg6egw.js +7 -0
- package/dist/_commonjsHelpers-CUmg6egw.js.map +1 -0
- package/dist/browser-BSniCNqO.js +3058 -0
- package/dist/browser-BSniCNqO.js.map +1 -0
- package/dist/browser-Cq59Ij19.cjs +2 -0
- package/dist/browser-Cq59Ij19.cjs.map +1 -0
- package/dist/cjs/holosphere.cjs +1 -1
- package/dist/esm/holosphere.js +50 -53
- package/dist/index-BB_vVJgv.cjs +5 -0
- package/dist/index-BB_vVJgv.cjs.map +1 -0
- package/dist/index-CBitK71M.cjs +12 -0
- package/dist/index-CBitK71M.cjs.map +1 -0
- package/dist/index-CV0eOogK.js +37423 -0
- package/dist/index-CV0eOogK.js.map +1 -0
- package/dist/index-Cz-PLCUR.js +15104 -0
- package/dist/index-Cz-PLCUR.js.map +1 -0
- package/dist/indexeddb-storage-CRsZyB2f.cjs +2 -0
- package/dist/indexeddb-storage-CRsZyB2f.cjs.map +1 -0
- package/dist/{indexeddb-storage-CMW4qRQS.js → indexeddb-storage-DZaGlY_a.js} +49 -13
- package/dist/indexeddb-storage-DZaGlY_a.js.map +1 -0
- package/dist/{memory-storage-DQzcAZlf.js → memory-storage-BkUi6sZG.js} +6 -2
- package/dist/memory-storage-BkUi6sZG.js.map +1 -0
- package/dist/{memory-storage-DmePEP2q.cjs → memory-storage-C0DuUsdY.cjs} +2 -2
- package/dist/memory-storage-C0DuUsdY.cjs.map +1 -0
- package/dist/secp256k1-0kPdAVkK.cjs +12 -0
- package/dist/secp256k1-0kPdAVkK.cjs.map +1 -0
- package/dist/{secp256k1-vOXp40Fx.js → secp256k1-DN4FVXcv.js} +2 -393
- package/dist/secp256k1-DN4FVXcv.js.map +1 -0
- package/docs/CONTRACTS.md +797 -0
- package/examples/demo.html +47 -0
- package/package.json +10 -5
- package/src/contracts/abis/Appreciative.json +1280 -0
- package/src/contracts/abis/AppreciativeFactory.json +101 -0
- package/src/contracts/abis/Bundle.json +1435 -0
- package/src/contracts/abis/BundleFactory.json +106 -0
- package/src/contracts/abis/Holon.json +881 -0
- package/src/contracts/abis/Holons.json +330 -0
- package/src/contracts/abis/Managed.json +1262 -0
- package/src/contracts/abis/ManagedFactory.json +149 -0
- package/src/contracts/abis/Membrane.json +261 -0
- package/src/contracts/abis/Splitter.json +1624 -0
- package/src/contracts/abis/SplitterFactory.json +220 -0
- package/src/contracts/abis/TestToken.json +321 -0
- package/src/contracts/abis/Zoned.json +1461 -0
- package/src/contracts/abis/ZonedFactory.json +154 -0
- package/src/contracts/chain-manager.js +375 -0
- package/src/contracts/deployer.js +443 -0
- package/src/contracts/event-listener.js +507 -0
- package/src/contracts/holon-contracts.js +344 -0
- package/src/contracts/index.js +83 -0
- package/src/contracts/networks.js +224 -0
- package/src/contracts/operations.js +670 -0
- package/src/contracts/queries.js +589 -0
- package/src/core/holosphere.js +453 -1
- package/src/crypto/nostr-utils.js +263 -0
- package/src/federation/handshake.js +455 -0
- package/src/federation/hologram.js +1 -1
- package/src/hierarchical/upcast.js +6 -5
- package/src/index.js +463 -1939
- package/src/lib/ai-methods.js +308 -0
- package/src/lib/contract-methods.js +293 -0
- package/src/lib/errors.js +23 -0
- package/src/lib/federation-methods.js +238 -0
- package/src/lib/index.js +26 -0
- package/src/spatial/h3-operations.js +2 -2
- package/src/storage/backends/gundb-backend.js +377 -46
- package/src/storage/global-tables.js +28 -1
- package/src/storage/gun-auth.js +303 -0
- package/src/storage/gun-federation.js +776 -0
- package/src/storage/gun-references.js +198 -0
- package/src/storage/gun-schema.js +291 -0
- package/src/storage/gun-wrapper.js +347 -31
- package/src/storage/indexeddb-storage.js +49 -11
- package/src/storage/memory-storage.js +5 -0
- package/src/storage/nostr-async.js +45 -23
- package/src/storage/nostr-client.js +11 -5
- package/src/storage/persistent-storage.js +6 -1
- package/src/storage/unified-storage.js +119 -0
- package/src/subscriptions/manager.js +1 -1
- package/types/index.d.ts +133 -0
- package/dist/index-CDfIuXew.js +0 -15974
- package/dist/index-CDfIuXew.js.map +0 -1
- package/dist/index-ifOgtDvd.cjs +0 -3
- package/dist/index-ifOgtDvd.cjs.map +0 -1
- package/dist/indexeddb-storage-CMW4qRQS.js.map +0 -1
- package/dist/indexeddb-storage-DLZOgetM.cjs +0 -2
- package/dist/indexeddb-storage-DLZOgetM.cjs.map +0 -1
- package/dist/memory-storage-DQzcAZlf.js.map +0 -1
- package/dist/memory-storage-DmePEP2q.cjs.map +0 -1
- package/dist/secp256k1-CP0ZkpAx.cjs +0 -13
- package/dist/secp256k1-CP0ZkpAx.cjs.map +0 -1
- package/dist/secp256k1-vOXp40Fx.js.map +0 -1
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* GunDB Storage Wrapper with radisk persistence
|
|
3
3
|
* Handles path construction and CRUD operations
|
|
4
|
+
* Note: GunDB doesn't handle nested objects well, so we store data as JSON strings
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
7
|
import { gunPromise, gunPut, gunCollect } from './gun-async.js';
|
|
7
8
|
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// PATH BUILDERS
|
|
11
|
+
// ============================================================================
|
|
12
|
+
|
|
8
13
|
/**
|
|
9
14
|
* Build Gun path from components
|
|
10
15
|
* @param {string} appname - Application namespace
|
|
@@ -25,6 +30,22 @@ export function buildPath(appname, holon, lens, key = null) {
|
|
|
25
30
|
return `${appname}/${encodedHolon}/${encodedLens}`;
|
|
26
31
|
}
|
|
27
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Build Gun path for global tables (app-wide data not tied to holons)
|
|
35
|
+
* @param {string} appname - Application namespace
|
|
36
|
+
* @param {string} tableName - Global table name (e.g., 'schemas', 'federation')
|
|
37
|
+
* @param {string} key - Data key (optional)
|
|
38
|
+
* @returns {string} Gun path
|
|
39
|
+
*/
|
|
40
|
+
export function buildGlobalPath(appname, tableName, key = null) {
|
|
41
|
+
const encodedTable = encodePathComponent(tableName);
|
|
42
|
+
if (key) {
|
|
43
|
+
const encodedKey = encodePathComponent(key);
|
|
44
|
+
return `${appname}/${encodedTable}/${encodedKey}`;
|
|
45
|
+
}
|
|
46
|
+
return `${appname}/${encodedTable}`;
|
|
47
|
+
}
|
|
48
|
+
|
|
28
49
|
/**
|
|
29
50
|
* Encode path component to handle special characters
|
|
30
51
|
* @private
|
|
@@ -33,6 +54,94 @@ function encodePathComponent(component) {
|
|
|
33
54
|
return encodeURIComponent(component).replace(/%2F/g, '/');
|
|
34
55
|
}
|
|
35
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Serialize data for GunDB storage
|
|
59
|
+
* Wraps data in { _json: string } format for Gun compatibility
|
|
60
|
+
* Gun requires an object at graph roots, so we can't store raw JSON strings
|
|
61
|
+
* The deserialization handles both this format and raw JSON strings for reading old data
|
|
62
|
+
* @private
|
|
63
|
+
*/
|
|
64
|
+
function serializeForGun(data) {
|
|
65
|
+
return { _json: JSON.stringify(data) };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Deserialize data from GunDB storage
|
|
70
|
+
* Handles multiple formats:
|
|
71
|
+
* - Direct JSON string (holosphere original - now default)
|
|
72
|
+
* - _json wrapped format (holosphere2 legacy)
|
|
73
|
+
* - Gun internal references (_["#"])
|
|
74
|
+
* - Gun node data (_[">"])
|
|
75
|
+
* - Plain objects
|
|
76
|
+
* @private
|
|
77
|
+
*/
|
|
78
|
+
function deserializeFromGun(data) {
|
|
79
|
+
if (!data) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
// Format 1: String data (holosphere original stores JSON as string directly)
|
|
85
|
+
if (typeof data === 'string') {
|
|
86
|
+
try {
|
|
87
|
+
return JSON.parse(data);
|
|
88
|
+
} catch (e) {
|
|
89
|
+
// Not JSON, return as-is
|
|
90
|
+
return data;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Format 2: _json wrapped format (holosphere2 default)
|
|
95
|
+
if (data._json && typeof data._json === 'string') {
|
|
96
|
+
try {
|
|
97
|
+
return JSON.parse(data._json);
|
|
98
|
+
} catch (e) {
|
|
99
|
+
console.warn('Failed to parse _json field:', e);
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Format 3: Gun internal reference (_["#"]) - this indicates a Gun reference
|
|
105
|
+
// The actual data retrieval should happen via resolveReference, we just identify it here
|
|
106
|
+
if (data._ && data._['#']) {
|
|
107
|
+
// This is a Gun reference - return as-is for reference resolution
|
|
108
|
+
// The caller should check isReference() and handle appropriately
|
|
109
|
+
return data;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Format 4: Gun node data with timestamps (_[">"])
|
|
113
|
+
// Gun stores metadata in _.> - find the actual value
|
|
114
|
+
if (data._ && data._['>']) {
|
|
115
|
+
const nodeValue = Object.entries(data).find(([k, v]) => k !== '_' && typeof v === 'string');
|
|
116
|
+
if (nodeValue) {
|
|
117
|
+
try {
|
|
118
|
+
return JSON.parse(nodeValue[1]);
|
|
119
|
+
} catch (e) {
|
|
120
|
+
return nodeValue[1];
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Format 5: Plain object - clean Gun metadata and return
|
|
126
|
+
if (typeof data === 'object' && data !== null) {
|
|
127
|
+
const cleaned = { ...data };
|
|
128
|
+
delete cleaned['_'];
|
|
129
|
+
|
|
130
|
+
// Check if any remaining keys - if empty after removing _, return null
|
|
131
|
+
if (Object.keys(cleaned).length === 0) {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return cleaned;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return data;
|
|
139
|
+
} catch (error) {
|
|
140
|
+
console.warn('Error deserializing Gun data:', error);
|
|
141
|
+
return data; // Return raw data as fallback
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
36
145
|
/**
|
|
37
146
|
* Write data to Gun with radisk persistence
|
|
38
147
|
* @param {Object} gun - Gun instance
|
|
@@ -42,7 +151,8 @@ function encodePathComponent(component) {
|
|
|
42
151
|
*/
|
|
43
152
|
export async function write(gun, path, data) {
|
|
44
153
|
try {
|
|
45
|
-
|
|
154
|
+
const serialized = serializeForGun(data);
|
|
155
|
+
await gunPut(gun.get(path), serialized, 2000);
|
|
46
156
|
// Delay to allow Gun to propagate the write (50ms for better reliability)
|
|
47
157
|
await new Promise(resolve => setTimeout(resolve, 50));
|
|
48
158
|
return true;
|
|
@@ -58,7 +168,13 @@ export async function write(gun, path, data) {
|
|
|
58
168
|
* @returns {Promise<Object|null>} Data or null if not found
|
|
59
169
|
*/
|
|
60
170
|
export async function read(gun, path) {
|
|
61
|
-
const
|
|
171
|
+
const rawData = await gunPromise(gun.get(path), 2000);
|
|
172
|
+
|
|
173
|
+
if (!rawData) {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const data = deserializeFromGun(rawData);
|
|
62
178
|
|
|
63
179
|
// Return null if deleted or not found
|
|
64
180
|
if (!data || data._deleted) {
|
|
@@ -70,22 +186,114 @@ export async function read(gun, path) {
|
|
|
70
186
|
|
|
71
187
|
/**
|
|
72
188
|
* Read all data under a path (lens query)
|
|
189
|
+
* Uses Gun's .open() which waits for nested data to load
|
|
190
|
+
* Falls back to .once() with iteration
|
|
73
191
|
* @param {Object} gun - Gun instance
|
|
74
192
|
* @param {string} path - Gun path
|
|
75
193
|
* @returns {Promise<Object[]>} Array of data objects
|
|
76
194
|
*/
|
|
77
195
|
export async function readAll(gun, path) {
|
|
78
|
-
|
|
196
|
+
return new Promise((resolve) => {
|
|
197
|
+
const output = new Map();
|
|
198
|
+
let settled = false;
|
|
79
199
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
.
|
|
83
|
-
|
|
84
|
-
if (
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
200
|
+
const ref = gun.get(path);
|
|
201
|
+
|
|
202
|
+
// Use .once() on parent to get all keys, then process
|
|
203
|
+
ref.once((parentData) => {
|
|
204
|
+
if (settled) return;
|
|
205
|
+
|
|
206
|
+
if (!parentData) {
|
|
207
|
+
settled = true;
|
|
208
|
+
resolve([]);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Get all keys except Gun metadata
|
|
213
|
+
const keys = Object.keys(parentData).filter(k => k !== '_');
|
|
214
|
+
|
|
215
|
+
if (keys.length === 0) {
|
|
216
|
+
settled = true;
|
|
217
|
+
resolve([]);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Process inline data directly from parentData
|
|
222
|
+
// Gun often includes the data inline as references or direct values
|
|
223
|
+
for (const key of keys) {
|
|
224
|
+
const rawItem = parentData[key];
|
|
225
|
+
if (!rawItem) continue;
|
|
226
|
+
|
|
227
|
+
let item = null;
|
|
228
|
+
|
|
229
|
+
// Check if it's a Gun reference (soul)
|
|
230
|
+
if (typeof rawItem === 'object' && rawItem['#']) {
|
|
231
|
+
// Skip references for now, map().once will handle them
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Try to parse as _json wrapper
|
|
236
|
+
if (rawItem._json && typeof rawItem._json === 'string') {
|
|
237
|
+
try {
|
|
238
|
+
item = JSON.parse(rawItem._json);
|
|
239
|
+
} catch (e) {}
|
|
240
|
+
}
|
|
241
|
+
// Try as direct JSON string
|
|
242
|
+
else if (typeof rawItem === 'string') {
|
|
243
|
+
try {
|
|
244
|
+
item = JSON.parse(rawItem);
|
|
245
|
+
} catch (e) {}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (item && item.id && !item._deleted) {
|
|
249
|
+
output.set(item.id, item);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// If we got all data inline, resolve now
|
|
254
|
+
if (output.size > 0) {
|
|
255
|
+
settled = true;
|
|
256
|
+
resolve(Array.from(output.values()));
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Otherwise use map().once() to resolve references
|
|
261
|
+
ref.map().once((data, key) => {
|
|
262
|
+
if (settled || !data || key === '_') return;
|
|
263
|
+
|
|
264
|
+
let item = null;
|
|
265
|
+
if (data._json && typeof data._json === 'string') {
|
|
266
|
+
try {
|
|
267
|
+
item = JSON.parse(data._json);
|
|
268
|
+
} catch (e) {}
|
|
269
|
+
} else if (typeof data === 'string') {
|
|
270
|
+
try {
|
|
271
|
+
item = JSON.parse(data);
|
|
272
|
+
} catch (e) {}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (item && item.id && !item._deleted) {
|
|
276
|
+
output.set(item.id, item);
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
// Give map().once() time to complete
|
|
281
|
+
setTimeout(() => {
|
|
282
|
+
if (!settled) {
|
|
283
|
+
settled = true;
|
|
284
|
+
resolve(Array.from(output.values()));
|
|
285
|
+
}
|
|
286
|
+
}, 500);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
// Overall timeout
|
|
290
|
+
setTimeout(() => {
|
|
291
|
+
if (!settled) {
|
|
292
|
+
settled = true;
|
|
293
|
+
resolve(Array.from(output.values()));
|
|
294
|
+
}
|
|
295
|
+
}, 2000);
|
|
296
|
+
});
|
|
89
297
|
}
|
|
90
298
|
|
|
91
299
|
/**
|
|
@@ -96,21 +304,24 @@ export async function readAll(gun, path) {
|
|
|
96
304
|
* @returns {Promise<boolean>} Success indicator
|
|
97
305
|
*/
|
|
98
306
|
export async function update(gun, path, updates) {
|
|
99
|
-
const
|
|
307
|
+
const rawData = await gunPromise(gun.get(path));
|
|
100
308
|
|
|
101
|
-
if (!
|
|
102
|
-
return false; // Not found
|
|
309
|
+
if (!rawData) {
|
|
310
|
+
return false; // Not found
|
|
103
311
|
}
|
|
104
312
|
|
|
105
|
-
//
|
|
106
|
-
const
|
|
107
|
-
|
|
313
|
+
// Deserialize existing data
|
|
314
|
+
const existing = deserializeFromGun(rawData);
|
|
315
|
+
if (!existing || !existing.id || existing._deleted) {
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
108
318
|
|
|
109
319
|
// Merge updates
|
|
110
|
-
const merged = { ...
|
|
320
|
+
const merged = { ...existing, ...updates };
|
|
111
321
|
|
|
112
322
|
try {
|
|
113
|
-
|
|
323
|
+
const serialized = serializeForGun(merged);
|
|
324
|
+
await gunPut(gun.get(path), serialized);
|
|
114
325
|
return true;
|
|
115
326
|
} catch (error) {
|
|
116
327
|
throw error;
|
|
@@ -125,21 +336,22 @@ export async function update(gun, path, updates) {
|
|
|
125
336
|
*/
|
|
126
337
|
export async function deleteData(gun, path) {
|
|
127
338
|
try {
|
|
128
|
-
//
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
if (!existing) {
|
|
339
|
+
// First read existing data to get the id
|
|
340
|
+
const rawData = await gunPromise(gun.get(path));
|
|
341
|
+
if (!rawData) {
|
|
132
342
|
return true; // Already deleted/doesn't exist
|
|
133
343
|
}
|
|
134
344
|
|
|
135
|
-
|
|
345
|
+
const existing = deserializeFromGun(rawData);
|
|
346
|
+
|
|
347
|
+
// Create tombstone object and serialize it
|
|
136
348
|
const tombstone = {
|
|
137
|
-
id: existing
|
|
349
|
+
id: existing?.id,
|
|
138
350
|
_deleted: true,
|
|
139
351
|
_deletedAt: Date.now()
|
|
140
352
|
};
|
|
141
353
|
|
|
142
|
-
await gunPut(gun.get(path), tombstone);
|
|
354
|
+
await gunPut(gun.get(path), serializeForGun(tombstone));
|
|
143
355
|
return true;
|
|
144
356
|
} catch (error) {
|
|
145
357
|
throw error;
|
|
@@ -186,8 +398,11 @@ export function subscribe(gun, path, callback, options = {}) {
|
|
|
186
398
|
const ref = gun.get(path);
|
|
187
399
|
|
|
188
400
|
ref.map().on((data, key) => {
|
|
189
|
-
if (data && !key.startsWith('_')
|
|
190
|
-
|
|
401
|
+
if (data && !key.startsWith('_')) {
|
|
402
|
+
const deserialized = deserializeFromGun(data);
|
|
403
|
+
if (deserialized && !deserialized._deleted) {
|
|
404
|
+
callback(deserialized, key);
|
|
405
|
+
}
|
|
191
406
|
}
|
|
192
407
|
});
|
|
193
408
|
|
|
@@ -203,8 +418,11 @@ export function subscribe(gun, path, callback, options = {}) {
|
|
|
203
418
|
} else {
|
|
204
419
|
// Subscribe to single item
|
|
205
420
|
const listener = gun.get(path).on((data, key) => {
|
|
206
|
-
if (data
|
|
207
|
-
|
|
421
|
+
if (data) {
|
|
422
|
+
const deserialized = deserializeFromGun(data);
|
|
423
|
+
if (deserialized && !deserialized._deleted) {
|
|
424
|
+
callback(deserialized, key);
|
|
425
|
+
}
|
|
208
426
|
}
|
|
209
427
|
});
|
|
210
428
|
|
|
@@ -219,3 +437,101 @@ export function subscribe(gun, path, callback, options = {}) {
|
|
|
219
437
|
};
|
|
220
438
|
}
|
|
221
439
|
}
|
|
440
|
+
|
|
441
|
+
// ============================================================================
|
|
442
|
+
// GLOBAL TABLE OPERATIONS
|
|
443
|
+
// ============================================================================
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Write data to a global table
|
|
447
|
+
* Global tables are app-wide data not tied to specific holons (e.g., schemas, federation)
|
|
448
|
+
* @param {Object} gun - Gun instance
|
|
449
|
+
* @param {string} appname - Application namespace
|
|
450
|
+
* @param {string} tableName - Global table name
|
|
451
|
+
* @param {Object} data - Data to write (must have 'id' field)
|
|
452
|
+
* @returns {Promise<boolean>} Success indicator
|
|
453
|
+
*/
|
|
454
|
+
export async function writeGlobal(gun, appname, tableName, data) {
|
|
455
|
+
if (!data || !data.id) {
|
|
456
|
+
throw new Error('writeGlobal: data must have an id field');
|
|
457
|
+
}
|
|
458
|
+
const path = buildGlobalPath(appname, tableName, data.id);
|
|
459
|
+
return write(gun, path, data);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Read data from a global table
|
|
464
|
+
* @param {Object} gun - Gun instance
|
|
465
|
+
* @param {string} appname - Application namespace
|
|
466
|
+
* @param {string} tableName - Global table name
|
|
467
|
+
* @param {string} key - Data key
|
|
468
|
+
* @returns {Promise<Object|null>} Data or null if not found
|
|
469
|
+
*/
|
|
470
|
+
export async function readGlobal(gun, appname, tableName, key) {
|
|
471
|
+
const path = buildGlobalPath(appname, tableName, key);
|
|
472
|
+
return read(gun, path);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Read all data from a global table
|
|
477
|
+
* Uses same approach as readAll
|
|
478
|
+
* @param {Object} gun - Gun instance
|
|
479
|
+
* @param {string} appname - Application namespace
|
|
480
|
+
* @param {string} tableName - Global table name
|
|
481
|
+
* @param {number} timeout - Timeout in ms (default: 2000)
|
|
482
|
+
* @returns {Promise<Object[]>} Array of data objects
|
|
483
|
+
*/
|
|
484
|
+
export async function readAllGlobal(gun, appname, tableName, timeout = 2000) {
|
|
485
|
+
const path = buildGlobalPath(appname, tableName);
|
|
486
|
+
return readAll(gun, path);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Delete data from a global table
|
|
491
|
+
* @param {Object} gun - Gun instance
|
|
492
|
+
* @param {string} appname - Application namespace
|
|
493
|
+
* @param {string} tableName - Global table name
|
|
494
|
+
* @param {string} key - Data key
|
|
495
|
+
* @returns {Promise<boolean>} Success indicator
|
|
496
|
+
*/
|
|
497
|
+
export async function deleteGlobal(gun, appname, tableName, key) {
|
|
498
|
+
const path = buildGlobalPath(appname, tableName, key);
|
|
499
|
+
return deleteData(gun, path);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Delete all data from a global table
|
|
504
|
+
* @param {Object} gun - Gun instance
|
|
505
|
+
* @param {string} appname - Application namespace
|
|
506
|
+
* @param {string} tableName - Global table name
|
|
507
|
+
* @returns {Promise<Object>} Deletion results { success: boolean, count: number }
|
|
508
|
+
*/
|
|
509
|
+
export async function deleteAllGlobal(gun, appname, tableName) {
|
|
510
|
+
const path = buildGlobalPath(appname, tableName);
|
|
511
|
+
return deleteAll(gun, path);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// ============================================================================
|
|
515
|
+
// DATA PARSING UTILITIES
|
|
516
|
+
// ============================================================================
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Parse data from Gun storage, handling various formats
|
|
520
|
+
* - JSON string in _json field
|
|
521
|
+
* - Legacy object format
|
|
522
|
+
* - Gun references
|
|
523
|
+
* @param {*} rawData - Raw data from Gun
|
|
524
|
+
* @returns {Object|null} Parsed data or null
|
|
525
|
+
*/
|
|
526
|
+
export function parse(rawData) {
|
|
527
|
+
return deserializeFromGun(rawData);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Serialize data for Gun storage
|
|
532
|
+
* @param {Object} data - Data to serialize
|
|
533
|
+
* @returns {string} JSON string
|
|
534
|
+
*/
|
|
535
|
+
export function serialize(data) {
|
|
536
|
+
return serializeForGun(data);
|
|
537
|
+
}
|
|
@@ -8,8 +8,10 @@ import { PersistentStorage } from './persistent-storage.js';
|
|
|
8
8
|
export class IndexedDBStorage extends PersistentStorage {
|
|
9
9
|
constructor() {
|
|
10
10
|
super();
|
|
11
|
+
/** @type {IDBDatabase|null} */
|
|
11
12
|
this.db = null;
|
|
12
|
-
|
|
13
|
+
/** @type {string} */
|
|
14
|
+
this.dbName = '';
|
|
13
15
|
this.storeName = 'events';
|
|
14
16
|
}
|
|
15
17
|
|
|
@@ -22,11 +24,12 @@ export class IndexedDBStorage extends PersistentStorage {
|
|
|
22
24
|
request.onerror = () => reject(request.error);
|
|
23
25
|
request.onsuccess = () => {
|
|
24
26
|
this.db = request.result;
|
|
25
|
-
resolve();
|
|
27
|
+
resolve(undefined);
|
|
26
28
|
};
|
|
27
29
|
|
|
28
30
|
request.onupgradeneeded = (event) => {
|
|
29
|
-
const
|
|
31
|
+
const target = /** @type {IDBOpenDBRequest} */ (event.target);
|
|
32
|
+
const db = target.result;
|
|
30
33
|
|
|
31
34
|
// Create object store if it doesn't exist
|
|
32
35
|
if (!db.objectStoreNames.contains(this.storeName)) {
|
|
@@ -40,18 +43,26 @@ export class IndexedDBStorage extends PersistentStorage {
|
|
|
40
43
|
|
|
41
44
|
async put(key, event) {
|
|
42
45
|
return new Promise((resolve, reject) => {
|
|
46
|
+
if (!this.db) {
|
|
47
|
+
reject(new Error('Database not initialized'));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
43
50
|
const transaction = this.db.transaction([this.storeName], 'readwrite');
|
|
44
51
|
const objectStore = transaction.objectStore(this.storeName);
|
|
45
52
|
|
|
46
53
|
const request = objectStore.put({ key, event, timestamp: Date.now() });
|
|
47
54
|
|
|
48
|
-
request.onsuccess = () => resolve();
|
|
55
|
+
request.onsuccess = () => resolve(undefined);
|
|
49
56
|
request.onerror = () => reject(request.error);
|
|
50
57
|
});
|
|
51
58
|
}
|
|
52
59
|
|
|
53
60
|
async get(key) {
|
|
54
61
|
return new Promise((resolve, reject) => {
|
|
62
|
+
if (!this.db) {
|
|
63
|
+
reject(new Error('Database not initialized'));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
55
66
|
const transaction = this.db.transaction([this.storeName], 'readonly');
|
|
56
67
|
const objectStore = transaction.objectStore(this.storeName);
|
|
57
68
|
|
|
@@ -65,20 +76,39 @@ export class IndexedDBStorage extends PersistentStorage {
|
|
|
65
76
|
});
|
|
66
77
|
}
|
|
67
78
|
|
|
79
|
+
/**
|
|
80
|
+
* @param {string} prefix
|
|
81
|
+
* @returns {Promise<any[]>}
|
|
82
|
+
*/
|
|
68
83
|
async getAll(prefix) {
|
|
69
84
|
return new Promise((resolve, reject) => {
|
|
85
|
+
if (!this.db) {
|
|
86
|
+
reject(new Error('Database not initialized'));
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
70
89
|
const transaction = this.db.transaction([this.storeName], 'readonly');
|
|
71
90
|
const objectStore = transaction.objectStore(this.storeName);
|
|
72
91
|
|
|
92
|
+
/** @type {any[]} */
|
|
73
93
|
const results = [];
|
|
74
|
-
|
|
94
|
+
|
|
95
|
+
// Use IDBKeyRange for efficient prefix query instead of full table scan
|
|
96
|
+
// This creates a range from "prefix" to "prefix\uffff" (highest unicode char)
|
|
97
|
+
// which efficiently uses the B-tree index
|
|
98
|
+
let request;
|
|
99
|
+
if (prefix) {
|
|
100
|
+
const range = IDBKeyRange.bound(prefix, prefix + '\uffff', false, false);
|
|
101
|
+
request = objectStore.openCursor(range);
|
|
102
|
+
} else {
|
|
103
|
+
// No prefix = get all
|
|
104
|
+
request = objectStore.openCursor();
|
|
105
|
+
}
|
|
75
106
|
|
|
76
107
|
request.onsuccess = (event) => {
|
|
77
|
-
const
|
|
108
|
+
const target = /** @type {IDBRequest} */ (event.target);
|
|
109
|
+
const cursor = target.result;
|
|
78
110
|
if (cursor) {
|
|
79
|
-
|
|
80
|
-
results.push(cursor.value.event);
|
|
81
|
-
}
|
|
111
|
+
results.push(cursor.value.event);
|
|
82
112
|
cursor.continue();
|
|
83
113
|
} else {
|
|
84
114
|
resolve(results);
|
|
@@ -91,24 +121,32 @@ export class IndexedDBStorage extends PersistentStorage {
|
|
|
91
121
|
|
|
92
122
|
async delete(key) {
|
|
93
123
|
return new Promise((resolve, reject) => {
|
|
124
|
+
if (!this.db) {
|
|
125
|
+
reject(new Error('Database not initialized'));
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
94
128
|
const transaction = this.db.transaction([this.storeName], 'readwrite');
|
|
95
129
|
const objectStore = transaction.objectStore(this.storeName);
|
|
96
130
|
|
|
97
131
|
const request = objectStore.delete(key);
|
|
98
132
|
|
|
99
|
-
request.onsuccess = () => resolve();
|
|
133
|
+
request.onsuccess = () => resolve(undefined);
|
|
100
134
|
request.onerror = () => reject(request.error);
|
|
101
135
|
});
|
|
102
136
|
}
|
|
103
137
|
|
|
104
138
|
async clear() {
|
|
105
139
|
return new Promise((resolve, reject) => {
|
|
140
|
+
if (!this.db) {
|
|
141
|
+
reject(new Error('Database not initialized'));
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
106
144
|
const transaction = this.db.transaction([this.storeName], 'readwrite');
|
|
107
145
|
const objectStore = transaction.objectStore(this.storeName);
|
|
108
146
|
|
|
109
147
|
const request = objectStore.clear();
|
|
110
148
|
|
|
111
|
-
request.onsuccess = () => resolve();
|
|
149
|
+
request.onsuccess = () => resolve(undefined);
|
|
112
150
|
request.onerror = () => reject(request.error);
|
|
113
151
|
});
|
|
114
152
|
}
|
|
@@ -32,7 +32,12 @@ export class MemoryStorage extends PersistentStorage {
|
|
|
32
32
|
return data ? JSON.parse(JSON.stringify(data)) : null; // Deep clone
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
/**
|
|
36
|
+
* @param {string} prefix
|
|
37
|
+
* @returns {Promise<any[]>}
|
|
38
|
+
*/
|
|
35
39
|
async getAll(prefix) {
|
|
40
|
+
/** @type {any[]} */
|
|
36
41
|
const results = [];
|
|
37
42
|
for (const [key, value] of this.data.entries()) {
|
|
38
43
|
if (key.startsWith(prefix)) {
|