holosphere 1.1.9 → 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.9');
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) {
@@ -54,6 +64,13 @@ class HoloSphere {
54
64
 
55
65
  // Initialize subscriptions
56
66
  this.subscriptions = {};
67
+
68
+ // Initialize schema cache
69
+ this.schemaCache = new Map();
70
+ }
71
+
72
+ getGun() {
73
+ return this.gun;
57
74
  }
58
75
 
59
76
  // ================================ SCHEMA FUNCTIONS ================================
@@ -65,76 +82,31 @@ class HoloSphere {
65
82
  * @returns {Promise} - Resolves when the schema is set.
66
83
  */
67
84
  async setSchema(lens, schema) {
68
- if (!lens || !schema) {
69
- throw new Error('setSchema: Missing required parameters');
70
- }
71
-
72
- // Basic schema validation
73
- if (!schema.type || typeof schema.type !== 'string') {
74
- throw new Error('setSchema: Schema must have a type field');
75
- }
76
-
77
- const metaSchema = {
78
- type: 'object',
79
- required: ['type', 'properties'],
80
- properties: {
81
- type: { type: 'string' },
82
- properties: {
83
- type: 'object',
84
- additionalProperties: {
85
- type: 'object',
86
- required: ['type'],
87
- properties: {
88
- type: { type: 'string' }
89
- }
90
- }
91
- },
92
- required: {
93
- type: 'array',
94
- items: { type: 'string' }
95
- }
96
- }
97
- };
98
-
99
- const valid = this.validator.validate(metaSchema, schema);
100
- if (!valid) {
101
- throw new Error(`Invalid schema structure: ${JSON.stringify(this.validator.errors)}`);
102
- }
103
-
104
- if (!schema.properties || typeof schema.properties !== 'object') {
105
- throw new Error('Schema must have properties in strict mode');
106
- }
107
-
108
- if (!schema.required || !Array.isArray(schema.required) || schema.required.length === 0) {
109
- throw new Error('Schema must have required fields in strict mode');
110
- }
111
-
112
- // Store schema in global table with lens as key
113
- await this.putGlobal('schemas', {
114
- id: lens,
115
- schema: schema,
116
- timestamp: Date.now()
117
- });
118
-
119
- return true;
85
+ // Delegate to the external function
86
+ return SchemaOps.setSchema(this, lens, schema);
120
87
  }
121
88
 
122
89
  /**
123
90
  * Retrieves the JSON schema for a specific lens.
124
91
  * @param {string} lens - The lens identifier.
92
+ * @param {object} [options] - Additional options
93
+ * @param {boolean} [options.useCache=true] - Whether to use the schema cache
94
+ * @param {number} [options.maxCacheAge=3600000] - Maximum cache age in milliseconds (default: 1 hour)
125
95
  * @returns {Promise<object|null>} - The retrieved schema or null if not found.
126
96
  */
127
- async getSchema(lens) {
128
- if (!lens) {
129
- throw new Error('getSchema: Missing lens parameter');
130
- }
131
-
132
- const schemaData = await this.getGlobal('schemas', lens);
133
- if (!schemaData || !schemaData.schema) {
134
- return null;
135
- }
97
+ async getSchema(lens, options = {}) {
98
+ // Delegate to the external function
99
+ return SchemaOps.getSchema(this, lens, options);
100
+ }
136
101
 
137
- return schemaData.schema;
102
+ /**
103
+ * Clears the schema cache or a specific schema from the cache.
104
+ * @param {string} [lens] - Optional lens to clear from cache. If not provided, clears entire cache.
105
+ * @returns {boolean} - Returns true if successful
106
+ */
107
+ clearSchemaCache(lens = null) {
108
+ // Delegate to the external function
109
+ return SchemaOps.clearSchemaCache(this, lens);
138
110
  }
139
111
 
140
112
  // ================================ CONTENT FUNCTIONS ================================
@@ -152,162 +124,8 @@ class HoloSphere {
152
124
  * @returns {Promise<boolean>} - Returns true if successful, false if there was an error
153
125
  */
154
126
  async put(holon, lens, data, password = null, options = {}) {
155
- if (!holon || !lens || !data) {
156
- throw new Error('put: Missing required parameters:', holon, lens, data );
157
- }
158
-
159
- if (!data.id) {
160
- data.id = this.generateId();
161
- }
162
-
163
- // Get and validate schema only in strict mode
164
- if (this.strict) {
165
- const schema = await this.getSchema(lens);
166
- if (!schema) {
167
- throw new Error('Schema required in strict mode');
168
- }
169
- const dataToValidate = JSON.parse(JSON.stringify(data));
170
- const valid = this.validator.validate(schema, dataToValidate);
171
-
172
- if (!valid) {
173
- const errorMsg = `Schema validation failed: ${JSON.stringify(this.validator.errors)}`;
174
- throw new Error(errorMsg);
175
- }
176
- }
177
-
178
- try {
179
- const user = this.gun.user();
180
-
181
- if (password) {
182
- try {
183
- await new Promise((resolve, reject) => {
184
- user.auth(this.userName(holon), password, (ack) => {
185
- if (ack.err) reject(new Error(ack.err));
186
- else resolve();
187
- });
188
- });
189
- } catch (loginError) {
190
- // If authentication fails, try to create user and then authenticate
191
- try {
192
- await new Promise((resolve, reject) => {
193
- user.create(this.userName(holon), password, (ack) => {
194
- if (ack.err) {
195
- // Don't reject if the user is already being created or already exists
196
- if (ack.err.includes('already being created') ||
197
- ack.err.includes('already created')) {
198
- console.warn(`User creation note: ${ack.err}, continuing...`);
199
- // Try to authenticate again
200
- user.auth(this.userName(holon), password, (authAck) => {
201
- if (authAck.err) {
202
- if (authAck.err.includes('already being created') ||
203
- authAck.err.includes('already created')) {
204
- console.warn(`Auth note: ${authAck.err}, continuing...`);
205
- resolve(); // Continue anyway
206
- } else {
207
- reject(new Error(authAck.err));
208
- }
209
- } else {
210
- resolve();
211
- }
212
- });
213
- } else {
214
- reject(new Error(ack.err));
215
- }
216
- } else {
217
- user.auth(this.userName(holon), password, (authAck) => {
218
- if (authAck.err) reject(new Error(authAck.err));
219
- else resolve();
220
- });
221
- }
222
- });
223
- });
224
- } catch (createError) {
225
- // Try one last authentication
226
- try {
227
- await new Promise((resolve, reject) => {
228
- setTimeout(() => {
229
- user.auth(this.userName(holon), password, (ack) => {
230
- if (ack.err) {
231
- // Continue even if auth fails at this point
232
- console.warn(`Final auth attempt note: ${ack.err}, continuing with limited functionality`);
233
- resolve();
234
- } else {
235
- resolve();
236
- }
237
- });
238
- }, 100); // Short delay before retry
239
- });
240
- } catch (finalAuthError) {
241
- console.warn('All authentication attempts failed, continuing with limited functionality');
242
- }
243
- }
244
- }
245
- }
246
-
247
- return new Promise((resolve, reject) => {
248
- try {
249
- const payload = JSON.stringify(data);
250
-
251
- const putCallback = async (ack) => {
252
- if (ack.err) {
253
- reject(new Error(ack.err));
254
- } else {
255
- this.notifySubscribers({
256
- holon,
257
- lens,
258
- ...data
259
- });
260
-
261
- // Auto-propagate to federation by default
262
- const shouldPropagate = options.autoPropagate !== false;
263
- let propagationResult = null;
264
-
265
- if (shouldPropagate) {
266
- try {
267
- // Default to using references
268
- const propagationOptions = {
269
- useReferences: true,
270
- ...options.propagationOptions
271
- };
272
-
273
- propagationResult = await this.propagate(
274
- holon,
275
- lens,
276
- data,
277
- propagationOptions
278
- );
279
-
280
- // Still resolve with true even if propagation had errors
281
- if (propagationResult.errors > 0) {
282
- console.warn('Auto-propagation had errors:', propagationResult);
283
- }
284
- } catch (propError) {
285
- console.warn('Error in auto-propagation:', propError);
286
- }
287
- }
288
-
289
- resolve({
290
- success: true,
291
- propagationResult
292
- });
293
- }
294
- };
295
-
296
- if (password) {
297
- // For private data, use the authenticated user's holon
298
- user.get('private').get(lens).get(data.id).put(payload, putCallback);
299
- } else {
300
- // For public data, use the regular path
301
- this.gun.get(this.appname).get(holon).get(lens).get(data.id).put(payload, putCallback);
302
- }
303
- } catch (error) {
304
- reject(error);
305
- }
306
- });
307
- } catch (error) {
308
- console.error('Error in put:', error);
309
- throw error;
310
- }
127
+ // Delegate to the external function
128
+ return ContentOps.put(this, holon, lens, data, password, options);
311
129
  }
312
130
 
313
131
  /**
@@ -321,215 +139,8 @@ class HoloSphere {
321
139
  * @returns {Promise<object|null>} - The retrieved content or null if not found.
322
140
  */
323
141
  async get(holon, lens, key, password = null, options = {}) {
324
- if (!holon || !lens || !key) {
325
- console.error('get: Missing required parameters:', { holon, lens, key });
326
- return null;
327
- }
328
-
329
- const { resolveReferences = true } = options;
330
-
331
- // Only check schema in strict mode
332
- let schema;
333
- if (this.strict) {
334
- schema = await this.getSchema(lens);
335
- if (!schema) {
336
- throw new Error('Schema required in strict mode');
337
- }
338
- }
339
-
340
- try {
341
- const user = this.gun.user();
342
-
343
- if (password) {
344
- try {
345
- await new Promise((resolve, reject) => {
346
- user.auth(this.userName(holon), password, (ack) => {
347
- if (ack.err) reject(new Error(ack.err));
348
- else resolve();
349
- });
350
- });
351
- } catch (loginError) {
352
- // If authentication fails, try to create user and then authenticate
353
- await new Promise((resolve, reject) => {
354
- user.create(this.userName(holon), password, (ack) => {
355
- if (ack.err) reject(new Error(ack.err));
356
- else {
357
- user.auth(this.userName(holon), password, (authAck) => {
358
- if (authAck.err) reject(new Error(authAck.err));
359
- else resolve();
360
- });
361
- }
362
- });
363
- });
364
- }
365
- }
366
-
367
- return new Promise((resolve) => {
368
- const handleData = async (data) => {
369
- if (!data) {
370
- resolve(null);
371
- return;
372
- }
373
-
374
- try {
375
- const parsed = await this.parse(data);
376
-
377
- if (!parsed) {
378
- resolve(null);
379
- return;
380
- }
381
-
382
- // Check if this is a reference that needs to be resolved
383
- if (resolveReferences !== false && parsed) {
384
- // Check if this is a simple reference (id + soul)
385
- if (parsed.soul) {
386
- console.log(`Resolving simple reference with soul: ${parsed.soul}`);
387
- try {
388
- // For direct soul resolution, we need to parse the soul to get the right path
389
- const soulParts = parsed.soul.split('/');
390
- if (soulParts.length >= 4) { // Expected format: appname/holon/lens/key
391
- const originHolon = soulParts[1];
392
- const originLens = soulParts[2];
393
- const originKey = soulParts[3];
394
-
395
- console.log(`Extracting from soul - holon: ${originHolon}, lens: ${originLens}, key: ${originKey}`);
396
-
397
- // Get original data using the extracted path components
398
- const originalData = await this.get(
399
- originHolon,
400
- originLens,
401
- originKey,
402
- null,
403
- { resolveReferences: false } // Prevent infinite recursion
404
- );
405
-
406
- if (originalData) {
407
- console.log(`Original data found through soul path resolution:`, originalData);
408
- resolve({
409
- ...originalData,
410
- _federation: {
411
- isReference: true,
412
- resolved: true,
413
- soul: parsed.soul,
414
- timestamp: Date.now()
415
- }
416
- });
417
- return;
418
- } else {
419
- console.warn(`Could not resolve reference: original data not found at extracted path`);
420
- }
421
- } else {
422
- console.warn(`Soul doesn't match expected format: ${parsed.soul}`);
423
- }
424
- } catch (error) {
425
- console.warn(`Error resolving reference by soul: ${error.message}`);
426
- }
427
- }
428
- // Legacy federation reference
429
- else if (parsed._federation && parsed._federation.isReference) {
430
- console.log(`Resolving legacy federation reference from ${parsed._federation.origin}`);
431
- try {
432
- const reference = parsed._federation;
433
- const originalData = await this.get(
434
- reference.origin,
435
- reference.lens,
436
- key,
437
- null,
438
- { resolveReferences: false } // Prevent infinite recursion
439
- );
440
-
441
- if (originalData) {
442
- return {
443
- ...originalData,
444
- _federation: {
445
- ...reference,
446
- resolved: true,
447
- timestamp: Date.now()
448
- }
449
- };
450
- } else {
451
- console.warn(`Could not resolve legacy reference: original data not found`);
452
- return parsed; // Return the reference if we can't resolve it
453
- }
454
- } catch (error) {
455
- console.warn(`Error resolving legacy reference: ${error.message}`);
456
- return parsed;
457
- }
458
- }
459
- }
460
-
461
- if (schema) {
462
- const valid = this.validator.validate(schema, parsed);
463
- if (!valid) {
464
- console.error('get: Invalid data according to schema:', this.validator.errors);
465
- if (this.strict) {
466
- resolve(null);
467
- return;
468
- }
469
- }
470
- }
471
-
472
- resolve(parsed);
473
- } catch (error) {
474
- console.error('Error parsing data:', error);
475
- resolve(null);
476
- }
477
- };
478
-
479
- if (password) {
480
- // For private data, use the authenticated user's holon
481
- user.get('private').get(lens).get(key).once(handleData);
482
- } else {
483
- // For public data, use the regular path
484
- this.gun.get(this.appname).get(holon).get(lens).get(key).once(handleData);
485
- }
486
- });
487
- } catch (error) {
488
- console.error('Error in get:', error);
489
- return null;
490
- }
491
- }
492
-
493
- /**
494
- * Retrieves a node directly using its soul path
495
- * @param {string} soul - The soul path of the node
496
- * @returns {Promise<any>} - The retrieved node or null if not found.
497
- */
498
- async getNodeBySoul(soul) {
499
- if (!soul) {
500
- throw new Error('getNodeBySoul: Missing soul parameter');
501
- }
502
-
503
- console.log(`getNodeBySoul: Accessing soul ${soul}`);
504
-
505
- return new Promise((resolve) => {
506
- try {
507
- const ref = this.getNodeRef(soul);
508
- ref.once((data) => {
509
- console.log(`getNodeBySoul: Retrieved data:`, data);
510
- if (!data) {
511
- resolve(null);
512
- return;
513
- }
514
- resolve(data); // Return the data directly
515
- });
516
- } catch (error) {
517
- console.error(`getNodeBySoul error:`, error);
518
- resolve(null);
519
- }
520
- });
521
- }
522
-
523
- /**
524
- * Propagates data to federated holons
525
- * @param {string} holon - The holon identifier
526
- * @param {string} lens - The lens identifier
527
- * @param {object} data - The data to propagate
528
- * @param {object} [options] - Propagation options
529
- * @returns {Promise<object>} - Result with success count and errors
530
- */
531
- async propagate(holon, lens, data, options = {}) {
532
- return Federation.propagate(this, holon, lens, data, options);
142
+ // Delegate to the external function
143
+ return ContentOps.get(this, holon, lens, key, password, options);
533
144
  }
534
145
 
535
146
  /**
@@ -540,75 +151,8 @@ class HoloSphere {
540
151
  * @returns {Promise<Array<object>>} - The retrieved content.
541
152
  */
542
153
  async getAll(holon, lens, password = null) {
543
- if (!holon || !lens) {
544
- throw new Error('getAll: Missing required parameters');
545
- }
546
-
547
- const schema = await this.getSchema(lens);
548
- if (!schema && this.strict) {
549
- throw new Error('getAll: Schema required in strict mode');
550
- }
551
-
552
- try {
553
- const user = this.gun.user();
554
-
555
- return new Promise((resolve) => {
556
- const output = new Map();
557
-
558
- const processData = async (data, key) => {
559
- if (!data || key === '_') return;
560
-
561
- try {
562
- const parsed = await this.parse(data);
563
- if (!parsed || !parsed.id) return;
564
-
565
- if (schema) {
566
- const valid = this.validator.validate(schema, parsed);
567
- if (valid || !this.strict) {
568
- output.set(parsed.id, parsed);
569
- }
570
- } else {
571
- output.set(parsed.id, parsed);
572
- }
573
- } catch (error) {
574
- console.error('Error processing data:', error);
575
- }
576
- };
577
-
578
- const handleData = async (data) => {
579
- if (!data) {
580
- resolve([]);
581
- return;
582
- }
583
-
584
- const initialPromises = [];
585
- Object.keys(data)
586
- .filter(key => key !== '_')
587
- .forEach(key => {
588
- initialPromises.push(processData(data[key], key));
589
- });
590
-
591
- try {
592
- await Promise.all(initialPromises);
593
- resolve(Array.from(output.values()));
594
- } catch (error) {
595
- console.error('Error in getAll:', error);
596
- resolve([]);
597
- }
598
- };
599
-
600
- if (password) {
601
- // For private data, use the authenticated user's holon
602
- user.get('private').get(lens).once(handleData);
603
- } else {
604
- // For public data, use the regular path
605
- this.gun.get(this.appname).get(holon).get(lens).once(handleData);
606
- }
607
- });
608
- } catch (error) {
609
- console.error('Error in getAll:', error);
610
- return [];
611
- }
154
+ // Delegate to the external function
155
+ return ContentOps.getAll(this, holon, lens, password);
612
156
  }
613
157
 
614
158
  /**
@@ -617,56 +161,8 @@ class HoloSphere {
617
161
  * @returns {Promise<object>} - The parsed data.
618
162
  */
619
163
  async parse(rawData) {
620
- let parsedData = {};
621
-
622
- if (!rawData) {
623
- throw new Error('parse: No data provided');
624
- }
625
-
626
- try {
627
-
628
- if (typeof rawData === 'string') {
629
- parsedData = await JSON.parse(rawData);
630
- }
631
-
632
-
633
- if (rawData.soul) {
634
- const data = await this.getNodeRef(rawData.soul).once();
635
- if (!data) {
636
- throw new Error('Referenced data not found');
637
- }
638
- return JSON.parse(data);
639
- }
640
-
641
-
642
- if (typeof rawData === 'object' && rawData !== null) {
643
- if (rawData._ && rawData._["#"]) {
644
- const pathParts = rawData._['#'].split('/');
645
- if (pathParts.length < 4) {
646
- throw new Error('Invalid reference format');
647
- }
648
- parsedData = await this.get(pathParts[1], pathParts[2], pathParts[3]);
649
- if (!parsedData) {
650
- throw new Error('Referenced data not found');
651
- }
652
- } else if (rawData._ && rawData._['>']) {
653
- const nodeValue = Object.values(rawData).find(v => typeof v !== 'object' && v !== '_');
654
- if (!nodeValue) {
655
- throw new Error('Invalid node data');
656
- }
657
- parsedData = JSON.parse(nodeValue);
658
- } else {
659
- parsedData = rawData;
660
- }
661
- }
662
-
663
- return parsedData;
664
-
665
- } catch (error) {
666
- console.log("Parsing not a JSON, returning raw data", rawData);
667
- return rawData;
668
- //throw new Error(`Parse error: ${error.message}`);
669
- }
164
+ // Delegate to the external function
165
+ return ContentOps.parse(this, rawData);
670
166
  }
671
167
 
672
168
  /**
@@ -678,40 +174,8 @@ class HoloSphere {
678
174
  * @returns {Promise<boolean>} - Returns true if successful
679
175
  */
680
176
  async delete(holon, lens, key, password = null) {
681
- if (!holon || !lens || !key) {
682
- throw new Error('delete: Missing required parameters');
683
- }
684
-
685
- try {
686
- // Get the appropriate holon
687
- const user = this.gun.user();
688
-
689
- // Delete data from holon
690
- return new Promise((resolve, reject) => {
691
- if (password) {
692
- // For private data, use the authenticated user's holon
693
- user.get('private').get(lens).get(key).put(null, ack => {
694
- if (ack.err) {
695
- reject(new Error(ack.err));
696
- } else {
697
- resolve(true);
698
- }
699
- });
700
- } else {
701
- // For public data, use the regular path
702
- this.gun.get(this.appname).get(holon).get(lens).get(key).put(null, ack => {
703
- if (ack.err) {
704
- reject(new Error(ack.err));
705
- } else {
706
- resolve(true);
707
- }
708
- });
709
- }
710
- });
711
- } catch (error) {
712
- console.error('Error in delete:', error);
713
- throw error;
714
- }
177
+ // Delegate to the external function (renamed to deleteFunc in module)
178
+ return ContentOps.deleteFunc(this, holon, lens, key, password);
715
179
  }
716
180
 
717
181
  /**
@@ -722,63 +186,8 @@ class HoloSphere {
722
186
  * @returns {Promise<boolean>} - Returns true if successful
723
187
  */
724
188
  async deleteAll(holon, lens, password = null) {
725
- if (!holon || !lens) {
726
- console.error('deleteAll: Missing holon or lens parameter');
727
- return false;
728
- }
729
-
730
- try {
731
- // Get the appropriate holon
732
- const user = this.gun.user();
733
-
734
- return new Promise((resolve) => {
735
- let deletionPromises = [];
736
-
737
- const dataPath = password ?
738
- user.get('private').get(lens) :
739
- this.gun.get(this.appname).get(holon).get(lens);
740
-
741
- // First get all the data to find keys to delete
742
- dataPath.once((data) => {
743
- if (!data) {
744
- resolve(true); // Nothing to delete
745
- return;
746
- }
747
-
748
- // Get all keys except Gun's metadata key '_'
749
- const keys = Object.keys(data).filter(key => key !== '_');
750
-
751
- // Create deletion promises for each key
752
- keys.forEach(key => {
753
- deletionPromises.push(
754
- new Promise((resolveDelete) => {
755
- const deletePath = password ?
756
- user.get('private').get(lens).get(key) :
757
- this.gun.get(this.appname).get(holon).get(lens).get(key);
758
-
759
- deletePath.put(null, ack => {
760
- resolveDelete(!!ack.ok); // Convert to boolean
761
- });
762
- })
763
- );
764
- });
765
-
766
- // Wait for all deletions to complete
767
- Promise.all(deletionPromises)
768
- .then(results => {
769
- const allSuccessful = results.every(result => result === true);
770
- resolve(allSuccessful);
771
- })
772
- .catch(error => {
773
- console.error('Error in deleteAll:', error);
774
- resolve(false);
775
- });
776
- });
777
- });
778
- } catch (error) {
779
- console.error('Error in deleteAll:', error);
780
- return false;
781
- }
189
+ // Delegate to the external function
190
+ return ContentOps.deleteAll(this, holon, lens, password);
782
191
  }
783
192
 
784
193
  // ================================ NODE FUNCTIONS ================================
@@ -791,27 +200,8 @@ class HoloSphere {
791
200
  * @param {object} data - The node to store.
792
201
  */
793
202
  async putNode(holon, lens, data) {
794
- if (!holon || !lens || !data) {
795
- throw new Error('putNode: Missing required parameters');
796
- }
797
-
798
- return new Promise((resolve, reject) => {
799
- try {
800
- this.gun.get(this.appname)
801
- .get(holon)
802
- .get(lens)
803
- .get('value') // Store at 'value' key
804
- .put(data.value, ack => { // Store the value directly
805
- if (ack.err) {
806
- reject(new Error(ack.err));
807
- } else {
808
- resolve(true);
809
- }
810
- });
811
- } catch (error) {
812
- reject(error);
813
- }
814
- });
203
+ // Delegate to the external function
204
+ return NodeOps.putNode(this, holon, lens, data);
815
205
  }
816
206
 
817
207
  /**
@@ -822,46 +212,28 @@ class HoloSphere {
822
212
  * @returns {Promise<any>} - The retrieved node or null if not found.
823
213
  */
824
214
  async getNode(holon, lens, key) {
825
- if (!holon || !lens || !key) {
826
- throw new Error('getNode: Missing required parameters');
827
- }
828
-
829
- return new Promise((resolve) => {
830
- this.gun.get(this.appname)
831
- .get(holon)
832
- .get(lens)
833
- .get(key)
834
- .once((data) => {
835
- if (!data) {
836
- resolve(null);
837
- return;
838
- }
839
- resolve(data); // Return the data directly
840
- });
841
- });
215
+ // Delegate to the external function
216
+ return NodeOps.getNode(this, holon, lens, key);
842
217
  }
843
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
+ */
844
224
  getNodeRef(soul) {
845
- if (typeof soul !== 'string' || !soul) {
846
- throw new Error('getNodeRef: Invalid soul parameter');
847
- }
848
-
849
- const parts = soul.split('/').filter(part => {
850
- if (!part.trim() || /[<>:"/\\|?*]/.test(part)) {
851
- throw new Error('getNodeRef: Invalid path segment');
852
- }
853
- return part.trim();
854
- });
855
-
856
- if (parts.length === 0) {
857
- throw new Error('getNodeRef: Invalid soul format');
858
- }
225
+ // Delegate to the external function
226
+ return NodeOps.getNodeRef(this, soul);
227
+ }
859
228
 
860
- let ref = this.gun.get(this.appname);
861
- parts.forEach(part => {
862
- ref = ref.get(part);
863
- });
864
- 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);
865
237
  }
866
238
 
867
239
  /**
@@ -872,22 +244,8 @@ class HoloSphere {
872
244
  * @returns {Promise<boolean>} - Returns true if successful
873
245
  */
874
246
  async deleteNode(holon, lens, key) {
875
- if (!holon || !lens || !key) {
876
- throw new Error('deleteNode: Missing required parameters');
877
- }
878
- return new Promise((resolve, reject) => {
879
- this.gun.get(this.appname)
880
- .get(holon)
881
- .get(lens)
882
- .get(key)
883
- .put(null, ack => {
884
- if (ack.err) {
885
- reject(new Error(ack.err));
886
- } else {
887
- resolve(true);
888
- }
889
- });
890
- });
247
+ // Delegate to the external function
248
+ return NodeOps.deleteNode(this, holon, lens, key);
891
249
  }
892
250
 
893
251
  // ================================ GLOBAL FUNCTIONS ================================
@@ -899,122 +257,8 @@ class HoloSphere {
899
257
  * @returns {Promise<void>}
900
258
  */
901
259
  async putGlobal(tableName, data, password = null) {
902
- try {
903
- if (!tableName || !data) {
904
- throw new Error('Table name and data are required');
905
- }
906
-
907
- const user = this.gun.user();
908
-
909
- if (password) {
910
- try {
911
- // Try to authenticate first
912
- await new Promise((resolve, reject) => {
913
- user.auth(this.userName(tableName), password, (ack) => {
914
- if (ack.err) {
915
- // Handle wrong username/password gracefully
916
- if (ack.err.includes('Wrong user or password') ||
917
- ack.err.includes('No user')) {
918
- console.warn(`Authentication failed for ${tableName}: ${ack.err}`);
919
- // Will try to create user next
920
- reject(new Error(ack.err));
921
- } else {
922
- reject(new Error(ack.err));
923
- }
924
- } else {
925
- resolve();
926
- }
927
- });
928
- });
929
- } catch (authError) {
930
- // If authentication fails, try to create user
931
- try {
932
- await new Promise((resolve, reject) => {
933
- user.create(this.userName(tableName), password, (ack) => {
934
- // Handle "User already created!" error gracefully
935
- if (ack.err && !ack.err.includes('already created')) {
936
- reject(new Error(ack.err));
937
- } else {
938
- // Whether user was created or already existed, try to authenticate
939
- user.auth(this.userName(tableName), password, (authAck) => {
940
- if (authAck.err) {
941
- console.warn(`Authentication failed after creation for ${tableName}: ${authAck.err}`);
942
- reject(new Error(authAck.err));
943
- } else {
944
- resolve();
945
- }
946
- });
947
- }
948
- });
949
- });
950
- } catch (createError) {
951
- // If both auth and create fail, try one last auth attempt
952
- await new Promise((resolve, reject) => {
953
- user.auth(this.userName(tableName), password, (ack) => {
954
- if (ack.err) {
955
- console.warn(`Final authentication attempt failed for ${tableName}: ${ack.err}`);
956
- // Continue with operation even if auth fails
957
- resolve();
958
- } else {
959
- resolve();
960
- }
961
- });
962
- });
963
- }
964
- }
965
- }
966
-
967
- return new Promise((resolve, reject) => {
968
- const payload = JSON.stringify(data);
969
-
970
- if (password) {
971
- // For private data, use the authenticated user's holon
972
- const path = user.get('private').get(tableName);
973
-
974
- if (data.id) {
975
- path.get(data.id).put(payload, ack => {
976
- if (ack.err) {
977
- reject(new Error(ack.err));
978
- } else {
979
- resolve();
980
- }
981
- });
982
- } else {
983
- path.put(payload, ack => {
984
- if (ack.err) {
985
- reject(new Error(ack.err));
986
- } else {
987
- resolve();
988
- }
989
- });
990
- }
991
- } else {
992
- // For public data, use the regular path
993
- const path = this.gun.get(this.appname).get(tableName);
994
-
995
- if (data.id) {
996
- path.get(data.id).put(payload, ack => {
997
- if (ack.err) {
998
- reject(new Error(ack.err));
999
- } else {
1000
- resolve();
1001
- }
1002
- });
1003
- } else {
1004
- path.put(payload, ack => {
1005
- if (ack.err) {
1006
- reject(new Error(ack.err));
1007
- } else {
1008
- resolve();
1009
- }
1010
- });
1011
- }
1012
- }
1013
- });
1014
- } catch (error) {
1015
- console.error('Error in putGlobal:', error);
1016
- throw error;
1017
- }
260
+ // Delegate to the external function
261
+ return GlobalOps.putGlobal(this, tableName, data, password);
1018
262
  }
1019
263
 
1020
264
  /**
@@ -1025,77 +269,8 @@ class HoloSphere {
1025
269
  * @returns {Promise<object|null>} - The parsed data for the key or null if not found.
1026
270
  */
1027
271
  async getGlobal(tableName, key, password = null) {
1028
- try {
1029
- const user = this.gun.user();
1030
-
1031
- if (password) {
1032
- try {
1033
- await new Promise((resolve, reject) => {
1034
- user.auth(this.userName(tableName), password, (ack) => {
1035
- if (ack.err) {
1036
- // Handle wrong username/password gracefully
1037
- if (ack.err.includes('Wrong user or password') ||
1038
- ack.err.includes('No user')) {
1039
- console.warn(`Authentication failed for ${tableName}: ${ack.err}`);
1040
- // Will try to create user next
1041
- reject(new Error(ack.err));
1042
- } else {
1043
- reject(new Error(ack.err));
1044
- }
1045
- } else {
1046
- resolve();
1047
- }
1048
- });
1049
- });
1050
- } catch (loginError) {
1051
- // If authentication fails, try to create user and then authenticate
1052
- await new Promise((resolve, reject) => {
1053
- user.create(this.userName(tableName), password, (ack) => {
1054
- // Handle "User already created!" error gracefully
1055
- if (ack.err && !ack.err.includes('already created')) {
1056
- reject(new Error(ack.err));
1057
- } else {
1058
- user.auth(this.userName(tableName), password, (authAck) => {
1059
- if (authAck.err) {
1060
- console.warn(`Authentication failed after creation for ${tableName}: ${authAck.err}`);
1061
- // Continue with operation even if auth fails
1062
- resolve();
1063
- } else {
1064
- resolve();
1065
- }
1066
- });
1067
- }
1068
- });
1069
- });
1070
- }
1071
- }
1072
-
1073
- return new Promise((resolve) => {
1074
- const handleData = (data) => {
1075
- if (!data) {
1076
- resolve(null);
1077
- return;
1078
- }
1079
- try {
1080
- const parsed = this.parse(data);
1081
- resolve(parsed);
1082
- } catch (e) {
1083
- resolve(null);
1084
- }
1085
- };
1086
-
1087
- if (password) {
1088
- // For private data, use the authenticated user's holon
1089
- user.get('private').get(tableName).get(key).once(handleData);
1090
- } else {
1091
- // For public data, use the regular path
1092
- this.gun.get(this.appname).get(tableName).get(key).once(handleData);
1093
- }
1094
- });
1095
- } catch (error) {
1096
- console.error('Error in getGlobal:', error);
1097
- return null;
1098
- }
272
+ // Delegate to the external function
273
+ return GlobalOps.getGlobal(this, tableName, key, password);
1099
274
  }
1100
275
 
1101
276
  /**
@@ -1105,75 +280,8 @@ class HoloSphere {
1105
280
  * @returns {Promise<Array<object>>} - The parsed data from the table as an array.
1106
281
  */
1107
282
  async getAllGlobal(tableName, password = null) {
1108
- if (!tableName) {
1109
- throw new Error('getAllGlobal: Missing table name parameter');
1110
- }
1111
-
1112
- try {
1113
- // Get the appropriate holon
1114
- const user = this.gun.user();
1115
-
1116
- return new Promise((resolve) => {
1117
- let output = [];
1118
- let isResolved = false;
1119
- let timeout = setTimeout(() => {
1120
- if (!isResolved) {
1121
- isResolved = true;
1122
- resolve(output);
1123
- }
1124
- }, 5000);
1125
-
1126
- const handleData = async (data) => {
1127
- if (!data) {
1128
- clearTimeout(timeout);
1129
- isResolved = true;
1130
- resolve([]);
1131
- return;
1132
- }
1133
-
1134
- const keys = Object.keys(data).filter(key => key !== '_');
1135
- const promises = keys.map(key =>
1136
- new Promise(async (resolveItem) => {
1137
- const itemPath = password ?
1138
- user.get('private').get(tableName).get(key) :
1139
- this.gun.get(this.appname).get(tableName).get(key);
1140
-
1141
- const itemData = await new Promise(resolveData => {
1142
- itemPath.once(resolveData);
1143
- });
1144
-
1145
- if (itemData) {
1146
- try {
1147
- const parsed = await this.parse(itemData);
1148
- if (parsed) output.push(parsed);
1149
- } catch (error) {
1150
- console.error('Error parsing data:', error);
1151
- }
1152
- }
1153
- resolveItem();
1154
- })
1155
- );
1156
-
1157
- await Promise.all(promises);
1158
- clearTimeout(timeout);
1159
- if (!isResolved) {
1160
- isResolved = true;
1161
- resolve(output);
1162
- }
1163
- };
1164
-
1165
- if (password) {
1166
- // For private data, use the authenticated user's holon
1167
- user.get('private').get(tableName).once(handleData);
1168
- } else {
1169
- // For public data, use the regular path
1170
- this.gun.get(this.appname).get(tableName).once(handleData);
1171
- }
1172
- });
1173
- } catch (error) {
1174
- console.error('Error in getAllGlobal:', error);
1175
- return [];
1176
- }
283
+ // Delegate to the external function
284
+ return GlobalOps.getAllGlobal(this, tableName, password);
1177
285
  }
1178
286
 
1179
287
  /**
@@ -1184,39 +292,8 @@ class HoloSphere {
1184
292
  * @returns {Promise<boolean>}
1185
293
  */
1186
294
  async deleteGlobal(tableName, key, password = null) {
1187
- if (!tableName || !key) {
1188
- throw new Error('deleteGlobal: Missing required parameters');
1189
- }
1190
-
1191
- try {
1192
- // Get the appropriate holon
1193
- const user = this.gun.user();
1194
-
1195
- return new Promise((resolve, reject) => {
1196
- if (password) {
1197
- // For private data, use the authenticated user's holon
1198
- user.get('private').get(tableName).get(key).put(null, ack => {
1199
- if (ack.err) {
1200
- reject(new Error(ack.err));
1201
- } else {
1202
- resolve(true);
1203
- }
1204
- });
1205
- } else {
1206
- // For public data, use the regular path
1207
- this.gun.get(this.appname).get(tableName).get(key).put(null, ack => {
1208
- if (ack.err) {
1209
- reject(new Error(ack.err));
1210
- } else {
1211
- resolve(true);
1212
- }
1213
- });
1214
- }
1215
- });
1216
- } catch (error) {
1217
- console.error('Error in deleteGlobal:', error);
1218
- throw error;
1219
- }
295
+ // Delegate to the external function
296
+ return GlobalOps.deleteGlobal(this, tableName, key, password);
1220
297
  }
1221
298
 
1222
299
  /**
@@ -1226,68 +303,55 @@ class HoloSphere {
1226
303
  * @returns {Promise<boolean>}
1227
304
  */
1228
305
  async deleteAllGlobal(tableName, password = null) {
1229
- if (!tableName) {
1230
- throw new Error('deleteAllGlobal: Missing table name parameter');
1231
- }
1232
-
1233
- try {
1234
- // Get the appropriate holon
1235
- const user = this.gun.user();
1236
-
1237
- return new Promise((resolve, reject) => {
1238
- try {
1239
- const deletions = new Set();
1240
- let timeout = setTimeout(() => {
1241
- if (deletions.size === 0) {
1242
- resolve(true); // No data to delete
1243
- }
1244
- }, 5000);
1245
-
1246
- const dataPath = password ?
1247
- user.get('private').get(tableName) :
1248
- this.gun.get(this.appname).get(tableName);
1249
-
1250
- dataPath.once(async (data) => {
1251
- if (!data) {
1252
- clearTimeout(timeout);
1253
- resolve(true);
1254
- return;
1255
- }
1256
-
1257
- const keys = Object.keys(data).filter(key => key !== '_');
1258
- const promises = keys.map(key =>
1259
- new Promise((resolveDelete) => {
1260
- const deletePath = password ?
1261
- user.get('private').get(tableName).get(key) :
1262
- this.gun.get(this.appname).get(tableName).get(key);
306
+ // Delegate to the external function
307
+ return GlobalOps.deleteAllGlobal(this, tableName, password);
308
+ }
1263
309
 
1264
- deletePath.put(null, ack => {
1265
- if (ack.err) {
1266
- console.error(`Failed to delete ${key}:`, ack.err);
1267
- }
1268
- resolveDelete();
1269
- });
1270
- })
1271
- );
310
+ // ================================ REFERENCE FUNCTIONS ================================
1272
311
 
1273
- try {
1274
- await Promise.all(promises);
1275
- // Finally delete the table itself
1276
- dataPath.put(null);
1277
- clearTimeout(timeout);
1278
- resolve(true);
1279
- } catch (error) {
1280
- reject(error);
1281
- }
1282
- });
1283
- } catch (error) {
1284
- reject(error);
1285
- }
1286
- });
1287
- } catch (error) {
1288
- console.error('Error in deleteAllGlobal:', error);
1289
- throw error;
1290
- }
312
+ /**
313
+ * Creates a soul hologram object for a data item
314
+ * @param {string} holon - The holon where the original data is stored
315
+ * @param {string} lens - The lens where the original data is stored
316
+ * @param {object} data - The data to create a hologram for
317
+ * @returns {object} - A hologram object with id and soul
318
+ */
319
+ createHologram(holon, lens, data) {
320
+ // Delegate to the external function
321
+ return HologramOps.createHologram(this, holon, lens, data);
322
+ }
323
+
324
+ /**
325
+ * Parses a soul path into its components
326
+ * @param {string} soul - The soul path to parse
327
+ * @returns {object|null} - The parsed components or null if invalid format
328
+ */
329
+ parseSoulPath(soul) {
330
+ // Delegate to the external function (doesn't need instance)
331
+ return HologramOps.parseSoulPath(soul);
332
+ }
333
+
334
+ /**
335
+ * Checks if an object is a hologram
336
+ * @param {object} data - The data to check
337
+ * @returns {boolean} - True if the object is a hologram
338
+ */
339
+ isHologram(data) {
340
+ // Delegate to the external function (doesn't need instance)
341
+ return HologramOps.isHologram(data);
342
+ }
343
+
344
+ /**
345
+ * Resolves a hologram to its actual data
346
+ * @param {object} hologram - The hologram to resolve
347
+ * @param {object} [options] - Optional parameters
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.
351
+ */
352
+ async resolveHologram(hologram, options = {}) {
353
+ // Delegate to the external function
354
+ return HologramOps.resolveHologram(this, hologram, options);
1291
355
  }
1292
356
 
1293
357
  // ================================ COMPUTE FUNCTIONS ================================
@@ -1300,26 +364,8 @@ class HoloSphere {
1300
364
  * @param {string} [password] - Optional password for private holons
1301
365
  */
1302
366
  async computeHierarchy(holon, lens, options, maxLevels = 15, password = null) {
1303
- let currentHolon = holon;
1304
- let currentRes = h3.getResolution(currentHolon);
1305
- const results = [];
1306
-
1307
- while (currentRes > 0 && maxLevels > 0) {
1308
- try {
1309
- const result = await this.compute(currentHolon, lens, options, password);
1310
- if (result) {
1311
- results.push(result);
1312
- }
1313
- currentHolon = h3.cellToParent(currentHolon, currentRes - 1);
1314
- currentRes--;
1315
- maxLevels--;
1316
- } catch (error) {
1317
- console.error('Error in compute hierarchy:', error);
1318
- break;
1319
- }
1320
- }
1321
-
1322
- return results;
367
+ // Delegate to the external function
368
+ return ComputeOps.computeHierarchy(this, holon, lens, options, maxLevels, password);
1323
369
  }
1324
370
 
1325
371
  /**
@@ -1334,132 +380,8 @@ class HoloSphere {
1334
380
  * @throws {Error} If parameters are invalid or missing
1335
381
  */
1336
382
  async compute(holon, lens, options, password = null) {
1337
- // Validate required parameters
1338
- if (!holon || !lens) {
1339
- throw new Error('compute: Missing required parameters');
1340
- }
1341
-
1342
- // Convert string operation to options object
1343
- if (typeof options === 'string') {
1344
- options = { operation: options };
1345
- }
1346
-
1347
- if (!options?.operation) {
1348
- throw new Error('compute: Missing required parameters');
1349
- }
1350
-
1351
- // Validate holon format and resolution first
1352
- let res;
1353
- try {
1354
- res = h3.getResolution(holon);
1355
- } catch (error) {
1356
- throw new Error('compute: Invalid holon format');
1357
- }
1358
-
1359
- if (res < 1 || res > 15) {
1360
- throw new Error('compute: Invalid holon resolution (must be between 1 and 15)');
1361
- }
1362
-
1363
- const {
1364
- operation,
1365
- fields = [],
1366
- targetField,
1367
- depth,
1368
- maxDepth
1369
- } = options;
1370
-
1371
- // Validate depth parameters if provided
1372
- if (depth !== undefined && depth < 0) {
1373
- throw new Error('compute: Invalid depth parameter');
1374
- }
1375
-
1376
- if (maxDepth !== undefined && (maxDepth < 1 || maxDepth > 15)) {
1377
- throw new Error('compute: Invalid maxDepth parameter (must be between 1 and 15)');
1378
- }
1379
-
1380
- // Validate operation
1381
- const validOperations = ['summarize', 'aggregate', 'concatenate'];
1382
- if (!validOperations.includes(operation)) {
1383
- throw new Error(`compute: Invalid operation (must be one of ${validOperations.join(', ')})`);
1384
- }
1385
-
1386
- const parent = h3.cellToParent(holon, res - 1);
1387
- const siblings = h3.cellToChildren(parent, res);
1388
-
1389
- // Collect all content from siblings
1390
- const contents = await Promise.all(
1391
- siblings.map(sibling => this.getAll(sibling, lens, password))
1392
- );
1393
-
1394
- const flatContents = contents.flat().filter(Boolean);
1395
-
1396
- if (flatContents.length > 0) {
1397
- try {
1398
- let computed;
1399
- switch (operation) {
1400
- case 'summarize':
1401
- // For summarize, concatenate specified fields or use entire content
1402
- const textToSummarize = fields.length > 0
1403
- ? flatContents.map(item => fields.map(field => item[field]).filter(Boolean).join('\n')).join('\n')
1404
- : JSON.stringify(flatContents);
1405
- computed = await this.summarize(textToSummarize);
1406
- break;
1407
-
1408
- case 'aggregate':
1409
- // For aggregate, sum numeric fields
1410
- computed = fields.reduce((acc, field) => {
1411
- acc[field] = flatContents.reduce((sum, item) => {
1412
- return sum + (Number(item[field]) || 0);
1413
- }, 0);
1414
- return acc;
1415
- }, {});
1416
- break;
1417
-
1418
- case 'concatenate':
1419
- // For concatenate, combine arrays or strings
1420
- computed = fields.reduce((acc, field) => {
1421
- acc[field] = flatContents.reduce((combined, item) => {
1422
- const value = item[field];
1423
- if (Array.isArray(value)) {
1424
- return [...combined, ...value];
1425
- } else if (value) {
1426
- return [...combined, value];
1427
- }
1428
- return combined;
1429
- }, []);
1430
- // Remove duplicates if array
1431
- acc[field] = Array.from(new Set(acc[field]));
1432
- return acc;
1433
- }, {});
1434
- break;
1435
- }
1436
-
1437
- if (computed) {
1438
- const resultId = `${parent}_${operation}`;
1439
- const result = {
1440
- id: resultId,
1441
- timestamp: Date.now()
1442
- };
1443
-
1444
- // Store result in targetField if specified, otherwise at root level
1445
- if (targetField) {
1446
- result[targetField] = computed;
1447
- } else if (typeof computed === 'object') {
1448
- Object.assign(result, computed);
1449
- } else {
1450
- result.value = computed;
1451
- }
1452
-
1453
- await this.put(parent, lens, result, password);
1454
- return result;
1455
- }
1456
- } catch (error) {
1457
- console.warn('Error in compute operation:', error);
1458
- throw error;
1459
- }
1460
- }
1461
-
1462
- return null;
383
+ // Delegate to the external function
384
+ return ComputeOps.compute(this, holon, lens, options, password);
1463
385
  }
1464
386
 
1465
387
  /**
@@ -1468,37 +390,12 @@ class HoloSphere {
1468
390
  * @returns {Promise<string>} - The summarized text.
1469
391
  */
1470
392
  async summarize(history) {
1471
- if (!this.openai) {
1472
- return 'OpenAI not initialized, please specify the API key in the constructor.'
1473
- }
1474
-
1475
- try {
1476
- const response = await this.openai.chat.completions.create({
1477
- model: "gpt-4",
1478
- messages: [
1479
- {
1480
- role: "system",
1481
- content: "You are a helpful assistant that summarizes text concisely while preserving key information. Keep summaries clear and focused."
1482
- },
1483
- {
1484
- role: "user",
1485
- content: history
1486
- }
1487
- ],
1488
- temperature: 0.7,
1489
- max_tokens: 500
1490
- });
1491
-
1492
- return response.choices[0].message.content.trim();
1493
- } catch (error) {
1494
- console.error('Error in summarize:', error);
1495
- throw new Error('Failed to generate summary');
1496
- }
393
+ // Delegate to the external function
394
+ return ComputeOps.summarize(this, history);
1497
395
  }
1498
396
 
1499
397
  /**
1500
- * Upcasts content to parent holonagons recursively using federation and soul references.
1501
- * This is the modern implementation that uses federation references instead of duplicating data.
398
+ * Upcasts content to parent holonagons recursively using references.
1502
399
  * @param {string} holon - The current holon identifier.
1503
400
  * @param {string} lens - The lens under which to upcast.
1504
401
  * @param {object} content - The content to upcast.
@@ -1506,41 +403,8 @@ class HoloSphere {
1506
403
  * @returns {Promise<object>} - The original content.
1507
404
  */
1508
405
  async upcast(holon, lens, content, maxLevels = 15) {
1509
- // Store the actual content at the original resolution
1510
- await this.put(holon, lens, content);
1511
-
1512
- let res = h3.getResolution(holon);
1513
-
1514
- // If already at the highest level (res 0) or reached max levels, we're done
1515
- if (res === 0 || maxLevels <= 0) {
1516
- return content;
1517
- }
1518
-
1519
- // Get the parent cell
1520
- let parent = h3.cellToParent(holon, res - 1);
1521
-
1522
- // Create federation relationship if it doesn't exist
1523
- await this.federate(holon, parent);
1524
-
1525
- // Create a soul reference to store in the parent
1526
- const soul = `${this.appname}/${holon}/${lens}/${content.id}`;
1527
- const reference = {
1528
- id: content.id,
1529
- soul: soul
1530
- };
1531
-
1532
- // Store the reference in the parent cell
1533
- // We use { autoPropagate: false } to prevent circular propagation
1534
- await this.put(parent, lens, reference, null, {
1535
- autoPropagate: false
1536
- });
1537
-
1538
- // Continue upcasting with the parent
1539
- if (res > 1 && maxLevels > 1) {
1540
- return this.upcast(parent, lens, reference, maxLevels - 1);
1541
- }
1542
-
1543
- return content;
406
+ // Delegate to the external function
407
+ return ComputeOps.upcast(this, holon, lens, content, maxLevels);
1544
408
  }
1545
409
 
1546
410
  /**
@@ -1550,19 +414,21 @@ class HoloSphere {
1550
414
  * @returns {Promise<object>} - The updated parent information.
1551
415
  */
1552
416
  async updateParent(id, report) {
1553
- let cellinfo = await this.getCellInfo(id)
1554
- let res = h3.getResolution(id)
1555
- let parent = h3.cellToParent(id, res - 1)
1556
- let parentInfo = await this.getCellInfo(parent)
1557
- parentInfo.wisdom[id] = report
1558
- //update summary
1559
- let summary = await this.summarize(Object.values(parentInfo.wisdom).join('\n'))
1560
- parentInfo.summary = summary
1561
-
1562
- await this.db.put('cell', parentInfo)
1563
- return parentInfo
417
+ // Delegate to the external function
418
+ return ComputeOps.updateParent(this, id, report);
1564
419
  }
1565
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
+ }
1566
432
 
1567
433
  /**
1568
434
  * Converts latitude and longitude to a holon identifier.
@@ -1572,7 +438,8 @@ class HoloSphere {
1572
438
  * @returns {Promise<string>} - The resulting holon identifier.
1573
439
  */
1574
440
  async getHolon(lat, lng, resolution) {
1575
- return h3.latLngToCell(lat, lng, resolution);
441
+ // Delegate to the external function
442
+ return Utils.getHolon(lat, lng, resolution);
1576
443
  }
1577
444
 
1578
445
  /**
@@ -1582,13 +449,8 @@ class HoloSphere {
1582
449
  * @returns {Array<string>} - List of holon identifiers.
1583
450
  */
1584
451
  getScalespace(lat, lng) {
1585
- let list = []
1586
- let cell = h3.latLngToCell(lat, lng, 14);
1587
- list.push(cell)
1588
- for (let i = 13; i >= 0; i--) {
1589
- list.push(h3.cellToParent(cell, i))
1590
- }
1591
- return list
452
+ // Delegate to the external function
453
+ return Utils.getScalespace(lat, lng);
1592
454
  }
1593
455
 
1594
456
  /**
@@ -1597,12 +459,8 @@ class HoloSphere {
1597
459
  * @returns {Array<string>} - List of holon identifiers.
1598
460
  */
1599
461
  getHolonScalespace(holon) {
1600
- let list = []
1601
- let res = h3.getResolution(holon)
1602
- for (let i = res; i >= 0; i--) {
1603
- list.push(h3.cellToParent(holon, i))
1604
- }
1605
- return list
462
+ // Delegate to the external function
463
+ return Utils.getHolonScalespace(holon);
1606
464
  }
1607
465
 
1608
466
  /**
@@ -1613,90 +471,24 @@ class HoloSphere {
1613
471
  * @returns {Promise<object>} - Subscription object with unsubscribe method
1614
472
  */
1615
473
  async subscribe(holon, lens, callback) {
1616
- if (!holon || !lens || typeof callback !== 'function') {
1617
- throw new Error('subscribe: Missing required parameters');
1618
- }
1619
-
1620
- const subscriptionId = this.generateId();
1621
-
1622
- try {
1623
- // Create the subscription
1624
- const gunSubscription = this.gun.get(this.appname).get(holon).get(lens).map().on(async (data, key) => {
1625
- if (data) {
1626
- try {
1627
- let parsed = await this.parse(data);
1628
- callback(parsed, key);
1629
- } catch (error) {
1630
- console.error('Error in subscribe:', error);
1631
- }
1632
- }
1633
- });
1634
-
1635
- // Store the subscription with its ID
1636
- this.subscriptions[subscriptionId] = {
1637
- id: subscriptionId,
1638
- holon,
1639
- lens,
1640
- active: true,
1641
- gunSubscription
1642
- };
1643
-
1644
- // Return an object with unsubscribe method
1645
- return {
1646
- unsubscribe: () => {
1647
- try {
1648
- // Turn off the Gun subscription
1649
- this.gun.get(this.appname).get(holon).get(lens).map().off();
1650
-
1651
- // Mark as inactive and remove from subscriptions
1652
- if (this.subscriptions[subscriptionId]) {
1653
- this.subscriptions[subscriptionId].active = false;
1654
- delete this.subscriptions[subscriptionId];
1655
- }
1656
- } catch (error) {
1657
- console.error('Error in unsubscribe:', error);
1658
- }
1659
- }
1660
- };
1661
- } catch (error) {
1662
- console.error('Error creating subscription:', error);
1663
- throw error;
1664
- }
474
+ // Delegate to the external function
475
+ return Utils.subscribe(this, holon, lens, callback);
1665
476
  }
1666
477
 
1667
-
1668
478
  /**
1669
479
  * Notifies subscribers about data changes
1670
480
  * @param {object} data - The data to notify about
1671
481
  * @private
1672
482
  */
1673
483
  notifySubscribers(data) {
1674
- if (!data || !data.holon || !data.lens) {
1675
- return;
1676
- }
1677
-
1678
- try {
1679
- Object.values(this.subscriptions).forEach(subscription => {
1680
- if (subscription.active &&
1681
- subscription.holon === data.holon &&
1682
- subscription.lens === data.lens) {
1683
- try {
1684
- if (subscription.callback && typeof subscription.callback === 'function') {
1685
- subscription.callback(data);
1686
- }
1687
- } catch (error) {
1688
- console.warn('Error in subscription callback:', error);
1689
- }
1690
- }
1691
- });
1692
- } catch (error) {
1693
- console.warn('Error notifying subscribers:', error);
1694
- }
484
+ // Delegate to the external function
485
+ return Utils.notifySubscribers(this, data);
1695
486
  }
1696
487
 
1697
488
  // Add ID generation method
1698
489
  generateId() {
1699
- return Date.now().toString(10) + Math.random().toString(2);
490
+ // Delegate to the external function
491
+ return Utils.generateId();
1700
492
  }
1701
493
 
1702
494
  // ================================ FEDERATION FUNCTIONS ================================
@@ -1705,13 +497,16 @@ class HoloSphere {
1705
497
  * Creates a federation relationship between two holons
1706
498
  * @param {string} holonId1 - The first holon ID
1707
499
  * @param {string} holonId2 - The second holon ID
1708
- * @param {string} password1 - Password for the first holon
500
+ * @param {string} [password1] - Optional password for the first holon
1709
501
  * @param {string} [password2] - Optional password for the second holon
1710
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)
1711
506
  * @returns {Promise<boolean>} - True if federation was created successfully
1712
507
  */
1713
- async federate(holonId1, holonId2, password1, password2 = null, bidirectional = true) {
1714
- 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);
1715
510
  }
1716
511
 
1717
512
  /**
@@ -1737,6 +532,17 @@ class HoloSphere {
1737
532
  async getFederation(holonId, password = null) {
1738
533
  return Federation.getFederation(this, holonId, password);
1739
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
+ }
1740
546
 
1741
547
  /**
1742
548
  * Removes a federation relationship between holons
@@ -1832,75 +638,8 @@ class HoloSphere {
1832
638
  * @returns {Promise<void>}
1833
639
  */
1834
640
  async close() {
1835
- try {
1836
- if (this.gun) {
1837
- // Unsubscribe from all subscriptions
1838
- const subscriptionIds = Object.keys(this.subscriptions);
1839
- for (const id of subscriptionIds) {
1840
- try {
1841
- const subscription = this.subscriptions[id];
1842
- if (subscription && subscription.active) {
1843
- // Turn off the Gun subscription
1844
- this.gun.get(this.appname)
1845
- .get(subscription.holon)
1846
- .get(subscription.lens)
1847
- .map().off();
1848
-
1849
- // Mark as inactive
1850
- subscription.active = false;
1851
- }
1852
- } catch (error) {
1853
- console.warn(`Error cleaning up subscription ${id}:`, error);
1854
- }
1855
- }
1856
-
1857
- // Clear subscriptions
1858
- this.subscriptions = {};
1859
-
1860
- // Close Gun connections
1861
- if (this.gun.back) {
1862
- try {
1863
- const mesh = this.gun.back('opt.mesh');
1864
- if (mesh && mesh.hear) {
1865
- try {
1866
- // Safely clear mesh.hear without modifying function properties
1867
- const hearKeys = Object.keys(mesh.hear);
1868
- for (const key of hearKeys) {
1869
- // Check if it's an array before trying to clear it
1870
- if (Array.isArray(mesh.hear[key])) {
1871
- mesh.hear[key] = [];
1872
- }
1873
- }
1874
-
1875
- // Create a new empty object for mesh.hear
1876
- // Only if mesh.hear is not a function
1877
- if (typeof mesh.hear !== 'function') {
1878
- mesh.hear = {};
1879
- }
1880
- } catch (meshError) {
1881
- console.warn('Error cleaning up Gun mesh hear:', meshError);
1882
- }
1883
- }
1884
- } catch (error) {
1885
- console.warn('Error accessing Gun mesh:', error);
1886
- }
1887
- }
1888
-
1889
- // Clear all Gun instance listeners
1890
- try {
1891
- this.gun.off();
1892
- } catch (error) {
1893
- console.warn('Error turning off Gun listeners:', error);
1894
- }
1895
-
1896
- // Wait a moment for cleanup to complete
1897
- await new Promise(resolve => setTimeout(resolve, 100));
1898
- }
1899
-
1900
- console.log('HoloSphere instance closed successfully');
1901
- } catch (error) {
1902
- console.error('Error closing HoloSphere instance:', error);
1903
- }
641
+ // Delegate to the external function
642
+ return Utils.close(this);
1904
643
  }
1905
644
 
1906
645
  /**
@@ -1910,8 +649,16 @@ class HoloSphere {
1910
649
  * @returns {string} - Namespaced username
1911
650
  */
1912
651
  userName(holonId) {
1913
- if (!holonId) return null;
1914
- 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;
1915
662
  }
1916
663
  }
1917
664