holosphere 1.0.8 → 1.1.1

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,26 +3,30 @@ 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.
9
10
  * @param {string} appname - The name of the application.
11
+ * @param {boolean} strict - Whether to enforce strict schema validation.
10
12
  * @param {string|null} openaikey - The OpenAI API key.
11
13
  */
12
- constructor(appname, openaikey = null) {
13
- this.validator = new Ajv2019({ allErrors: false, strict: false });
14
+ constructor(appname, strict = false, openaikey = null) {
15
+ this.appname = appname
16
+ this.strict = strict;
17
+ this.validator = new Ajv2019({
18
+ allErrors: true,
19
+ strict: false, // Keep this false to avoid Ajv strict mode issues
20
+ validateSchema: true // Always validate schemas
21
+ });
14
22
  this.gun = Gun({
15
- peers: ['http://gun.holons.io','https://59.src.eco/gun'],
23
+ peers: ['https://gun.holons.io', 'https://59.src.eco/gun'],
16
24
  axe: false,
17
25
  // uuid: (content) => { // generate a unique id for each node
18
26
  // console.log('uuid', content);
19
27
  // return content;}
20
28
  });
21
29
 
22
- this.gun = this.gun.get(appname)
23
- this.users = {}; // Initialize users
24
- this.holonagonVotes = {}; // Initialize holonagonVotes
25
-
26
30
  if (openaikey != null) {
27
31
  this.openai = new OpenAI({
28
32
  apiKey: openaikey,
@@ -30,6 +34,8 @@ class HoloSphere {
30
34
  }
31
35
  }
32
36
 
37
+ // ================================ SCHEMA FUNCTIONS ================================
38
+
33
39
  /**
34
40
  * Sets the JSON schema for a specific lens.
35
41
  * @param {string} lens - The lens identifier.
@@ -37,16 +43,84 @@ class HoloSphere {
37
43
  * @returns {Promise} - Resolves when the schema is set.
38
44
  */
39
45
  async setSchema(lens, schema) {
40
- return new Promise((resolve, reject) => {
41
- this.gun.get(lens).get('schema').put(JSON.stringify(schema), ack => {
42
- if (ack.err) {
43
- resolve(new Error('Failed to add schema: ' + ack.err));
44
- } else {
45
- console.log('Schema added successfully under lens:', lens);
46
- resolve(ack);
46
+ if (!lens || !schema) {
47
+ console.error('setSchema: Missing required parameters');
48
+ return false;
49
+ }
50
+
51
+ // Basic schema validation - check for required fields
52
+ if (!schema.type || typeof schema.type !== 'string') {
53
+ console.error('setSchema: Schema must have a type field');
54
+ return false;
55
+ }
56
+
57
+ if (this.strict) {
58
+ try {
59
+ // Validate schema against JSON Schema meta-schema
60
+ const metaSchema = {
61
+ type: 'object',
62
+ required: ['type', 'properties'],
63
+ properties: {
64
+ type: { type: 'string' },
65
+ properties: {
66
+ type: 'object',
67
+ additionalProperties: {
68
+ type: 'object',
69
+ required: ['type'],
70
+ properties: {
71
+ type: { type: 'string' }
72
+ }
73
+ }
74
+ },
75
+ required: {
76
+ type: 'array',
77
+ items: { type: 'string' }
78
+ }
79
+ }
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;
47
86
  }
48
- })
49
- })
87
+
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
+ }
93
+
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;
101
+ }
102
+ }
103
+
104
+ return new Promise((resolve) => {
105
+ try {
106
+ const schemaString = JSON.stringify(schema);
107
+ this.gun.get(this.appname)
108
+ .get(lens)
109
+ .get('schema')
110
+ .put(schemaString, ack => {
111
+ if (ack.err) {
112
+ console.error('Failed to add schema:', ack.err);
113
+ resolve(false);
114
+ } else {
115
+ console.log('Schema added successfully for lens:', lens);
116
+ resolve(true);
117
+ }
118
+ });
119
+ } catch (error) {
120
+ console.error('setSchema: Error stringifying schema:', error);
121
+ resolve(false);
122
+ }
123
+ });
50
124
  }
51
125
 
52
126
  /**
@@ -55,236 +129,455 @@ class HoloSphere {
55
129
  * @returns {Promise<object|null>} - The retrieved schema or null if not found.
56
130
  */
57
131
  async getSchema(lens) {
132
+ if (!lens) {
133
+ console.error('getSchema: Missing lens parameter');
134
+ return null;
135
+ }
136
+
58
137
  return new Promise((resolve) => {
59
- this.gun.get(lens).get('schema').once(data => {
60
- if (data) {
61
- let parsed;
62
- try {
63
- parsed = JSON.parse(data);
138
+ this.gun.get(this.appname)
139
+ .get(lens)
140
+ .get('schema')
141
+ .once(data => {
142
+ if (!data) {
143
+ resolve(null);
144
+ return;
64
145
  }
65
- catch (e) {
66
- resolve(null)
146
+
147
+ 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 =>
155
+ typeof v === 'string' && v.includes('"type":'));
156
+ if (schemaStr) {
157
+ resolve(JSON.parse(schemaStr));
158
+ } else {
159
+ resolve(null);
160
+ }
161
+ } else {
162
+ resolve(null);
163
+ }
164
+ } catch (error) {
165
+ console.error('getSchema: Error parsing schema:', error);
166
+ resolve(null);
67
167
  }
68
- resolve(parsed);
69
- } else {
70
- resolve(null);
71
- }
72
- })
73
- })
74
- }
75
- /**
76
- * Deletes a specific tag from a given ID.
77
- * @param {string} id - The identifier from which to delete the tag.
78
- * @param {string} tag - The tag to delete.
79
- */
80
- async delete(id, tag) {
81
- await this.gun.get(id).get(tag).put(null)
168
+ });
169
+ });
82
170
  }
83
171
 
172
+ // ================================ CONTENT FUNCTIONS ================================
173
+
84
174
  /**
85
175
  * Stores content in the specified holon and lens.
86
176
  * @param {string} holon - The holon identifier.
87
177
  * @param {string} lens - The lens under which to store the content.
88
- * @param {object} content - The content to store.
178
+ * @param {object} data - The data to store.
179
+ * @returns {Promise<boolean>} - Returns true if successful, false if there was an error
89
180
  */
90
- async put(holon, lens, content) {
91
- if (!holon || !lens || !content) return;
92
- console.error('Error in put:', holon, lens, content);
93
- // Retrieve the schema for the lens
94
- let schema = await this.getSchema(lens)
181
+ async put(holon, lens, data) {
182
+ if (!holon || !lens || !data) {
183
+ console.error('put: Missing required parameters:', { holon, lens, data });
184
+ return false;
185
+ }
186
+
187
+ if (!data.id) {
188
+ console.error('put: Data must have an id field');
189
+ return false;
190
+ }
191
+
192
+ // Strict validation of schema and data
193
+ const schema = await this.getSchema(lens);
95
194
  if (schema) {
96
- // Validate the content against the schema
97
- const valid = this.validator.validate(schema, content);
98
- if (!valid) {
99
- console.error('Not committing invalid content:', this.validator.errors);
100
- return null;
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;
101
205
  }
206
+ } else if (this.strict) {
207
+ console.error('put: Schema required in strict mode for lens:', lens);
208
+ return false;
102
209
  }
103
210
 
104
- // Create a node for the content
105
- const payload = JSON.stringify(content);
211
+ return new Promise((resolve) => {
212
+ try {
213
+ const payload = JSON.stringify(data);
214
+
215
+ this.gun.get(this.appname)
216
+ .get(holon)
217
+ .get(lens)
218
+ .get(data.id)
219
+ .put(payload, ack => {
220
+ if (ack.err) {
221
+ console.error("Error adding data to GunDB:", ack.err);
222
+ resolve(false);
223
+ } else {
224
+ resolve(true);
225
+ }
226
+ });
227
+ } catch (error) {
228
+ console.error('Error in put operation:', error);
229
+ resolve(false);
230
+ }
231
+ });
232
+ }
106
233
 
107
- let noderef;
234
+ /**
235
+ * Retrieves content from the specified holon and lens.
236
+ * @param {string} holon - The holon identifier.
237
+ * @param {string} lens - The lens from which to retrieve content.
238
+ * @returns {Promise<Array<object>>} - The retrieved content.
239
+ */
240
+ async getAll(holon, lens) {
241
+ if (!holon || !lens) {
242
+ console.error('getAll: Missing required parameters:', { holon, lens });
243
+ return [];
244
+ }
108
245
 
109
- if (content.id) { //use the user-defined id. Important to be able to send updates using put
110
- noderef = this.gun.get(lens).get(content.id).put(payload)
111
- this.gun.get(holon.toString()).get(lens).get(content.id).put(payload)
112
- } else { // create a content-addressable reference like IPFS. Note: no updates possible using put
113
- const hashBuffer = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(payload));
114
- const hashArray = Array.from(new Uint8Array(hashBuffer));
115
- const hashholon = hashArray.map(byte => byte.toString(16).padStart(2, "0")).join("");
116
- noderef = this.gun.get(lens).get(hashholon).put(payload)
117
- this.gun.get(holon.toString()).get(lens).get(hashholon).put(payload)
246
+ const schema = await this.getSchema(lens);
247
+ if (!schema && this.strict) {
248
+ console.error('getAll: Schema required in strict mode for lens:', lens);
249
+ return [];
118
250
  }
119
251
 
120
- }
252
+ return new Promise((resolve) => {
253
+ let output = [];
254
+ let counter = 0;
121
255
 
122
- async putNode(holon, lens, node) {
123
- this.gun.get(holon).get(lens).set(node)
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) {
286
+ await this.delete(holon, lens, key);
287
+ }
288
+ }
289
+ }
290
+
291
+ if (counter === mapLength) {
292
+ resolve(output);
293
+ }
294
+ });
295
+ });
296
+ });
124
297
  }
