holosphere 1.1.10 → 1.1.11

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/holosphere.js CHANGED
@@ -1,35 +1,37 @@
1
+ /**
2
+ * @module holosphere
3
+ * @version 1.1.11
4
+ * @description Holonic Geospatial Communication Infrastructure
5
+ * @author Roberto Valenti
6
+ * @license GPL-3.0-or-later
7
+ */
8
+
1
9
  import * as h3 from 'h3-js';
2
10
  import OpenAI from 'openai';
3
11
  import Gun from 'gun'
4
12
  import Ajv2019 from 'ajv/dist/2019.js'
5
13
  import * as Federation from './federation.js';
14
+ import * as SchemaOps from './schema.js';
15
+ import * as ContentOps from './content.js';
16
+ import * as NodeOps from './node.js';
17
+ import * as GlobalOps from './global.js';
18
+ import * as HologramOps from './hologram.js';
19
+ import * as ComputeOps from './compute.js';
20
+ import * as Utils from './utils.js';
6
21
 
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
- */
22
+ // Define the version constant
23
+ const HOLOSPHERE_VERSION = '1.1.11';
22
24
 
23
25
  class HoloSphere {
24
26
  /**
25
27
  * Initializes a new instance of the HoloSphere class.
26
28
  * @param {string} appname - The name of the application.
27
- * @param {boolean} strict - Whether to enforce strict schema validation.
28
- * @param {string|null} openaikey - The OpenAI API key.
29
- * @param {Gun|null} gunInstance - The Gun instance to use.
29
+ * @param {boolean} [strict=false] - Whether to enforce strict schema validation.
30
+ * @param {string|null} [openaikey=null] - The OpenAI API key.
31
+ * @param {object} [gunOptions={}] - Optional Gun constructor options (e.g., peers, localStorage, radisk).
30
32
  */
31
- constructor(appname, strict = false, openaikey = null) {
32
- console.log('HoloSphere v1.1.10');
33
+ constructor(appname, strict = false, openaikey = null, gunOptions = {}) {
34
+ console.log('HoloSphere v' + HOLOSPHERE_VERSION);
33
35
  this.appname = appname
34
36
  this.strict = strict;
35
37
  this.validator = new Ajv2019({
@@ -39,11 +41,19 @@ class HoloSphere {
39
41
  });
40
42
 
41
43
 
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
- });
44
+ // Define default Gun options
45
+ const defaultGunOptions = {
46
+ peers: ['https://gun.holons.io/gun','https://59.src.eco/gun'],
47
+ axe: false
48
+ // Add other potential defaults here if needed
49
+ };
50
+
51
+ // Merge provided options with defaults
52
+ const finalGunOptions = { ...defaultGunOptions, ...gunOptions };
53
+ console.log("Initializing Gun with options:", finalGunOptions);
54
+
55
+ // Use provided Gun instance or create new one with final options
56
+ this.gun = Gun(finalGunOptions); // Pass the merged options
47
57
 
48
58
 
49
59
  if (openaikey != null) {
@@ -59,6 +69,10 @@ class HoloSphere {
59
69
  this.schemaCache = new Map();
60
70
  }
61
71
 
72
+ getGun() {
73
+ return this.gun;
74
+ }
75
+
62
76
  // ================================ SCHEMA FUNCTIONS ================================
63
77
 
64
78
  /**
@@ -68,64 +82,8 @@ class HoloSphere {
68
82
  * @returns {Promise} - Resolves when the schema is set.
69
83
  */
70
84
  async setSchema(lens, schema) {
71
- if (!lens || !schema) {
72
- throw new Error('setSchema: Missing required parameters');
73
- }
74
-
75
- // Basic schema validation
76
- if (!schema.type || typeof schema.type !== 'string') {
77
- throw new Error('setSchema: Schema must have a type field');
78
- }
79
-
80
- const metaSchema = {
81
- type: 'object',
82
- required: ['type', 'properties'],
83
- properties: {
84
- type: { type: 'string' },
85
- properties: {
86
- type: 'object',
87
- additionalProperties: {
88
- type: 'object',
89
- required: ['type'],
90
- properties: {
91
- type: { type: 'string' }
92
- }
93
- }
94
- },
95
- required: {
96
- type: 'array',
97
- items: { type: 'string' }
98
- }
99
- }
100
- };
101
-
102
- const valid = this.validator.validate(metaSchema, schema);
103
- if (!valid) {
104
- throw new Error(`Invalid schema structure: ${JSON.stringify(this.validator.errors)}`);
105
- }
106
-
107
- if (!schema.properties || typeof schema.properties !== 'object') {
108
- throw new Error('Schema must have properties in strict mode');
109
- }
110
-
111
- if (!schema.required || !Array.isArray(schema.required) || schema.required.length === 0) {
112
- throw new Error('Schema must have required fields in strict mode');
113
- }
114
-
115
- // Store schema in global table with lens as key
116
- await this.putGlobal('schemas', {
117
- id: lens,
118
- schema: schema,
119
- timestamp: Date.now()
120
- });
121
-
122
- // Update the cache with the new schema
123
- this.schemaCache.set(lens, {
124
- schema,
125
- timestamp: Date.now()
126
- });
127
-
128
- return true;
85
+ // Delegate to the external function
86
+ return SchemaOps.setSchema(this, lens, schema);
129
87
  }
130
88
 
131
89
  /**
@@ -137,37 +95,8 @@ class HoloSphere {
137
95
  * @returns {Promise<object|null>} - The retrieved schema or null if not found.
138
96
  */
139
97
  async getSchema(lens, options = {}) {
140
- if (!lens) {
141
- throw new Error('getSchema: Missing lens parameter');
142
- }
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
158
- const schemaData = await this.getGlobal('schemas', lens);
159
-
160
- if (!schemaData || !schemaData.schema) {
161
- return null;
162
- }
163
-
164
- // Update cache with fetched schema
165
- this.schemaCache.set(lens, {
166
- schema: schemaData.schema,
167
- timestamp: Date.now()
168
- });
169
-
170
- return schemaData.schema;
98
+ // Delegate to the external function
99
+ return SchemaOps.getSchema(this, lens, options);
171
100
  }
172
101
 
173
102
  /**
@@ -176,14 +105,8 @@ class HoloSphere {
176
105
  * @returns {boolean} - Returns true if successful
177
106
  */
178
107
  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
- }
108
+ // Delegate to the external function
109
+ return SchemaOps.clearSchemaCache(this, lens);
187
110
  }
188
111
 
189
112
  // ================================ CONTENT FUNCTIONS ================================
@@ -201,110 +124,8 @@ class HoloSphere {
201
124
  * @returns {Promise<boolean>} - Returns true if successful, false if there was an error
202
125
  */
203
126
  async put(holon, lens, data, password = null, options = {}) {
204
- if (!holon || !lens || !data) {
205
- throw new Error('put: Missing required parameters:', holon, lens, data );
206
- }
207
-
208
- if (!data.id) {
209
- data.id = this.generateId();
210
- }
211
-
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) {
217
- const schema = await this.getSchema(lens);
218
- if (!schema) {
219
- throw new Error('Schema required in strict mode');
220
- }
221
- const dataToValidate = JSON.parse(JSON.stringify(data));
222
- const valid = this.validator.validate(schema, dataToValidate);
223
-
224
- if (!valid) {
225
- const errorMsg = `Schema validation failed: ${JSON.stringify(this.validator.errors)}`;
226
- throw new Error(errorMsg);
227
- }
228
- }
229
-
230
- try {
231
- let user = null;
232
- if (password) {
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();
238
- });
239
- });
240
- }
241
-
242
- return new Promise((resolve, reject) => {
243
- try {
244
- const payload = JSON.stringify(data);
245
-
246
- const putCallback = async (ack) => {
247
- if (ack.err) {
248
- reject(new Error(ack.err));
249
- } else {
250
- // Only notify subscribers for actual data, not references
251
- if (!isRef) {
252
- this.notifySubscribers({
253
- holon,
254
- lens,
255
- ...data
256
- });
257
- }
258
-
259
- // Auto-propagate to federation by default (if not a reference)
260
- const shouldPropagate = options.autoPropagate !== false && !isRef;
261
- let propagationResult = null;
262
-
263
- if (shouldPropagate) {
264
- try {
265
- // Default to using references
266
- const propagationOptions = {
267
- useReferences: true,
268
- ...options.propagationOptions
269
- };
270
-
271
- propagationResult = await this.propagate(
272
- holon,
273
- lens,
274
- data,
275
- propagationOptions
276
- );
277
-
278
- // Still resolve with true even if propagation had errors
279
- if (propagationResult.errors > 0) {
280
- console.warn('Auto-propagation had errors:', propagationResult);
281
- }
282
- } catch (propError) {
283
- console.warn('Error in auto-propagation:', propError);
284
- }
285
- }
286
-
287
- resolve({
288
- success: true,
289
- isReference: isRef,
290
- propagationResult
291
- });
292
- }
293
- };
294
-
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);
300
- } catch (error) {
301
- reject(error);
302
- }
303
- });
304
- } catch (error) {
305
- console.error('Error in put:', error);
306
- throw error;
307
- }
127
+ // Delegate to the external function
128
+ return ContentOps.put(this, holon, lens, data, password, options);
308
129
  }
