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.d.ts +43 -48
- package/holosphere.js +459 -269
- package/package.json +4 -2
- package/test/holosphere.test.js +295 -107
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: ['
|
|
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
|
-
|
|
47
|
-
return false;
|
|
49
|
+
throw new Error('setSchema: Missing required parameters');
|
|
48
50
|
}
|
|
49
51
|
|
|
50
|
-
// Basic schema validation
|
|
52
|
+
// Basic schema validation
|
|
51
53
|
if (!schema.type || typeof schema.type !== 'string') {
|
|
52
|
-
|
|
53
|
-
return false;
|
|
54
|
+
throw new Error('setSchema: Schema must have a type field');
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
if (this.strict) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
const metaSchema = {
|
|
59
|
+
type: 'object',
|
|
60
|
+
required: ['type', 'properties'],
|
|
61
|
+
properties: {
|
|
62
|
+
type: { type: 'string' },
|
|
62
63
|
properties: {
|
|
63
|
-
type:
|
|
64
|
-
|
|
64
|
+
type: 'object',
|
|
65
|
+
additionalProperties: {
|
|
65
66
|
type: 'object',
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
-
|
|
183
|
-
return false;
|
|
165
|
+
throw new Error('put: Missing required parameters');
|
|
184
166
|
}
|
|
185
167
|
|
|
186
168
|
if (!data.id) {
|
|
187
|
-
|
|
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
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
248
|
-
return [];
|
|
219
|
+
throw new Error('getAll: Schema required in strict mode');
|
|
249
220
|
}
|
|
250
221
|
|
|
251
222
|
return new Promise((resolve) => {
|
|
252
|
-
|
|
253
|
-
|
|
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
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
if (
|
|
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
|
|
|
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 =
|
|
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
|
|
364
|
-
|
|
365
|
-
|
|
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
|
-
|
|
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
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
462
|
-
|
|
463
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
564
|
+
return new Promise((resolve, reject) => {
|
|
565
|
+
try {
|
|
507
566
|
if (!tableName || !data) {
|
|
508
|
-
|
|
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
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
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
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
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
|
-
|
|
578
|
-
|
|
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
|
-
|
|
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
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
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
|
-
|
|
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
|
-
|
|
658
|
-
|
|
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
|
-
|
|
798
|
+
if (!holon || !lens) {
|
|
799
|
+
throw new Error('clearlens: Missing required parameters');
|
|
800
|
+
}
|
|
668
801
|
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
807
|
-
|
|
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
|
|