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 +352 -290
- package/package.json +1 -1
- package/test/holosphere.test.js +295 -107
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
|
-
|
|
48
|
-
return false;
|
|
49
|
+
throw new Error('setSchema: Missing required parameters');
|
|
49
50
|
}
|
|
50
51
|
|
|
51
|
-
// Basic schema validation
|
|
52
|
+
// Basic schema validation
|
|
52
53
|
if (!schema.type || typeof schema.type !== 'string') {
|
|
53
|
-
|
|
54
|
-
return false;
|
|
54
|
+
throw new Error('setSchema: Schema must have a type field');
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
if (this.strict) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
58
|
+
const metaSchema = {
|
|
59
|
+
type: 'object',
|
|
60
|
+
required: ['type', 'properties'],
|
|
61
|
+
properties: {
|
|
62
|
+
type: { type: 'string' },
|
|
63
63
|
properties: {
|
|
64
|
-
type:
|
|
65
|
-
|
|
64
|
+
type: 'object',
|
|
65
|
+
additionalProperties: {
|
|
66
66
|
type: 'object',
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
184
|
-
return false;
|
|
165
|
+
throw new Error('put: Missing required parameters');
|
|
185
166
|
}
|
|
186
167
|
|
|
187
168
|
if (!data.id) {
|
|
188
|
-
|
|
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
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
249
|
-
return [];
|
|
219
|
+
throw new Error('getAll: Schema required in strict mode');
|
|
250
220
|
}
|
|
251
221
|
|
|
252
222
|
return new Promise((resolve) => {
|
|
253
|
-
|
|
254
|
-
|
|
223
|
+
const output = new Set();
|
|
224
|
+
const promises = new Set();
|
|
225
|
+
let timeout;
|
|
255
226
|
|
|
256
|
-
|
|
257
|
-
if (
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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
|
-
|
|
292
|
-
|
|
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
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
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
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
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
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
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
|
-
|
|
520
|
-
|
|
521
|
-
|
|
501
|
+
.get(holon)
|
|
502
|
+
.get(lens)
|
|
503
|
+
.get(key)
|
|
522
504
|
|
|
523
505
|
}
|
|
524
506
|
|
|
525
507
|
getNodeRef(soul) {
|
|
526
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
565
|
+
try {
|
|
566
|
+
if (!tableName || !data) {
|
|
567
|
+
throw new Error('Table name and data are required');
|
|
568
|
+
}
|
|
578
569
|
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
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
|
-
//
|
|
639
|
-
//
|
|
640
|
-
//
|
|
641
|
-
//
|
|
642
|
-
//
|
|
643
|
-
//
|
|
644
|
-
|
|
645
|
-
//
|
|
646
|
-
//
|
|
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
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
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
|
-
|
|
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
|
-
|
|
785
|
-
|
|
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
|
-
|
|
798
|
+
if (!holon || !lens) {
|
|
799
|
+
throw new Error('clearlens: Missing required parameters');
|
|
800
|
+
}
|
|
795
801
|
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
934
|
-
|
|
935
|
-
callback(
|
|
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
|
|