holosphere 2.0.0-alpha1 → 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 +2 -0
- package/dist/cjs/holosphere.cjs.map +1 -0
- package/dist/esm/holosphere.js +53 -0
- package/dist/esm/holosphere.js.map +1 -0
- 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-DZaGlY_a.js +132 -0
- package/dist/indexeddb-storage-DZaGlY_a.js.map +1 -0
- package/dist/memory-storage-BkUi6sZG.js +51 -0
- package/dist/memory-storage-BkUi6sZG.js.map +1 -0
- package/dist/memory-storage-C0DuUsdY.cjs +2 -0
- 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-DN4FVXcv.js +1890 -0
- package/dist/secp256k1-DN4FVXcv.js.map +1 -0
- package/docs/CONTRACTS.md +797 -0
- package/docs/FOSDEM_PROPOSAL.md +388 -0
- package/docs/LOCALFIRST.md +266 -0
- package/docs/contracts/api-interface.md +793 -0
- package/docs/data-model.md +476 -0
- package/docs/gun-async-usage.md +338 -0
- package/docs/plan.md +349 -0
- package/docs/quickstart.md +674 -0
- package/docs/research.md +362 -0
- package/docs/spec.md +244 -0
- package/docs/storage-backends.md +326 -0
- package/docs/tasks.md +947 -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/tests/unit/ai/aggregation.test.js +0 -295
- package/tests/unit/ai/breakdown.test.js +0 -446
- package/tests/unit/ai/classifier.test.js +0 -294
- package/tests/unit/ai/council.test.js +0 -262
- package/tests/unit/ai/embeddings.test.js +0 -384
- package/tests/unit/ai/federation-ai.test.js +0 -344
- package/tests/unit/ai/h3-ai.test.js +0 -458
- package/tests/unit/ai/index.test.js +0 -304
- package/tests/unit/ai/json-ops.test.js +0 -307
- package/tests/unit/ai/llm-service.test.js +0 -390
- package/tests/unit/ai/nl-query.test.js +0 -383
- package/tests/unit/ai/relationships.test.js +0 -311
- package/tests/unit/ai/schema-extractor.test.js +0 -384
- package/tests/unit/ai/spatial.test.js +0 -279
- package/tests/unit/ai/tts.test.js +0 -279
- package/tests/unit/content.test.js +0 -332
- package/tests/unit/contract/core.test.js +0 -88
- package/tests/unit/contract/crypto.test.js +0 -198
- package/tests/unit/contract/data.test.js +0 -223
- package/tests/unit/contract/federation.test.js +0 -181
- package/tests/unit/contract/hierarchical.test.js +0 -113
- package/tests/unit/contract/schema.test.js +0 -114
- package/tests/unit/contract/social.test.js +0 -217
- package/tests/unit/contract/spatial.test.js +0 -110
- package/tests/unit/contract/subscriptions.test.js +0 -128
- package/tests/unit/contract/utils.test.js +0 -159
- package/tests/unit/core.test.js +0 -152
- package/tests/unit/crypto.test.js +0 -328
- package/tests/unit/federation.test.js +0 -234
- package/tests/unit/gun-async.test.js +0 -252
- package/tests/unit/hierarchical.test.js +0 -399
- package/tests/unit/integration/scenario-01-geographic-storage.test.js +0 -74
- package/tests/unit/integration/scenario-02-federation.test.js +0 -76
- package/tests/unit/integration/scenario-03-subscriptions.test.js +0 -102
- package/tests/unit/integration/scenario-04-validation.test.js +0 -129
- package/tests/unit/integration/scenario-05-hierarchy.test.js +0 -125
- package/tests/unit/integration/scenario-06-social.test.js +0 -135
- package/tests/unit/integration/scenario-07-persistence.test.js +0 -130
- package/tests/unit/integration/scenario-08-authorization.test.js +0 -161
- package/tests/unit/integration/scenario-09-cross-dimensional.test.js +0 -139
- package/tests/unit/integration/scenario-10-cross-holosphere-capabilities.test.js +0 -357
- package/tests/unit/integration/scenario-11-cross-holosphere-federation.test.js +0 -410
- package/tests/unit/integration/scenario-12-capability-federated-read.test.js +0 -719
- package/tests/unit/performance/benchmark.test.js +0 -85
- package/tests/unit/schema.test.js +0 -213
- package/tests/unit/spatial.test.js +0 -158
- package/tests/unit/storage.test.js +0 -195
- package/tests/unit/subscriptions.test.js +0 -328
- package/tests/unit/test-data-permanence-debug.js +0 -197
- package/tests/unit/test-data-permanence.js +0 -340
- package/tests/unit/test-key-persistence-fixed.js +0 -148
- package/tests/unit/test-key-persistence.js +0 -172
- package/tests/unit/test-relay-permanence.js +0 -376
- package/tests/unit/test-second-node.js +0 -95
- package/tests/unit/test-simple-write.js +0 -89
|
@@ -1,17 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* GunDB Storage Backend
|
|
3
|
-
*
|
|
3
|
+
* Full-featured backend with authentication, schema validation, references, and global tables
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { StorageBackend } from '../backend-interface.js';
|
|
7
7
|
import * as wrapper from '../gun-wrapper.js';
|
|
8
8
|
import { gunPromise, gunPut, gunMap, gunCollect } from '../gun-async.js';
|
|
9
|
+
import { GunReferenceHandler } from '../gun-references.js';
|
|
10
|
+
import { GunAuth } from '../gun-auth.js';
|
|
11
|
+
import { GunSchemaValidator } from '../gun-schema.js';
|
|
9
12
|
|
|
10
13
|
export class GunDBBackend extends StorageBackend {
|
|
11
14
|
constructor(config) {
|
|
12
15
|
super(config);
|
|
13
16
|
this.gun = null;
|
|
14
17
|
this.keyPair = null;
|
|
18
|
+
this.appName = config.appName || 'holosphere';
|
|
19
|
+
|
|
20
|
+
// New modules
|
|
21
|
+
this.references = null;
|
|
22
|
+
this.auth = null;
|
|
23
|
+
this.schemaValidator = null;
|
|
24
|
+
|
|
25
|
+
// Subscription tracking
|
|
26
|
+
this.subscriptions = new Map();
|
|
27
|
+
this.subscriptionCounter = 0;
|
|
15
28
|
}
|
|
16
29
|
|
|
17
30
|
async init() {
|
|
@@ -50,28 +63,93 @@ export class GunDBBackend extends StorageBackend {
|
|
|
50
63
|
}
|
|
51
64
|
} else {
|
|
52
65
|
// SEA not available, use simple identifier
|
|
53
|
-
this.publicKey = this.
|
|
66
|
+
this.publicKey = this.appName;
|
|
54
67
|
}
|
|
55
68
|
} catch (error) {
|
|
56
69
|
// SEA might not be loaded
|
|
57
|
-
this.publicKey = this.
|
|
70
|
+
this.publicKey = this.appName;
|
|
58
71
|
}
|
|
72
|
+
|
|
73
|
+
// Initialize modules
|
|
74
|
+
this.references = new GunReferenceHandler(this.appName);
|
|
75
|
+
this.auth = new GunAuth(this.gun, this.appName);
|
|
76
|
+
this.schemaValidator = new GunSchemaValidator({
|
|
77
|
+
strict: this.config.strict || false,
|
|
78
|
+
cacheMaxAge: this.config.schemaCacheMaxAge || 3600000
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Try to initialize schema validator (may fail if AJV not installed)
|
|
82
|
+
await this.schemaValidator.init();
|
|
59
83
|
}
|
|
60
84
|
|
|
85
|
+
// ============================================================================
|
|
86
|
+
// PATH BUILDING
|
|
87
|
+
// ============================================================================
|
|
88
|
+
|
|
61
89
|
buildPath(appName, holonId, lensName, key = null) {
|
|
62
90
|
return wrapper.buildPath(appName, holonId, lensName, key);
|
|
63
91
|
}
|
|
64
92
|
|
|
93
|
+
buildGlobalPath(tableName, key = null) {
|
|
94
|
+
return wrapper.buildGlobalPath(this.appName, tableName, key);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ============================================================================
|
|
98
|
+
// BASIC CRUD OPERATIONS
|
|
99
|
+
// ============================================================================
|
|
100
|
+
|
|
65
101
|
async write(path, data, options = {}) {
|
|
102
|
+
// Validate against schema if strict mode
|
|
103
|
+
if (this.schemaValidator && this.schemaValidator.isStrict()) {
|
|
104
|
+
const pathParts = path.split('/');
|
|
105
|
+
if (pathParts.length >= 3) {
|
|
106
|
+
const lens = pathParts[2];
|
|
107
|
+
// Skip validation for references
|
|
108
|
+
if (!this.isReference(data)) {
|
|
109
|
+
const result = await this.schemaValidator.validateData(
|
|
110
|
+
this.gun, this.appName, lens, data
|
|
111
|
+
);
|
|
112
|
+
if (!result.valid) {
|
|
113
|
+
throw new Error(`Schema validation failed: ${JSON.stringify(result.errors)}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
66
119
|
return wrapper.write(this.gun, path, data);
|
|
67
120
|
}
|
|
68
121
|
|
|
69
122
|
async read(path, options = {}) {
|
|
70
|
-
|
|
123
|
+
const data = await wrapper.read(this.gun, path);
|
|
124
|
+
|
|
125
|
+
// Resolve references if requested
|
|
126
|
+
if (data && options.resolveReferences && this.isReference(data)) {
|
|
127
|
+
return this.resolveReference(data);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return data;
|
|
71
131
|
}
|
|
72
132
|
|
|
73
133
|
async readAll(path, options = {}) {
|
|
74
|
-
|
|
134
|
+
const items = await wrapper.readAll(this.gun, path);
|
|
135
|
+
|
|
136
|
+
// Resolve references if requested
|
|
137
|
+
if (options.resolveReferences) {
|
|
138
|
+
const resolved = [];
|
|
139
|
+
for (const item of items) {
|
|
140
|
+
if (this.isReference(item)) {
|
|
141
|
+
const resolvedItem = await this.resolveReference(item);
|
|
142
|
+
if (resolvedItem) {
|
|
143
|
+
resolved.push(resolvedItem);
|
|
144
|
+
}
|
|
145
|
+
} else {
|
|
146
|
+
resolved.push(item);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return resolved;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return items;
|
|
75
153
|
}
|
|
76
154
|
|
|
77
155
|
async update(path, updates) {
|
|
@@ -83,7 +161,6 @@ export class GunDBBackend extends StorageBackend {
|
|
|
83
161
|
}
|
|
84
162
|
|
|
85
163
|
async deleteAll(path) {
|
|
86
|
-
// Gun doesn't have a native deleteAll, so we iterate and delete
|
|
87
164
|
const items = await this.readAll(path);
|
|
88
165
|
let count = 0;
|
|
89
166
|
|
|
@@ -98,58 +175,272 @@ export class GunDBBackend extends StorageBackend {
|
|
|
98
175
|
return { success: true, count };
|
|
99
176
|
}
|
|
100
177
|
|
|
178
|
+
// ============================================================================
|
|
179
|
+
// GLOBAL TABLE OPERATIONS
|
|
180
|
+
// ============================================================================
|
|
181
|
+
|
|
182
|
+
async writeGlobal(tableName, data) {
|
|
183
|
+
return wrapper.writeGlobal(this.gun, this.appName, tableName, data);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async readGlobal(tableName, key) {
|
|
187
|
+
return wrapper.readGlobal(this.gun, this.appName, tableName, key);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async readAllGlobal(tableName, timeout = 5000) {
|
|
191
|
+
return wrapper.readAllGlobal(this.gun, this.appName, tableName, timeout);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async deleteGlobal(tableName, key) {
|
|
195
|
+
return wrapper.deleteGlobal(this.gun, this.appName, tableName, key);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async deleteAllGlobal(tableName) {
|
|
199
|
+
return wrapper.deleteAllGlobal(this.gun, this.appName, tableName);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ============================================================================
|
|
203
|
+
// REFERENCE OPERATIONS
|
|
204
|
+
// ============================================================================
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Create a reference to data
|
|
208
|
+
*/
|
|
209
|
+
createReference(holon, lens, data) {
|
|
210
|
+
return this.references.createReference(holon, lens, data);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Check if data is a reference
|
|
215
|
+
*/
|
|
216
|
+
isReference(data) {
|
|
217
|
+
return this.references.isReference(data);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Resolve a reference to get actual data
|
|
222
|
+
*/
|
|
223
|
+
async resolveReference(reference, options = {}) {
|
|
224
|
+
return this.references.resolveReference(this.gun, reference, options);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Parse a soul path
|
|
229
|
+
*/
|
|
230
|
+
parseSoulPath(soul) {
|
|
231
|
+
return this.references.parseSoulPath(soul);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Get soul for data
|
|
236
|
+
*/
|
|
237
|
+
getSoul(holon, lens, key) {
|
|
238
|
+
return this.references.getSoul(holon, lens, key);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// ============================================================================
|
|
242
|
+
// AUTHENTICATION OPERATIONS
|
|
243
|
+
// ============================================================================
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Authenticate for a holon
|
|
247
|
+
*/
|
|
248
|
+
async authenticate(holonId, password) {
|
|
249
|
+
return this.auth.authenticate(holonId, password);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Create a new user
|
|
254
|
+
*/
|
|
255
|
+
async createUser(holonId, password) {
|
|
256
|
+
return this.auth.createUser(holonId, password);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Logout current user
|
|
261
|
+
*/
|
|
262
|
+
logout() {
|
|
263
|
+
this.auth.logout();
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Check if authenticated
|
|
268
|
+
*/
|
|
269
|
+
isAuthenticated() {
|
|
270
|
+
return this.auth.isAuthenticated();
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Write to private storage
|
|
275
|
+
*/
|
|
276
|
+
async writePrivate(lens, key, data) {
|
|
277
|
+
return this.auth.writePrivate(lens, key, data);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Read from private storage
|
|
282
|
+
*/
|
|
283
|
+
async readPrivate(lens, key) {
|
|
284
|
+
return this.auth.readPrivate(lens, key);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Read all from private lens
|
|
289
|
+
*/
|
|
290
|
+
async readAllPrivate(lens, timeout = 2000) {
|
|
291
|
+
return this.auth.readAllPrivate(lens, timeout);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Delete from private storage
|
|
296
|
+
*/
|
|
297
|
+
async deletePrivate(lens, key) {
|
|
298
|
+
return this.auth.deletePrivate(lens, key);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// ============================================================================
|
|
302
|
+
// SCHEMA OPERATIONS
|
|
303
|
+
// ============================================================================
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Set a schema for a lens
|
|
307
|
+
*/
|
|
308
|
+
async setSchema(lens, schema) {
|
|
309
|
+
return this.schemaValidator.setSchema(this.gun, this.appName, lens, schema);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Get a schema for a lens
|
|
314
|
+
*/
|
|
315
|
+
async getSchema(lens, options = {}) {
|
|
316
|
+
return this.schemaValidator.getSchema(this.gun, this.appName, lens, options);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Get all schemas
|
|
321
|
+
*/
|
|
322
|
+
async getAllSchemas() {
|
|
323
|
+
return this.schemaValidator.getAllSchemas(this.gun, this.appName);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Delete a schema
|
|
328
|
+
*/
|
|
329
|
+
async deleteSchema(lens) {
|
|
330
|
+
return this.schemaValidator.deleteSchema(this.gun, this.appName, lens);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Validate data against a schema
|
|
335
|
+
*/
|
|
336
|
+
validate(schema, data) {
|
|
337
|
+
return this.schemaValidator.validate(schema, data);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Validate data against a lens schema
|
|
342
|
+
*/
|
|
343
|
+
async validateData(lens, data) {
|
|
344
|
+
return this.schemaValidator.validateData(this.gun, this.appName, lens, data);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Clear schema cache
|
|
349
|
+
*/
|
|
350
|
+
clearSchemaCache(lens = null) {
|
|
351
|
+
this.schemaValidator.clearCache(lens);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// ============================================================================
|
|
355
|
+
// SUBSCRIPTIONS
|
|
356
|
+
// ============================================================================
|
|
357
|
+
|
|
101
358
|
async subscribe(path, callback, options = {}) {
|
|
102
|
-
|
|
359
|
+
const subscriptionId = ++this.subscriptionCounter;
|
|
103
360
|
const pathParts = path.split('/');
|
|
104
361
|
const isPrefix = pathParts.length <= 3;
|
|
105
362
|
|
|
363
|
+
const subscription = {
|
|
364
|
+
id: subscriptionId,
|
|
365
|
+
path,
|
|
366
|
+
active: true,
|
|
367
|
+
gunRef: null
|
|
368
|
+
};
|
|
369
|
+
|
|
106
370
|
if (isPrefix) {
|
|
107
|
-
// Subscribe to all items under this prefix
|
|
108
371
|
const ref = this.gun.get(path);
|
|
109
|
-
|
|
372
|
+
subscription.gunRef = ref;
|
|
373
|
+
|
|
374
|
+
ref.map().on(async (data, key) => {
|
|
375
|
+
if (!this.subscriptions.get(subscriptionId)?.active) return;
|
|
376
|
+
if (!data || key.startsWith('_') || data._deleted) return;
|
|
377
|
+
|
|
378
|
+
// Parse data
|
|
379
|
+
let parsed = wrapper.parse(data);
|
|
380
|
+
if (!parsed) return;
|
|
110
381
|
|
|
111
|
-
|
|
112
|
-
if (
|
|
113
|
-
|
|
382
|
+
// Resolve references if requested
|
|
383
|
+
if (options.resolveReferences && this.isReference(parsed)) {
|
|
384
|
+
parsed = await this.resolveReference(parsed);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (parsed) {
|
|
388
|
+
callback(parsed, key);
|
|
114
389
|
}
|
|
115
390
|
});
|
|
391
|
+
} else {
|
|
392
|
+
const ref = this.gun.get(path);
|
|
393
|
+
subscription.gunRef = ref;
|
|
116
394
|
|
|
117
|
-
|
|
118
|
-
|
|
395
|
+
ref.on(async (data, key) => {
|
|
396
|
+
if (!this.subscriptions.get(subscriptionId)?.active) return;
|
|
397
|
+
if (!data) return;
|
|
119
398
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
}
|
|
130
|
-
};
|
|
131
|
-
} else {
|
|
132
|
-
// Subscribe to single item
|
|
133
|
-
const unsubFn = wrapper.subscribe(this.gun, path, callback);
|
|
134
|
-
return {
|
|
135
|
-
unsubscribe: typeof unsubFn === 'function' ? unsubFn : () => {},
|
|
136
|
-
};
|
|
399
|
+
let parsed = wrapper.parse(data);
|
|
400
|
+
if (!parsed || parsed._deleted) return;
|
|
401
|
+
|
|
402
|
+
if (options.resolveReferences && this.isReference(parsed)) {
|
|
403
|
+
parsed = await this.resolveReference(parsed);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if (parsed) {
|
|
407
|
+
callback(parsed, key);
|
|
408
|
+
}
|
|
409
|
+
});
|
|
137
410
|
}
|
|
411
|
+
|
|
412
|
+
this.subscriptions.set(subscriptionId, subscription);
|
|
413
|
+
|
|
414
|
+
return {
|
|
415
|
+
id: subscriptionId,
|
|
416
|
+
unsubscribe: () => {
|
|
417
|
+
const sub = this.subscriptions.get(subscriptionId);
|
|
418
|
+
if (sub) {
|
|
419
|
+
sub.active = false;
|
|
420
|
+
if (sub.gunRef) {
|
|
421
|
+
try {
|
|
422
|
+
sub.gunRef.off();
|
|
423
|
+
} catch (e) {}
|
|
424
|
+
}
|
|
425
|
+
this.subscriptions.delete(subscriptionId);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
};
|
|
138
429
|
}
|
|
139
430
|
|
|
431
|
+
// ============================================================================
|
|
432
|
+
// EXPORT/IMPORT
|
|
433
|
+
// ============================================================================
|
|
434
|
+
|
|
140
435
|
async exportData(pathPrefix = '') {
|
|
141
436
|
const records = [];
|
|
142
|
-
|
|
143
|
-
// Get the app-level data
|
|
144
|
-
const appName = this.config.appName || 'holosphere';
|
|
145
|
-
const basePath = pathPrefix || appName;
|
|
437
|
+
const basePath = pathPrefix || this.appName;
|
|
146
438
|
|
|
147
439
|
try {
|
|
148
440
|
const items = await gunMap(this.gun.get(basePath), 2000);
|
|
149
441
|
|
|
150
442
|
for (const [key, data] of Object.entries(items)) {
|
|
151
443
|
if (!key.startsWith('_') && data && typeof data === 'object' && !data._deleted) {
|
|
152
|
-
// Recursively collect nested data
|
|
153
444
|
await this._collectRecords(basePath, key, data, records);
|
|
154
445
|
}
|
|
155
446
|
}
|
|
@@ -164,14 +455,12 @@ export class GunDBBackend extends StorageBackend {
|
|
|
164
455
|
const currentPath = `${basePath}/${key}`;
|
|
165
456
|
|
|
166
457
|
if (data.id) {
|
|
167
|
-
// This is a leaf data node
|
|
168
458
|
records.push({
|
|
169
459
|
path: currentPath,
|
|
170
460
|
data: this._cleanGunData(data),
|
|
171
461
|
timestamp: data._meta?.timestamp || Date.now(),
|
|
172
462
|
});
|
|
173
463
|
} else {
|
|
174
|
-
// This might be a nested structure, explore further
|
|
175
464
|
try {
|
|
176
465
|
const nested = await gunMap(this.gun.get(currentPath), 500);
|
|
177
466
|
for (const [nestedKey, nestedData] of Object.entries(nested)) {
|
|
@@ -179,14 +468,11 @@ export class GunDBBackend extends StorageBackend {
|
|
|
179
468
|
await this._collectRecords(currentPath, nestedKey, nestedData, records);
|
|
180
469
|
}
|
|
181
470
|
}
|
|
182
|
-
} catch (e) {
|
|
183
|
-
// Ignore nested exploration errors
|
|
184
|
-
}
|
|
471
|
+
} catch (e) {}
|
|
185
472
|
}
|
|
186
473
|
}
|
|
187
474
|
|
|
188
475
|
_cleanGunData(data) {
|
|
189
|
-
// Remove Gun's internal metadata
|
|
190
476
|
const cleaned = { ...data };
|
|
191
477
|
delete cleaned['_'];
|
|
192
478
|
return cleaned;
|
|
@@ -208,14 +494,56 @@ export class GunDBBackend extends StorageBackend {
|
|
|
208
494
|
return results;
|
|
209
495
|
}
|
|
210
496
|
|
|
497
|
+
// ============================================================================
|
|
498
|
+
// LIFECYCLE
|
|
499
|
+
// ============================================================================
|
|
500
|
+
|
|
211
501
|
close() {
|
|
212
|
-
//
|
|
502
|
+
// 1. Unsubscribe all active subscriptions
|
|
503
|
+
for (const [id, subscription] of this.subscriptions) {
|
|
504
|
+
if (subscription.active) {
|
|
505
|
+
subscription.active = false;
|
|
506
|
+
if (subscription.gunRef) {
|
|
507
|
+
try {
|
|
508
|
+
subscription.gunRef.off();
|
|
509
|
+
} catch (e) {}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
this.subscriptions.clear();
|
|
514
|
+
|
|
515
|
+
// 2. Clear schema cache
|
|
516
|
+
if (this.schemaValidator) {
|
|
517
|
+
this.schemaValidator.clearCache();
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// 3. Logout auth
|
|
521
|
+
if (this.auth) {
|
|
522
|
+
this.auth.logout();
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// 4. Clean up Gun connections
|
|
213
526
|
if (this.gun) {
|
|
214
527
|
try {
|
|
215
|
-
//
|
|
528
|
+
// Clean up mesh connections
|
|
529
|
+
const mesh = this.gun.back('opt.mesh');
|
|
530
|
+
if (mesh) {
|
|
531
|
+
if (mesh.way) {
|
|
532
|
+
Object.values(mesh.way).forEach(conn => {
|
|
533
|
+
if (conn?.wire?.close) conn.wire.close();
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
if (mesh.opt?.peers) mesh.opt.peers = {};
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// Close web server if present
|
|
540
|
+
const server = this.gun.back('opt.web');
|
|
541
|
+
if (server?.close) server.close();
|
|
542
|
+
|
|
543
|
+
// Turn off all listeners
|
|
216
544
|
this.gun.off();
|
|
217
|
-
} catch (
|
|
218
|
-
|
|
545
|
+
} catch (error) {
|
|
546
|
+
console.warn('Error during Gun cleanup:', error);
|
|
219
547
|
}
|
|
220
548
|
}
|
|
221
549
|
}
|
|
@@ -226,6 +554,9 @@ export class GunDBBackend extends StorageBackend {
|
|
|
226
554
|
publicKey: this.publicKey,
|
|
227
555
|
peers: this.config.peers || [],
|
|
228
556
|
connected: !!this.gun,
|
|
557
|
+
authenticated: this.isAuthenticated(),
|
|
558
|
+
subscriptionCount: this.subscriptions.size,
|
|
559
|
+
schemaValidationEnabled: this.schemaValidator?.isStrict() || false
|
|
229
560
|
};
|
|
230
561
|
}
|
|
231
562
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Non-location-specific storage (FR-044, FR-045)
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { write, read, readAll, update, deleteData, deleteAll } from './
|
|
6
|
+
import { write, read, readAll, update, deleteData, deleteAll, subscribe } from './unified-storage.js';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Write data to global table
|
|
@@ -79,3 +79,30 @@ export async function deleteAllGlobal(client, appname, table) {
|
|
|
79
79
|
const path = `${appname}/${table}`;
|
|
80
80
|
return deleteAll(client, path);
|
|
81
81
|
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get all data from global table
|
|
85
|
+
* @param {Object} client - Nostr client instance
|
|
86
|
+
* @param {string} appname - Application namespace
|
|
87
|
+
* @param {string} table - Global table name
|
|
88
|
+
* @returns {Promise<Object[]>} Array of data objects
|
|
89
|
+
*/
|
|
90
|
+
export async function getAllGlobal(client, appname, table) {
|
|
91
|
+
const path = `${appname}/${table}`;
|
|
92
|
+
return readAll(client, path);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Subscribe to global table changes
|
|
97
|
+
* @param {Object} client - Nostr client instance
|
|
98
|
+
* @param {string} appname - Application namespace
|
|
99
|
+
* @param {string} table - Global table name
|
|
100
|
+
* @param {string} key - Data key (optional, if not provided subscribes to all)
|
|
101
|
+
* @param {Function} callback - Called on data changes
|
|
102
|
+
* @param {Object} options - Subscription options
|
|
103
|
+
* @returns {Promise<Object>} Subscription object with unsubscribe method
|
|
104
|
+
*/
|
|
105
|
+
export async function subscribeGlobal(client, appname, table, key, callback, options = {}) {
|
|
106
|
+
const path = key ? `${appname}/${table}/${key}` : `${appname}/${table}`;
|
|
107
|
+
return subscribe(client, path, callback, options);
|
|
108
|
+
}
|