309
130
 
310
131
  /**
@@ -318,137 +139,8 @@ class HoloSphere {
318
139
  * @returns {Promise<object|null>} - The retrieved content or null if not found.
319
140
  */
320
141
  async get(holon, lens, key, password = null, options = {}) {
321
- if (!holon || !lens || !key) {
322
- console.error('get: Missing required parameters:', { holon, lens, key });
323
- return null;
324
- }
325
-
326
- const { resolveReferences = true } = options;
327
-
328
- // Only check schema in strict mode
329
- let schema;
330
- if (this.strict) {
331
- schema = await this.getSchema(lens);
332
- if (!schema) {
333
- throw new Error('Schema required in strict mode');
334
- }
335
- }
336
-
337
- try {
338
- let user = null;
339
- if (password) {
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();
345
- });
346
- });
347
- }
348
-
349
- return new Promise((resolve) => {
350
- const handleData = async (data) => {
351
- if (!data) {
352
- resolve(null);
353
- return;
354
- }
355
-
356
- try {
357
- const parsed = await this.parse(data);
358
-
359
- if (!parsed) {
360
- resolve(null);
361
- return;
362
- }
363
-
364
- // Check if this is a reference that needs to be resolved
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;
378
- }
379
- }
380
-
381
- // Perform schema validation if needed
382
- if (schema) {
383
- const valid = this.validator.validate(schema, parsed);
384
- if (!valid) {
385
- console.error('get: Invalid data according to schema:', this.validator.errors);
386
- if (this.strict) {
387
- resolve(null);
388
- return;
389
- }
390
- }
391
- }
392
-
393
- resolve(parsed);
394
- } catch (error) {
395
- console.error('Error parsing data:', error);
396
- resolve(null);
397
- }
398
- };
399
-
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);
405
- });
406
- } catch (error) {
407
- console.error('Error in get:', error);
408
- return null;
409
- }
410
- }
411
-
412
- /**
413
- * Retrieves a node directly using its soul path
414
- * @param {string} soul - The soul path of the node
415
- * @returns {Promise<any>} - The retrieved node or null if not found.
416
- */
417
- async getNodeBySoul(soul) {
418
- if (!soul) {
419
- throw new Error('getNodeBySoul: Missing soul parameter');
420
- }
421
-
422
- console.log(`getNodeBySoul: Accessing soul ${soul}`);
423
-
424
- return new Promise((resolve, reject) => {
425
- try {
426
- const ref = this.getNodeRef(soul);
427
- ref.once((data) => {
428
- console.log(`getNodeBySoul: Retrieved data:`, data);
429
- if (!data) {
430
- resolve(null);
431
- return;
432
- }
433
- resolve(data); // Return the data directly
434
- });
435
- } catch (error) {
436
- console.error(`getNodeBySoul error:`, error);
437
- reject(error);
438
- }
439
- });
440
- }
441
-
442
- /**
443
- * Propagates data to federated holons
444
- * @param {string} holon - The holon identifier
445
- * @param {string} lens - The lens identifier
446
- * @param {object} data - The data to propagate
447
- * @param {object} [options] - Propagation options
448
- * @returns {Promise<object>} - Result with success count and errors
449
- */
450
- async propagate(holon, lens, data, options = {}) {
451
- return Federation.propagate(this, holon, lens, data, options);
142
+ // Delegate to the external function
143
+ return ContentOps.get(this, holon, lens, key, password, options);
452
144
  }
453
145
 
454
146
  /**
@@ -459,102 +151,8 @@ class HoloSphere {
459
151
  * @returns {Promise<Array<object>>} - The retrieved content.
460
152
  */