125
298
 
126
- async parse(data) {
127
- let parsed = {};
299
+ /**
300
+ * Parses data from GunDB, handling various data formats and references.
301
+ * @param {*} data - The data to parse, could be a string, object, or GunDB reference.
302
+ * @returns {Promise<object>} - The parsed data.
303
+ */
304
+ 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
+ }
128
310
 
129
- if (typeof data === 'object' && data !== null) {
130
- if (data._ && data._["#"]) {
311
+ if (typeof rawData === 'object' && rawData !== null) {
312
+ if (rawData._ && rawData._["#"]) {
313
+ console.log('Parsing object reference:', rawData._['#']);
131
314
  // If the data is a reference, fetch the actual content
132
- let query = data._['#'].split('/');
133
- let holon = query[1];
134
- let lens = query[2];
135
- let key = query[3];
136
- parsed = await this.getKey(holon, lens, key);
137
- } else if (data._ && data._['>']) {
138
- // This might be a gun node, try to get its value
139
- const nodeValue = Object.values(data).find(v => typeof v !== 'object' && v !== '_');
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 !== '_');
140
324
  if (nodeValue) {
141
325
  try {
142
- parsed = JSON.parse(nodeValue);
326
+ parsedData = JSON.parse(nodeValue);
143
327
  } catch (e) {
144
328
  console.log('Invalid JSON in node value:', nodeValue);
145
- parsed = nodeValue; // return the raw data
329
+ parsedData = nodeValue; // return the raw data
146
330
  }
147
331
  } else {
148
- console.log('Unable to parse gun node:', data);
149
- parsed = data; // return the original data
332
+ console.log('Unable to parse GunDB node:', rawData);
333
+ parsedData = rawData; // return the original data
150
334
  }
151
335
  } else {
152
- // Treat it as regular data
153
- parsed = data;
336
+ // Treat it as object data
337
+ console.log('Parsing object data:', rawData);
338
+ parsedData = rawData;
154
339
  }
155
340
  } else {
156
341
  // If it's not an object, try parsing it as JSON
157
342
  try {
158
- parsed = JSON.parse(data);
343
+ 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
+ }
159
349
  } catch (e) {
160
- console.log('Invalid JSON:', data);
161
- parsed = data; // return the raw data
350
+ console.log('Failed to parse, returning raw data', e);
351
+ parsedData = rawData; // return the raw data
162
352
  }
163
353
  }
