holosphere 1.1.6 → 1.1.8
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.md +108 -160
- package/README.md +140 -0
- package/examples/federation.js +154 -0
- package/examples/references.js +177 -0
- package/federation.js +637 -372
- package/holosphere.d.ts +445 -20
- package/holosphere.js +343 -126
- package/package.json +1 -4
- package/test/delete.test.js +225 -0
- package/test/federation.test.js +50 -2
- package/config/default.js +0 -1
- package/services/environmentalApi.js +0 -253
- package/services/environmentalApitest.js +0 -164
- package/test/ai.test.js +0 -425
- package/test/sea.html +0 -33
- /package/test/{holonauth.test.js → auth.test.js} +0 -0
package/holosphere.js
CHANGED
|
@@ -5,6 +5,7 @@ import SEA from 'gun/sea.js'
|
|
|
5
5
|
import Ajv2019 from 'ajv/dist/2019.js'
|
|
6
6
|
import * as Federation from './federation.js';
|
|
7
7
|
|
|
8
|
+
export { federateMessage, getFederatedMessages, updateFederatedMessages, removeNotify } from './federation.js';
|
|
8
9
|
|
|
9
10
|
class HoloSphere {
|
|
10
11
|
/**
|
|
@@ -15,7 +16,7 @@ class HoloSphere {
|
|
|
15
16
|
* @param {Gun|null} gunInstance - The Gun instance to use.
|
|
16
17
|
*/
|
|
17
18
|
constructor(appname, strict = false, openaikey = null, gunInstance = null) {
|
|
18
|
-
console.log('HoloSphere v1.1.
|
|
19
|
+
console.log('HoloSphere v1.1.7');
|
|
19
20
|
this.appname = appname
|
|
20
21
|
this.strict = strict;
|
|
21
22
|
this.validator = new Ajv2019({
|
|
@@ -137,12 +138,16 @@ class HoloSphere {
|
|
|
137
138
|
* @param {string} holon - The holon identifier.
|
|
138
139
|
* @param {string} lens - The lens under which to store the content.
|
|
139
140
|
* @param {object} data - The data to store.
|
|
140
|
-
* @param {string} [password] - Optional password for private
|
|
141
|
+
* @param {string} [password] - Optional password for private holon.
|
|
142
|
+
* @param {object} [options] - Additional options
|
|
143
|
+
* @param {boolean} [options.autoPropagate=true] - Whether to automatically propagate to federated holons (default: true)
|
|
144
|
+
* @param {object} [options.propagationOptions] - Options to pass to propagate
|
|
145
|
+
* @param {boolean} [options.propagationOptions.useReferences=true] - Whether to use references instead of duplicating data
|
|
141
146
|
* @returns {Promise<boolean>} - Returns true if successful, false if there was an error
|
|
142
147
|
*/
|
|
143
|
-
async put(holon, lens, data, password = null) {
|
|
148
|
+
async put(holon, lens, data, password = null, options = {}) {
|
|
144
149
|
if (!holon || !lens || !data) {
|
|
145
|
-
throw new Error('put: Missing required parameters');
|
|
150
|
+
throw new Error('put: Missing required parameters:', holon, lens, data );
|
|
146
151
|
}
|
|
147
152
|
|
|
148
153
|
if (!data.id) {
|
|
@@ -237,34 +242,57 @@ class HoloSphere {
|
|
|
237
242
|
try {
|
|
238
243
|
const payload = JSON.stringify(data);
|
|
239
244
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
245
|
+
const putCallback = async (ack) => {
|
|
246
|
+
if (ack.err) {
|
|
247
|
+
reject(new Error(ack.err));
|
|
248
|
+
} else {
|
|
249
|
+
this.notifySubscribers({
|
|
250
|
+
holon,
|
|
251
|
+
lens,
|
|
252
|
+
...data
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// Auto-propagate to federation by default
|
|
256
|
+
const shouldPropagate = options.autoPropagate !== false;
|
|
257
|
+
let propagationResult = null;
|
|
258
|
+
|
|
259
|
+
if (shouldPropagate) {
|
|
260
|
+
try {
|
|
261
|
+
// Default to using references
|
|
262
|
+
const propagationOptions = {
|
|
263
|
+
useReferences: true,
|
|
264
|
+
...options.propagationOptions
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
propagationResult = await this.propagate(
|
|
268
|
+
holon,
|
|
269
|
+
lens,
|
|
270
|
+
data,
|
|
271
|
+
propagationOptions
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
// Still resolve with true even if propagation had errors
|
|
275
|
+
if (propagationResult.errors > 0) {
|
|
276
|
+
console.warn('Auto-propagation had errors:', propagationResult);
|
|
277
|
+
}
|
|
278
|
+
} catch (propError) {
|
|
279
|
+
console.warn('Error in auto-propagation:', propError);
|
|
280
|
+
}
|
|
252
281
|
}
|
|
253
|
-
|
|
282
|
+
|
|
283
|
+
resolve({
|
|
284
|
+
success: true,
|
|
285
|
+
propagationResult
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
if (password) {
|
|
291
|
+
// For private data, use the authenticated user's holon
|
|
292
|
+
user.get('private').get(lens).get(data.id).put(payload, putCallback);
|
|
254
293
|
} else {
|
|
255
294
|
// For public data, use the regular path
|
|
256
|
-
this.gun.get(this.appname).get(holon).get(lens).get(data.id).put(payload,
|
|
257
|
-
if (ack.err) {
|
|
258
|
-
reject(new Error(ack.err));
|
|
259
|
-
} else {
|
|
260
|
-
this.notifySubscribers({
|
|
261
|
-
holon,
|
|
262
|
-
lens,
|
|
263
|
-
...data
|
|
264
|
-
});
|
|
265
|
-
resolve(true);
|
|
266
|
-
}
|
|
267
|
-
});
|
|
295
|
+
this.gun.get(this.appname).get(holon).get(lens).get(data.id).put(payload, putCallback);
|
|
268
296
|
}
|
|
269
297
|
} catch (error) {
|
|
270
298
|
reject(error);
|
|
@@ -281,15 +309,19 @@ class HoloSphere {
|
|
|
281
309
|
* @param {string} holon - The holon identifier.
|
|
282
310
|
* @param {string} lens - The lens from which to retrieve content.
|
|
283
311
|
* @param {string} key - The specific key to retrieve.
|
|
284
|
-
* @param {string} [password] - Optional password for private
|
|
312
|
+
* @param {string} [password] - Optional password for private holon.
|
|
313
|
+
* @param {object} [options] - Additional options
|
|
314
|
+
* @param {boolean} [options.resolveReferences=true] - Whether to automatically resolve federation references
|
|
285
315
|
* @returns {Promise<object|null>} - The retrieved content or null if not found.
|
|
286
316
|
*/
|
|
287
|
-
async get(holon, lens, key, password = null) {
|
|
317
|
+
async get(holon, lens, key, password = null, options = {}) {
|
|
288
318
|
if (!holon || !lens || !key) {
|
|
289
319
|
console.error('get: Missing required parameters:', { holon, lens, key });
|
|
290
320
|
return null;
|
|
291
321
|
}
|
|
292
322
|
|
|
323
|
+
const { resolveReferences = true } = options;
|
|
324
|
+
|
|
293
325
|
// Only check schema in strict mode
|
|
294
326
|
let schema;
|
|
295
327
|
if (this.strict) {
|
|
@@ -336,6 +368,90 @@ class HoloSphere {
|
|
|
336
368
|
try {
|
|
337
369
|
const parsed = await this.parse(data);
|
|
338
370
|
|
|
371
|
+
if (!parsed) {
|
|
372
|
+
resolve(null);
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Check if this is a reference that needs to be resolved
|
|
377
|
+
if (resolveReferences !== false && parsed) {
|
|
378
|
+
// Check if this is a simple reference (id + soul)
|
|
379
|
+
if (parsed.soul) {
|
|
380
|
+
console.log(`Resolving simple reference with soul: ${parsed.soul}`);
|
|
381
|
+
try {
|
|
382
|
+
// For direct soul resolution, we need to parse the soul to get the right path
|
|
383
|
+
const soulParts = parsed.soul.split('/');
|
|
384
|
+
if (soulParts.length >= 4) { // Expected format: appname/holon/lens/key
|
|
385
|
+
const originHolon = soulParts[1];
|
|
386
|
+
const originLens = soulParts[2];
|
|
387
|
+
const originKey = soulParts[3];
|
|
388
|
+
|
|
389
|
+
console.log(`Extracting from soul - holon: ${originHolon}, lens: ${originLens}, key: ${originKey}`);
|
|
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
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
339
455
|
if (schema) {
|
|
340
456
|
const valid = this.validator.validate(schema, parsed);
|
|
341
457
|
if (!valid) {
|
|
@@ -355,7 +471,7 @@ class HoloSphere {
|
|
|
355
471
|
};
|
|
356
472
|
|
|
357
473
|
if (password) {
|
|
358
|
-
// For private data, use the authenticated user's
|
|
474
|
+
// For private data, use the authenticated user's holon
|
|
359
475
|
user.get('private').get(lens).get(key).once(handleData);
|
|
360
476
|
} else {
|
|
361
477
|
// For public data, use the regular path
|
|
@@ -369,31 +485,52 @@ class HoloSphere {
|
|
|
369
485
|
}
|
|
370
486
|
|
|
371
487
|
/**
|
|
372
|
-
*
|
|
488
|
+
* Retrieves a node directly using its soul path
|
|
489
|
+
* @param {string} soul - The soul path of the node
|
|
490
|
+
* @returns {Promise<any>} - The retrieved node or null if not found.
|
|
491
|
+
*/
|
|
492
|
+
async getNodeBySoul(soul) {
|
|
493
|
+
if (!soul) {
|
|
494
|
+
throw new Error('getNodeBySoul: Missing soul parameter');
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
console.log(`getNodeBySoul: Accessing soul ${soul}`);
|
|
498
|
+
|
|
499
|
+
return new Promise((resolve) => {
|
|
500
|
+
try {
|
|
501
|
+
const ref = this.getNodeRef(soul);
|
|
502
|
+
ref.once((data) => {
|
|
503
|
+
console.log(`getNodeBySoul: Retrieved data:`, data);
|
|
504
|
+
if (!data) {
|
|
505
|
+
resolve(null);
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
resolve(data); // Return the data directly
|
|
509
|
+
});
|
|
510
|
+
} catch (error) {
|
|
511
|
+
console.error(`getNodeBySoul error:`, error);
|
|
512
|
+
resolve(null);
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Propagates data to federated holons
|
|
373
519
|
* @param {string} holon - The holon identifier
|
|
374
520
|
* @param {string} lens - The lens identifier
|
|
375
521
|
* @param {object} data - The data to propagate
|
|
376
522
|
* @param {object} [options] - Propagation options
|
|
377
523
|
* @returns {Promise<object>} - Result with success count and errors
|
|
378
524
|
*/
|
|
379
|
-
async
|
|
380
|
-
return Federation.
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
/**
|
|
384
|
-
* @private
|
|
385
|
-
* @deprecated Use propagateToFederation instead
|
|
386
|
-
*/
|
|
387
|
-
async _propagateToFederation(holon, lens, data) {
|
|
388
|
-
console.warn('_propagateToFederation is deprecated, use propagateToFederation instead');
|
|
389
|
-
return this.propagateToFederation(holon, lens, data);
|
|
525
|
+
async propagate(holon, lens, data, options = {}) {
|
|
526
|
+
return Federation.propagate(this, holon, lens, data, options);
|
|
390
527
|
}
|
|
391
528
|
|
|
392
529
|
/**
|
|
393
530
|
* Retrieves all content from the specified holon and lens.
|
|
394
531
|
* @param {string} holon - The holon identifier.
|
|
395
532
|
* @param {string} lens - The lens from which to retrieve content.
|
|
396
|
-
* @param {string} [password] - Optional password for private
|
|
533
|
+
* @param {string} [password] - Optional password for private holon.
|
|
397
534
|
* @returns {Promise<Array<object>>} - The retrieved content.
|
|
398
535
|
*/
|
|
399
536
|
async getAll(holon, lens, password = null) {
|
|
@@ -455,7 +592,7 @@ class HoloSphere {
|
|
|
455
592
|
};
|
|
456
593
|
|
|
457
594
|
if (password) {
|
|
458
|
-
// For private data, use the authenticated user's
|
|
595
|
+
// For private data, use the authenticated user's holon
|
|
459
596
|
user.get('private').get(lens).once(handleData);
|
|
460
597
|
} else {
|
|
461
598
|
// For public data, use the regular path
|
|
@@ -531,7 +668,7 @@ class HoloSphere {
|
|
|
531
668
|
* @param {string} holon - The holon identifier.
|
|
532
669
|
* @param {string} lens - The lens from which to delete the key.
|
|
533
670
|
* @param {string} key - The specific key to delete.
|
|
534
|
-
* @param {string} [password] - Optional password for private
|
|
671
|
+
* @param {string} [password] - Optional password for private holon.
|
|
535
672
|
* @returns {Promise<boolean>} - Returns true if successful
|
|
536
673
|
*/
|
|
537
674
|
async delete(holon, lens, key, password = null) {
|
|
@@ -540,13 +677,13 @@ class HoloSphere {
|
|
|
540
677
|
}
|
|
541
678
|
|
|
542
679
|
try {
|
|
543
|
-
// Get the appropriate
|
|
680
|
+
// Get the appropriate holon
|
|
544
681
|
const user = this.gun.user();
|
|
545
682
|
|
|
546
|
-
// Delete data from
|
|
683
|
+
// Delete data from holon
|
|
547
684
|
return new Promise((resolve, reject) => {
|
|
548
685
|
if (password) {
|
|
549
|
-
// For private data, use the authenticated user's
|
|
686
|
+
// For private data, use the authenticated user's holon
|
|
550
687
|
user.get('private').get(lens).get(key).put(null, ack => {
|
|
551
688
|
if (ack.err) {
|
|
552
689
|
reject(new Error(ack.err));
|
|
@@ -575,7 +712,7 @@ class HoloSphere {
|
|
|
575
712
|
* Deletes all keys from a given holon and lens.
|
|
576
713
|
* @param {string} holon - The holon identifier.
|
|
577
714
|
* @param {string} lens - The lens from which to delete all keys.
|
|
578
|
-
* @param {string} [password] - Optional password for private
|
|
715
|
+
* @param {string} [password] - Optional password for private holon.
|
|
579
716
|
* @returns {Promise<boolean>} - Returns true if successful
|
|
580
717
|
*/
|
|
581
718
|
async deleteAll(holon, lens, password = null) {
|
|
@@ -585,7 +722,7 @@ class HoloSphere {
|
|
|
585
722
|
}
|
|
586
723
|
|
|
587
724
|
try {
|
|
588
|
-
// Get the appropriate
|
|
725
|
+
// Get the appropriate holon
|
|
589
726
|
const user = this.gun.user();
|
|
590
727
|
|
|
591
728
|
return new Promise((resolve) => {
|
|
@@ -752,7 +889,7 @@ class HoloSphere {
|
|
|
752
889
|
* Stores data in a global (non-holon-specific) table.
|
|
753
890
|
* @param {string} tableName - The table name to store data in.
|
|
754
891
|
* @param {object} data - The data to store. If it has an 'id' field, it will be used as the key.
|
|
755
|
-
* @param {string} [password] - Optional password for private
|
|
892
|
+
* @param {string} [password] - Optional password for private holon.
|
|
756
893
|
* @returns {Promise<void>}
|
|
757
894
|
*/
|
|
758
895
|
async putGlobal(tableName, data, password = null) {
|
|
@@ -825,7 +962,7 @@ class HoloSphere {
|
|
|
825
962
|
const payload = JSON.stringify(data);
|
|
826
963
|
|
|
827
964
|
if (password) {
|
|
828
|
-
// For private data, use the authenticated user's
|
|
965
|
+
// For private data, use the authenticated user's holon
|
|
829
966
|
const path = user.get('private').get(tableName);
|
|
830
967
|
|
|
831
968
|
if (data.id) {
|
|
@@ -878,7 +1015,7 @@ class HoloSphere {
|
|
|
878
1015
|
* Retrieves a specific key from a global table.
|
|
879
1016
|
* @param {string} tableName - The table name to retrieve from.
|
|
880
1017
|
* @param {string} key - The key to retrieve.
|
|
881
|
-
* @param {string} [password] - Optional password for private
|
|
1018
|
+
* @param {string} [password] - Optional password for private holon.
|
|
882
1019
|
* @returns {Promise<object|null>} - The parsed data for the key or null if not found.
|
|
883
1020
|
*/
|
|
884
1021
|
async getGlobal(tableName, key, password = null) {
|
|
@@ -942,7 +1079,7 @@ class HoloSphere {
|
|
|
942
1079
|
};
|
|
943
1080
|
|
|
944
1081
|
if (password) {
|
|
945
|
-
// For private data, use the authenticated user's
|
|
1082
|
+
// For private data, use the authenticated user's holon
|
|
946
1083
|
user.get('private').get(tableName).get(key).once(handleData);
|
|
947
1084
|
} else {
|
|
948
1085
|
// For public data, use the regular path
|
|
@@ -958,7 +1095,7 @@ class HoloSphere {
|
|
|
958
1095
|
/**
|
|
959
1096
|
* Retrieves all data from a global table.
|
|
960
1097
|
* @param {string} tableName - The table name to retrieve data from.
|
|
961
|
-
* @param {string} [password] - Optional password for private
|
|
1098
|
+
* @param {string} [password] - Optional password for private holon.
|
|
962
1099
|
* @returns {Promise<Array<object>>} - The parsed data from the table as an array.
|
|
963
1100
|
*/
|
|
964
1101
|
async getAllGlobal(tableName, password = null) {
|
|
@@ -967,7 +1104,7 @@ class HoloSphere {
|
|
|
967
1104
|
}
|
|
968
1105
|
|
|
969
1106
|
try {
|
|
970
|
-
// Get the appropriate
|
|
1107
|
+
// Get the appropriate holon
|
|
971
1108
|
const user = this.gun.user();
|
|
972
1109
|
|
|
973
1110
|
return new Promise((resolve) => {
|
|
@@ -1020,7 +1157,7 @@ class HoloSphere {
|
|
|
1020
1157
|
};
|
|
1021
1158
|
|
|
1022
1159
|
if (password) {
|
|
1023
|
-
// For private data, use the authenticated user's
|
|
1160
|
+
// For private data, use the authenticated user's holon
|
|
1024
1161
|
user.get('private').get(tableName).once(handleData);
|
|
1025
1162
|
} else {
|
|
1026
1163
|
// For public data, use the regular path
|
|
@@ -1037,7 +1174,7 @@ class HoloSphere {
|
|
|
1037
1174
|
* Deletes a specific key from a global table.
|
|
1038
1175
|
* @param {string} tableName - The table name to delete from.
|
|
1039
1176
|
* @param {string} key - The key to delete.
|
|
1040
|
-
* @param {string} [password] - Optional password for private
|
|
1177
|
+
* @param {string} [password] - Optional password for private holon.
|
|
1041
1178
|
* @returns {Promise<boolean>}
|
|
1042
1179
|
*/
|
|
1043
1180
|
async deleteGlobal(tableName, key, password = null) {
|
|
@@ -1046,12 +1183,12 @@ class HoloSphere {
|
|
|
1046
1183
|
}
|
|
1047
1184
|
|
|
1048
1185
|
try {
|
|
1049
|
-
// Get the appropriate
|
|
1186
|
+
// Get the appropriate holon
|
|
1050
1187
|
const user = this.gun.user();
|
|
1051
1188
|
|
|
1052
1189
|
return new Promise((resolve, reject) => {
|
|
1053
1190
|
if (password) {
|
|
1054
|
-
// For private data, use the authenticated user's
|
|
1191
|
+
// For private data, use the authenticated user's holon
|
|
1055
1192
|
user.get('private').get(tableName).get(key).put(null, ack => {
|
|
1056
1193
|
if (ack.err) {
|
|
1057
1194
|
reject(new Error(ack.err));
|
|
@@ -1079,7 +1216,7 @@ class HoloSphere {
|
|
|
1079
1216
|
/**
|
|
1080
1217
|
* Deletes an entire global table.
|
|
1081
1218
|
* @param {string} tableName - The table name to delete.
|
|
1082
|
-
* @param {string} [password] - Optional password for private
|
|
1219
|
+
* @param {string} [password] - Optional password for private holon.
|
|
1083
1220
|
* @returns {Promise<boolean>}
|
|
1084
1221
|
*/
|
|
1085
1222
|
async deleteAllGlobal(tableName, password = null) {
|
|
@@ -1088,7 +1225,7 @@ class HoloSphere {
|
|
|
1088
1225
|
}
|
|
1089
1226
|
|
|
1090
1227
|
try {
|
|
1091
|
-
// Get the appropriate
|
|
1228
|
+
// Get the appropriate holon
|
|
1092
1229
|
const user = this.gun.user();
|
|
1093
1230
|
|
|
1094
1231
|
return new Promise((resolve, reject) => {
|
|
@@ -1154,7 +1291,7 @@ class HoloSphere {
|
|
|
1154
1291
|
* @param {string} lens - The lens to compute
|
|
1155
1292
|
* @param {object} options - Computation options
|
|
1156
1293
|
* @param {number} [maxLevels=15] - Maximum levels to compute up
|
|
1157
|
-
* @param {string} [password] - Optional password for private
|
|
1294
|
+
* @param {string} [password] - Optional password for private holons
|
|
1158
1295
|
*/
|
|
1159
1296
|
async computeHierarchy(holon, lens, options, maxLevels = 15, password = null) {
|
|
1160
1297
|
let currentHolon = holon;
|
|
@@ -1187,7 +1324,7 @@ class HoloSphere {
|
|
|
1187
1324
|
* @param {string} options.operation - The operation to perform ('summarize', 'aggregate', 'concatenate')
|
|
1188
1325
|
* @param {string[]} [options.fields] - Fields to perform operation on
|
|
1189
1326
|
* @param {string} [options.targetField] - Field to store the result in
|
|
1190
|
-
* @param {string} [password] - Optional password for private
|
|
1327
|
+
* @param {string} [password] - Optional password for private holons
|
|
1191
1328
|
* @throws {Error} If parameters are invalid or missing
|
|
1192
1329
|
*/
|
|
1193
1330
|
async compute(holon, lens, options, password = null) {
|
|
@@ -1354,27 +1491,52 @@ class HoloSphere {
|
|
|
1354
1491
|
}
|
|
1355
1492
|
|
|
1356
1493
|
/**
|
|
1357
|
-
* Upcasts content to parent holonagons recursively.
|
|
1494
|
+
* Upcasts content to parent holonagons recursively using federation and soul references.
|
|
1495
|
+
* This is the modern implementation that uses federation references instead of duplicating data.
|
|
1358
1496
|
* @param {string} holon - The current holon identifier.
|
|
1359
1497
|
* @param {string} lens - The lens under which to upcast.
|
|
1360
1498
|
* @param {object} content - The content to upcast.
|
|
1361
|
-
* @
|
|
1499
|
+
* @param {number} [maxLevels=15] - Maximum levels to upcast.
|
|
1500
|
+
* @returns {Promise<object>} - The original content.
|
|
1362
1501
|
*/
|
|
1363
|
-
async upcast(holon, lens, content) {
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1502
|
+
async upcast(holon, lens, content, maxLevels = 15) {
|
|
1503
|
+
// Store the actual content at the original resolution
|
|
1504
|
+
await this.put(holon, lens, content);
|
|
1505
|
+
|
|
1506
|
+
let res = h3.getResolution(holon);
|
|
1507
|
+
|
|
1508
|
+
// If already at the highest level (res 0) or reached max levels, we're done
|
|
1509
|
+
if (res === 0 || maxLevels <= 0) {
|
|
1510
|
+
return content;
|
|
1368
1511
|
}
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1512
|
+
|
|
1513
|
+
// Get the parent cell
|
|
1514
|
+
let parent = h3.cellToParent(holon, res - 1);
|
|
1515
|
+
|
|
1516
|
+
// Create federation relationship if it doesn't exist
|
|
1517
|
+
await this.federate(holon, parent);
|
|
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
|
+
};
|
|
1525
|
+
|
|
1526
|
+
// Store the reference in the parent cell
|
|
1527
|
+
// We use { autoPropagate: false } to prevent circular propagation
|
|
1528
|
+
await this.put(parent, lens, reference, null, {
|
|
1529
|
+
autoPropagate: false
|
|
1530
|
+
});
|
|
1531
|
+
|
|
1532
|
+
// Continue upcasting with the parent
|
|
1533
|
+
if (res > 1 && maxLevels > 1) {
|
|
1534
|
+
return this.upcast(parent, lens, reference, maxLevels - 1);
|
|
1374
1535
|
}
|
|
1536
|
+
|
|
1537
|
+
return content;
|
|
1375
1538
|
}
|
|
1376
1539
|
|
|
1377
|
-
|
|
1378
1540
|
/**
|
|
1379
1541
|
* Updates the parent holon with a new report.
|
|
1380
1542
|
* @param {string} id - The child holon identifier.
|
|
@@ -1534,65 +1696,131 @@ class HoloSphere {
|
|
|
1534
1696
|
// ================================ FEDERATION FUNCTIONS ================================
|
|
1535
1697
|
|
|
1536
1698
|
/**
|
|
1537
|
-
* Creates a federation relationship between two
|
|
1538
|
-
* @param {string}
|
|
1539
|
-
* @param {string}
|
|
1540
|
-
* @param {string} password1 - Password for the first
|
|
1541
|
-
* @param {string} [password2] - Optional password for the second
|
|
1699
|
+
* Creates a federation relationship between two holons
|
|
1700
|
+
* @param {string} holonId1 - The first holon ID
|
|
1701
|
+
* @param {string} holonId2 - The second holon ID
|
|
1702
|
+
* @param {string} password1 - Password for the first holon
|
|
1703
|
+
* @param {string} [password2] - Optional password for the second holon
|
|
1704
|
+
* @param {boolean} [bidirectional=true] - Whether to set up bidirectional notifications automatically
|
|
1542
1705
|
* @returns {Promise<boolean>} - True if federation was created successfully
|
|
1543
1706
|
*/
|
|
1544
|
-
async federate(
|
|
1545
|
-
return Federation.federate(this,
|
|
1707
|
+
async federate(holonId1, holonId2, password1, password2 = null, bidirectional = true) {
|
|
1708
|
+
return Federation.federate(this, holonId1, holonId2, password1, password2, bidirectional);
|
|
1546
1709
|
}
|
|
1547
1710
|
|
|
1548
1711
|
/**
|
|
1549
|
-
* Subscribes to federation notifications for a
|
|
1550
|
-
* @param {string}
|
|
1551
|
-
* @param {string} password - Password for the
|
|
1712
|
+
* Subscribes to federation notifications for a holon
|
|
1713
|
+
* @param {string} holonId - The holon ID to subscribe to
|
|
1714
|
+
* @param {string} password - Password for the holon
|
|
1552
1715
|
* @param {function} callback - The callback to execute on notifications
|
|
1553
1716
|
* @param {object} [options] - Subscription options
|
|
1554
1717
|
* @param {string[]} [options.lenses] - Specific lenses to subscribe to (default: all)
|
|
1555
1718
|
* @param {number} [options.throttle] - Throttle notifications in ms (default: 0)
|
|
1556
1719
|
* @returns {Promise<object>} - Subscription object with unsubscribe() method
|
|
1557
1720
|
*/
|
|
1558
|
-
async subscribeFederation(
|
|
1559
|
-
return Federation.subscribeFederation(this,
|
|
1721
|
+
async subscribeFederation(holonId, password, callback, options = {}) {
|
|
1722
|
+
return Federation.subscribeFederation(this, holonId, password, callback, options);
|
|
1560
1723
|
}
|
|
1561
1724
|
|
|
1562
1725
|
/**
|
|
1563
|
-
* Gets federation info for a
|
|
1564
|
-
* @param {string}
|
|
1565
|
-
* @param {string} [password] - Optional password for the
|
|
1726
|
+
* Gets federation info for a holon
|
|
1727
|
+
* @param {string} holonId - The holon ID
|
|
1728
|
+
* @param {string} [password] - Optional password for the holon
|
|
1566
1729
|
* @returns {Promise<object|null>} - Federation info or null if not found
|
|
1567
1730
|
*/
|
|
1568
|
-
async getFederation(
|
|
1569
|
-
return Federation.getFederation(this,
|
|
1731
|
+
async getFederation(holonId, password = null) {
|
|
1732
|
+
return Federation.getFederation(this, holonId, password);
|
|
1570
1733
|
}
|
|
1571
1734
|
|
|
1572
1735
|
/**
|
|
1573
|
-
* Removes a federation relationship between
|
|
1574
|
-
* @param {string}
|
|
1575
|
-
* @param {string}
|
|
1576
|
-
* @param {string} password1 - Password for the first
|
|
1577
|
-
* @param {string} [password2] - Optional password for the second
|
|
1736
|
+
* Removes a federation relationship between holons
|
|
1737
|
+
* @param {string} holonId1 - The first holon ID
|
|
1738
|
+
* @param {string} holonId2 - The second holon ID
|
|
1739
|
+
* @param {string} password1 - Password for the first holon
|
|
1740
|
+
* @param {string} [password2] - Optional password for the second holon
|
|
1578
1741
|
* @returns {Promise<boolean>} - True if federation was removed successfully
|
|
1579
1742
|
*/
|
|
1580
|
-
async unfederate(
|
|
1581
|
-
return Federation.unfederate(this,
|
|
1743
|
+
async unfederate(holonId1, holonId2, password1, password2 = null) {
|
|
1744
|
+
return await Federation.unfederate(this, holonId1, holonId2, password1, password2);
|
|
1582
1745
|
}
|
|
1583
1746
|
|
|
1584
1747
|
/**
|
|
1585
|
-
*
|
|
1586
|
-
*
|
|
1587
|
-
*
|
|
1588
|
-
* @param {
|
|
1589
|
-
* @param {string}
|
|
1590
|
-
* @
|
|
1748
|
+
* Removes a notification relationship between two spaces
|
|
1749
|
+
* This removes spaceId2 from the notify list of spaceId1
|
|
1750
|
+
*
|
|
1751
|
+
* @param {string} holonId1 - The space to modify (remove from its notify list)
|
|
1752
|
+
* @param {string} holonId2 - The space to be removed from notifications
|
|
1753
|
+
* @param {string} [password1] - Optional password for the first space
|
|
1754
|
+
* @returns {Promise<boolean>} - True if notification was removed successfully
|
|
1755
|
+
*/
|
|
1756
|
+
async removeNotify(holonId1, holonId2, password1 = null) {
|
|
1757
|
+
console.log(`HoloSphere.removeNotify called: ${holonId1}, ${holonId2}`);
|
|
1758
|
+
try {
|
|
1759
|
+
const result = await Federation.removeNotify(this, holonId1, holonId2, password1);
|
|
1760
|
+
console.log(`HoloSphere.removeNotify completed successfully: ${result}`);
|
|
1761
|
+
return result;
|
|
1762
|
+
} catch (error) {
|
|
1763
|
+
console.error(`HoloSphere.removeNotify failed:`, error);
|
|
1764
|
+
throw error;
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
|
|
1768
|
+
/**
|
|
1769
|
+
* Get and aggregate data from federated holons
|
|
1770
|
+
* @param {string} holon The holon name
|
|
1771
|
+
* @param {string} lens The lens name
|
|
1772
|
+
* @param {Object} options Options for retrieval and aggregation
|
|
1773
|
+
* @returns {Promise<Array>} Combined array of local and federated data
|
|
1591
1774
|
*/
|
|
1592
|
-
async getFederated(holon, lens, options = {}
|
|
1593
|
-
return Federation.getFederated(this, holon, lens, options
|
|
1775
|
+
async getFederated(holon, lens, options = {}) {
|
|
1776
|
+
return Federation.getFederated(this, holon, lens, options);
|
|
1594
1777
|
}
|
|
1595
1778
|
|
|
1779
|
+
/**
|
|
1780
|
+
* Tracks a federated message across different chats
|
|
1781
|
+
* @param {string} originalChatId - The ID of the original chat
|
|
1782
|
+
* @param {string} messageId - The ID of the original message
|
|
1783
|
+
* @param {string} federatedChatId - The ID of the federated chat
|
|
1784
|
+
* @param {string} federatedMessageId - The ID of the message in the federated chat
|
|
1785
|
+
* @param {string} type - The type of message (e.g., 'quest', 'announcement')
|
|
1786
|
+
* @returns {Promise<void>}
|
|
1787
|
+
*/
|
|
1788
|
+
async federateMessage(originalChatId, messageId, federatedChatId, federatedMessageId, type = 'generic') {
|
|
1789
|
+
return Federation.federateMessage(this, originalChatId, messageId, federatedChatId, federatedMessageId, type);
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
/**
|
|
1793
|
+
* Gets all federated messages for a given original message
|
|
1794
|
+
* @param {string} originalChatId - The ID of the original chat
|
|
1795
|
+
* @param {string} messageId - The ID of the original message
|
|
1796
|
+
* @returns {Promise<Object|null>} The tracking information for the message
|
|
1797
|
+
*/
|
|
1798
|
+
async getFederatedMessages(originalChatId, messageId) {
|
|
1799
|
+
return Federation.getFederatedMessages(this, originalChatId, messageId);
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1802
|
+
/**
|
|
1803
|
+
* Updates a federated message across all federated chats
|
|
1804
|
+
* @param {string} originalChatId - The ID of the original chat
|
|
1805
|
+
* @param {string} messageId - The ID of the original message
|
|
1806
|
+
* @param {Function} updateCallback - Function to update the message in each chat
|
|
1807
|
+
* @returns {Promise<void>}
|
|
1808
|
+
*/
|
|
1809
|
+
async updateFederatedMessages(originalChatId, messageId, updateCallback) {
|
|
1810
|
+
return Federation.updateFederatedMessages(this, originalChatId, messageId, updateCallback);
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
/**
|
|
1814
|
+
* Resets the federation settings for a holon
|
|
1815
|
+
* @param {string} holonId - The holon ID
|
|
1816
|
+
* @param {string} [password] - Optional password for the holon
|
|
1817
|
+
* @returns {Promise<boolean>} - True if federation was reset successfully
|
|
1818
|
+
*/
|
|
1819
|
+
async resetFederation(holonId, password = null) {
|
|
1820
|
+
return Federation.resetFederation(this, holonId, password);
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
// ================================ END FEDERATION FUNCTIONS ================================
|
|
1596
1824
|
/**
|
|
1597
1825
|
* Closes the HoloSphere instance and cleans up resources.
|
|
1598
1826
|
* @returns {Promise<void>}
|
|
@@ -1669,26 +1897,15 @@ class HoloSphere {
|
|
|
1669
1897
|
}
|
|
1670
1898
|
}
|
|
1671
1899
|
|
|
1672
|
-
/**
|
|
1673
|
-
* Gets the name of a chat/space
|
|
1674
|
-
* @param {string} spaceId - The space ID
|
|
1675
|
-
* @param {string} [password] - Optional password for the space
|
|
1676
|
-
* @returns {Promise<string>} - The space name or the ID if not found
|
|
1677
|
-
*/
|
|
1678
|
-
async getChatName(spaceId, password = null) {
|
|
1679
|
-
const spaceInfo = await this.getGlobal('spaces', spaceId, password);
|
|
1680
|
-
return spaceInfo?.name || spaceId;
|
|
1681
|
-
}
|
|
1682
|
-
|
|
1683
1900
|
/**
|
|
1684
1901
|
* Creates a namespaced username for Gun authentication
|
|
1685
1902
|
* @private
|
|
1686
|
-
* @param {string}
|
|
1903
|
+
* @param {string} holonId - The holon ID
|
|
1687
1904
|
* @returns {string} - Namespaced username
|
|
1688
1905
|
*/
|
|
1689
|
-
userName(
|
|
1690
|
-
if (!
|
|
1691
|
-
return `${this.appname}:${
|
|
1906
|
+
userName(holonId) {
|
|
1907
|
+
if (!holonId) return null;
|
|
1908
|
+
return `${this.appname}:${holonId}`;
|
|
1692
1909
|
}
|
|
1693
1910
|
}
|
|
1694
1911
|
|