461
153
  async getAll(holon, lens, password = null) {
462
- if (!holon || !lens) {
463
- throw new Error('getAll: Missing required parameters');
464
- }
465
-
466
- const schema = await this.getSchema(lens);
467
- if (!schema && this.strict) {
468
- throw new Error('getAll: Schema required in strict mode');
469
- }
470
-
471
- try {
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
- }
482
-
483
- return new Promise((resolve) => {
484
- const output = new Map();
485
-
486
- const processData = async (data, key) => {
487
- if (!data || key === '_') return;
488
-
489
- try {
490
- const parsed = await this.parse(data);
491
- if (!parsed || !parsed.id) return;
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
-
513
- if (schema) {
514
- const valid = this.validator.validate(schema, parsed);
515
- if (valid || !this.strict) {
516
- output.set(parsed.id, parsed);
517
- }
518
- } else {
519
- output.set(parsed.id, parsed);
520
- }
521
- } catch (error) {
522
- console.error('Error processing data:', error);
523
- }
524
- };
525
-
526
- const handleData = async (data) => {
527
- if (!data) {
528
- resolve([]);
529
- return;
530
- }
531
-
532
- const initialPromises = [];
533
- Object.keys(data)
534
- .filter(key => key !== '_')
535
- .forEach(key => {
536
- initialPromises.push(processData(data[key], key));
537
- });
538
-
539
- try {
540
- await Promise.all(initialPromises);
541
- resolve(Array.from(output.values()));
542
- } catch (error) {
543
- console.error('Error in getAll:', error);
544
- resolve([]);
545
- }
546
- };
547
-
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);
553
- });
554
- } catch (error) {
555
- console.error('Error in getAll:', error);
556
- return [];
557
- }
154
+ // Delegate to the external function
155
+ return ContentOps.getAll(this, holon, lens, password);
558
156
  }
559
157
 
560
158
  /**
@@ -563,56 +161,8 @@ class HoloSphere {
563
161
  * @returns {Promise<object>} - The parsed data.
564
162
  */
565
163
  async parse(rawData) {
566
- let parsedData = {};
567
-
568
- if (!rawData) {
569
- throw new Error('parse: No data provided');
570
- }
571
-
572
- try {
573
-
574
- if (typeof rawData === 'string') {
575
- parsedData = await JSON.parse(rawData);
576
- }
577
-
578
-
579
- if (rawData.soul) {
580
- const data = await this.getNodeRef(rawData.soul).once();
581
- if (!data) {
582
- throw new Error('Referenced data not found');
583
- }
584
- return JSON.parse(data);
585
- }
586
-
587
-
588
- if (typeof rawData === 'object' && rawData !== null) {
589
- if (rawData._ && rawData._["#"]) {
590
- const pathParts = rawData._['#'].split('/');
591
- if (pathParts.length < 4) {
592
- throw new Error('Invalid reference format');
593
- }
594
- parsedData = await this.get(pathParts[1], pathParts[2], pathParts[3]);
595
- if (!parsedData) {
596
- throw new Error('Referenced data not found');
597
- }
598
- } else if (rawData._ && rawData._['>']) {
599
- const nodeValue = Object.values(rawData).find(v => typeof v !== 'object' && v !== '_');
600
- if (!nodeValue) {
601
- throw new Error('Invalid node data');
602
- }
603
- parsedData = JSON.parse(nodeValue);
604
- } else {
605
- parsedData = rawData;
606
- }
607
- }
608
-
609
- return parsedData;
610
-
611
- } catch (error) {
612
- console.log("Parsing not a JSON, returning raw data", rawData);
613
- return rawData;
614
- //throw new Error(`Parse error: ${error.message}`);
615
- }
164
+ // Delegate to the external function
165
+ return ContentOps.parse(this, rawData);
616
166
  }
617
167
 
618
168
  /**
@@ -624,39 +174,8 @@ class HoloSphere {
624
174
  * @returns {Promise<boolean>} - Returns true if successful
625
175
  */
626
176
  async delete(holon, lens, key, password = null) {
627
- if (!holon || !lens || !key) {
628
- throw new Error('delete: Missing required parameters');
629
- }
630
-
631
- try {
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
- }
642
-
643
- return new Promise((resolve, reject) => {
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
- });
655
- });
656
- } catch (error) {
657
- console.error('Error in delete:', error);
658
- throw error;
659
- }
177
+ // Delegate to the external function (renamed to deleteFunc in module)
178
+ return ContentOps.deleteFunc(this, holon, lens, key, password);
660
179
  }
661
180
 
662
181
  /**
@@ -667,71 +186,8 @@ class HoloSphere {
667
186
  * @returns {Promise<boolean>} - Returns true if successful
668
187
  */
669
188
  async deleteAll(holon, lens, password = null) {
670
- if (!holon || !lens) {
671
- console.error('deleteAll: Missing holon or lens parameter');
672
- return false;
673
- }
674
-
675
- try {
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
- }
686
-
687
- return new Promise((resolve) => {
688
- let deletionPromises = [];
689
-
690
- const dataPath = password ?
691
- user.get('private').get(lens) :
692
- this.gun.get(this.appname).get(holon).get(lens);
693
-
694
- // First get all the data to find keys to delete
695
- dataPath.once((data) => {
696
- if (!data) {
697
- resolve(true); // Nothing to delete
698
- return;
699
- }
700
-
701
- // Get all keys except Gun's metadata key '_'
702
- const keys = Object.keys(data).filter(key => key !== '_');
703
-
704
- // Create deletion promises for each key
705
- keys.forEach(key => {
706
- deletionPromises.push(
707
- new Promise((resolveDelete) => {
708
- const deletePath = password ?
709
- user.get('private').get(lens).get(key) :
710
- this.gun.get(this.appname).get(holon).get(lens).get(key);
711
-
712
- deletePath.put(null, ack => {
713
- resolveDelete(!!ack.ok); // Convert to boolean
714
- });
715
- })
716
- );
717
- });
718
-
719
- // Wait for all deletions to complete
720
- Promise.all(deletionPromises)
721
- .then(results => {
722
- const allSuccessful = results.every(result => result === true);
723
- resolve(allSuccessful);
724
- })
725
- .catch(error => {
726
- console.error('Error in deleteAll:', error);
727
- resolve(false);
728
- });
729
- });
730
- });
731
- } catch (error) {
732
- console.error('Error in deleteAll:', error);
733
- return false;
734
- }
189
+ // Delegate to the external function
190
+ return ContentOps.deleteAll(this, holon, lens, password);
735
191
  }
736
192
 
737
193
  // ================================ NODE FUNCTIONS ================================
