holosphere 1.1.8 → 1.1.10
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/federation.js +67 -21
- package/holosphere.js +601 -455
- package/package.json +1 -1
- package/test/auth.test.js +35 -19
- package/test/delete.test.js +1 -0
- package/test/holosphere.test.js +189 -5
- package/test/reference.test.js +211 -0
- package/test/subscription.test.js +329 -0
- package/babel.config.js +0 -5
- package/babel.config.json +0 -12
package/holosphere.js
CHANGED
|
@@ -1,11 +1,24 @@
|
|
|
1
1
|
import * as h3 from 'h3-js';
|
|
2
2
|
import OpenAI from 'openai';
|
|
3
3
|
import Gun from 'gun'
|
|
4
|
-
import SEA from 'gun/sea.js'
|
|
5
4
|
import Ajv2019 from 'ajv/dist/2019.js'
|
|
6
5
|
import * as Federation from './federation.js';
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
/**
|
|
8
|
+
* HoloSphere is a cutting-edge holonic communication infrastructure designed to facilitate
|
|
9
|
+
* the creation, validation, and sharing of information.. By leveraging advanced technologies
|
|
10
|
+
* such as H3 for geospatial indexing, OpenAI for natural language processing, and Gun for decentralized
|
|
11
|
+
* data storage, HoloSphere provides a robust platform for developers to build innovative stygmergic applications.
|
|
12
|
+
*
|
|
13
|
+
* Key Features:
|
|
14
|
+
* - Geospatial indexing using H3
|
|
15
|
+
* - Natural language processing with OpenAI
|
|
16
|
+
* - Decentralized data storage with Gun
|
|
17
|
+
* - JSON schema validation with Ajv
|
|
18
|
+
* - Federation capabilities for distributed systems
|
|
19
|
+
*
|
|
20
|
+
*
|
|
21
|
+
*/
|
|
9
22
|
|
|
10
23
|
class HoloSphere {
|
|
11
24
|
/**
|
|
@@ -15,8 +28,8 @@ class HoloSphere {
|
|
|
15
28
|
* @param {string|null} openaikey - The OpenAI API key.
|
|
16
29
|
* @param {Gun|null} gunInstance - The Gun instance to use.
|
|
17
30
|
*/
|
|
18
|
-
constructor(appname, strict = false, openaikey = null
|
|
19
|
-
console.log('HoloSphere v1.1.
|
|
31
|
+
constructor(appname, strict = false, openaikey = null) {
|
|
32
|
+
console.log('HoloSphere v1.1.10');
|
|
20
33
|
this.appname = appname
|
|
21
34
|
this.strict = strict;
|
|
22
35
|
this.validator = new Ajv2019({
|
|
@@ -25,20 +38,13 @@ class HoloSphere {
|
|
|
25
38
|
validateSchema: true // Always validate schemas
|
|
26
39
|
});
|
|
27
40
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
peers: ['https://gun.holons.io/gun'],
|
|
36
|
-
axe: false,
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Initialize SEA
|
|
41
|
-
this.sea = SEA;
|
|
41
|
+
|
|
42
|
+
// Use provided Gun instance or create new one with default options
|
|
43
|
+
this.gun = Gun({
|
|
44
|
+
peers: ['https://gun.holons.io/gun'],
|
|
45
|
+
axe: false,
|
|
46
|
+
});
|
|
47
|
+
|
|
42
48
|
|
|
43
49
|
if (openaikey != null) {
|
|
44
50
|
this.openai = new OpenAI({
|
|
@@ -48,6 +54,9 @@ class HoloSphere {
|
|
|
48
54
|
|
|
49
55
|
// Initialize subscriptions
|
|
50
56
|
this.subscriptions = {};
|
|
57
|
+
|
|
58
|
+
// Initialize schema cache
|
|
59
|
+
this.schemaCache = new Map();
|
|
51
60
|
}
|
|
52
61
|
|
|
53
62
|
// ================================ SCHEMA FUNCTIONS ================================
|
|
@@ -109,6 +118,12 @@ class HoloSphere {
|
|
|
109
118
|
schema: schema,
|
|
110
119
|
timestamp: Date.now()
|
|
111
120
|
});
|
|
121
|
+
|
|
122
|
+
// Update the cache with the new schema
|
|
123
|
+
this.schemaCache.set(lens, {
|
|
124
|
+
schema,
|
|
125
|
+
timestamp: Date.now()
|
|
126
|
+
});
|
|
112
127
|
|
|
113
128
|
return true;
|
|
114
129
|
}
|
|
@@ -116,21 +131,61 @@ class HoloSphere {
|
|
|
116
131
|
/**
|
|
117
132
|
* Retrieves the JSON schema for a specific lens.
|
|
118
133
|
* @param {string} lens - The lens identifier.
|
|
134
|
+
* @param {object} [options] - Additional options
|
|
135
|
+
* @param {boolean} [options.useCache=true] - Whether to use the schema cache
|
|
136
|
+
* @param {number} [options.maxCacheAge=3600000] - Maximum cache age in milliseconds (default: 1 hour)
|
|
119
137
|
* @returns {Promise<object|null>} - The retrieved schema or null if not found.
|
|
120
138
|
*/
|
|
121
|
-
async getSchema(lens) {
|
|
139
|
+
async getSchema(lens, options = {}) {
|
|
122
140
|
if (!lens) {
|
|
123
141
|
throw new Error('getSchema: Missing lens parameter');
|
|
124
142
|
}
|
|
125
|
-
|
|
143
|
+
|
|
144
|
+
const { useCache = true, maxCacheAge = 3600000 } = options;
|
|
145
|
+
|
|
146
|
+
// Check cache first if enabled
|
|
147
|
+
if (useCache && this.schemaCache.has(lens)) {
|
|
148
|
+
const cached = this.schemaCache.get(lens);
|
|
149
|
+
const cacheAge = Date.now() - cached.timestamp;
|
|
150
|
+
|
|
151
|
+
// Use cache if it's fresh enough
|
|
152
|
+
if (cacheAge < maxCacheAge) {
|
|
153
|
+
return cached.schema;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Cache miss or expired, fetch from storage
|
|
126
158
|
const schemaData = await this.getGlobal('schemas', lens);
|
|
159
|
+
|
|
127
160
|
if (!schemaData || !schemaData.schema) {
|
|
128
161
|
return null;
|
|
129
162
|
}
|
|
130
|
-
|
|
163
|
+
|
|
164
|
+
// Update cache with fetched schema
|
|
165
|
+
this.schemaCache.set(lens, {
|
|
166
|
+
schema: schemaData.schema,
|
|
167
|
+
timestamp: Date.now()
|
|
168
|
+
});
|
|
169
|
+
|
|
131
170
|
return schemaData.schema;
|
|
132
171
|
}
|
|
133
172
|
|
|
173
|
+
/**
|
|
174
|
+
* Clears the schema cache or a specific schema from the cache.
|
|
175
|
+
* @param {string} [lens] - Optional lens to clear from cache. If not provided, clears entire cache.
|
|
176
|
+
* @returns {boolean} - Returns true if successful
|
|
177
|
+
*/
|
|
178
|
+
clearSchemaCache(lens = null) {
|
|
179
|
+
if (lens) {
|
|
180
|
+
// Clear specific schema
|
|
181
|
+
return this.schemaCache.delete(lens);
|
|
182
|
+
} else {
|
|
183
|
+
// Clear entire cache
|
|
184
|
+
this.schemaCache.clear();
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
134
189
|
// ================================ CONTENT FUNCTIONS ================================
|
|
135
190
|
|
|
136
191
|
/**
|
|
@@ -154,8 +209,11 @@ class HoloSphere {
|
|
|
154
209
|
data.id = this.generateId();
|
|
155
210
|
}
|
|
156
211
|
|
|
157
|
-
//
|
|
158
|
-
|
|
212
|
+
// Check if this is a reference we're storing
|
|
213
|
+
const isRef = this.isReference(data);
|
|
214
|
+
|
|
215
|
+
// Get and validate schema only in strict mode for non-references
|
|
216
|
+
if (this.strict && !isRef) {
|
|
159
217
|
const schema = await this.getSchema(lens);
|
|
160
218
|
if (!schema) {
|
|
161
219
|
throw new Error('Schema required in strict mode');
|
|
@@ -170,72 +228,15 @@ class HoloSphere {
|
|
|
170
228
|
}
|
|
171
229
|
|
|
172
230
|
try {
|
|
173
|
-
|
|
174
|
-
|
|
231
|
+
let user = null;
|
|
175
232
|
if (password) {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
});
|
|
233
|
+
user = this.gun.user();
|
|
234
|
+
await new Promise((resolve, reject) => {
|
|
235
|
+
user.auth(this.userName(holon), password, (ack) => {
|
|
236
|
+
if (ack.err) reject(new Error(ack.err));
|
|
237
|
+
else resolve();
|
|
182
238
|
});
|
|
183
|
-
}
|
|
184
|
-
// If authentication fails, try to create user and then authenticate
|
|
185
|
-
try {
|
|
186
|
-
await new Promise((resolve, reject) => {
|
|
187
|
-
user.create(this.userName(holon), password, (ack) => {
|
|
188
|
-
if (ack.err) {
|
|
189
|
-
// Don't reject if the user is already being created or already exists
|
|
190
|
-
if (ack.err.includes('already being created') ||
|
|
191
|
-
ack.err.includes('already created')) {
|
|
192
|
-
console.warn(`User creation note: ${ack.err}, continuing...`);
|
|
193
|
-
// Try to authenticate again
|
|
194
|
-
user.auth(this.userName(holon), password, (authAck) => {
|
|
195
|
-
if (authAck.err) {
|
|
196
|
-
if (authAck.err.includes('already being created') ||
|
|
197
|
-
authAck.err.includes('already created')) {
|
|
198
|
-
console.warn(`Auth note: ${authAck.err}, continuing...`);
|
|
199
|
-
resolve(); // Continue anyway
|
|
200
|
-
} else {
|
|
201
|
-
reject(new Error(authAck.err));
|
|
202
|
-
}
|
|
203
|
-
} else {
|
|
204
|
-
resolve();
|
|
205
|
-
}
|
|
206
|
-
});
|
|
207
|
-
} else {
|
|
208
|
-
reject(new Error(ack.err));
|
|
209
|
-
}
|
|
210
|
-
} else {
|
|
211
|
-
user.auth(this.userName(holon), password, (authAck) => {
|
|
212
|
-
if (authAck.err) reject(new Error(authAck.err));
|
|
213
|
-
else resolve();
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
|
-
});
|
|
217
|
-
});
|
|
218
|
-
} catch (createError) {
|
|
219
|
-
// Try one last authentication
|
|
220
|
-
try {
|
|
221
|
-
await new Promise((resolve, reject) => {
|
|
222
|
-
setTimeout(() => {
|
|
223
|
-
user.auth(this.userName(holon), password, (ack) => {
|
|
224
|
-
if (ack.err) {
|
|
225
|
-
// Continue even if auth fails at this point
|
|
226
|
-
console.warn(`Final auth attempt note: ${ack.err}, continuing with limited functionality`);
|
|
227
|
-
resolve();
|
|
228
|
-
} else {
|
|
229
|
-
resolve();
|
|
230
|
-
}
|
|
231
|
-
});
|
|
232
|
-
}, 100); // Short delay before retry
|
|
233
|
-
});
|
|
234
|
-
} catch (finalAuthError) {
|
|
235
|
-
console.warn('All authentication attempts failed, continuing with limited functionality');
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
+
});
|
|
239
240
|
}
|
|
240
241
|
|
|
241
242
|
return new Promise((resolve, reject) => {
|
|
@@ -246,14 +247,17 @@ class HoloSphere {
|
|
|
246
247
|
if (ack.err) {
|
|
247
248
|
reject(new Error(ack.err));
|
|
248
249
|
} else {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
250
|
+
// Only notify subscribers for actual data, not references
|
|
251
|
+
if (!isRef) {
|
|
252
|
+
this.notifySubscribers({
|
|
253
|
+
holon,
|
|
254
|
+
lens,
|
|
255
|
+
...data
|
|
256
|
+
});
|
|
257
|
+
}
|
|
254
258
|
|
|
255
|
-
// Auto-propagate to federation by default
|
|
256
|
-
const shouldPropagate = options.autoPropagate !== false;
|
|
259
|
+
// Auto-propagate to federation by default (if not a reference)
|
|
260
|
+
const shouldPropagate = options.autoPropagate !== false && !isRef;
|
|
257
261
|
let propagationResult = null;
|
|
258
262
|
|
|
259
263
|
if (shouldPropagate) {
|
|
@@ -282,18 +286,17 @@ class HoloSphere {
|
|
|
282
286
|
|
|
283
287
|
resolve({
|
|
284
288
|
success: true,
|
|
289
|
+
isReference: isRef,
|
|
285
290
|
propagationResult
|
|
286
291
|
});
|
|
287
292
|
}
|
|
288
293
|
};
|
|
289
294
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
this.gun.get(this.appname).get(holon).get(lens).get(data.id).put(payload, putCallback);
|
|
296
|
-
}
|
|
295
|
+
const dataPath = password ?
|
|
296
|
+
user.get('private').get(lens).get(data.id) :
|
|
297
|
+
this.gun.get(this.appname).get(holon).get(lens).get(data.id);
|
|
298
|
+
|
|
299
|
+
dataPath.put(payload, putCallback);
|
|
297
300
|
} catch (error) {
|
|
298
301
|
reject(error);
|
|
299
302
|
}
|
|
@@ -332,30 +335,15 @@ class HoloSphere {
|
|
|
332
335
|
}
|
|
333
336
|
|
|
334
337
|
try {
|
|
335
|
-
|
|
336
|
-
|
|
338
|
+
let user = null;
|
|
337
339
|
if (password) {
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
});
|
|
344
|
-
});
|
|
345
|
-
} catch (loginError) {
|
|
346
|
-
// If authentication fails, try to create user and then authenticate
|
|
347
|
-
await new Promise((resolve, reject) => {
|
|
348
|
-
user.create(this.userName(holon), password, (ack) => {
|
|
349
|
-
if (ack.err) reject(new Error(ack.err));
|
|
350
|
-
else {
|
|
351
|
-
user.auth(this.userName(holon), password, (authAck) => {
|
|
352
|
-
if (authAck.err) reject(new Error(authAck.err));
|
|
353
|
-
else resolve();
|
|
354
|
-
});
|
|
355
|
-
}
|
|
356
|
-
});
|
|
340
|
+
user = this.gun.user();
|
|
341
|
+
await new Promise((resolve, reject) => {
|
|
342
|
+
user.auth(this.userName(holon), password, (ack) => {
|
|
343
|
+
if (ack.err) reject(new Error(ack.err));
|
|
344
|
+
else resolve();
|
|
357
345
|
});
|
|
358
|
-
}
|
|
346
|
+
});
|
|
359
347
|
}
|
|
360
348
|
|
|
361
349
|
return new Promise((resolve) => {
|
|
@@ -374,84 +362,23 @@ class HoloSphere {
|
|
|
374
362
|
}
|
|
375
363
|
|
|
376
364
|
// Check if this is a reference that needs to be resolved
|
|
377
|
-
if (resolveReferences
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
// Get original data using the extracted path components
|
|
392
|
-
const originalData = await this.get(
|
|
393
|
-
originHolon,
|
|
394
|
-
originLens,
|
|
395
|
-
originKey,
|
|
396
|
-
null,
|
|
397
|
-
{ resolveReferences: false } // Prevent infinite recursion
|
|
398
|
-
);
|
|
399
|
-
|
|
400
|
-
if (originalData) {
|
|
401
|
-
console.log(`Original data found through soul path resolution:`, originalData);
|
|
402
|
-
resolve({
|
|
403
|
-
...originalData,
|
|
404
|
-
_federation: {
|
|
405
|
-
isReference: true,
|
|
406
|
-
resolved: true,
|
|
407
|
-
soul: parsed.soul,
|
|
408
|
-
timestamp: Date.now()
|
|
409
|
-
}
|
|
410
|
-
});
|
|
411
|
-
return;
|
|
412
|
-
} else {
|
|
413
|
-
console.warn(`Could not resolve reference: original data not found at extracted path`);
|
|
414
|
-
}
|
|
415
|
-
} else {
|
|
416
|
-
console.warn(`Soul doesn't match expected format: ${parsed.soul}`);
|
|
417
|
-
}
|
|
418
|
-
} catch (error) {
|
|
419
|
-
console.warn(`Error resolving reference by soul: ${error.message}`);
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
// Legacy federation reference
|
|
423
|
-
else if (parsed._federation && parsed._federation.isReference) {
|
|
424
|
-
console.log(`Resolving legacy federation reference from ${parsed._federation.origin}`);
|
|
425
|
-
try {
|
|
426
|
-
const reference = parsed._federation;
|
|
427
|
-
const originalData = await this.get(
|
|
428
|
-
reference.origin,
|
|
429
|
-
reference.lens,
|
|
430
|
-
key,
|
|
431
|
-
null,
|
|
432
|
-
{ resolveReferences: false } // Prevent infinite recursion
|
|
433
|
-
);
|
|
434
|
-
|
|
435
|
-
if (originalData) {
|
|
436
|
-
return {
|
|
437
|
-
...originalData,
|
|
438
|
-
_federation: {
|
|
439
|
-
...reference,
|
|
440
|
-
resolved: true,
|
|
441
|
-
timestamp: Date.now()
|
|
442
|
-
}
|
|
443
|
-
};
|
|
444
|
-
} else {
|
|
445
|
-
console.warn(`Could not resolve legacy reference: original data not found`);
|
|
446
|
-
return parsed; // Return the reference if we can't resolve it
|
|
447
|
-
}
|
|
448
|
-
} catch (error) {
|
|
449
|
-
console.warn(`Error resolving legacy reference: ${error.message}`);
|
|
450
|
-
return parsed;
|
|
451
|
-
}
|
|
365
|
+
if (resolveReferences && this.isReference(parsed)) {
|
|
366
|
+
const resolved = await this.resolveReference(parsed, {
|
|
367
|
+
followReferences: true // Always follow nested references when resolving
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
if (schema && resolved._federation) {
|
|
371
|
+
// Skip schema validation for resolved references
|
|
372
|
+
resolve(resolved);
|
|
373
|
+
return;
|
|
374
|
+
} else if (resolved !== parsed) {
|
|
375
|
+
// Reference was resolved successfully
|
|
376
|
+
resolve(resolved);
|
|
377
|
+
return;
|
|
452
378
|
}
|
|
453
379
|
}
|
|
454
380
|
|
|
381
|
+
// Perform schema validation if needed
|
|
455
382
|
if (schema) {
|
|
456
383
|
const valid = this.validator.validate(schema, parsed);
|
|
457
384
|
if (!valid) {
|
|
@@ -470,13 +397,11 @@ class HoloSphere {
|
|
|
470
397
|
}
|
|
471
398
|
};
|
|
472
399
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
this.gun.get(this.appname).get(holon).get(lens).get(key).once(handleData);
|
|
479
|
-
}
|
|
400
|
+
const dataPath = password ?
|
|
401
|
+
user.get('private').get(lens).get(key) :
|
|
402
|
+
this.gun.get(this.appname).get(holon).get(lens).get(key);
|
|
403
|
+
|
|
404
|
+
dataPath.once(handleData);
|
|
480
405
|
});
|
|
481
406
|
} catch (error) {
|
|
482
407
|
console.error('Error in get:', error);
|
|
@@ -496,7 +421,7 @@ class HoloSphere {
|
|
|
496
421
|
|
|
497
422
|
console.log(`getNodeBySoul: Accessing soul ${soul}`);
|
|
498
423
|
|
|
499
|
-
return new Promise((resolve) => {
|
|
424
|
+
return new Promise((resolve, reject) => {
|
|
500
425
|
try {
|
|
501
426
|
const ref = this.getNodeRef(soul);
|
|
502
427
|
ref.once((data) => {
|
|
@@ -509,7 +434,7 @@ class HoloSphere {
|
|
|
509
434
|
});
|
|
510
435
|
} catch (error) {
|
|
511
436
|
console.error(`getNodeBySoul error:`, error);
|
|
512
|
-
|
|
437
|
+
reject(error);
|
|
513
438
|
}
|
|
514
439
|
});
|
|
515
440
|
}
|
|
@@ -544,7 +469,16 @@ class HoloSphere {
|
|
|
544
469
|
}
|
|
545
470
|
|
|
546
471
|
try {
|
|
547
|
-
|
|
472
|
+
let user = null;
|
|
473
|
+
if (password) {
|
|
474
|
+
user = this.gun.user();
|
|
475
|
+
await new Promise((resolve, reject) => {
|
|
476
|
+
user.auth(this.userName(holon), password, (ack) => {
|
|
477
|
+
if (ack.err) reject(new Error(ack.err));
|
|
478
|
+
else resolve();
|
|
479
|
+
});
|
|
480
|
+
});
|
|
481
|
+
}
|
|
548
482
|
|
|
549
483
|
return new Promise((resolve) => {
|
|
550
484
|
const output = new Map();
|
|
@@ -556,6 +490,26 @@ class HoloSphere {
|
|
|
556
490
|
const parsed = await this.parse(data);
|
|
557
491
|
if (!parsed || !parsed.id) return;
|
|
558
492
|
|
|
493
|
+
// Check if this is a reference that needs to be resolved
|
|
494
|
+
if (this.isReference(parsed)) {
|
|
495
|
+
const resolved = await this.resolveReference(parsed, {
|
|
496
|
+
followReferences: true // Always follow references
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
if (resolved !== parsed) {
|
|
500
|
+
// Reference was resolved successfully
|
|
501
|
+
if (schema) {
|
|
502
|
+
const valid = this.validator.validate(schema, resolved);
|
|
503
|
+
if (valid || !this.strict) {
|
|
504
|
+
output.set(resolved.id, resolved);
|
|
505
|
+
}
|
|
506
|
+
} else {
|
|
507
|
+
output.set(resolved.id, resolved);
|
|
508
|
+
}
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
559
513
|
if (schema) {
|
|
560
514
|
const valid = this.validator.validate(schema, parsed);
|
|
561
515
|
if (valid || !this.strict) {
|
|
@@ -591,13 +545,11 @@ class HoloSphere {
|
|
|
591
545
|
}
|
|
592
546
|
};
|
|
593
547
|
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
this.gun.get(this.appname).get(holon).get(lens).once(handleData);
|
|
600
|
-
}
|
|
548
|
+
const dataPath = password ?
|
|
549
|
+
user.get('private').get(lens) :
|
|
550
|
+
this.gun.get(this.appname).get(holon).get(lens);
|
|
551
|
+
|
|
552
|
+
dataPath.once(handleData);
|
|
601
553
|
});
|
|
602
554
|
} catch (error) {
|
|
603
555
|
console.error('Error in getAll:', error);
|
|
@@ -677,30 +629,29 @@ class HoloSphere {
|
|
|
677
629
|
}
|
|
678
630
|
|
|
679
631
|
try {
|
|
680
|
-
|
|
681
|
-
|
|
632
|
+
let user = null;
|
|
633
|
+
if (password) {
|
|
634
|
+
user = this.gun.user();
|
|
635
|
+
await new Promise((resolve, reject) => {
|
|
636
|
+
user.auth(this.userName(holon), password, (ack) => {
|
|
637
|
+
if (ack.err) reject(new Error(ack.err));
|
|
638
|
+
else resolve();
|
|
639
|
+
});
|
|
640
|
+
});
|
|
641
|
+
}
|
|
682
642
|
|
|
683
|
-
// Delete data from holon
|
|
684
643
|
return new Promise((resolve, reject) => {
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
this.gun.get(this.appname).get(holon).get(lens).get(key).put(null, ack => {
|
|
697
|
-
if (ack.err) {
|
|
698
|
-
reject(new Error(ack.err));
|
|
699
|
-
} else {
|
|
700
|
-
resolve(true);
|
|
701
|
-
}
|
|
702
|
-
});
|
|
703
|
-
}
|
|
644
|
+
const dataPath = password ?
|
|
645
|
+
user.get('private').get(lens).get(key) :
|
|
646
|
+
this.gun.get(this.appname).get(holon).get(lens).get(key);
|
|
647
|
+
|
|
648
|
+
dataPath.put(null, ack => {
|
|
649
|
+
if (ack.err) {
|
|
650
|
+
reject(new Error(ack.err));
|
|
651
|
+
} else {
|
|
652
|
+
resolve(true);
|
|
653
|
+
}
|
|
654
|
+
});
|
|
704
655
|
});
|
|
705
656
|
} catch (error) {
|
|
706
657
|
console.error('Error in delete:', error);
|
|
@@ -722,8 +673,16 @@ class HoloSphere {
|
|
|
722
673
|
}
|
|
723
674
|
|
|
724
675
|
try {
|
|
725
|
-
|
|
726
|
-
|
|
676
|
+
let user = null;
|
|
677
|
+
if (password) {
|
|
678
|
+
user = this.gun.user();
|
|
679
|
+
await new Promise((resolve, reject) => {
|
|
680
|
+
user.auth(this.userName(holon), password, (ack) => {
|
|
681
|
+
if (ack.err) reject(new Error(ack.err));
|
|
682
|
+
else resolve();
|
|
683
|
+
});
|
|
684
|
+
});
|
|
685
|
+
}
|
|
727
686
|
|
|
728
687
|
return new Promise((resolve) => {
|
|
729
688
|
let deletionPromises = [];
|
|
@@ -820,18 +779,22 @@ class HoloSphere {
|
|
|
820
779
|
throw new Error('getNode: Missing required parameters');
|
|
821
780
|
}
|
|
822
781
|
|
|
823
|
-
return new Promise((resolve) => {
|
|
824
|
-
|
|
825
|
-
.get(
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
782
|
+
return new Promise((resolve, reject) => {
|
|
783
|
+
try {
|
|
784
|
+
this.gun.get(this.appname)
|
|
785
|
+
.get(holon)
|
|
786
|
+
.get(lens)
|
|
787
|
+
.get(key)
|
|
788
|
+
.once((data) => {
|
|
789
|
+
if (!data) {
|
|
790
|
+
resolve(null);
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
resolve(data); // Return the data directly
|
|
794
|
+
});
|
|
795
|
+
} catch (error) {
|
|
796
|
+
reject(error);
|
|
797
|
+
}
|
|
835
798
|
});
|
|
836
799
|
}
|
|
837
800
|
|
|
@@ -898,96 +861,29 @@ class HoloSphere {
|
|
|
898
861
|
throw new Error('Table name and data are required');
|
|
899
862
|
}
|
|
900
863
|
|
|
901
|
-
|
|
902
|
-
|
|
864
|
+
let user = null;
|
|
903
865
|
if (password) {
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
// Handle wrong username/password gracefully
|
|
910
|
-
if (ack.err.includes('Wrong user or password') ||
|
|
911
|
-
ack.err.includes('No user')) {
|
|
912
|
-
console.warn(`Authentication failed for ${tableName}: ${ack.err}`);
|
|
913
|
-
// Will try to create user next
|
|
914
|
-
reject(new Error(ack.err));
|
|
915
|
-
} else {
|
|
916
|
-
reject(new Error(ack.err));
|
|
917
|
-
}
|
|
918
|
-
} else {
|
|
919
|
-
resolve();
|
|
920
|
-
}
|
|
921
|
-
});
|
|
866
|
+
user = this.gun.user();
|
|
867
|
+
await new Promise((resolve, reject) => {
|
|
868
|
+
user.auth(this.userName(tableName), password, (ack) => {
|
|
869
|
+
if (ack.err) reject(new Error(ack.err));
|
|
870
|
+
else resolve();
|
|
922
871
|
});
|
|
923
|
-
}
|
|
924
|
-
// If authentication fails, try to create user
|
|
925
|
-
try {
|
|
926
|
-
await new Promise((resolve, reject) => {
|
|
927
|
-
user.create(this.userName(tableName), password, (ack) => {
|
|
928
|
-
// Handle "User already created!" error gracefully
|
|
929
|
-
if (ack.err && !ack.err.includes('already created')) {
|
|
930
|
-
reject(new Error(ack.err));
|
|
931
|
-
} else {
|
|
932
|
-
// Whether user was created or already existed, try to authenticate
|
|
933
|
-
user.auth(this.userName(tableName), password, (authAck) => {
|
|
934
|
-
if (authAck.err) {
|
|
935
|
-
console.warn(`Authentication failed after creation for ${tableName}: ${authAck.err}`);
|
|
936
|
-
reject(new Error(authAck.err));
|
|
937
|
-
} else {
|
|
938
|
-
resolve();
|
|
939
|
-
}
|
|
940
|
-
});
|
|
941
|
-
}
|
|
942
|
-
});
|
|
943
|
-
});
|
|
944
|
-
} catch (createError) {
|
|
945
|
-
// If both auth and create fail, try one last auth attempt
|
|
946
|
-
await new Promise((resolve, reject) => {
|
|
947
|
-
user.auth(this.userName(tableName), password, (ack) => {
|
|
948
|
-
if (ack.err) {
|
|
949
|
-
console.warn(`Final authentication attempt failed for ${tableName}: ${ack.err}`);
|
|
950
|
-
// Continue with operation even if auth fails
|
|
951
|
-
resolve();
|
|
952
|
-
} else {
|
|
953
|
-
resolve();
|
|
954
|
-
}
|
|
955
|
-
});
|
|
956
|
-
});
|
|
957
|
-
}
|
|
958
|
-
}
|
|
872
|
+
});
|
|
959
873
|
}
|
|
960
874
|
|
|
961
875
|
return new Promise((resolve, reject) => {
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
if (password) {
|
|
965
|
-
// For private data, use the authenticated user's holon
|
|
966
|
-
const path = user.get('private').get(tableName);
|
|
876
|
+
try {
|
|
877
|
+
const payload = JSON.stringify(data);
|
|
967
878
|
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
reject(new Error(ack.err));
|
|
972
|
-
} else {
|
|
973
|
-
resolve();
|
|
974
|
-
}
|
|
975
|
-
});
|
|
976
|
-
} else {
|
|
977
|
-
path.put(payload, ack => {
|
|
978
|
-
if (ack.err) {
|
|
979
|
-
reject(new Error(ack.err));
|
|
980
|
-
} else {
|
|
981
|
-
resolve();
|
|
982
|
-
}
|
|
983
|
-
});
|
|
984
|
-
}
|
|
985
|
-
} else {
|
|
986
|
-
// For public data, use the regular path
|
|
987
|
-
const path = this.gun.get(this.appname).get(tableName);
|
|
879
|
+
const dataPath = password ?
|
|
880
|
+
user.get('private').get(tableName) :
|
|
881
|
+
this.gun.get(this.appname).get(tableName);
|
|
988
882
|
|
|
989
883
|
if (data.id) {
|
|
990
|
-
|
|
884
|
+
// Store at the specific key path
|
|
885
|
+
dataPath.get(data.id).put(payload, ack => {
|
|
886
|
+
|
|
991
887
|
if (ack.err) {
|
|
992
888
|
reject(new Error(ack.err));
|
|
993
889
|
} else {
|
|
@@ -995,7 +891,7 @@ class HoloSphere {
|
|
|
995
891
|
}
|
|
996
892
|
});
|
|
997
893
|
} else {
|
|
998
|
-
|
|
894
|
+
dataPath.put(payload, ack => {
|
|
999
895
|
if (ack.err) {
|
|
1000
896
|
reject(new Error(ack.err));
|
|
1001
897
|
} else {
|
|
@@ -1003,6 +899,8 @@ class HoloSphere {
|
|
|
1003
899
|
}
|
|
1004
900
|
});
|
|
1005
901
|
}
|
|
902
|
+
} catch (error) {
|
|
903
|
+
reject(error);
|
|
1006
904
|
}
|
|
1007
905
|
});
|
|
1008
906
|
} catch (error) {
|
|
@@ -1019,72 +917,59 @@ class HoloSphere {
|
|
|
1019
917
|
* @returns {Promise<object|null>} - The parsed data for the key or null if not found.
|
|
1020
918
|
*/
|
|
1021
919
|
async getGlobal(tableName, key, password = null) {
|
|
1022
|
-
try {
|
|
1023
|
-
|
|
1024
|
-
|
|
920
|
+
try {
|
|
921
|
+
let user = null;
|
|
1025
922
|
if (password) {
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
if (ack.err.includes('Wrong user or password') ||
|
|
1032
|
-
ack.err.includes('No user')) {
|
|
1033
|
-
console.warn(`Authentication failed for ${tableName}: ${ack.err}`);
|
|
1034
|
-
// Will try to create user next
|
|
1035
|
-
reject(new Error(ack.err));
|
|
1036
|
-
} else {
|
|
1037
|
-
reject(new Error(ack.err));
|
|
1038
|
-
}
|
|
1039
|
-
} else {
|
|
1040
|
-
resolve();
|
|
1041
|
-
}
|
|
1042
|
-
});
|
|
1043
|
-
});
|
|
1044
|
-
} catch (loginError) {
|
|
1045
|
-
// If authentication fails, try to create user and then authenticate
|
|
1046
|
-
await new Promise((resolve, reject) => {
|
|
1047
|
-
user.create(this.userName(tableName), password, (ack) => {
|
|
1048
|
-
// Handle "User already created!" error gracefully
|
|
1049
|
-
if (ack.err && !ack.err.includes('already created')) {
|
|
1050
|
-
reject(new Error(ack.err));
|
|
1051
|
-
} else {
|
|
1052
|
-
user.auth(this.userName(tableName), password, (authAck) => {
|
|
1053
|
-
if (authAck.err) {
|
|
1054
|
-
console.warn(`Authentication failed after creation for ${tableName}: ${authAck.err}`);
|
|
1055
|
-
// Continue with operation even if auth fails
|
|
1056
|
-
resolve();
|
|
1057
|
-
} else {
|
|
1058
|
-
resolve();
|
|
1059
|
-
}
|
|
1060
|
-
});
|
|
1061
|
-
}
|
|
1062
|
-
});
|
|
923
|
+
user = this.gun.user();
|
|
924
|
+
await new Promise((resolve, reject) => {
|
|
925
|
+
user.auth(this.userName(tableName), password, (ack) => {
|
|
926
|
+
if (ack.err) reject(new Error(ack.err));
|
|
927
|
+
else resolve();
|
|
1063
928
|
});
|
|
1064
|
-
}
|
|
929
|
+
});
|
|
1065
930
|
}
|
|
1066
931
|
|
|
1067
932
|
return new Promise((resolve) => {
|
|
1068
|
-
const handleData = (data) => {
|
|
933
|
+
const handleData = async (data) => {
|
|
1069
934
|
if (!data) {
|
|
1070
935
|
resolve(null);
|
|
1071
936
|
return;
|
|
1072
937
|
}
|
|
938
|
+
|
|
1073
939
|
try {
|
|
1074
|
-
|
|
940
|
+
// The data should be a stringified JSON from putGlobal
|
|
941
|
+
const parsed = await this.parse(data);
|
|
942
|
+
|
|
943
|
+
if (!parsed) {
|
|
944
|
+
resolve(null);
|
|
945
|
+
return;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
// Check if this is a reference that needs to be resolved
|
|
949
|
+
if (this.isReference(parsed)) {
|
|
950
|
+
const resolved = await this.resolveReference(parsed, {
|
|
951
|
+
followReferences: true // Always follow references
|
|
952
|
+
});
|
|
953
|
+
|
|
954
|
+
if (resolved !== parsed) {
|
|
955
|
+
// Reference was resolved successfully
|
|
956
|
+
resolve(resolved);
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
|
|
1075
961
|
resolve(parsed);
|
|
1076
962
|
} catch (e) {
|
|
963
|
+
console.error('Error parsing data in getGlobal:', e);
|
|
1077
964
|
resolve(null);
|
|
1078
965
|
}
|
|
1079
966
|
};
|
|
1080
967
|
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
this.gun.get(this.appname).get(tableName).get(key).once(handleData);
|
|
1087
|
-
}
|
|
968
|
+
const dataPath = password ?
|
|
969
|
+
user.get('private').get(tableName) :
|
|
970
|
+
this.gun.get(this.appname).get(tableName);
|
|
971
|
+
|
|
972
|
+
dataPath.get(key).once(handleData);
|
|
1088
973
|
});
|
|
1089
974
|
} catch (error) {
|
|
1090
975
|
console.error('Error in getGlobal:', error);
|
|
@@ -1104,8 +989,16 @@ class HoloSphere {
|
|
|
1104
989
|
}
|
|
1105
990
|
|
|
1106
991
|
try {
|
|
1107
|
-
|
|
1108
|
-
|
|
992
|
+
let user = null;
|
|
993
|
+
if (password) {
|
|
994
|
+
user = this.gun.user();
|
|
995
|
+
await new Promise((resolve, reject) => {
|
|
996
|
+
user.auth(this.userName(tableName), password, (ack) => {
|
|
997
|
+
if (ack.err) reject(new Error(ack.err));
|
|
998
|
+
else resolve();
|
|
999
|
+
});
|
|
1000
|
+
});
|
|
1001
|
+
}
|
|
1109
1002
|
|
|
1110
1003
|
return new Promise((resolve) => {
|
|
1111
1004
|
let output = [];
|
|
@@ -1139,7 +1032,23 @@ class HoloSphere {
|
|
|
1139
1032
|
if (itemData) {
|
|
1140
1033
|
try {
|
|
1141
1034
|
const parsed = await this.parse(itemData);
|
|
1142
|
-
if (parsed)
|
|
1035
|
+
if (parsed) {
|
|
1036
|
+
// Check if this is a reference that needs to be resolved
|
|
1037
|
+
if (this.isReference(parsed)) {
|
|
1038
|
+
const resolved = await this.resolveReference(parsed, {
|
|
1039
|
+
followReferences: true // Always follow references
|
|
1040
|
+
});
|
|
1041
|
+
|
|
1042
|
+
if (resolved !== parsed) {
|
|
1043
|
+
// Reference was resolved successfully
|
|
1044
|
+
output.push(resolved);
|
|
1045
|
+
} else {
|
|
1046
|
+
output.push(parsed);
|
|
1047
|
+
}
|
|
1048
|
+
} else {
|
|
1049
|
+
output.push(parsed);
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1143
1052
|
} catch (error) {
|
|
1144
1053
|
console.error('Error parsing data:', error);
|
|
1145
1054
|
}
|
|
@@ -1156,13 +1065,11 @@ class HoloSphere {
|
|
|
1156
1065
|
}
|
|
1157
1066
|
};
|
|
1158
1067
|
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
this.gun.get(this.appname).get(tableName).once(handleData);
|
|
1165
|
-
}
|
|
1068
|
+
const dataPath = password ?
|
|
1069
|
+
user.get('private').get(tableName) :
|
|
1070
|
+
this.gun.get(this.appname).get(tableName);
|
|
1071
|
+
|
|
1072
|
+
dataPath.once(handleData);
|
|
1166
1073
|
});
|
|
1167
1074
|
} catch (error) {
|
|
1168
1075
|
console.error('Error in getAllGlobal:', error);
|
|
@@ -1183,29 +1090,45 @@ class HoloSphere {
|
|
|
1183
1090
|
}
|
|
1184
1091
|
|
|
1185
1092
|
try {
|
|
1186
|
-
|
|
1187
|
-
|
|
1093
|
+
console.log('deleteGlobal - Starting deletion:', { tableName, key, hasPassword: !!password });
|
|
1094
|
+
|
|
1095
|
+
let user = null;
|
|
1096
|
+
if (password) {
|
|
1097
|
+
user = this.gun.user();
|
|
1098
|
+
await new Promise((resolve, reject) => {
|
|
1099
|
+
user.auth(this.userName(tableName), password, (ack) => {
|
|
1100
|
+
if (ack.err) reject(new Error(ack.err));
|
|
1101
|
+
else resolve();
|
|
1102
|
+
});
|
|
1103
|
+
});
|
|
1104
|
+
}
|
|
1188
1105
|
|
|
1189
1106
|
return new Promise((resolve, reject) => {
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
//
|
|
1201
|
-
|
|
1107
|
+
const dataPath = password ?
|
|
1108
|
+
user.get('private').get(tableName) :
|
|
1109
|
+
this.gun.get(this.appname).get(tableName);
|
|
1110
|
+
|
|
1111
|
+
console.log('deleteGlobal - Constructed base path:', dataPath._.back);
|
|
1112
|
+
|
|
1113
|
+
// First verify the data exists
|
|
1114
|
+
dataPath.get(key).once((data) => {
|
|
1115
|
+
console.log('deleteGlobal - Data before deletion:', data);
|
|
1116
|
+
|
|
1117
|
+
// Now perform the deletion
|
|
1118
|
+
dataPath.get(key).put(null, ack => {
|
|
1119
|
+
console.log('deleteGlobal - Deletion acknowledgment:', ack);
|
|
1202
1120
|
if (ack.err) {
|
|
1121
|
+
console.error('deleteGlobal - Deletion error:', ack.err);
|
|
1203
1122
|
reject(new Error(ack.err));
|
|
1204
1123
|
} else {
|
|
1205
|
-
|
|
1124
|
+
// Verify deletion
|
|
1125
|
+
dataPath.get(key).once((deletedData) => {
|
|
1126
|
+
console.log('deleteGlobal - Data after deletion:', deletedData);
|
|
1127
|
+
resolve(true);
|
|
1128
|
+
});
|
|
1206
1129
|
}
|
|
1207
1130
|
});
|
|
1208
|
-
}
|
|
1131
|
+
});
|
|
1209
1132
|
});
|
|
1210
1133
|
} catch (error) {
|
|
1211
1134
|
console.error('Error in deleteGlobal:', error);
|
|
@@ -1225,8 +1148,16 @@ class HoloSphere {
|
|
|
1225
1148
|
}
|
|
1226
1149
|
|
|
1227
1150
|
try {
|
|
1228
|
-
|
|
1229
|
-
|
|
1151
|
+
let user = null;
|
|
1152
|
+
if (password) {
|
|
1153
|
+
user = this.gun.user();
|
|
1154
|
+
await new Promise((resolve, reject) => {
|
|
1155
|
+
user.auth(this.userName(tableName), password, (ack) => {
|
|
1156
|
+
if (ack.err) reject(new Error(ack.err));
|
|
1157
|
+
else resolve();
|
|
1158
|
+
});
|
|
1159
|
+
});
|
|
1160
|
+
}
|
|
1230
1161
|
|
|
1231
1162
|
return new Promise((resolve, reject) => {
|
|
1232
1163
|
try {
|
|
@@ -1250,7 +1181,7 @@ class HoloSphere {
|
|
|
1250
1181
|
|
|
1251
1182
|
const keys = Object.keys(data).filter(key => key !== '_');
|
|
1252
1183
|
const promises = keys.map(key =>
|
|
1253
|
-
new Promise((resolveDelete) => {
|
|
1184
|
+
new Promise((resolveDelete, rejectDelete) => {
|
|
1254
1185
|
const deletePath = password ?
|
|
1255
1186
|
user.get('private').get(tableName).get(key) :
|
|
1256
1187
|
this.gun.get(this.appname).get(tableName).get(key);
|
|
@@ -1258,8 +1189,10 @@ class HoloSphere {
|
|
|
1258
1189
|
deletePath.put(null, ack => {
|
|
1259
1190
|
if (ack.err) {
|
|
1260
1191
|
console.error(`Failed to delete ${key}:`, ack.err);
|
|
1192
|
+
rejectDelete(new Error(ack.err));
|
|
1193
|
+
} else {
|
|
1194
|
+
resolveDelete();
|
|
1261
1195
|
}
|
|
1262
|
-
resolveDelete();
|
|
1263
1196
|
});
|
|
1264
1197
|
})
|
|
1265
1198
|
);
|
|
@@ -1284,6 +1217,159 @@ class HoloSphere {
|
|
|
1284
1217
|
}
|
|
1285
1218
|
}
|
|
1286
1219
|
|
|
1220
|
+
// ================================ REFERENCE FUNCTIONS ================================
|
|
1221
|
+
|
|
1222
|
+
/**
|
|
1223
|
+
* Creates a soul reference object for a data item
|
|
1224
|
+
* @param {string} holon - The holon where the original data is stored
|
|
1225
|
+
* @param {string} lens - The lens where the original data is stored
|
|
1226
|
+
* @param {object} data - The data to create a reference for
|
|
1227
|
+
* @returns {object} - A reference object with id and soul
|
|
1228
|
+
*/
|
|
1229
|
+
createReference(holon, lens, data) {
|
|
1230
|
+
if (!holon || !lens || !data || !data.id) {
|
|
1231
|
+
throw new Error('createReference: Missing required parameters');
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
const soul = `${this.appname}/${holon}/${lens}/${data.id}`;
|
|
1235
|
+
return {
|
|
1236
|
+
id: data.id,
|
|
1237
|
+
soul: soul
|
|
1238
|
+
};
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
/**
|
|
1242
|
+
* Parses a soul path into its components
|
|
1243
|
+
* @param {string} soul - The soul path to parse
|
|
1244
|
+
* @returns {object|null} - The parsed components or null if invalid format
|
|
1245
|
+
*/
|
|
1246
|
+
parseSoulPath(soul) {
|
|
1247
|
+
if (!soul || typeof soul !== 'string') {
|
|
1248
|
+
return null;
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
const soulParts = soul.split('/');
|
|
1252
|
+
if (soulParts.length < 4) {
|
|
1253
|
+
return null;
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
return {
|
|
1257
|
+
appname: soulParts[0],
|
|
1258
|
+
holon: soulParts[1],
|
|
1259
|
+
lens: soulParts[2],
|
|
1260
|
+
key: soulParts[3]
|
|
1261
|
+
};
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
/**
|
|
1265
|
+
* Checks if an object is a reference
|
|
1266
|
+
* @param {object} data - The data to check
|
|
1267
|
+
* @returns {boolean} - True if the object is a reference
|
|
1268
|
+
*/
|
|
1269
|
+
isReference(data) {
|
|
1270
|
+
if (!data || typeof data !== 'object') {
|
|
1271
|
+
return false;
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
// Check for direct soul reference
|
|
1275
|
+
if (data.soul && typeof data.soul === 'string' && data.id) {
|
|
1276
|
+
return true;
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
// Check for legacy federation reference
|
|
1280
|
+
if (data._federation && data._federation.isReference) {
|
|
1281
|
+
return true;
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
return false;
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
/**
|
|
1288
|
+
* Resolves a reference to its actual data
|
|
1289
|
+
* @param {object} reference - The reference to resolve
|
|
1290
|
+
* @param {object} [options] - Optional parameters
|
|
1291
|
+
* @param {boolean} [options.followReferences=true] - Whether to follow nested references
|
|
1292
|
+
* @returns {Promise<object|null>} - The resolved data or null if not found
|
|
1293
|
+
*/
|
|
1294
|
+
async resolveReference(reference, options = {}) {
|
|
1295
|
+
if (!this.isReference(reference)) {
|
|
1296
|
+
return reference; // Not a reference, return as is
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
const { followReferences = true } = options;
|
|
1300
|
+
|
|
1301
|
+
try {
|
|
1302
|
+
// Handle direct soul reference
|
|
1303
|
+
if (reference.soul) {
|
|
1304
|
+
const soulInfo = this.parseSoulPath(reference.soul);
|
|
1305
|
+
if (!soulInfo) {
|
|
1306
|
+
console.warn(`Invalid soul format: ${reference.soul}`);
|
|
1307
|
+
return reference;
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
console.log(`Resolving reference with soul: ${reference.soul}`);
|
|
1311
|
+
|
|
1312
|
+
// Get original data using the extracted path components
|
|
1313
|
+
const originalData = await this.get(
|
|
1314
|
+
soulInfo.holon,
|
|
1315
|
+
soulInfo.lens,
|
|
1316
|
+
soulInfo.key,
|
|
1317
|
+
null,
|
|
1318
|
+
{ resolveReferences: followReferences } // Control recursion
|
|
1319
|
+
);
|
|
1320
|
+
|
|
1321
|
+
if (originalData) {
|
|
1322
|
+
console.log(`Original data found through soul path resolution`);
|
|
1323
|
+
return {
|
|
1324
|
+
...originalData,
|
|
1325
|
+
_federation: {
|
|
1326
|
+
isReference: true,
|
|
1327
|
+
resolved: true,
|
|
1328
|
+
soul: reference.soul,
|
|
1329
|
+
timestamp: Date.now()
|
|
1330
|
+
}
|
|
1331
|
+
};
|
|
1332
|
+
} else {
|
|
1333
|
+
console.warn(`Could not resolve reference: original data not found at extracted path`);
|
|
1334
|
+
return reference; // Return original reference if resolution fails
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
// Handle legacy federation reference
|
|
1339
|
+
else if (reference._federation && reference._federation.isReference) {
|
|
1340
|
+
const fedRef = reference._federation;
|
|
1341
|
+
console.log(`Resolving legacy federation reference from ${fedRef.origin}`);
|
|
1342
|
+
|
|
1343
|
+
const originalData = await this.get(
|
|
1344
|
+
fedRef.origin,
|
|
1345
|
+
fedRef.lens,
|
|
1346
|
+
reference.id || fedRef.key,
|
|
1347
|
+
null,
|
|
1348
|
+
{ resolveReferences: followReferences }
|
|
1349
|
+
);
|
|
1350
|
+
|
|
1351
|
+
if (originalData) {
|
|
1352
|
+
return {
|
|
1353
|
+
...originalData,
|
|
1354
|
+
_federation: {
|
|
1355
|
+
...fedRef,
|
|
1356
|
+
resolved: true,
|
|
1357
|
+
timestamp: Date.now()
|
|
1358
|
+
}
|
|
1359
|
+
};
|
|
1360
|
+
} else {
|
|
1361
|
+
console.warn(`Could not resolve legacy reference: original data not found`);
|
|
1362
|
+
return reference;
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
return reference;
|
|
1367
|
+
} catch (error) {
|
|
1368
|
+
console.error(`Error resolving reference: ${error.message}`, error);
|
|
1369
|
+
return reference;
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1287
1373
|
// ================================ COMPUTE FUNCTIONS ================================
|
|
1288
1374
|
/**
|
|
1289
1375
|
* Computes operations across multiple layers up the hierarchy
|
|
@@ -1491,8 +1577,7 @@ class HoloSphere {
|
|
|
1491
1577
|
}
|
|
1492
1578
|
|
|
1493
1579
|
/**
|
|
1494
|
-
* Upcasts content to parent holonagons recursively using
|
|
1495
|
-
* This is the modern implementation that uses federation references instead of duplicating data.
|
|
1580
|
+
* Upcasts content to parent holonagons recursively using references.
|
|
1496
1581
|
* @param {string} holon - The current holon identifier.
|
|
1497
1582
|
* @param {string} lens - The lens under which to upcast.
|
|
1498
1583
|
* @param {object} content - The content to upcast.
|
|
@@ -1513,18 +1598,10 @@ class HoloSphere {
|
|
|
1513
1598
|
// Get the parent cell
|
|
1514
1599
|
let parent = h3.cellToParent(holon, res - 1);
|
|
1515
1600
|
|
|
1516
|
-
// Create
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
// Create a soul reference to store in the parent
|
|
1520
|
-
const soul = `${this.appname}/${holon}/${lens}/${content.id}`;
|
|
1521
|
-
const reference = {
|
|
1522
|
-
id: content.id,
|
|
1523
|
-
soul: soul
|
|
1524
|
-
};
|
|
1601
|
+
// Create a reference to store in the parent
|
|
1602
|
+
const reference = this.createReference(holon, lens, content);
|
|
1525
1603
|
|
|
1526
1604
|
// Store the reference in the parent cell
|
|
1527
|
-
// We use { autoPropagate: false } to prevent circular propagation
|
|
1528
1605
|
await this.put(parent, lens, reference, null, {
|
|
1529
1606
|
autoPropagate: false
|
|
1530
1607
|
});
|
|
@@ -1607,8 +1684,12 @@ class HoloSphere {
|
|
|
1607
1684
|
* @returns {Promise<object>} - Subscription object with unsubscribe method
|
|
1608
1685
|
*/
|
|
1609
1686
|
async subscribe(holon, lens, callback) {
|
|
1610
|
-
if (!holon || !lens
|
|
1611
|
-
throw new Error('subscribe: Missing
|
|
1687
|
+
if (!holon || !lens) {
|
|
1688
|
+
throw new Error('subscribe: Missing holon or lens parameters:', holon, lens);
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
if (!callback || typeof callback !== 'function') {
|
|
1692
|
+
throw new Error('subscribe: Callback must be a function');
|
|
1612
1693
|
}
|
|
1613
1694
|
|
|
1614
1695
|
const subscriptionId = this.generateId();
|
|
@@ -1616,10 +1697,35 @@ class HoloSphere {
|
|
|
1616
1697
|
try {
|
|
1617
1698
|
// Create the subscription
|
|
1618
1699
|
const gunSubscription = this.gun.get(this.appname).get(holon).get(lens).map().on(async (data, key) => {
|
|
1700
|
+
// Check if subscription is still active before processing
|
|
1701
|
+
if (!this.subscriptions[subscriptionId]?.active) {
|
|
1702
|
+
return;
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1619
1705
|
if (data) {
|
|
1620
1706
|
try {
|
|
1621
1707
|
let parsed = await this.parse(data);
|
|
1622
|
-
|
|
1708
|
+
|
|
1709
|
+
// Check if the parsed data is a reference that needs resolution
|
|
1710
|
+
if (parsed && this.isReference(parsed)) {
|
|
1711
|
+
const resolved = await this.resolveReference(parsed, {
|
|
1712
|
+
followReferences: true // Always follow references
|
|
1713
|
+
});
|
|
1714
|
+
|
|
1715
|
+
if (resolved !== parsed) {
|
|
1716
|
+
// Reference was resolved successfully
|
|
1717
|
+
// Check again if subscription is still active
|
|
1718
|
+
if (this.subscriptions[subscriptionId]?.active) {
|
|
1719
|
+
callback(resolved, key);
|
|
1720
|
+
}
|
|
1721
|
+
return;
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
// Check again if subscription is still active before final callback
|
|
1726
|
+
if (this.subscriptions[subscriptionId]?.active) {
|
|
1727
|
+
callback(parsed, key);
|
|
1728
|
+
}
|
|
1623
1729
|
} catch (error) {
|
|
1624
1730
|
console.error('Error in subscribe:', error);
|
|
1625
1731
|
}
|
|
@@ -1632,6 +1738,7 @@ class HoloSphere {
|
|
|
1632
1738
|
holon,
|
|
1633
1739
|
lens,
|
|
1634
1740
|
active: true,
|
|
1741
|
+
callback,
|
|
1635
1742
|
gunSubscription
|
|
1636
1743
|
};
|
|
1637
1744
|
|
|
@@ -1639,14 +1746,18 @@ class HoloSphere {
|
|
|
1639
1746
|
return {
|
|
1640
1747
|
unsubscribe: () => {
|
|
1641
1748
|
try {
|
|
1642
|
-
//
|
|
1643
|
-
this.gun.get(this.appname).get(holon).get(lens).map().off();
|
|
1644
|
-
|
|
1645
|
-
// Mark as inactive and remove from subscriptions
|
|
1749
|
+
// Mark as inactive first to prevent any new callbacks
|
|
1646
1750
|
if (this.subscriptions[subscriptionId]) {
|
|
1647
1751
|
this.subscriptions[subscriptionId].active = false;
|
|
1648
|
-
delete this.subscriptions[subscriptionId];
|
|
1649
1752
|
}
|
|
1753
|
+
|
|
1754
|
+
// Turn off the Gun subscription using the stored reference
|
|
1755
|
+
if (this.subscriptions[subscriptionId]?.gunSubscription) {
|
|
1756
|
+
this.subscriptions[subscriptionId].gunSubscription.off();
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
// Remove from subscriptions
|
|
1760
|
+
delete this.subscriptions[subscriptionId];
|
|
1650
1761
|
} catch (error) {
|
|
1651
1762
|
console.error('Error in unsubscribe:', error);
|
|
1652
1763
|
}
|
|
@@ -1658,7 +1769,6 @@ class HoloSphere {
|
|
|
1658
1769
|
}
|
|
1659
1770
|
}
|
|
1660
1771
|
|
|
1661
|
-
|
|
1662
1772
|
/**
|
|
1663
1773
|
* Notifies subscribers about data changes
|
|
1664
1774
|
* @param {object} data - The data to notify about
|
|
@@ -1834,11 +1944,10 @@ class HoloSphere {
|
|
|
1834
1944
|
try {
|
|
1835
1945
|
const subscription = this.subscriptions[id];
|
|
1836
1946
|
if (subscription && subscription.active) {
|
|
1837
|
-
// Turn off the Gun subscription
|
|
1838
|
-
|
|
1839
|
-
.
|
|
1840
|
-
|
|
1841
|
-
.map().off();
|
|
1947
|
+
// Turn off the Gun subscription using the stored reference
|
|
1948
|
+
if (subscription.gunSubscription) {
|
|
1949
|
+
subscription.gunSubscription.off();
|
|
1950
|
+
}
|
|
1842
1951
|
|
|
1843
1952
|
// Mark as inactive
|
|
1844
1953
|
subscription.active = false;
|
|
@@ -1850,29 +1959,66 @@ class HoloSphere {
|
|
|
1850
1959
|
|
|
1851
1960
|
// Clear subscriptions
|
|
1852
1961
|
this.subscriptions = {};
|
|
1962
|
+
|
|
1963
|
+
// Clear schema cache
|
|
1964
|
+
this.clearSchemaCache();
|
|
1853
1965
|
|
|
1854
1966
|
// Close Gun connections
|
|
1855
1967
|
if (this.gun.back) {
|
|
1856
1968
|
try {
|
|
1969
|
+
// Clean up mesh connections
|
|
1857
1970
|
const mesh = this.gun.back('opt.mesh');
|
|
1858
|
-
if (mesh
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1971
|
+
if (mesh) {
|
|
1972
|
+
// Clean up mesh.hear
|
|
1973
|
+
if (mesh.hear) {
|
|
1974
|
+
try {
|
|
1975
|
+
// Safely clear mesh.hear without modifying function properties
|
|
1976
|
+
const hearKeys = Object.keys(mesh.hear);
|
|
1977
|
+
for (const key of hearKeys) {
|
|
1978
|
+
// Check if it's an array before trying to clear it
|
|
1979
|
+
if (Array.isArray(mesh.hear[key])) {
|
|
1980
|
+
mesh.hear[key] = [];
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
|
|
1984
|
+
// Create a new empty object for mesh.hear
|
|
1985
|
+
// Only if mesh.hear is not a function
|
|
1986
|
+
if (typeof mesh.hear !== 'function') {
|
|
1987
|
+
mesh.hear = {};
|
|
1866
1988
|
}
|
|
1989
|
+
} catch (meshError) {
|
|
1990
|
+
console.warn('Error cleaning up Gun mesh hear:', meshError);
|
|
1867
1991
|
}
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
// Close any open sockets in the mesh
|
|
1995
|
+
if (mesh.way) {
|
|
1996
|
+
try {
|
|
1997
|
+
Object.values(mesh.way).forEach(connection => {
|
|
1998
|
+
if (connection && connection.wire && connection.wire.close) {
|
|
1999
|
+
connection.wire.close();
|
|
2000
|
+
}
|
|
2001
|
+
});
|
|
2002
|
+
} catch (sockError) {
|
|
2003
|
+
console.warn('Error closing mesh sockets:', sockError);
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
|
|
2007
|
+
// Clear the peers list
|
|
2008
|
+
if (mesh.opt && mesh.opt.peers) {
|
|
2009
|
+
mesh.opt.peers = {};
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
|
|
2013
|
+
// Attempt to clean up any TCP connections
|
|
2014
|
+
if (this.gun.back('opt.web')) {
|
|
2015
|
+
try {
|
|
2016
|
+
const server = this.gun.back('opt.web');
|
|
2017
|
+
if (server && server.close) {
|
|
2018
|
+
server.close();
|
|
1873
2019
|
}
|
|
1874
|
-
} catch (
|
|
1875
|
-
console.warn('Error
|
|
2020
|
+
} catch (webError) {
|
|
2021
|
+
console.warn('Error closing web server:', webError);
|
|
1876
2022
|
}
|
|
1877
2023
|
}
|
|
1878
2024
|
} catch (error) {
|