164
354
 
165
- return parsed;
355
+ return parsedData;
166
356
  }
167
357
 
168
358
  /**
169
- * Retrieves content from the specified holon and lens.
359
+ * Retrieves a specific key from the specified holon and lens.
170
360
  * @param {string} holon - The holon identifier.
171
- * @param {string} lens - The lens from which to retrieve content.
172
- * @returns {Promise<Array<object>>} - The retrieved content.
361
+ * @param {string} lens - The lens from which to retrieve the key.
362
+ * @param {string} key - The specific key to retrieve.
363
+ * @returns {Promise<object|null>} - The retrieved content or null if not found.
173
364
  */
174
- async get(holon, lens) {
175
- if (!holon || !lens) {
176
- console.log('Wrong get:', holon, lens)
177
- return;
178
- }
179
- // Wrap the gun operation in a promise
180
- //retrieve lens schema
181
- const schema = await this.getSchema(lens);
182
-
183
- if (!schema) {
184
- console.log('The schema for "' + lens + '" is not defined');
185
- // return null; // No schema found, return null if strict about it
365
+ async get(holon, lens, key) {
366
+ if (!holon || !lens || !key) {
367
+ console.error('get: Missing required parameters:', { holon, lens, key });
368
+ return null;
186
369
  }
187
370
 
188
- return new Promise(async (resolve, reject) => {
189
- let output = []
190
- let counter = 0
191
- this.gun.get(holon.toString()).get(lens).once((data, key) => {
192
- if (data) {
193
- const maplenght = Object.keys(data).length - 1
194
- console.log('Map length:', maplenght)
195
- this.gun.get(holon.toString()).get(lens).map().once(async (itemdata, key) => {
196
- counter += 1
197
- if (itemdata) {
198
- let parsed = await this.parse (itemdata)
199
-
371
+ // Get schema for validation
372
+ const schema = await this.getSchema(lens);
200
373
 
201
- if (schema) {
202
- let valid = this.validator.validate(schema, parsed);
203
- if (!valid || parsed == null || parsed == undefined) {
204
- console.log('Removing Invalid content:', this.validator.errors);
205
- this.gun.get(holon).get(lens).get(key).put(null);
374
+ return new Promise((resolve) => {
375
+ let timeout = setTimeout(() => {
376
+ console.warn('get: Operation timed out');
377
+ resolve(null);
378
+ }, 5000); // 5 second timeout
379
+
380
+ this.gun.get(this.appname)
381
+ .get(holon)
382
+ .get(lens)
383
+ .get(key)
384
+ .once((data,key) => {
385
+ clearTimeout(timeout);
386
+
387
+ if (!data) {
388
+ resolve(null);
389
+ return;
390
+ }
206
391
 
207
- } else {
208
- output.push(parsed);
392
+ try {
393
+ const parsed = this.parse(data);
394
+
395
+ // Validate against schema if one exists
396
+ if (schema) {
397
+ const valid = this.validator.validate(schema, parsed);
398
+ if (!valid) {
399
+ console.error('get: Invalid data according to schema:', this.validator.errors);
400
+ if (this.strict) {
401
+ resolve(null);
402
+ return;
209
403
  }
210
404
  }
211
- else {
212
- output.push(parsed);
213
- }
214
- }
215
-
216
- if (counter == maplenght) {
217
- resolve(output);
218
405
  }
406
+ resolve(parsed);
407
+ } catch (error) {
408
+ console.error('Error parsing data:', error);
409
+ resolve(null);
219
410
  }
220
- );
221
- } else resolve(output)
222
- })
411
+ });
412
+ });
413
+ }
414
+
415
+ /**
416
+ * Deletes a specific key from a given holon and lens.
417
+ * @param {string} holon - The holon identifier.
418
+ * @param {string} lens - The lens from which to delete the key.
419
+ * @param {string} key - The specific key to delete.
420
+ */
421
+ async delete(holon, lens, key) {
422
+ 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
+ });
430
+ });
431
+ }
432
+
433
+ /**
434
+ * Deletes all keys from a given holon and lens.
435
+ * @param {string} holon - The holon identifier.
436
+ * @param {string} lens - The lens from which to delete all keys.
437
+ * @returns {Promise<boolean>} - Returns true if successful, false if there was an error
438
+ */
439
+ async deleteAll(holon, lens) {
440
+ if (!holon || !lens) {
441
+ console.error('deleteAll: Missing holon or lens parameter');
442
+ return false;
223
443
  }
224
- );
444
+
445
+ return new Promise((resolve) => {
446
+ let deletionPromises = [];
447
+
448
+ // First get all the data to find keys to delete
449
+ this.gun.get(this.appname).get(holon).get(lens).once((data) => {
450
+ if (!data) {
451
+ resolve(true); // Nothing to delete
452
+ return;
453
+ }
454
+
455
+ // Get all keys except Gun's metadata key '_'
456
+ const keys = Object.keys(data).filter(key => key !== '_');
457
+
458
+ // Create deletion promises for each key
459
+ keys.forEach(key => {
460
+ deletionPromises.push(
461
+ new Promise((resolveDelete) => {
462
+ this.gun.get(this.appname).get(holon).get(lens).get(key).put(null, ack => {
463
+ resolveDelete(!!ack.ok); // Convert to boolean
464
+ });
465
+ })
466
+ );
467
+ });
468
+
469
+ // Wait for all deletions to complete
470
+ Promise.all(deletionPromises)
471
+ .then(results => {
472
+ const allSuccessful = results.every(result => result === true);
473
+ resolve(allSuccessful);
474
+ })
475
+ .catch(error => {
476
+ console.error('Error in deleteAll:', error);
477
+ resolve(false);
478
+ });
479
+ });
480
+ });
225
481
  }
226
482
 
227
- /**
228
- * Retrieves a specific key from the specified holon and lens.
483
+ // ================================ NODE FUNCTIONS ================================
484
+
485
+
486
+ /**
487
+ * Stores a specific gun node in a given holon and lens.
229
488
  * @param {string} holon - The holon identifier.
230
- * @param {string} lens - The lens from which to retrieve the key.
231
- * @param {string} key - The specific key to retrieve.
232
- * @returns {Promise<object|null>} - The retrieved content or null if not found.
489
+ * @param {string} lens - The lens under which to store the node.
490
+ * @param {object} node - The node to store.
233
491
  */
234
- async getKey(holon, lens, key) {
492
+ async putNode(holon, lens, node) {
235
493
  return new Promise((resolve) => {
236
- // Use Gun to get the data
237
- this.gun.get(holon).get(lens).get(key).once((data, key) => {
238
- if (data) {
239
- console.log('Data getting parsed:', data)
240
- try {
241
- let parsed = JSON.parse(data); // Resolve the promise with the data if data is found
242
- resolve(parsed);
243
- }
244
- catch (e) {
245
- resolve(data)
246
- }
247
-
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);
248
498
  } else {
249
- resolve(null); // Reject the promise if no data is found
499
+ resolve(true);
250
500
  }
251
501
  });
252
502
  });
253
-
254
503
  }
255
504
 
256
505
  /**
257
506
  * Retrieves a specific gun node from the specified holon and lens.
258
507
  * @param {string} holon - The holon identifier.
259
- * @param {string} lens - The lens from which to retrieve the key.
508
+ * @param {string} lens - The lens identifier.
260
509
  * @param {string} key - The specific key to retrieve.
261
- * @returns {Promise<object|null>} - The retrieved content or null if not found.
510
+ * @returns {Promise<object|null>} - The retrieved node or null if not found.
262
511
  */
263
- getNode(holon, lens, key) {
264
- // Use Gun to get the data
265
- return this.gun.get(holon).get(lens).get(key)
512
+ getNode(holon, lens, key) {
513
+ if (!holon || !lens || !key) {
514
+ console.error('getNode: Missing required parameters');
515
+ return null;
516
+ }
517
+
518
+ return this.gun.get(this.appname)
519
+ .get(holon)
520
+ .get(lens)
521
+ .get(key)
522
+
266
523
  }
267
524
 
268
- //GLOBAL FUNCTIONS
269
- async deleteNode(nodeId, tag) {
270
- await this.gun.get(nodeId).get(tag).put(null)
525
+ getNodeRef(soul) {
526
+ const parts = soul.split('/');
527
+ let ref = this.gun.get(this.appname);
528
+ parts.forEach(part => {
529
+ ref = ref.get(part);
530
+ });
531
+ return ref;
271
532
  }
533
+
534
+ /**
535
+ * Deletes a specific gun node from a given holon and lens.
536
+ * @param {string} holon - The holon identifier.
537
+ * @param {string} lens - The lens identifier.
538
+ * @param {string} key - The key of the node to delete.
539
+ * @returns {Promise<boolean>} - Returns true if successful
540
+ */
541
+ async deleteNode(holon, lens, key) {
542
+ if (!holon || !lens || !key) {
543
+ console.error('deleteNode: Missing required parameters');
544
+ return false;
545
+ }
546
+
547
+ return new Promise((resolve) => {
548
+ this.gun.get(this.appname)
549
+ .get(holon)
550
+ .get(lens)
551
+ .get(key)
552
+ .put(null, ack => {
553
+ if (ack.err) {
554
+ console.error('deleteNode: Error deleting node:', ack.err);
555
+ resolve(false);
556
+ } else {
557
+ resolve(true);
558
+ }
559
+ });
560
+ });
561
+ }
562
+
272
563
  // ================================ GLOBAL FUNCTIONS ================================
273
564
  /**
274
565
  * Stores data in a global (non-holon-specific) table.
275
- * @param {string} table - The table name to store data in.
566
+ * @param {string} tableName - The table name to store data in.
276
567
  * @param {object} data - The data to store. If it has an 'id' field, it will be used as the key.
277
568
  * @returns {Promise<void>}
278
569
  */
279
570
  async putGlobal(tableName, data) {
571
+
280
572
  return new Promise((resolve, reject) => {
281
573
  if (!tableName || !data) {
282
574
  reject(new Error('Table name and data are required'));
283
575
  return;
284
576
  }
285
577
 
578
+
286
579
  if (data.id) {
287
- this.gun.get(tableName).get(data.id).put(JSON.stringify(data), ack => {
580
+ this.gun.get(this.appname).get(tableName).get(data.id).put(JSON.stringify(data), ack => {
288
581
  if (ack.err) {
289
582
  reject(new Error(ack.err));
290
583
  } else {
@@ -292,7 +585,7 @@ class HoloSphere {
292
585
  }
293
586
  });
294
587
  } else {
295
- this.gun.get(tableName).put(JSON.stringify(data), ack => {
588
+ this.gun.get(this.appname).get(tableName).put(JSON.stringify(data), ack => {
296
589
  if (ack.err) {
297
590
  reject(new Error(ack.err));
298
591
  } else {
@@ -303,21 +596,21 @@ class HoloSphere {
303
596
  });
304
597
  }
305
598
 
306
- /**
307
- * Retrieves a specific key from a global table.
308
- * @param {string} tableName - The table name to retrieve from.
309
- * @param {string} key - The key to retrieve.
310
- * @returns {Promise<object|null>} - The parsed data for the key or null if not found.
311
- */
312
- async getGlobal(tableName, key) {
599
+ /**
600
+ * Retrieves a specific key from a global table.
601
+ * @param {string} tableName - The table name to retrieve from.
602
+ * @param {string} key - The key to retrieve.
603
+ * @returns {Promise<object|null>} - The parsed data for the key or null if not found.
604
+ */
605
+ async getGlobal(tableName, key) {
313
606
  return new Promise((resolve) => {
314
- this.gun.get(tableName).get(key).once((data) => {
607
+ this.gun.get(this.appname).get(tableName).get(key).once((data) => {
315
608
  if (!data) {
316
609
  resolve(null);
317
610
  return;
318
611
  }
319
612
  try {
320
- const parsed = JSON.parse(data);
613
+ const parsed = this.parse(data);
321
614
  resolve(parsed);
322
615
  } catch (e) {
323
616
  resolve(null);
@@ -326,43 +619,124 @@ class HoloSphere {
326
619
  });
327
620
  }
328
621
 
622
+
623
+
329
624
  /**
330
625
  * Retrieves all data from a global table.
331
626
  * @param {string} tableName - The table name to retrieve data from.
332
627
  * @returns {Promise<object|null>} - The parsed data from the table or null if not found.
333
628
  */
334
- async getAllGlobal(tableName) {
629
+ // async getAllGlobal(tableName) {
630
+ // return new Promise(async (resolve, reject) => {
631
+ // let output = []
632
+ // let counter = 0
633
+ // 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
+
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);
647
+
648
+ // }
649
+ // }
650
+ // );
651
+ // } else resolve(output)
652
+ // })
653
+ // }
654
+ // )
655
+ // }
656
+ async getAllGlobal(lens) {
657
+ if ( !lens) {
658
+ console.error('getAll: Missing required parameters:', { lens });
659
+ return [];
660
+ }
661
+
662
+ const schema = await this.getSchema(lens);
663
+ if (!schema && this.strict) {
664
+ console.error('getAll: Schema required in strict mode for lens:', lens);
665
+ return [];
666
+ }
667
+
335
668
  return new Promise((resolve) => {
336
- this.gun.get(tableName).once((data) => {
669
+ let output = [];
670
+ let counter = 0;
671
+
672
+ this.gun.get(this.appname).get(lens).once((data, key) => {
337
673
  if (!data) {
338
- resolve(null);
674
+ resolve(output);
339
675
  return;
340
676
  }
341
- try {
342
- const parsed = JSON.parse(data);
343
- resolve(parsed);
344
- } catch (e) {
345
- resolve(null);
346
- }
677
+
678
+ const mapLength = Object.keys(data).length - 1;
679
+
680
+ this.gun.get(this.appname).get(lens).map().once(async (itemdata, key) => {
681
+ counter += 1;
682
+ if (itemdata) {
683
+ try {
684
+ const parsed = await this.parse(itemdata);
685
+ if (schema) {
686
+ const valid = this.validator.validate(schema, parsed);
687
+ if (valid) {
688
+ output.push(parsed);
689
+ } else if (this.strict) {
690
+ console.warn('Invalid data removed:', key, this.validator.errors);
691
+ await this.delete(holon, lens, key);
692
+ } else {
693
+ console.warn('Invalid data found:', key, this.validator.errors);
694
+ output.push(parsed);
695
+ }
696
+ } else {
697
+ output.push(parsed);
698
+ }
699
+ } catch (error) {
700
+ console.error('Error parsing data:', error);
701
+ if (this.strict) {
702
+ await this.delete(holon, lens, key);
703
+ }
704
+ }
705
+ }
706
+
707
+ if (counter === mapLength) {
708
+ resolve(output);
709
+ }
710
+ });
347
711
  });
348
712
  });
349
713
  }
350
-
351
-
714
+ /**
715
+ * Deletes a specific key from a global table.
716
+ * @param {string} tableName - The table name to delete from.
717
+ * @param {string} key - The key to delete.
718
+ * @returns {Promise<void>}
719
+ */
720
+ async deleteGlobal(tableName, key) {
721
+ await this.gun.get(this.appname).get(tableName).get(key).put(null)
722
+ }
352
723
 
353
724
  /**
354
725
  * Deletes an entire global table.
355
- * @param {string} table - The table name to delete.
726
+ * @param {string} tableName - The table name to delete.
356
727
  * @returns {Promise<void>}
357
728
  */
358
729
  async deleteAllGlobal(tableName) {
359
-
360
- return new Promise((resolve) => {
361
- this.gun.get(tableName).map().put(null)
362
- this.gun.get(tableName).put(null, ack => {
363
- resolve();
364
- });
365
- });
730
+ // return new Promise((resolve) => {
731
+ 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
+ })
737
+ // resolve();
738
+ //});
739
+ // });
366
740
  }
367
741
 
368
742
  // ================================ COMPUTE FUNCTIONS ================================
@@ -391,7 +765,7 @@ class HoloSphere {
391
765
  resolve(); // Resolve the promise to prevent it from hanging
392
766
  }, 1000); // Timeout of 5 seconds
393
767
 
394
- this.gun.get(siblings[i]).get(lens).map().once((data, key) => {
768
+ this.gun.get(this.appname).get(siblings[i]).get(lens).map().once((data, key) => {
395
769
  clearTimeout(timeout); // Clear the timeout if data is received
396
770
  if (data) {
397
771
  content.push(data.content);
@@ -405,7 +779,7 @@ class HoloSphere {
405
779
  console.log('Content:', content);
406
780
  let computed = await this.summarize(content.join('\n'))
407
781
  console.log('Computed:', computed)
408
- let node = await this.gun.get(parent + '_summary').put({ id: parent + '_summary', content: computed })
782
+ let node = await this.gun.get(this.appname).get(parent + '_summary').put({ id: parent + '_summary', content: computed })
409
783
 
410
784
  this.put(parent, lens, node);
411
785
  this.compute(parent, lens, operation)
@@ -420,10 +794,10 @@ class HoloSphere {
420
794
  let entities = {};
421
795
 
422
796
  // Get list out of Gun
423
- this.gun.get(holon).get(lens).map().once((data, key) => {
797
+ this.gun.get(this.appname).get(holon).get(lens).map().once((data, key) => {
424
798
  //entities = data;
425
799
  //const id = Object.keys(entities)[0] // since this would be in object form, you can manipulate it as you would like.
426
- this.gun.get(holon).get(lens).put({ [key]: null })
800
+ this.gun.get(this.appname).get(holon).get(lens).put({ [key]: null })
427
801
  })
428
802
  }
429
803
 
@@ -555,92 +929,12 @@ class HoloSphere {
555
929
  * @param {string} lens - The lens to subscribe to.
556
930
  * @param {function} callback - The callback to execute on changes.
557
931
  */
558
- subscribe(holon, lens, callback) {
559
- this.gun.get(holon).get(lens).map().on((data, key) => {
560
- callback(data, key)
932
+ 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)
561
936
  })
562
937
  }
563
-
564
- // ================================ GOVERNANCE FUNCTIONS ================================
565
- /**
566
- * Retrieves the final vote for a user, considering delegations.
567
- * @param {string} userId - The user's identifier.
568
- * @param {string} topic - The voting topic.
569
- * @param {object} votes - The current votes.
570
- * @param {Set<string>} [visited=new Set()] - Set of visited users to prevent cycles.
571
- * @returns {string|null} - The final vote or null if not found.
572
- */
573
- getFinalVote(userId, topic, votes, visited = new Set()) {
574
- if (this.users[userId]) { // Added this.users
575
- if (visited.has(userId)) {
576
- return null; // Avoid circular delegations
577
- }
578
- visited.add(userId);
579
-
580
- const delegation = this.users[userId].delegations[topic];
581
- if (delegation && votes[delegation] === undefined) {
582
- return this.getFinalVote(delegation, topic, votes, visited); // Prefixed with this
583
- }
584
-
585
- return votes[userId] !== undefined ? votes[userId] : null;
586
- }
587
- return null;
588
- }
589
-
590
- /**
591
- * Aggregates votes for a specific holon and topic.
592
- * @param {string} holonId - The holon identifier.
593
- * @param {string} topic - The voting topic.
594
- * @returns {object} - Aggregated vote counts.
595
- */
596
- aggregateVotes(holonId, topic) {
597
- if (!this.holonagonVotes[holonId] || !this.holonagonVotes[holonId][topic]) {
598
- return {}; // Handle undefined votes
599
- }
600
- const votes = this.holonagonVotes[holonId][topic];
601
- const aggregatedVotes = {};
602
-
603
- Object.keys(votes).forEach(userId => {
604
- const finalVote = this.getFinalVote(userId, topic, votes); // Prefixed with this
605
- if (finalVote !== null) {
606
- aggregatedVotes[finalVote] = (aggregatedVotes[finalVote] || 0) + 1;
607
- }
608
- });
609
-
610
- return aggregatedVotes;
611
- }
612
-
613
- /**
614
- * Delegates a user's vote to another user.
615
- * @param {string} userId - The user's identifier.
616
- * @param {string} topic - The voting topic.
617
- * @param {string} delegateTo - The user to delegate the vote to.
618
- */
619
- async delegateVote(userId, topic, delegateTo) {
620
- const response = await fetch('/delegate', {
621
- method: 'POST',
622
- headers: { 'Content-Type': 'application/json' },
623
- body: JSON.stringify({ userId, topic, delegateTo })
624
- });
625
- alert(await response.text());
626
- }
627
-
628
- /**
629
- * Casts a vote for a user on a specific topic and holon.
630
- * @param {string} userId - The user's identifier.
631
- * @param {string} holonId - The holon identifier.
632
- * @param {string} topic - The voting topic.
633
- * @param {string} vote - The vote choice.
634
- */
635
- async vote(userId, holonId, topic, vote) {
636
- const response = await fetch('/vote', {
637
- method: 'POST',
638
- headers: { 'Content-Type': 'application/json' },
639
- body: JSON.stringify({ userId, holonId, topic, vote })
640
- });
641
- alert(await response.text());
642
- }
643
-
644
938
  }
645
939
 
646
940
  export default HoloSphere;