@@ -744,27 +200,8 @@ class HoloSphere {
744
200
  * @param {object} data - The node to store.
745
201
  */
746
202
  async putNode(holon, lens, data) {
747
- if (!holon || !lens || !data) {
748
- throw new Error('putNode: Missing required parameters');
749
- }
750
-
751
- return new Promise((resolve, reject) => {
752
- try {
753
- this.gun.get(this.appname)
754
- .get(holon)
755
- .get(lens)
756
- .get('value') // Store at 'value' key
757
- .put(data.value, ack => { // Store the value directly
758
- if (ack.err) {
759
- reject(new Error(ack.err));
760
- } else {
761
- resolve(true);
762
- }
763
- });
764
- } catch (error) {
765
- reject(error);
766
- }
767
- });
203
+ // Delegate to the external function
204
+ return NodeOps.putNode(this, holon, lens, data);
768
205
  }
769
206
 
770
207
  /**
@@ -775,50 +212,28 @@ class HoloSphere {
775
212
  * @returns {Promise<any>} - The retrieved node or null if not found.
776
213
  */
777
214
  async getNode(holon, lens, key) {
778
- if (!holon || !lens || !key) {
779
- throw new Error('getNode: Missing required parameters');
780
- }
781
-
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
- }
798
- });
215
+ // Delegate to the external function
216
+ return NodeOps.getNode(this, holon, lens, key);
799
217
  }
800
218
 
219
+ /**
220
+ * Retrieves a Gun node reference using its soul path
221
+ * @param {string} soul - The soul path of the node
222
+ * @returns {Gun.ChainReference} - The Gun node reference
223
+ */
801
224
  getNodeRef(soul) {
802
- if (typeof soul !== 'string' || !soul) {
803
- throw new Error('getNodeRef: Invalid soul parameter');
804
- }
805
-
806
- const parts = soul.split('/').filter(part => {
807
- if (!part.trim() || /[<>:"/\\|?*]/.test(part)) {
808
- throw new Error('getNodeRef: Invalid path segment');
809
- }
810
- return part.trim();
811
- });
812
-
813
- if (parts.length === 0) {
814
- throw new Error('getNodeRef: Invalid soul format');
815
- }
225
+ // Delegate to the external function
226
+ return NodeOps.getNodeRef(this, soul);
227
+ }
816
228
 
817
- let ref = this.gun.get(this.appname);
818
- parts.forEach(part => {
819
- ref = ref.get(part);
820
- });
821
- return ref;
229
+ /**
230
+ * Retrieves a node directly using its soul path
231
+ * @param {string} soul - The soul path of the node
232
+ * @returns {Promise<any>} - The retrieved node or null if not found.
233
+ */
234
+ async getNodeBySoul(soul) {
235
+ // Delegate to the external function
236
+ return NodeOps.getNodeBySoul(this, soul);
822
237
  }
823
238
 
824
239
  /**
@@ -829,22 +244,8 @@ class HoloSphere {
829
244
  * @returns {Promise<boolean>} - Returns true if successful
830
245
  */
831
246
  async deleteNode(holon, lens, key) {
832
- if (!holon || !lens || !key) {
833
- throw new Error('deleteNode: Missing required parameters');
834
- }
835
- return new Promise((resolve, reject) => {
836
- this.gun.get(this.appname)
837
- .get(holon)
838
- .get(lens)
839
- .get(key)
840
- .put(null, ack => {
841
- if (ack.err) {
842
- reject(new Error(ack.err));
843
- } else {
844
- resolve(true);
845
- }
846
- });
847
- });
247
+ // Delegate to the external function
248
+ return NodeOps.deleteNode(this, holon, lens, key);
848
249
  }
849
250
 
850
251
  // ================================ GLOBAL FUNCTIONS ================================
@@ -856,57 +257,8 @@ class HoloSphere {
856
257
  * @returns {Promise<void>}
857
258
  */
858
259
  async putGlobal(tableName, data, password = null) {
859
- try {
860
- if (!tableName || !data) {
861
- throw new Error('Table name and data are required');
862
- }
863
-
864
- let user = null;
865
- if (password) {
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();
871
- });
872
- });
873
- }
874
-
875
- return new Promise((resolve, reject) => {
876
- try {
877
- const payload = JSON.stringify(data);
878
-
879
- const dataPath = password ?
880
- user.get('private').get(tableName) :
881
- this.gun.get(this.appname).get(tableName);
882
-
883
- if (data.id) {
884
- // Store at the specific key path
885
- dataPath.get(data.id).put(payload, ack => {
886
-
887
- if (ack.err) {
888
- reject(new Error(ack.err));
889
- } else {
890
- resolve();
891
- }
892
- });
893
- } else {
894
- dataPath.put(payload, ack => {
895
- if (ack.err) {
896
- reject(new Error(ack.err));
897
- } else {
898
- resolve();
899
- }
900
- });
901
- }
902
- } catch (error) {
903
- reject(error);
904
- }
905
- });
906
- } catch (error) {
907
- console.error('Error in putGlobal:', error);
908
- throw error;
909
- }
260
+ // Delegate to the external function
261
+ return GlobalOps.putGlobal(this, tableName, data, password);
910
262
  }
911
263
 
912
264
  /**
@@ -917,64 +269,8 @@ class HoloSphere {
917
269
  * @returns {Promise<object|null>} - The parsed data for the key or null if not found.
918
270
  */
919
271
  async getGlobal(tableName, key, password = null) {
920
- try {
921
- let user = null;
922
- if (password) {
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();
928
- });
929
- });
930
- }
931
-
932
- return new Promise((resolve) => {
933
- const handleData = async (data) => {
934
- if (!data) {
935
- resolve(null);
936
- return;
937
- }
938
-
939
- try {
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
-
961
- resolve(parsed);
962
- } catch (e) {
963
- console.error('Error parsing data in getGlobal:', e);
964
- resolve(null);
965
- }
966
- };
967
-
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);
973
- });
974
- } catch (error) {
975
- console.error('Error in getGlobal:', error);
976
- return null;
977
- }
272
+ // Delegate to the external function
273
+ return GlobalOps.getGlobal(this, tableName, key, password);
978
274
  }
979
275
 
980
276
  /**
@@ -984,97 +280,8 @@ class HoloSphere {
984
280
  * @returns {Promise<Array<object>>} - The parsed data from the table as an array.
985
281
  */
