holosphere 1.1.0 → 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
@@ -3,6 +3,7 @@ import OpenAI from 'openai';
3
3
  import Gun from 'gun'
4
4
  import Ajv2019 from 'ajv/dist/2019.js'
5
5
 
6
+
6
7
  class HoloSphere {
7
8
  /**
8
9
  * Initializes a new instance of the HoloSphere class.
@@ -13,13 +14,13 @@ class HoloSphere {
13
14
  constructor(appname, strict = false, openaikey = null) {
14
15
  this.appname = appname
15
16
  this.strict = strict;
16
- this.validator = new Ajv2019({
17
+ this.validator = new Ajv2019({
17
18
  allErrors: true,
18
19
  strict: false, // Keep this false to avoid Ajv strict mode issues
19
20
  validateSchema: true // Always validate schemas
20
21
  });
21
22
  this.gun = Gun({
22
- peers: ['http://gun.holons.io', 'https://59.src.eco/gun'],
23
+ peers: ['https://gun.holons.io/gun', 'https://59.src.eco/gun'],
23
24
  axe: false,
24
25
  // uuid: (content) => { // generate a unique id for each node
25
26
  // console.log('uuid', content);
@@ -31,6 +32,8 @@ class HoloSphere {
31
32
  apiKey: openaikey,
32
33
  });
33
34
  }
35
+
36
+ this.subscriptions = new Map(); // Track active subscriptions
34
37
  }
35
38
 
36
39
  // ================================ SCHEMA FUNCTIONS ================================
@@ -43,64 +46,52 @@ class HoloSphere {
43
46
  */
44
47
  async setSchema(lens, schema) {
45
48
  if (!lens || !schema) {
46
- console.error('setSchema: Missing required parameters');
47
- return false;
49
+ throw new Error('setSchema: Missing required parameters');
48
50
  }
49
51
 
50
- // Basic schema validation - check for required fields
52
+ // Basic schema validation
51
53
  if (!schema.type || typeof schema.type !== 'string') {
52
- console.error('setSchema: Schema must have a type field');
53
- return false;
54
+ throw new Error('setSchema: Schema must have a type field');
54
55
  }
55
56
 
56
57
  if (this.strict) {
57
- try {
58
- // Validate schema against JSON Schema meta-schema
59
- const metaSchema = {
60
- type: 'object',
61
- required: ['type', 'properties'],
58
+ const metaSchema = {
59
+ type: 'object',
60
+ required: ['type', 'properties'],
61
+ properties: {
62
+ type: { type: 'string' },
62
63
  properties: {
63
- type: { type: 'string' },
64
- properties: {
64
+ type: 'object',
65
+ additionalProperties: {
65
66
  type: 'object',
66
- additionalProperties: {
67
- type: 'object',
68
- required: ['type'],
69
- properties: {
70
- type: { type: 'string' }
71
- }
67
+ required: ['type'],
68
+ properties: {
69
+ type: { type: 'string' }
72
70
  }
73
- },
74
- required: {
75
- type: 'array',
76
- items: { type: 'string' }
77
71
  }
72
+ },
73
+ required: {
74
+ type: 'array',
75
+ items: { type: 'string' }
78
76
  }
79
- };
80
-
81
- const valid = this.validator.validate(metaSchema, schema);
82
- if (!valid) {
83
- console.error('setSchema: Invalid schema structure:', this.validator.errors);
84
- return false;
85
77
  }
78
+ };
86
79
 
87
- // Additional strict mode checks
88
- if (!schema.properties || typeof schema.properties !== 'object') {
89
- console.error('setSchema: Schema must have properties in strict mode');
90
- return false;
91
- }
80
+ const valid = this.validator.validate(metaSchema, schema);
81
+ if (!valid) {
82
+ throw new Error(`Invalid schema structure: ${JSON.stringify(this.validator.errors)}`);
83
+ }
92
84
 
93
- if (!schema.required || !Array.isArray(schema.required) || schema.required.length === 0) {
94
- console.error('setSchema: Schema must have required fields in strict mode');
95
- return false;
96
- }
97
- } catch (error) {
98
- console.error('setSchema: Schema validation error:', error);
99
- 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');
100
91
  }
101
92
  }
102
93
 
103
- return new Promise((resolve) => {
94
+ return new Promise((resolve, reject) => {
104
95
  try {
105
96
  const schemaString = JSON.stringify(schema);
106
97
  this.gun.get(this.appname)
@@ -108,16 +99,13 @@ class HoloSphere {
108
99
  .get('schema')
109
100
  .put(schemaString, ack => {
110
101
  if (ack.err) {
111
- console.error('Failed to add schema:', ack.err);
112
- resolve(false);
102
+ reject(new Error(ack.err));
113
103
  } else {
114
- console.log('Schema added successfully for lens:', lens);
115
104
  resolve(true);
116
105
  }
117
106
  });
118
107
  } catch (error) {
119
- console.error('setSchema: Error stringifying schema:', error);
120
- resolve(false);
108
+ reject(error);
121
109
  }
122
110
  });
123
111
  }
@@ -129,8 +117,7 @@ class HoloSphere {
129
117
  */
130
118
  async getSchema(lens) {
131
119
  if (!lens) {
132
- console.error('getSchema: Missing lens parameter');
133
- return null;
120
+ throw new Error('getSchema: Missing lens parameter');
134
121
  }
135
122
 
136
123
  return new Promise((resolve) => {
@@ -142,21 +129,17 @@ class HoloSphere {
142
129
  resolve(null);
143
130
  return;
144
131
  }
145
-
132
+
146
133
  try {
147
- // If data is already a string, parse it
148
- if (typeof data === 'string') {
149
- resolve(JSON.parse(data));
150
- }
151
- // If data is an object with a string value (GunDB format)
152
- else if (typeof data === 'object' && data !== null) {
153
- const schemaStr = Object.values(data).find(v =>
154
- typeof v === 'string' && v.includes('"type":'));
155
- if (schemaStr) {
156
- resolve(JSON.parse(schemaStr));
157
- } else {
158
- resolve(null);
159
- }
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 =>
138
+ typeof v === 'string' && v.includes('"type":'));
139
+ }
140
+
141
+ if (schemaStr) {
142
+ resolve(JSON.parse(schemaStr));
160
143
  } else {
161
144
  resolve(null);
162
145
  }
@@ -179,53 +162,43 @@ class HoloSphere {
179
162
  */
180
163
  async put(holon, lens, data) {
181
164
  if (!holon || !lens || !data) {
182
- console.error('put: Missing required parameters:', { holon, lens, data });
183
- return false;
165
+ throw new Error('put: Missing required parameters');
184
166
  }
185
167
 
186
168
  if (!data.id) {
187
- console.error('put: Data must have an id field');
188
- return false;
169
+ data.id = this.generateId();
189
170
  }
190
171
 
191
- // Strict validation of schema and data
192
172
  const schema = await this.getSchema(lens);
193
173
  if (schema) {
194
- try {
195
- const valid = this.validator.validate(schema, data);
196
- if (!valid) {
197
- const errors = this.validator.errors;
198
- console.error('put: Schema validation failed:', errors);
199
- return false;
200
- }
201
- } catch (error) {
202
- console.error('put: Schema validation error:', error);
203
- 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)}`);
204
181
  }
205
182
  } else if (this.strict) {
206
- console.error('put: Schema required in strict mode for lens:', lens);
207
- return false;
183
+ throw new Error('Schema required in strict mode');
208
184
  }
209
185
 
210
- return new Promise((resolve) => {
186
+ return new Promise((resolve, reject) => {
211
187
  try {
212
188
  const payload = JSON.stringify(data);
213
-
214
189
  this.gun.get(this.appname)
215
190
  .get(holon)
216
191
  .get(lens)
217
192
  .get(data.id)
218
193
  .put(payload, ack => {
219
194
  if (ack.err) {
220
- console.error("Error adding data to GunDB:", ack.err);
221
- resolve(false);
195
+ reject(new Error(ack.err));
222
196
  } else {
223
197
  resolve(true);
224
198
  }
225
199
  });
226
200
  } catch (error) {
227
- console.error('Error in put operation:', error);
228
- resolve(false);
201
+ reject(error);
229
202
  }
230
203
  });
231
204
  }
@@ -238,64 +211,110 @@ class HoloSphere {
238
211
  */
239
212
  async getAll(holon, lens) {
240
213
  if (!holon || !lens) {
241
- console.error('getAll: Missing required parameters:', { holon, lens });
242
- return [];
214
+ throw new Error('getAll: Missing required parameters');
243
215
  }
244
216
 
245
217
  const schema = await this.getSchema(lens);
246
218
  if (!schema && this.strict) {
247
- console.error('getAll: Schema required in strict mode for lens:', lens);
248
- return [];
219
+ throw new Error('getAll: Schema required in strict mode');
249
220
  }
250
221
 
251
222
  return new Promise((resolve) => {
252
- let output = [];
253
- let counter = 0;
254
-
255
- this.gun.get(this.appname).get(holon).get(lens).once((data, key) => {
256
- if (!data) {
257
- resolve(output);
258
- return;
259
- }
223
+ const output = new Set();
224
+ const promises = new Set();
225
+ let timeout;
260
226
 
261
- const mapLength = Object.keys(data).length - 1;
262
-
263
- this.gun.get(this.appname).get(holon).get(lens).map().once(async (itemdata, key) => {
264
- counter += 1;
265
- if (itemdata) {
266
- try {
267
- const parsed = JSON.parse(itemdata);
268
-
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
 
269
+ /**
270
+ * Parses data from GunDB, handling various data formats and references.
271
+ * @param {*} data - The data to parse, could be a string, object, or GunDB reference.
272
+ * @returns {Promise<object>} - The parsed data.
273
+ */
274
+ async parse(rawData) {
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');
303
+ }
304
+ parsedData = JSON.parse(nodeValue);
305
+ } else {
306
+ parsedData = rawData;
307
+ }
308
+ } else {
309
+ parsedData = JSON.parse(rawData);
310
+ }
311
+
312
+ return parsedData;
313
+ } catch (error) {
314
+ throw new Error(`Parse error: ${error.message}`);
315
+ }
316
+ }
317
+
299
318
  /**
300
319
  * Retrieves a specific key from the specified holon and lens.
301
320
  * @param {string} holon - The holon identifier.
@@ -322,17 +341,17 @@ class HoloSphere {
322
341
  .get(holon)
323
342
  .get(lens)
324
343
  .get(key)
325
- .once((data) => {
344
+ .once((data,key) => {
326
345
  clearTimeout(timeout);
327
-
346
+
328
347
  if (!data) {
329
348
  resolve(null);
330
349
  return;
331
350
  }
332
351
 
333
352
  try {
334
- const parsed = JSON.parse(data);
335
-
353
+ const parsed = this.parse(data);
354
+
336
355
  // Validate against schema if one exists
337
356
  if (schema) {
338
357
  const valid = this.validator.validate(schema, parsed);
@@ -344,7 +363,6 @@ class HoloSphere {
344
363
  }
345
364
  }
346
365
  }
347
-
348
366
  resolve(parsed);
349
367
  } catch (error) {
350
368
  console.error('Error parsing data:', error);
@@ -360,18 +378,30 @@ class HoloSphere {
360
378
  * @param {string} lens - The lens from which to delete the key.
361
379
  * @param {string} key - The specific key to delete.
362
380
  */
363
- async delete (holon, lens, key) {
364
- return new Promise((resolve, reject) => {
365
- this.gun.get(this.appname).get(holon).get(lens).get(key).put(null, ack => {
366
- if (ack.err) {
367
- resolve(ack.err);
368
- } else {
369
- resolve(ack.ok);
370
- }
371
- });
372
- });
381
+ async delete(holon, lens, key) {
382
+ if (!holon || !lens || !key) {
383
+ throw new Error('delete: Missing required parameters');
373
384
  }
374
385
 
386
+ return new Promise((resolve, reject) => {
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
+ }
402
+ });
403
+ }
404
+
375
405
  /**
376
406
  * Deletes all keys from a given holon and lens.
377
407
  * @param {string} holon - The holon identifier.
@@ -386,7 +416,7 @@ class HoloSphere {
386
416
 
387
417
  return new Promise((resolve) => {
388
418
  let deletionPromises = [];
389
-
419
+
390
420
  // First get all the data to find keys to delete
391
421
  this.gun.get(this.appname).get(holon).get(lens).once((data) => {
392
422
  if (!data) {
@@ -396,7 +426,7 @@ class HoloSphere {
396
426
 
397
427
  // Get all keys except Gun's metadata key '_'
398
428
  const keys = Object.keys(data).filter(key => key !== '_');
399
-
429
+
400
430
  // Create deletion promises for each key
401
431
  keys.forEach(key => {
402
432
  deletionPromises.push(
@@ -417,7 +447,7 @@ class HoloSphere {
417
447
  .catch(error => {
418
448
  console.error('Error in deleteAll:', error);
419
449
  resolve(false);
420
- });
450
+ });
421
451
  });
422
452
  });
423
453
  }
@@ -432,37 +462,69 @@ class HoloSphere {
432
462
  * @param {object} node - The node to store.
433
463
  */
434
464
  async putNode(holon, lens, node) {
435
- this.gun.get(this.appname).get(holon).get(lens).put(node)
465
+ if (!holon || !lens || !node) {
466
+ throw new Error('putNode: Missing required parameters');
436
467
  }
437
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
+ }
484
+ });
485
+ }
486
+
438
487
  /**
439
- * Retrieves a specific gun node from the specified holon and lens.
440
- * @param {string} holon - The holon identifier.
441
- * @param {string} lens - The lens identifier.
442
- * @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.
443
492
  * @returns {Promise<object|null>} - The retrieved node or null if not found.
444
- */
445
- async getNode(holon, lens, key) {
493
+ */
494
+ getNode(holon, lens, key) {
446
495
  if (!holon || !lens || !key) {
447
496
  console.error('getNode: Missing required parameters');
448
497
  return null;
449
498
  }
450
499
 
451
- return new Promise((resolve) => {
452
- let timeout = setTimeout(() => {
453
- console.warn('getNode: Operation timed out');
454
- resolve(null);
455
- }, 5000);
456
-
457
- this.gun.get(this.appname)
500
+ return this.gun.get(this.appname)
458
501
  .get(holon)
459
502
  .get(lens)
460
503
  .get(key)
461
- .once((data) => {
462
- clearTimeout(timeout);
463
- resolve(data || null);
464
- });
504
+
505
+ }
506
+
507
+ getNodeRef(soul) {
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();
465
517
  });
518
+
519
+ if (parts.length === 0) {
520
+ throw new Error('getNodeRef: Invalid soul format');
521
+ }
522
+
523
+ let ref = this.gun.get(this.appname);
524
+ parts.forEach(part => {
525
+ ref = ref.get(part);
526
+ });
527
+ return ref;
466
528
  }
467
529
 
468
530
  /**
@@ -474,19 +536,16 @@ class HoloSphere {
474
536
  */
475
537
  async deleteNode(holon, lens, key) {
476
538
  if (!holon || !lens || !key) {
477
- console.error('deleteNode: Missing required parameters');
478
- return false;
539
+ throw new Error('deleteNode: Missing required parameters');
479
540
  }
480
-
481
- return new Promise((resolve) => {
541
+ return new Promise((resolve, reject) => {
482
542
  this.gun.get(this.appname)
483
543
  .get(holon)
484
544
  .get(lens)
485
545
  .get(key)
486
546
  .put(null, ack => {
487
547
  if (ack.err) {
488
- console.error('deleteNode: Error deleting node:', ack.err);
489
- resolve(false);
548
+ reject(new Error(ack.err));
490
549
  } else {
491
550
  resolve(true);
492
551
  }
@@ -502,14 +561,12 @@ class HoloSphere {
502
561
  * @returns {Promise<void>}
503
562
  */
504
563
  async putGlobal(tableName, data) {
505
-
506
- return new Promise((resolve, reject) => {
564
+ return new Promise((resolve, reject) => {
565
+ try {
507
566
  if (!tableName || !data) {
508
- reject(new Error('Table name and data are required'));
509
- return;
567
+ throw new Error('Table name and data are required');
510
568
  }
511
569
 
512
-
513
570
  if (data.id) {
514
571
  this.gun.get(this.appname).get(tableName).get(data.id).put(JSON.stringify(data), ack => {
515
572
  if (ack.err) {
@@ -527,8 +584,11 @@ class HoloSphere {
527
584
  }
528
585
  });
529
586
  }
530
- });
531
- }
587
+ } catch (error) {
588
+ reject(error);
589
+ }
590
+ });
591
+ }
532
592
 
533
593
  /**
534
594
  * Retrieves a specific key from a global table.
@@ -537,21 +597,21 @@ class HoloSphere {
537
597
  * @returns {Promise<object|null>} - The parsed data for the key or null if not found.
538
598
  */
539
599
  async getGlobal(tableName, key) {
540
- return new Promise((resolve) => {
541
- this.gun.get(this.appname).get(tableName).get(key).once((data) => {
542
- if (!data) {
543
- resolve(null);
544
- return;
545
- }
546
- try {
547
- const parsed = this.parse(data);
548
- resolve(parsed);
549
- } catch (e) {
550
- resolve(null);
551
- }
552
- });
600
+ return new Promise((resolve) => {
601
+ this.gun.get(this.appname).get(tableName).get(key).once((data) => {
602
+ if (!data) {
603
+ resolve(null);
604
+ return;
605
+ }
606
+ try {
607
+ const parsed = this.parse(data);
608
+ resolve(parsed);
609
+ } catch (e) {
610
+ resolve(null);
611
+ }
553
612
  });
554
- }
613
+ });
614
+ }
555
615
 
556
616
 
557
617
 
@@ -560,31 +620,86 @@ class HoloSphere {
560
620
  * @param {string} tableName - The table name to retrieve data from.
561
621
  * @returns {Promise<object|null>} - The parsed data from the table or null if not found.
562
622
  */
563
- async getAllGlobal(tableName) {
564
- return new Promise(async (resolve, reject) => {
565
- let output = []
566
- let counter = 0
567
- this.gun.get(tableName.toString()).once((data, key) => {
568
- if (data) {
569
- const maplenght = Object.keys(data).length - 1
570
- this.gun.get(tableName.toString()).map().once(async (itemdata, key) => {
571
- counter += 1
572
- if (itemdata) {
573
- let parsed = await this.parse(itemdata)
623
+ // async getAllGlobal(tableName) {
624
+ // return new Promise(async (resolve, reject) => {
625
+ // let output = []
626
+ // let counter = 0
627
+ // this.gun.get(this.appname).get(tableName.toString()).once((data, key) => {
628
+
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);
638
+
639
+ // }
640
+ // }
641
+ // );
642
+ // }
643
+ // )
644
+ // }
645
+ async getAllGlobal(lens) {
646
+ if ( !lens) {
647
+ console.error('getAll: Missing required parameters:', { lens });
648
+ return [];
649
+ }
650
+
651
+ const schema = await this.getSchema(lens);
652
+ if (!schema && this.strict) {
653
+ console.error('getAll: Schema required in strict mode for lens:', lens);
654
+ return [];
655
+ }
656
+
657
+ return new Promise((resolve) => {
658
+ let output = [];
659
+ let counter = 0;
660
+
661
+ this.gun.get(this.appname).get(lens).once((data, key) => {
662
+ if (!data) {
663
+ resolve(output);
664
+ return;
665
+ }
666
+
667
+ const mapLength = Object.keys(data).length - 1;
668
+
669
+ this.gun.get(this.appname).get(lens).map().once(async (itemdata, key) => {
670
+ counter += 1;
671
+ if (itemdata) {
672
+ try {
673
+ const parsed = await this.parse(itemdata);
674
+ if (schema) {
675
+ const valid = this.validator.validate(schema, parsed);
676
+ if (valid) {
677
+ output.push(parsed);
678
+ } else if (this.strict) {
679
+ console.warn('Invalid data removed:', key, this.validator.errors);
680
+ await this.delete(holon, lens, key);
681
+ } else {
682
+ console.warn('Invalid data found:', key, this.validator.errors);
683
+ output.push(parsed);
684
+ }
685
+ } else {
574
686
  output.push(parsed);
575
687
  }
576
-
577
- if (counter == maplenght) {
578
- resolve(output);
688
+ } catch (error) {
689
+ console.error('Error parsing data:', error);
690
+ if (this.strict) {
691
+ await this.delete(holon, lens, key);
579
692
  }
580
693
  }
581
- );
582
- } else resolve(output)
583
- })
584
- }
585
- )
586
- }
694
+ }
587
695
 
696
+ if (counter === mapLength) {
697
+ resolve(output);
698
+ }
699
+ });
700
+ });
701
+ });
702
+ }
588
703
  /**
589
704
  * Deletes a specific key from a global table.
590
705
  * @param {string} tableName - The table name to delete from.
@@ -592,8 +707,8 @@ class HoloSphere {
592
707
  * @returns {Promise<void>}
593
708
  */
594
709
  async deleteGlobal(tableName, key) {
595
- await this.gun.get(this.appname).get(tableName).get(key).put(null)
596
- }
710
+ await this.gun.get(this.appname).get(tableName).get(key).put(null)
711
+ }
597
712
 
598
713
  /**
599
714
  * Deletes an entire global table.
@@ -601,16 +716,17 @@ class HoloSphere {
601
716
  * @returns {Promise<void>}
602
717
  */
603
718
  async deleteAllGlobal(tableName) {
604
-
605
- return new Promise((resolve) => {
606
- this.gun.get(this.appname).get(tableName).map().put(null).once(
607
- (data, key) => this.gun.get(this.appname).get(tableName).get(key).put(null)
608
- )
609
- this.gun.get(this.appname).get(tableName).put({}, ack => {
610
- resolve();
611
- });
612
- });
613
- }
719
+ // return new Promise((resolve) => {
720
+ this.gun.get(this.appname).get(tableName).map().once( (data, key)=> {
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
+ })
726
+ // resolve();
727
+ //});
728
+ // });
729
+ }
614
730
 
615
731
  // ================================ COMPUTE FUNCTIONS ================================
616
732
  /**
@@ -619,43 +735,58 @@ class HoloSphere {
619
735
  * @param {string} lens - The lens to compute.
620
736
  * @param {string} operation - The operation to perform.
621
737
  */
622
- async compute(holon, lens, operation) {
623
-
624
- let res = h3.getResolution(holon);
625
- if(res < 1 || res > 15) return;
626
- console.log(res)
627
- let parent = h3.cellToParent(holon, res - 1);
628
- let siblings = h3.cellToChildren(parent, res);
629
- console.log(holon, parent, siblings, res)
630
-
631
- let content = [];
632
- let promises = [];
633
-
634
- for (let i = 0; i < siblings.length; i++) {
635
- promises.push(new Promise((resolve) => {
636
- let timeout = setTimeout(() => {
637
- console.log(`Timeout for sibling ${i}`);
638
- resolve(); // Resolve the promise to prevent it from hanging
639
- }, 1000); // Timeout of 5 seconds
640
-
641
- this.gun.get(this.appname).get(siblings[i]).get(lens).map().once((data, key) => {
642
- clearTimeout(timeout); // Clear the timeout if data is received
643
- if (data) {
644
- content.push(data.content);
645
- }
646
- resolve(); // Resolve after processing data
647
- });
648
- }));
738
+ async compute(holon, lens, operation, depth = 0, maxDepth = 15) {
739
+ if (!holon || !lens) {
740
+ throw new Error('compute: Missing required parameters');
649
741
  }
650
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
+
651
775
  await Promise.all(promises);
652
- console.log('Content:', content);
653
- let computed = await this.summarize(content.join('\n'))
654
- console.log('Computed:', computed)
655
- let node = await this.gun.get(this.appname).get(parent + '_summary').put({ id: parent + '_summary', content: computed })
656
776
 
657
- this.put(parent, lens, node);
658
- 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
+ }
659
790
  }
660
791
 
661
792
  /**
@@ -664,14 +795,51 @@ class HoloSphere {
664
795
  * @param {string} lens - The lens to clear.
665
796
  */
666
797
  async clearlens(holon, lens) {
667
- let entities = {};
798
+ if (!holon || !lens) {
799
+ throw new Error('clearlens: Missing required parameters');
800
+ }
668
801
 
669
- // Get list out of Gun
670
- this.gun.get(this.appname).get(holon).get(lens).map().once((data, key) => {
671
- //entities = data;
672
- //const id = Object.keys(entities)[0] // since this would be in object form, you can manipulate it as you would like.
673
- this.gun.get(this.appname).get(holon).get(lens).put({ [key]: null })
674
- })
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
+ });
675
843
  }
676
844
 
677
845
 
@@ -722,12 +890,12 @@ class HoloSphere {
722
890
  async upcast(holon, lens, content) {
723
891
  let res = h3.getResolution(holon)
724
892
  if (res == 0) {
725
- await this.putNode(holon, lens, content)
893
+ await this.put(holon, lens, content)
726
894
  return content
727
895
  }
728
896
  else {
729
897
  console.log('Upcasting ', holon, lens, content, res)
730
- await this.putNode(holon, lens, content)
898
+ await this.put(holon, lens, content)
731
899
  let parent = h3.cellToParent(holon, res - 1)
732
900
  return this.upcast(parent, lens, content)
733
901
  }
@@ -802,10 +970,32 @@ class HoloSphere {
802
970
  * @param {string} lens - The lens to subscribe to.
803
971
  * @param {function} callback - The callback to execute on changes.
804
972
  */
805
- subscribe(holon, lens, callback) {
806
- this.gun.get(this.appname).get(holon).get(lens).map().on((data, key) => {
807
- callback(data, key)
808
- })
973
+ async subscribe(holon, lens, callback) {
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);
809
999
  }
810
1000
  }
811
1001