holosphere 1.1.1 → 1.1.2

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
@@ -20,7 +20,7 @@ class HoloSphere {
20
20
  validateSchema: true // Always validate schemas
21
21
  });
22
22
  this.gun = Gun({
23
- peers: ['https://gun.holons.io', 'https://59.src.eco/gun'],
23
+ peers: ['https://gun.holons.io/gun', 'https://59.src.eco/gun'],
24
24
  axe: false,
25
25
  // uuid: (content) => { // generate a unique id for each node
26
26
  // console.log('uuid', content);
@@ -32,6 +32,8 @@ class HoloSphere {
32
32
  apiKey: openaikey,
33
33
  });
34
34
  }
35
+
36
+ this.subscriptions = new Map(); // Track active subscriptions
35
37
  }
36
38
 
37
39
  // ================================ SCHEMA FUNCTIONS ================================
@@ -44,64 +46,52 @@ class HoloSphere {
44
46
  */
45
47
  async setSchema(lens, schema) {
46
48
  if (!lens || !schema) {
47
- console.error('setSchema: Missing required parameters');
48
- return false;
49
+ throw new Error('setSchema: Missing required parameters');
49
50
  }
50
51
 
51
- // Basic schema validation - check for required fields
52
+ // Basic schema validation
52
53
  if (!schema.type || typeof schema.type !== 'string') {
53
- console.error('setSchema: Schema must have a type field');
54
- return false;
54
+ throw new Error('setSchema: Schema must have a type field');
55
55
  }
56
56
 
57
57
  if (this.strict) {
58
- try {
59
- // Validate schema against JSON Schema meta-schema
60
- const metaSchema = {
61
- type: 'object',
62
- required: ['type', 'properties'],
58
+ const metaSchema = {
59
+ type: 'object',
60
+ required: ['type', 'properties'],
61
+ properties: {
62
+ type: { type: 'string' },
63
63
  properties: {
64
- type: { type: 'string' },
65
- properties: {
64
+ type: 'object',
65
+ additionalProperties: {
66
66
  type: 'object',
67
- additionalProperties: {
68
- type: 'object',
69
- required: ['type'],
70
- properties: {
71
- type: { type: 'string' }
72
- }
67
+ required: ['type'],
68
+ properties: {
69
+ type: { type: 'string' }
73
70
  }
74
- },
75
- required: {
76
- type: 'array',
77
- items: { type: 'string' }
78
71
  }
72
+ },
73
+ required: {
74
+ type: 'array',
75
+ items: { type: 'string' }
79
76
  }
80
- };
81
-
82
- const valid = this.validator.validate(metaSchema, schema);
83
- if (!valid) {
84
- console.error('setSchema: Invalid schema structure:', this.validator.errors);
85
- return false;
86
77
  }
78
+ };
87
79
 
88
- // Additional strict mode checks
89
- if (!schema.properties || typeof schema.properties !== 'object') {
90
- console.error('setSchema: Schema must have properties in strict mode');
91
- return false;
92
- }
80
+ const valid = this.validator.validate(metaSchema, schema);
81
+ if (!valid) {
82
+ throw new Error(`Invalid schema structure: ${JSON.stringify(this.validator.errors)}`);
83
+ }
93
84
 
94
- if (!schema.required || !Array.isArray(schema.required) || schema.required.length === 0) {
95
- console.error('setSchema: Schema must have required fields in strict mode');
96
- return false;
97
- }
98
- } catch (error) {
99
- console.error('setSchema: Schema validation error:', error);
100
- return false;
85
+ if (!schema.properties || typeof schema.properties !== 'object') {
86
+ throw new Error('Schema must have properties in strict mode');
87
+ }
88
+
89
+ if (!schema.required || !Array.isArray(schema.required) || schema.required.length === 0) {
90
+ throw new Error('Schema must have required fields in strict mode');
101
91
  }
102
92
  }
103
93
 
104
- return new Promise((resolve) => {
94
+ return new Promise((resolve, reject) => {
105
95
  try {
106
96
  const schemaString = JSON.stringify(schema);
107
97
  this.gun.get(this.appname)
@@ -109,16 +99,13 @@ class HoloSphere {
109
99
  .get('schema')
110
100
  .put(schemaString, ack => {
111
101
  if (ack.err) {
112
- console.error('Failed to add schema:', ack.err);
113
- resolve(false);
102
+ reject(new Error(ack.err));
114
103
  } else {
115
- console.log('Schema added successfully for lens:', lens);
116
104
  resolve(true);
117
105
  }
118
106
  });
119
107
  } catch (error) {
120
- console.error('setSchema: Error stringifying schema:', error);
121
- resolve(false);
108
+ reject(error);
122
109
  }
123
110
  });
124
111
  }
@@ -130,8 +117,7 @@ class HoloSphere {
130
117
  */
131
118
  async getSchema(lens) {
132
119
  if (!lens) {
133
- console.error('getSchema: Missing lens parameter');
134
- return null;
120
+ throw new Error('getSchema: Missing lens parameter');
135
121
  }
136
122
 
137
123
  return new Promise((resolve) => {
@@ -145,19 +131,15 @@ class HoloSphere {
145
131
  }
146
132
 
147
133
  try {
148
- // If data is already a string, parse it
149
- if (typeof data === 'string') {
150
- resolve(JSON.parse(data));
151
- }
152
- // If data is an object with a string value (GunDB format)
153
- else if (typeof data === 'object' && data !== null) {
154
- const schemaStr = Object.values(data).find(v =>
134
+ // Handle both direct string and GunDB object formats
135
+ let schemaStr = data;
136
+ if (typeof data === 'object' && data !== null) {
137
+ schemaStr = Object.values(data).find(v =>
155
138
  typeof v === 'string' && v.includes('"type":'));
156
- if (schemaStr) {
157
- resolve(JSON.parse(schemaStr));
158
- } else {
159
- resolve(null);
160
- }
139
+ }
140
+
141
+ if (schemaStr) {
142
+ resolve(JSON.parse(schemaStr));
161
143
  } else {
162
144
  resolve(null);
163
145
  }
@@ -180,53 +162,43 @@ class HoloSphere {
180
162
  */
181
163
  async put(holon, lens, data) {
182
164
  if (!holon || !lens || !data) {
183
- console.error('put: Missing required parameters:', { holon, lens, data });
184
- return false;
165
+ throw new Error('put: Missing required parameters');
185
166
  }
186
167
 
187
168
  if (!data.id) {
188
- console.error('put: Data must have an id field');
189
- return false;
169
+ data.id = this.generateId();
190
170
  }
191
171
 
192
- // Strict validation of schema and data
193
172
  const schema = await this.getSchema(lens);
194
173
  if (schema) {
195
- try {
196
- const valid = this.validator.validate(schema, data);
197
- if (!valid) {
198
- const errors = this.validator.errors;
199
- console.error('put: Schema validation failed:', errors);
200
- return false;
201
- }
202
- } catch (error) {
203
- console.error('put: Schema validation error:', error);
204
- return false;
174
+ // Clone data to avoid modifying original
175
+ const dataToValidate = JSON.parse(JSON.stringify(data));
176
+
177
+ // Validate against schema
178
+ const valid = this.validator.validate(schema, dataToValidate);
179
+ if (!valid) {
180
+ throw new Error(`Schema validation failed: ${JSON.stringify(this.validator.errors)}`);
205
181
  }
206
182
  } else if (this.strict) {
207
- console.error('put: Schema required in strict mode for lens:', lens);
208
- return false;
183
+ throw new Error('Schema required in strict mode');
209
184
  }
210
185
 
211
- return new Promise((resolve) => {
186
+ return new Promise((resolve, reject) => {
212
187
  try {
213
188
  const payload = JSON.stringify(data);
214
-
215
189
  this.gun.get(this.appname)
216
190
  .get(holon)
217
191
  .get(lens)
218
192
  .get(data.id)
219
193
  .put(payload, ack => {
220
194
  if (ack.err) {
221
- console.error("Error adding data to GunDB:", ack.err);
222
- resolve(false);
195
+ reject(new Error(ack.err));
223
196
  } else {
224
197
  resolve(true);
225
198
  }
226
199
  });
227
200
  } catch (error) {
228
- console.error('Error in put operation:', error);
229
- resolve(false);
201
+ reject(error);
230
202
  }
231
203
  });
232
204
  }
@@ -239,60 +211,58 @@ class HoloSphere {
239
211
  */
240
212
  async getAll(holon, lens) {
241
213
  if (!holon || !lens) {
242
- console.error('getAll: Missing required parameters:', { holon, lens });
243
- return [];
214
+ throw new Error('getAll: Missing required parameters');
244
215
  }
245
216
 
246
217
  const schema = await this.getSchema(lens);
247
218
  if (!schema && this.strict) {
248
- console.error('getAll: Schema required in strict mode for lens:', lens);
249
- return [];
219
+ throw new Error('getAll: Schema required in strict mode');
250
220
  }
251
221
 
252
222
  return new Promise((resolve) => {
253
- let output = [];
254
- let counter = 0;
223
+ const output = new Set();
224
+ const promises = new Set();
225
+ let timeout;
255
226
 
256
- this.gun.get(this.appname).get(holon).get(lens).once((data, key) => {
257
- if (!data) {
258
- resolve(output);
259
- return;
260
- }
261
-
262
- const mapLength = Object.keys(data).length - 1;
263
-
264
- this.gun.get(this.appname).get(holon).get(lens).map().once(async (itemdata, key) => {
265
- counter += 1;
266
- if (itemdata) {
267
- try {
268
- const parsed = await this.parse(itemdata);
269
- if (schema) {
270
- const valid = this.validator.validate(schema, parsed);
271
- if (valid) {
272
- output.push(parsed);
273
- } else if (this.strict) {
274
- console.warn('Invalid data removed:', key, this.validator.errors);
275
- await this.delete(holon, lens, key);
276
- } else {
277
- console.warn('Invalid data found:', key, this.validator.errors);
278
- output.push(parsed);
279
- }
280
- } else {
281
- output.push(parsed);
282
- }
283
- } catch (error) {
284
- console.error('Error parsing data:', error);
285
- if (this.strict) {
227
+ const processData = async (itemdata, key) => {
228
+ if (itemdata) {
229
+ try {
230
+ const parsed = await this.parse(itemdata);
231
+ if (schema) {
232
+ const valid = this.validator.validate(schema, parsed);
233
+ if (valid || !this.strict) {
234
+ output.add(parsed);
235
+ } else if (this.strict) {
286
236
  await this.delete(holon, lens, key);
287
237
  }
238
+ } else {
239
+ output.add(parsed);
240
+ }
241
+ } catch (error) {
242
+ console.error('Error parsing data:', error);
243
+ if (this.strict) {
244
+ await this.delete(holon, lens, key);
288
245
  }
289
246
  }
247
+ }
248
+ };
290
249
 
291
- if (counter === mapLength) {
292
- resolve(output);
293
- }
250
+ const listener = this.gun.get(this.appname)
251
+ .get(holon)
252
+ .get(lens)
253
+ .map()
254
+ .on(async (data, key) => {
255
+ const promise = processData(data, key);
256
+ promises.add(promise);
257
+ promise.finally(() => promises.delete(promise));
258
+
259
+ // Reset timeout on new data
260
+ clearTimeout(timeout);
261
+ timeout = setTimeout(() => {
262
+ listener.off();
263
+ Promise.all(promises).then(() => resolve(Array.from(output)));
264
+ }, 1000); // Wait 1 second after last received data
294
265
  });
295
- });
296
266
  });
297
267
  }
298
268
 
@@ -302,57 +272,47 @@ class HoloSphere {
302
272
  * @returns {Promise<object>} - The parsed data.
303
273
  */
304
274
  async parse(rawData) {
305
- let parsedData = {};
306
- if (rawData.soul) {
307
- console.log('Parsing link:', rawData.soul);
308
- this.getNodeRef(rawData.soul).once (data => {return JSON.parse(data)});
309
- }
310
-
311
- if (typeof rawData === 'object' && rawData !== null) {
312
- if (rawData._ && rawData._["#"]) {
313
- console.log('Parsing object reference:', rawData._['#']);
314
- // If the data is a reference, fetch the actual content
315
- let pathParts = rawData._['#'].split('/');
316
- let hexId = pathParts[1];
317
- let lensId = pathParts[2];
318
- let dataKey = pathParts[3];
319
- parsedData = await this.get(hexId, lensId, dataKey);
320
- } else if (rawData._ && rawData._['>']) {
321
- console.log('Parsing objectnode:', rawData._['>']);
322
- // This might be a GunDB node, try to get its value
323
- const nodeValue = Object.values(rawData).find(v => typeof v !== 'object' && v !== '_');
324
- if (nodeValue) {
325
- try {
326
- parsedData = JSON.parse(nodeValue);
327
- } catch (e) {
328
- console.log('Invalid JSON in node value:', nodeValue);
329
- parsedData = nodeValue; // return the raw data
275
+ if (!rawData) {
276
+ throw new Error('parse: No data provided');
277
+ }
278
+
279
+ try {
280
+ if (rawData.soul) {
281
+ const data = await this.getNodeRef(rawData.soul).once();
282
+ if (!data) {
283
+ throw new Error('Referenced data not found');
284
+ }
285
+ return JSON.parse(data);
286
+ }
287
+
288
+ let parsedData = {};
289
+ if (typeof rawData === 'object' && rawData !== null) {
290
+ if (rawData._ && rawData._["#"]) {
291
+ const pathParts = rawData._['#'].split('/');
292
+ if (pathParts.length < 4) {
293
+ throw new Error('Invalid reference format');
294
+ }
295
+ parsedData = await this.get(pathParts[1], pathParts[2], pathParts[3]);
296
+ if (!parsedData) {
297
+ throw new Error('Referenced data not found');
298
+ }
299
+ } else if (rawData._ && rawData._['>']) {
300
+ const nodeValue = Object.values(rawData).find(v => typeof v !== 'object' && v !== '_');
301
+ if (!nodeValue) {
302
+ throw new Error('Invalid node data');
330
303
  }
304
+ parsedData = JSON.parse(nodeValue);
331
305
  } else {
332
- console.log('Unable to parse GunDB node:', rawData);
333
- parsedData = rawData; // return the original data
306
+ parsedData = rawData;
334
307
  }
335
308
  } else {
336
- // Treat it as object data
337
- console.log('Parsing object data:', rawData);
338
- parsedData = rawData;
339
- }
340
- } else {
341
- // If it's not an object, try parsing it as JSON
342
- try {
343
309
  parsedData = JSON.parse(rawData);
344
- //if the data has a soul, return the soul node
345
- if (parsedData.soul) {
346
- console.log('Parsing link:', parsedData.soul);
347
- parsedData = await this.get(parsedData.soul.split('/')[1], parsedData.soul.split('/')[2], parsedData.soul.split('/')[3]);
348
- }
349
- } catch (e) {
350
- console.log('Failed to parse, returning raw data', e);
351
- parsedData = rawData; // return the raw data
352
310
  }
353
- }
354
311
 
355
- return parsedData;
312
+ return parsedData;
313
+ } catch (error) {
314
+ throw new Error(`Parse error: ${error.message}`);
315
+ }
356
316
  }
357
317
 
358
318
  /**
@@ -419,14 +379,26 @@ class HoloSphere {
419
379
  * @param {string} key - The specific key to delete.
420
380
  */
421
381
  async delete(holon, lens, key) {
382
+ if (!holon || !lens || !key) {
383
+ throw new Error('delete: Missing required parameters');
384
+ }
385
+
422
386
  return new Promise((resolve, reject) => {
423
- this.gun.get(this.appname).get(holon).get(lens).get(key).put(null, ack => {
424
- if (ack.err) {
425
- resolve(ack.err);
426
- } else {
427
- resolve(ack.ok);
428
- }
429
- });
387
+ try {
388
+ this.gun.get(this.appname)
389
+ .get(holon)
390
+ .get(lens)
391
+ .get(key)
392
+ .put(null, ack => {
393
+ if (ack.err) {
394
+ reject(new Error(ack.err));
395
+ } else {
396
+ resolve(true);
397
+ }
398
+ });
399
+ } catch (error) {
400
+ reject(error);
401
+ }
430
402
  });
431
403
  }
432
404
 
@@ -475,7 +447,7 @@ class HoloSphere {
475
447
  .catch(error => {
476
448
  console.error('Error in deleteAll:', error);
477
449
  resolve(false);
478
- });
450
+ });
479
451
  });
480
452
  });
481
453
  }
@@ -490,25 +462,35 @@ class HoloSphere {
490
462
  * @param {object} node - The node to store.
491
463
  */
492
464
  async putNode(holon, lens, node) {
493
- return new Promise((resolve) => {
494
- this.gun.get(this.appname).get(holon).get(lens).put(node, ack => {
495
- if (ack.err) {
496
- console.error("Error adding data to GunDB:", ack.err);
497
- resolve(false);
498
- } else {
499
- resolve(true);
500
- }
501
- });
465
+ if (!holon || !lens || !node) {
466
+ throw new Error('putNode: Missing required parameters');
467
+ }
468
+
469
+ return new Promise((resolve, reject) => {
470
+ try {
471
+ this.gun.get(this.appname)
472
+ .get(holon)
473
+ .get(lens)
474
+ .put(node, ack => {
475
+ if (ack.err) {
476
+ reject(new Error(ack.err));
477
+ } else {
478
+ resolve(true);
479
+ }
480
+ });
481
+ } catch (error) {
482
+ reject(error);
483
+ }
502
484
  });
503
485
  }
504
486
 
505
487
  /**
506
- * Retrieves a specific gun node from the specified holon and lens.
507
- * @param {string} holon - The holon identifier.
508
- * @param {string} lens - The lens identifier.
509
- * @param {string} key - The specific key to retrieve.
488
+ * Retrieves a specific gun node from the specified holon and lens.
489
+ * @param {string} holon - The holon identifier.
490
+ * @param {string} lens - The lens identifier.
491
+ * @param {string} key - The specific key to retrieve.
510
492
  * @returns {Promise<object|null>} - The retrieved node or null if not found.
511
- */
493
+ */
512
494
  getNode(holon, lens, key) {
513
495
  if (!holon || !lens || !key) {
514
496
  console.error('getNode: Missing required parameters');
@@ -516,14 +498,28 @@ class HoloSphere {
516
498
  }
517
499
 
518
500
  return this.gun.get(this.appname)
519
- .get(holon)
520
- .get(lens)
521
- .get(key)
501
+ .get(holon)
502
+ .get(lens)
503
+ .get(key)
522
504
 
523
505
  }
524
506
 
525
507
  getNodeRef(soul) {
526
- const parts = soul.split('/');
508
+ if (typeof soul !== 'string' || !soul) {
509
+ throw new Error('getNodeRef: Invalid soul parameter');
510
+ }
511
+
512
+ const parts = soul.split('/').filter(part => {
513
+ if (!part.trim() || /[<>:"/\\|?*]/.test(part)) {
514
+ throw new Error('getNodeRef: Invalid path segment');
515
+ }
516
+ return part.trim();
517
+ });
518
+
519
+ if (parts.length === 0) {
520
+ throw new Error('getNodeRef: Invalid soul format');
521
+ }
522
+
527
523
  let ref = this.gun.get(this.appname);
528
524
  parts.forEach(part => {
529
525
  ref = ref.get(part);
@@ -540,19 +536,16 @@ class HoloSphere {
540
536
  */
541
537
  async deleteNode(holon, lens, key) {
542
538
  if (!holon || !lens || !key) {
543
- console.error('deleteNode: Missing required parameters');
544
- return false;
539
+ throw new Error('deleteNode: Missing required parameters');
545
540
  }
546
-
547
- return new Promise((resolve) => {
541
+ return new Promise((resolve, reject) => {
548
542
  this.gun.get(this.appname)
549
543
  .get(holon)
550
544
  .get(lens)
551
545
  .get(key)
552
546
  .put(null, ack => {
553
547
  if (ack.err) {
554
- console.error('deleteNode: Error deleting node:', ack.err);
555
- resolve(false);
548
+ reject(new Error(ack.err));
556
549
  } else {
557
550
  resolve(true);
558
551
  }
@@ -568,30 +561,31 @@ class HoloSphere {
568
561
  * @returns {Promise<void>}
569
562
  */
570
563
  async putGlobal(tableName, data) {
571
-
572
564
  return new Promise((resolve, reject) => {
573
- if (!tableName || !data) {
574
- reject(new Error('Table name and data are required'));
575
- return;
576
- }
577
-
565
+ try {
566
+ if (!tableName || !data) {
567
+ throw new Error('Table name and data are required');
568
+ }
578
569
 
579
- if (data.id) {
580
- this.gun.get(this.appname).get(tableName).get(data.id).put(JSON.stringify(data), ack => {
581
- if (ack.err) {
582
- reject(new Error(ack.err));
583
- } else {
584
- resolve();
585
- }
586
- });
587
- } else {
588
- this.gun.get(this.appname).get(tableName).put(JSON.stringify(data), ack => {
589
- if (ack.err) {
590
- reject(new Error(ack.err));
591
- } else {
592
- resolve();
593
- }
594
- });
570
+ if (data.id) {
571
+ this.gun.get(this.appname).get(tableName).get(data.id).put(JSON.stringify(data), ack => {
572
+ if (ack.err) {
573
+ reject(new Error(ack.err));
574
+ } else {
575
+ resolve();
576
+ }
577
+ });
578
+ } else {
579
+ this.gun.get(this.appname).get(tableName).put(JSON.stringify(data), ack => {
580
+ if (ack.err) {
581
+ reject(new Error(ack.err));
582
+ } else {
583
+ resolve();
584
+ }
585
+ });
586
+ }
587
+ } catch (error) {
588
+ reject(error);
595
589
  }
596
590
  });
597
591
  }
@@ -631,25 +625,20 @@ class HoloSphere {
631
625
  // let output = []
632
626
  // let counter = 0
633
627
  // this.gun.get(this.appname).get(tableName.toString()).once((data, key) => {
634
- // if (data) {
635
- // const maplenght = Object.keys(data).length - 1
636
- // this.gun.get(this.appname).get(tableName.toString()).map().once(async (itemdata, key) => {
637
628
 
638
- // counter += 1
639
- // if (itemdata) {
640
- // let parsed = await this.parse(itemdata)
641
- // output.push(parsed);
642
- // console.log('getAllGlobal: parsed: ', parsed)
643
- // }
644
-
645
- // if (counter == maplenght) {
646
- // resolve(output);
629
+ // counter += 1
630
+ // if (itemdata) {
631
+ // let parsed = await this.parse(itemdata)
632
+ // output.push(parsed);
633
+ // console.log('getAllGlobal: parsed: ', parsed)
634
+ // }
635
+
636
+ // if (counter == maplenght) {
637
+ // resolve(output);
647
638
 
648
- // }
649
- // }
650
- // );
651
- // } else resolve(output)
652
- // })
639
+ // }
640
+ // }
641
+ // );
653
642
  // }
654
643
  // )
655
644
  // }
@@ -729,11 +718,11 @@ class HoloSphere {
729
718
  async deleteAllGlobal(tableName) {
730
719
  // return new Promise((resolve) => {
731
720
  this.gun.get(this.appname).get(tableName).map().once( (data, key)=> {
732
- this.gun.get(this.appname).get(tableName).get(key).put(null)
733
- })
734
- this.gun.get(this.appname).get(tableName).put(null, ack => {
735
- console.log('deleteAllGlobal: ack: ', ack)
736
- })
721
+ this.gun.get(this.appname).get(tableName).get(key).put(null)
722
+ })
723
+ this.gun.get(this.appname).get(tableName).put(null, ack => {
724
+ console.log('deleteAllGlobal: ack: ', ack)
725
+ })
737
726
  // resolve();
738
727
  //});
739
728
  // });
@@ -746,43 +735,58 @@ class HoloSphere {
746
735
  * @param {string} lens - The lens to compute.
747
736
  * @param {string} operation - The operation to perform.
748
737
  */
749
- async compute(holon, lens, operation) {
750
-
751
- let res = h3.getResolution(holon);
752
- if (res < 1 || res > 15) return;
753
- console.log(res)
754
- let parent = h3.cellToParent(holon, res - 1);
755
- let siblings = h3.cellToChildren(parent, res);
756
- console.log(holon, parent, siblings, res)
757
-
758
- let content = [];
759
- let promises = [];
760
-
761
- for (let i = 0; i < siblings.length; i++) {
762
- promises.push(new Promise((resolve) => {
763
- let timeout = setTimeout(() => {
764
- console.log(`Timeout for sibling ${i}`);
765
- resolve(); // Resolve the promise to prevent it from hanging
766
- }, 1000); // Timeout of 5 seconds
767
-
768
- this.gun.get(this.appname).get(siblings[i]).get(lens).map().once((data, key) => {
769
- clearTimeout(timeout); // Clear the timeout if data is received
770
- if (data) {
771
- content.push(data.content);
772
- }
773
- resolve(); // Resolve after processing data
774
- });
775
- }));
738
+ async compute(holon, lens, operation, depth = 0, maxDepth = 15) {
739
+ if (!holon || !lens) {
740
+ throw new Error('compute: Missing required parameters');
776
741
  }
777
742
 
743
+ if (depth >= maxDepth) return;
744
+
745
+ const res = h3.getResolution(holon);
746
+ if (res < 1 || res > 15) {
747
+ throw new Error('compute: Invalid holon resolution');
748
+ }
749
+
750
+ const parent = h3.cellToParent(holon, res - 1);
751
+ const siblings = h3.cellToChildren(parent, res);
752
+
753
+ const content = [];
754
+ const promises = siblings.map(sibling =>
755
+ new Promise((resolve) => {
756
+ const timeout = setTimeout(() => {
757
+ console.warn(`Timeout for sibling ${sibling}`);
758
+ resolve();
759
+ }, 1000);
760
+
761
+ this.gun.get(this.appname)
762
+ .get(sibling)
763
+ .get(lens)
764
+ .map()
765
+ .once((data) => {
766
+ clearTimeout(timeout);
767
+ if (data?.content) {
768
+ content.push(data.content);
769
+ }
770
+ resolve();
771
+ });
772
+ })
773
+ );
774
+
778
775
  await Promise.all(promises);
779
- console.log('Content:', content);
780
- let computed = await this.summarize(content.join('\n'))
781
- console.log('Computed:', computed)
782
- let node = await this.gun.get(this.appname).get(parent + '_summary').put({ id: parent + '_summary', content: computed })
783
776
 
784
- this.put(parent, lens, node);
785
- this.compute(parent, lens, operation)
777
+ if (content.length > 0) {
778
+ const computed = await this.summarize(content.join('\n'));
779
+ const summaryId = `${parent}_summary`;
780
+ await this.put(parent, lens, {
781
+ id: summaryId,
782
+ content: computed,
783
+ timestamp: Date.now()
784
+ });
785
+
786
+ if (res > 1) { // Only recurse if not at top level
787
+ await this.compute(parent, lens, operation, depth + 1, maxDepth);
788
+ }
789
+ }
786
790
  }
787
791
 
788
792
  /**
@@ -791,14 +795,51 @@ class HoloSphere {
791
795
  * @param {string} lens - The lens to clear.
792
796
  */
793
797
  async clearlens(holon, lens) {
794
- let entities = {};
798
+ if (!holon || !lens) {
799
+ throw new Error('clearlens: Missing required parameters');
800
+ }
795
801
 
796
- // Get list out of Gun
797
- this.gun.get(this.appname).get(holon).get(lens).map().once((data, key) => {
798
- //entities = data;
799
- //const id = Object.keys(entities)[0] // since this would be in object form, you can manipulate it as you would like.
800
- this.gun.get(this.appname).get(holon).get(lens).put({ [key]: null })
801
- })
802
+ return new Promise((resolve, reject) => {
803
+ try {
804
+ const deletions = new Set();
805
+ const timeout = setTimeout(() => {
806
+ if (deletions.size === 0) {
807
+ resolve(); // No data to delete
808
+ }
809
+ }, 1000);
810
+
811
+ this.gun.get(this.appname)
812
+ .get(holon)
813
+ .get(lens)
814
+ .map()
815
+ .once((data, key) => {
816
+ if (data) {
817
+ const deletion = new Promise((resolveDelete) => {
818
+ this.gun.get(this.appname)
819
+ .get(holon)
820
+ .get(lens)
821
+ .get(key)
822
+ .put(null, ack => {
823
+ if (ack.err) {
824
+ console.error(`Failed to delete ${key}:`, ack.err);
825
+ }
826
+ resolveDelete();
827
+ });
828
+ });
829
+ deletions.add(deletion);
830
+ deletion.finally(() => {
831
+ deletions.delete(deletion);
832
+ if (deletions.size === 0) {
833
+ clearTimeout(timeout);
834
+ resolve();
835
+ }
836
+ });
837
+ }
838
+ });
839
+ } catch (error) {
840
+ reject(error);
841
+ }
842
+ });
802
843
  }
803
844
 
804
845
 
@@ -849,12 +890,12 @@ class HoloSphere {
849
890
  async upcast(holon, lens, content) {
850
891
  let res = h3.getResolution(holon)
851
892
  if (res == 0) {
852
- await this.putNode(holon, lens, content)
893
+ await this.put(holon, lens, content)
853
894
  return content
854
895
  }
855
896
  else {
856
897
  console.log('Upcasting ', holon, lens, content, res)
857
- await this.putNode(holon, lens, content)
898
+ await this.put(holon, lens, content)
858
899
  let parent = h3.cellToParent(holon, res - 1)
859
900
  return this.upcast(parent, lens, content)
860
901
  }
@@ -930,10 +971,31 @@ class HoloSphere {
930
971
  * @param {function} callback - The callback to execute on changes.
931
972
  */
932
973
  async subscribe(holon, lens, callback) {
933
- this.gun.get(this.appname).get(holon).get(lens).map().on(async (data, key) => {
934
- if (data)
935
- callback( await this.parse(data), key)
936
- })
974
+ const subscriptionId = `${holon}:${lens}:${Date.now()}`;
975
+ const listener = this.gun.get(this.appname).get(holon).get(lens).map().on(async (data, key) => {
976
+ if (data) callback(await this.parse(data), key);
977
+ });
978
+
979
+ this.subscriptions.set(subscriptionId, listener);
980
+
981
+ return {
982
+ unsubscribe: () => {
983
+ listener.off();
984
+ this.subscriptions.delete(subscriptionId);
985
+ },
986
+ id: subscriptionId
987
+ };
988
+ }
989
+
990
+ // Add cleanup method
991
+ cleanup() {
992
+ this.subscriptions.forEach(listener => listener.off());
993
+ this.subscriptions.clear();
994
+ }
995
+
996
+ // Add ID generation method
997
+ generateId() {
998
+ return Date.now().toString(36) + Math.random().toString(36).substr(2);
937
999
  }
938
1000
  }
939
1001