986
282
  async getAllGlobal(tableName, password = null) {
987
- if (!tableName) {
988
- throw new Error('getAllGlobal: Missing table name parameter');
989
- }
990
-
991
- try {
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
- }
1002
-
1003
- return new Promise((resolve) => {
1004
- let output = [];
1005
- let isResolved = false;
1006
- let timeout = setTimeout(() => {
1007
- if (!isResolved) {
1008
- isResolved = true;
1009
- resolve(output);
1010
- }
1011
- }, 5000);
1012
-
1013
- const handleData = async (data) => {
1014
- if (!data) {
1015
- clearTimeout(timeout);
1016
- isResolved = true;
1017
- resolve([]);
1018
- return;
1019
- }
1020
-
1021
- const keys = Object.keys(data).filter(key => key !== '_');
1022
- const promises = keys.map(key =>
1023
- new Promise(async (resolveItem) => {
1024
- const itemPath = password ?
1025
- user.get('private').get(tableName).get(key) :
1026
- this.gun.get(this.appname).get(tableName).get(key);
1027
-
1028
- const itemData = await new Promise(resolveData => {
1029
- itemPath.once(resolveData);
1030
- });
1031
-
1032
- if (itemData) {
1033
- try {
1034
- const parsed = await this.parse(itemData);
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
- }
1052
- } catch (error) {
1053
- console.error('Error parsing data:', error);
1054
- }
1055
- }
1056
- resolveItem();
1057
- })
1058
- );
1059
-
1060
- await Promise.all(promises);
1061
- clearTimeout(timeout);
1062
- if (!isResolved) {
1063
- isResolved = true;
1064
- resolve(output);
1065
- }
1066
- };
1067
-
1068
- const dataPath = password ?
1069
- user.get('private').get(tableName) :
1070
- this.gun.get(this.appname).get(tableName);
1071
-
1072
- dataPath.once(handleData);
1073
- });
1074
- } catch (error) {
1075
- console.error('Error in getAllGlobal:', error);
1076
- return [];
1077
- }
283
+ // Delegate to the external function
284
+ return GlobalOps.getAllGlobal(this, tableName, password);
1078
285
  }
1079
286
 
1080
287
  /**
@@ -1085,55 +292,8 @@ class HoloSphere {
1085
292
  * @returns {Promise<boolean>}
1086
293
  */
1087
294
  async deleteGlobal(tableName, key, password = null) {
1088
- if (!tableName || !key) {
1089
- throw new Error('deleteGlobal: Missing required parameters');
1090
- }
1091
-
1092
- try {
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
- }
1105
-
1106
- return new Promise((resolve, reject) => {
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);
1120
- if (ack.err) {
1121
- console.error('deleteGlobal - Deletion error:', ack.err);
1122
- reject(new Error(ack.err));
1123
- } else {
1124
- // Verify deletion
1125
- dataPath.get(key).once((deletedData) => {
1126
- console.log('deleteGlobal - Data after deletion:', deletedData);
1127
- resolve(true);
1128
- });
1129
- }
1130
- });
1131
- });
1132
- });
1133
- } catch (error) {
1134
- console.error('Error in deleteGlobal:', error);
1135
- throw error;
1136
- }
295
+ // Delegate to the external function
296
+ return GlobalOps.deleteGlobal(this, tableName, key, password);
1137
297
  }
1138
298
 
1139
299
  /**
@@ -1143,99 +303,22 @@ class HoloSphere {
1143
303
  * @returns {Promise<boolean>}
1144
304
  */
1145
305
  async deleteAllGlobal(tableName, password = null) {
1146
- if (!tableName) {
1147
- throw new Error('deleteAllGlobal: Missing table name parameter');
1148
- }
1149
-
1150
- try {
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
- }
1161
-
1162
- return new Promise((resolve, reject) => {
1163
- try {
1164
- const deletions = new Set();
1165
- let timeout = setTimeout(() => {
1166
- if (deletions.size === 0) {
1167
- resolve(true); // No data to delete
1168
- }
1169
- }, 5000);
1170
-
1171
- const dataPath = password ?
1172
- user.get('private').get(tableName) :
1173
- this.gun.get(this.appname).get(tableName);
1174
-
1175
- dataPath.once(async (data) => {
1176
- if (!data) {
1177
- clearTimeout(timeout);
1178
- resolve(true);
1179
- return;
1180
- }
1181
-
1182
- const keys = Object.keys(data).filter(key => key !== '_');
1183
- const promises = keys.map(key =>
1184
- new Promise((resolveDelete, rejectDelete) => {
1185
- const deletePath = password ?
1186
- user.get('private').get(tableName).get(key) :
1187
- this.gun.get(this.appname).get(tableName).get(key);
1188
-
1189
- deletePath.put(null, ack => {
1190
- if (ack.err) {
1191
- console.error(`Failed to delete ${key}:`, ack.err);
1192
- rejectDelete(new Error(ack.err));
1193
- } else {
1194
- resolveDelete();
1195
- }
1196
- });
1197
- })
1198
- );
1199
-
1200
- try {
1201
- await Promise.all(promises);
1202
- // Finally delete the table itself
1203
- dataPath.put(null);
1204
- clearTimeout(timeout);
1205
- resolve(true);
1206
- } catch (error) {
1207
- reject(error);
1208
- }
1209
- });
1210
- } catch (error) {
1211
- reject(error);
1212
- }
1213
- });
1214
- } catch (error) {
1215
- console.error('Error in deleteAllGlobal:', error);
1216
- throw error;
1217
- }
306
+ // Delegate to the external function
307
+ return GlobalOps.deleteAllGlobal(this, tableName, password);
1218
308
  }
1219
309
 
1220
310
  // ================================ REFERENCE FUNCTIONS ================================
1221
311
 
1222
312
  /**
1223
- * Creates a soul reference object for a data item
313
+ * Creates a soul hologram object for a data item
1224
314
  * @param {string} holon - The holon where the original data is stored
1225
315
  * @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
316
+ * @param {object} data - The data to create a hologram for
317
+ * @returns {object} - A hologram object with id and soul
1228
318
  */
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
- };
319
+ createHologram(holon, lens, data) {
320
+ // Delegate to the external function
321
+ return HologramOps.createHologram(this, holon, lens, data);
1239
322
  }
1240
323
 
1241
324
  /**
@@ -1244,130 +327,31 @@ class HoloSphere {
1244
327
  * @returns {object|null} - The parsed components or null if invalid format
1245
328
  */
1246
329
  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
- };
330
+ // Delegate to the external function (doesn't need instance)
331
+ return HologramOps.parseSoulPath(soul);
1262
332
  }
1263
333
 
1264
334
  /**
1265
- * Checks if an object is a reference
335
+ * Checks if an object is a hologram
1266
336
  * @param {object} data - The data to check
1267
- * @returns {boolean} - True if the object is a reference
337
+ * @returns {boolean} - True if the object is a hologram
1268
338
  */
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;
339
+ isHologram(data) {
340
+ // Delegate to the external function (doesn't need instance)
341
+ return HologramOps.isHologram(data);
1285
342
  }
1286
343
 
1287
344
  /**
1288
- * Resolves a reference to its actual data
1289
- * @param {object} reference - The reference to resolve
345
+ * Resolves a hologram to its actual data
346
+ * @param {object} hologram - The hologram to resolve
1290
347
  * @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
348
+ * @param {boolean} [options.followHolograms=true] - Whether to follow nested holograms
349
+ * @param {Set<string>} [options.visited] - Internal use: Tracks visited souls to prevent loops
350
+ * @returns {Promise<object|null>} - The resolved data, null if resolution failed due to target not found, or the original hologram for circular/invalid cases.
1293
351
  */
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
- }
352
+ async resolveHologram(hologram, options = {}) {
353
+ // Delegate to the external function
354
+ return HologramOps.resolveHologram(this, hologram, options);
1371
355
  }
1372
356
 
1373
357
  // ================================ COMPUTE FUNCTIONS ================================
@@ -1380,26 +364,8 @@ class HoloSphere {
1380
364
  * @param {string} [password] - Optional password for private holons
1381
365
  */
1382
366
  async computeHierarchy(holon, lens, options, maxLevels = 15, password = null) {
1383
- let currentHolon = holon;
1384
- let currentRes = h3.getResolution(currentHolon);
1385
- const results = [];
1386
-
1387
- while (currentRes > 0 && maxLevels > 0) {
1388
- try {
1389
- const result = await this.compute(currentHolon, lens, options, password);
1390
- if (result) {
1391
- results.push(result);
1392
- }
1393
- currentHolon = h3.cellToParent(currentHolon, currentRes - 1);
1394
- currentRes--;
1395
- maxLevels--;
1396
- } catch (error) {
1397
- console.error('Error in compute hierarchy:', error);
1398
- break;
1399
- }
1400
- }
1401
-
1402
- return results;
367
+ // Delegate to the external function
368
+ return ComputeOps.computeHierarchy(this, holon, lens, options, maxLevels, password);
1403
369
  }
1404
370
 
1405
371
  /**
@@ -1414,132 +380,8 @@ class HoloSphere {
1414
380
  * @throws {Error} If parameters are invalid or missing
1415
381
  */
1416
382
  async compute(holon, lens, options, password = null) {
1417
- // Validate required parameters
1418
- if (!holon || !lens) {
1419
- throw new Error('compute: Missing required parameters');
1420
- }
1421
-
1422
- // Convert string operation to options object
1423
- if (typeof options === 'string') {
1424
- options = { operation: options };
1425
- }
1426
-
1427
- if (!options?.operation) {
1428
- throw new Error('compute: Missing required parameters');
1429
- }
1430
-
1431
- // Validate holon format and resolution first
1432
- let res;
1433
- try {
1434
- res = h3.getResolution(holon);
1435
- } catch (error) {
1436
- throw new Error('compute: Invalid holon format');
1437
- }
1438
-
1439
- if (res < 1 || res > 15) {
1440
- throw new Error('compute: Invalid holon resolution (must be between 1 and 15)');
1441
- }
1442
-
1443
- const {
1444
- operation,
1445
- fields = [],
1446
- targetField,
1447
- depth,
1448
- maxDepth
1449
- } = options;
1450
-
1451
- // Validate depth parameters if provided
1452
- if (depth !== undefined && depth < 0) {
1453
- throw new Error('compute: Invalid depth parameter');
1454
- }
1455
-
1456
- if (maxDepth !== undefined && (maxDepth < 1 || maxDepth > 15)) {
1457
- throw new Error('compute: Invalid maxDepth parameter (must be between 1 and 15)');
1458
- }
1459
-
1460
- // Validate operation
1461
- const validOperations = ['summarize', 'aggregate', 'concatenate'];
1462
- if (!validOperations.includes(operation)) {
1463
- throw new Error(`compute: Invalid operation (must be one of ${validOperations.join(', ')})`);
1464
- }
1465
-
1466
- const parent = h3.cellToParent(holon, res - 1);
1467
- const siblings = h3.cellToChildren(parent, res);
1468
-
1469
- // Collect all content from siblings
1470
- const contents = await Promise.all(
1471
- siblings.map(sibling => this.getAll(sibling, lens, password))
1472
- );
1473
-
1474
- const flatContents = contents.flat().filter(Boolean);
1475
-
1476
- if (flatContents.length > 0) {
1477
- try {
1478
- let computed;
1479
- switch (operation) {
1480
- case 'summarize':
1481
- // For summarize, concatenate specified fields or use entire content
1482
- const textToSummarize = fields.length > 0
1483
- ? flatContents.map(item => fields.map(field => item[field]).filter(Boolean).join('\n')).join('\n')
1484
- : JSON.stringify(flatContents);
1485
- computed = await this.summarize(textToSummarize);
1486
- break;
1487
-
1488
- case 'aggregate':
1489
- // For aggregate, sum numeric fields
1490
- computed = fields.reduce((acc, field) => {
1491
- acc[field] = flatContents.reduce((sum, item) => {
1492
- return sum + (Number(item[field]) || 0);
1493
- }, 0);
1494
- return acc;
1495
- }, {});
1496
- break;
1497
-
1498
- case 'concatenate':
1499
- // For concatenate, combine arrays or strings
1500
- computed = fields.reduce((acc, field) => {
1501
- acc[field] = flatContents.reduce((combined, item) => {
1502
- const value = item[field];
1503
- if (Array.isArray(value)) {
1504
- return [...combined, ...value];
1505
- } else if (value) {
1506
- return [...combined, value];
1507
- }
1508
- return combined;
1509
- }, []);
1510
- // Remove duplicates if array
1511
- acc[field] = Array.from(new Set(acc[field]));
1512
- return acc;
1513
- }, {});
1514
- break;
1515
- }
1516
-
1517
- if (computed) {
1518
- const resultId = `${parent}_${operation}`;
1519
- const result = {
1520
- id: resultId,
1521
- timestamp: Date.now()
1522
- };
1523
-
1524
- // Store result in targetField if specified, otherwise at root level
1525
- if (targetField) {
1526
- result[targetField] = computed;
1527
- } else if (typeof computed === 'object') {
1528
- Object.assign(result, computed);
1529
- } else {
1530
- result.value = computed;
1531
- }
1532
-
1533
- await this.put(parent, lens, result, password);
1534
- return result;
1535
- }
1536
- } catch (error) {
1537
- console.warn('Error in compute operation:', error);
1538
- throw error;
1539
- }
1540
- }
1541
-
1542
- return null;
383
+ // Delegate to the external function
384
+ return ComputeOps.compute(this, holon, lens, options, password);
1543
385
  }
1544
386
 
1545
387
  /**
@@ -1548,32 +390,8 @@ class HoloSphere {
1548
390
  * @returns {Promise<string>} - The summarized text.
1549
391
  */
1550
392
  async summarize(history) {
1551
- if (!this.openai) {
1552
- return 'OpenAI not initialized, please specify the API key in the constructor.'
1553
- }
1554
-
1555
- try {
1556
- const response = await this.openai.chat.completions.create({
1557
- model: "gpt-4",
1558
- messages: [
1559
- {
1560
- role: "system",
1561
- content: "You are a helpful assistant that summarizes text concisely while preserving key information. Keep summaries clear and focused."
1562
- },
1563
- {
1564
- role: "user",
1565
- content: history
1566
- }
1567
- ],
1568
- temperature: 0.7,
1569
- max_tokens: 500
1570
- });
1571
-
1572
- return response.choices[0].message.content.trim();
1573
- } catch (error) {
1574
- console.error('Error in summarize:', error);
1575
- throw new Error('Failed to generate summary');
1576
- }
393
+ // Delegate to the external function
394
+ return ComputeOps.summarize(this, history);
1577
395
  }
1578
396
 
1579
397
  /**
@@ -1585,33 +403,8 @@ class HoloSphere {
1585
403
  * @returns {Promise<object>} - The original content.
1586
404
  */
1587
405
  async upcast(holon, lens, content, maxLevels = 15) {
1588
- // Store the actual content at the original resolution
1589
- await this.put(holon, lens, content);
1590
-
1591
- let res = h3.getResolution(holon);
1592
-
1593
- // If already at the highest level (res 0) or reached max levels, we're done
1594
- if (res === 0 || maxLevels <= 0) {
1595
- return content;
1596
- }
1597
-
1598
- // Get the parent cell
1599
- let parent = h3.cellToParent(holon, res - 1);
1600
-
1601
- // Create a reference to store in the parent
1602
- const reference = this.createReference(holon, lens, content);
1603
-
1604
- // Store the reference in the parent cell
1605
- await this.put(parent, lens, reference, null, {
1606
- autoPropagate: false
1607
- });
1608
-
1609
- // Continue upcasting with the parent
1610
- if (res > 1 && maxLevels > 1) {
1611
- return this.upcast(parent, lens, reference, maxLevels - 1);
1612
- }
1613
-
1614
- return content;
406
+ // Delegate to the external function
407
+ return ComputeOps.upcast(this, holon, lens, content, maxLevels);
1615
408
  }
1616
409
 
1617
410
  /**
@@ -1621,19 +414,21 @@ class HoloSphere {
1621
414
  * @returns {Promise<object>} - The updated parent information.
1622
415
  */
1623
416
  async updateParent(id, report) {
1624
- let cellinfo = await this.getCellInfo(id)
1625
- let res = h3.getResolution(id)
1626
- let parent = h3.cellToParent(id, res - 1)
1627
- let parentInfo = await this.getCellInfo(parent)
1628
- parentInfo.wisdom[id] = report
1629
- //update summary
1630
- let summary = await this.summarize(Object.values(parentInfo.wisdom).join('\n'))
1631
- parentInfo.summary = summary
1632
-
1633
- await this.db.put('cell', parentInfo)
1634
- return parentInfo
417
+ // Delegate to the external function
418
+ return ComputeOps.updateParent(this, id, report);
1635
419
  }
1636
420
 
421
+ /**
422
+ * Propagates data to federated holons
423
+ * @param {string} holon - The holon identifier
424
+ * @param {string} lens - The lens identifier
425
+ * @param {object} data - The data to propagate
426
+ * @param {object} [options] - Propagation options
427
+ * @returns {Promise<object>} - Result with success count and errors
428
+ */
429
+ async propagate(holon, lens, data, options = {}) {
430
+ return Federation.propagate(this, holon, lens, data, options);
431
+ }
1637
432
 
1638
433
  /**
1639
434
  * Converts latitude and longitude to a holon identifier.
@@ -1643,7 +438,8 @@ class HoloSphere {
1643
438
  * @returns {Promise<string>} - The resulting holon identifier.
1644
439
  */
1645
440
  async getHolon(lat, lng, resolution) {
1646
- return h3.latLngToCell(lat, lng, resolution);
441
+ // Delegate to the external function
442
+ return Utils.getHolon(lat, lng, resolution);
1647
443
  }
1648
444
 
1649
445
  /**
@@ -1653,13 +449,8 @@ class HoloSphere {
1653
449
  * @returns {Array<string>} - List of holon identifiers.
1654
450
  */
1655
451
  getScalespace(lat, lng) {
1656
- let list = []
1657
- let cell = h3.latLngToCell(lat, lng, 14);
1658
- list.push(cell)
1659
- for (let i = 13; i >= 0; i--) {
1660
- list.push(h3.cellToParent(cell, i))
1661
- }
1662
- return list
452
+ // Delegate to the external function
453
+ return Utils.getScalespace(lat, lng);
1663
454
  }
1664
455
 
1665
456
  /**
@@ -1668,12 +459,8 @@ class HoloSphere {
1668
459
  * @returns {Array<string>} - List of holon identifiers.
1669
460
  */
1670
461
  getHolonScalespace(holon) {
1671
- let list = []
1672
- let res = h3.getResolution(holon)
1673
- for (let i = res; i >= 0; i--) {
1674
- list.push(h3.cellToParent(holon, i))
1675
- }
1676
- return list
462
+ // Delegate to the external function
463
+ return Utils.getHolonScalespace(holon);
1677
464
  }
1678
465
 
1679
466
  /**
@@ -1684,89 +471,8 @@ class HoloSphere {
1684
471
  * @returns {Promise<object>} - Subscription object with unsubscribe method
1685
472
  */
1686
473
  async subscribe(holon, lens, callback) {
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');
1693
- }
1694
-
1695
- const subscriptionId = this.generateId();
1696
-
1697
- try {
1698
- // Create the subscription
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
-
1705
- if (data) {
1706
- try {
1707
- let parsed = await this.parse(data);
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
- }
1729
- } catch (error) {
1730
- console.error('Error in subscribe:', error);
1731
- }
1732
- }
1733
- });
1734
-
1735
- // Store the subscription with its ID
1736
- this.subscriptions[subscriptionId] = {
1737
- id: subscriptionId,
1738
- holon,
1739
- lens,
1740
- active: true,
1741
- callback,
1742
- gunSubscription
1743
- };
1744
-
1745
- // Return an object with unsubscribe method
1746
- return {
1747
- unsubscribe: () => {
1748
- try {
1749
- // Mark as inactive first to prevent any new callbacks
1750
- if (this.subscriptions[subscriptionId]) {
1751
- this.subscriptions[subscriptionId].active = false;
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];
1761
- } catch (error) {
1762
- console.error('Error in unsubscribe:', error);
1763
- }
1764
- }
1765
- };
1766
- } catch (error) {
1767
- console.error('Error creating subscription:', error);
1768
- throw error;
1769
- }
474
+ // Delegate to the external function
475
+ return Utils.subscribe(this, holon, lens, callback);
1770
476
  }
1771
477
 
1772
478
  /**
@@ -1775,32 +481,14 @@ class HoloSphere {
1775
481
  * @private
1776
482
  */
1777
483
  notifySubscribers(data) {
1778
- if (!data || !data.holon || !data.lens) {
1779
- return;
1780
- }
1781
-
1782
- try {
1783
- Object.values(this.subscriptions).forEach(subscription => {
1784
- if (subscription.active &&
1785
- subscription.holon === data.holon &&
1786
- subscription.lens === data.lens) {
1787
- try {
1788
- if (subscription.callback && typeof subscription.callback === 'function') {
1789
- subscription.callback(data);
1790
- }
1791
- } catch (error) {
1792
- console.warn('Error in subscription callback:', error);
1793
- }
1794
- }
1795
- });
1796
- } catch (error) {
1797
- console.warn('Error notifying subscribers:', error);
1798
- }
484
+ // Delegate to the external function
485
+ return Utils.notifySubscribers(this, data);
1799
486
  }
1800
487
 
1801
488
  // Add ID generation method
1802
489
  generateId() {
1803
- return Date.now().toString(10) + Math.random().toString(2);
490
+ // Delegate to the external function
491
+ return Utils.generateId();
1804
492
  }
1805
493
 
1806
494
  // ================================ FEDERATION FUNCTIONS ================================
@@ -1809,13 +497,16 @@ class HoloSphere {
1809
497
  * Creates a federation relationship between two holons
1810
498
  * @param {string} holonId1 - The first holon ID
1811
499
  * @param {string} holonId2 - The second holon ID
1812
- * @param {string} password1 - Password for the first holon
500
+ * @param {string} [password1] - Optional password for the first holon
1813
501
  * @param {string} [password2] - Optional password for the second holon
1814
502
  * @param {boolean} [bidirectional=true] - Whether to set up bidirectional notifications automatically
503
+ * @param {object} [lensConfig] - Optional lens-specific configuration
504
+ * @param {string[]} [lensConfig.federate] - List of lenses to federate (default: all)
505
+ * @param {string[]} [lensConfig.notify] - List of lenses to notify (default: all)
1815
506
  * @returns {Promise<boolean>} - True if federation was created successfully
1816
507
  */
1817
- async federate(holonId1, holonId2, password1, password2 = null, bidirectional = true) {
1818
- return Federation.federate(this, holonId1, holonId2, password1, password2, bidirectional);
508
+ async federate(holonId1, holonId2, password1 = null, password2 = null, bidirectional = true, lensConfig = {}) {
509
+ return Federation.federate(this, holonId1, holonId2, password1, password2, bidirectional, lensConfig);
1819
510
  }
1820
511
 
1821
512
  /**
@@ -1841,6 +532,17 @@ class HoloSphere {
1841
532
  async getFederation(holonId, password = null) {
1842
533
  return Federation.getFederation(this, holonId, password);
1843
534
  }
535
+
536
+ /**
537
+ * Retrieves the lens-specific configuration for a federation link between two holons.
538
+ * @param {string} holonId - The ID of the source holon.
539
+ * @param {string} targetHolonId - The ID of the target holon in the federation link.
540
+ * @param {string} [password] - Optional password for the source holon.
541
+ * @returns {Promise<object|null>} - An object with 'federate' and 'notify' arrays, or null if not found.
542
+ */
543
+ async getFederatedConfig(holonId, targetHolonId, password = null) {
544
+ return Federation.getFederatedConfig(this, holonId, targetHolonId, password);
545
+ }
1844
546
 
1845
547
  /**
1846
548
  * Removes a federation relationship between holons
@@ -1936,111 +638,8 @@ class HoloSphere {
1936
638
  * @returns {Promise<void>}
1937
639
  */
1938
640
  async close() {
1939
- try {
1940
- if (this.gun) {
1941
- // Unsubscribe from all subscriptions
1942
- const subscriptionIds = Object.keys(this.subscriptions);
1943
- for (const id of subscriptionIds) {
1944
- try {
1945
- const subscription = this.subscriptions[id];
1946
- if (subscription && subscription.active) {
1947
- // Turn off the Gun subscription using the stored reference
1948
- if (subscription.gunSubscription) {
1949
- subscription.gunSubscription.off();
1950
- }
1951
-
1952
- // Mark as inactive
1953
- subscription.active = false;
1954
- }
1955
- } catch (error) {
1956
- console.warn(`Error cleaning up subscription ${id}:`, error);
1957
- }
1958
- }
1959
-
1960
- // Clear subscriptions
1961
- this.subscriptions = {};
1962
-
1963
- // Clear schema cache
1964
- this.clearSchemaCache();
1965
-
1966
- // Close Gun connections
1967
- if (this.gun.back) {
1968
- try {
1969
- // Clean up mesh connections
1970
- const mesh = this.gun.back('opt.mesh');
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 = {};
1988
- }
1989
- } catch (meshError) {
1990
- console.warn('Error cleaning up Gun mesh hear:', meshError);
1991
- }
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();
2019
- }
2020
- } catch (webError) {
2021
- console.warn('Error closing web server:', webError);
2022
- }
2023
- }
2024
- } catch (error) {
2025
- console.warn('Error accessing Gun mesh:', error);
2026
- }
2027
- }
2028
-
2029
- // Clear all Gun instance listeners
2030
- try {
2031
- this.gun.off();
2032
- } catch (error) {
2033
- console.warn('Error turning off Gun listeners:', error);
2034
- }
2035
-
2036
- // Wait a moment for cleanup to complete
2037
- await new Promise(resolve => setTimeout(resolve, 100));
2038
- }
2039
-
2040
- console.log('HoloSphere instance closed successfully');
2041
- } catch (error) {
2042
- console.error('Error closing HoloSphere instance:', error);
2043
- }
641
+ // Delegate to the external function
642
+ return Utils.close(this);
2044
643
  }
2045
644
 
2046
645
  /**
@@ -2050,8 +649,16 @@ class HoloSphere {
2050
649
  * @returns {string} - Namespaced username
2051
650
  */
2052
651
  userName(holonId) {
2053
- if (!holonId) return null;
2054
- return `${this.appname}:${holonId}`;
652
+ // Delegate to the external function
653
+ return Utils.userName(this, holonId);
654
+ }
655
+
656
+ /**
657
+ * Returns the current version of the HoloSphere library.
658
+ * @returns {string} The library version.
659
+ */
660
+ getVersion() {
661
+ return HOLOSPHERE_VERSION;
2055
662
  }
2056
663
  }
2057
664