holosphere 1.1.10 → 1.1.12
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/.cursor/rules/futura.mdc +55 -0
- package/FEDERATION.md +17 -17
- package/compute.js +289 -0
- package/content.js +946 -0
- package/examples/federation.js +98 -90
- package/examples/hologram-updates-example.js +106 -0
- package/examples/{references.js → holograms.js} +49 -51
- package/federation.js +427 -245
- package/global.js +725 -0
- package/hologram.js +156 -0
- package/holosphere.d.ts +109 -7
- package/holosphere.js +172 -1565
- package/node.js +240 -0
- package/package.json +2 -5
- package/schema.js +132 -0
- package/test/auth.test.js +55 -37
- package/test/delete.test.js +15 -12
- package/test/federation.test.js +179 -0
- package/test/hologram-deletion.test.js +197 -0
- package/test/hologram-updates-return.test.js +166 -0
- package/test/hologram-updates.test.js +143 -0
- package/test/hologram.test.js +316 -0
- package/test/meta-strip.test.js +159 -0
- package/test/parent-propagation.test.js +138 -0
- package/test/subscription.test.js +105 -70
- package/utils.js +290 -0
- package/test/reference.test.js +0 -211
package/holosphere.js
CHANGED
|
@@ -1,35 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module holosphere
|
|
3
|
+
* @version 1.1.12
|
|
4
|
+
* @description Holonic Geospatial Communication Infrastructure
|
|
5
|
+
* @author Roberto Valenti
|
|
6
|
+
* @license GPL-3.0-or-later
|
|
7
|
+
*/
|
|
8
|
+
|
|
1
9
|
import * as h3 from 'h3-js';
|
|
2
10
|
import OpenAI from 'openai';
|
|
3
11
|
import Gun from 'gun'
|
|
4
12
|
import Ajv2019 from 'ajv/dist/2019.js'
|
|
5
13
|
import * as Federation from './federation.js';
|
|
14
|
+
import * as SchemaOps from './schema.js';
|
|
15
|
+
import * as ContentOps from './content.js';
|
|
16
|
+
import * as NodeOps from './node.js';
|
|
17
|
+
import * as GlobalOps from './global.js';
|
|
18
|
+
import * as HologramOps from './hologram.js';
|
|
19
|
+
import * as ComputeOps from './compute.js';
|
|
20
|
+
import * as Utils from './utils.js';
|
|
6
21
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
* the creation, validation, and sharing of information.. By leveraging advanced technologies
|
|
10
|
-
* such as H3 for geospatial indexing, OpenAI for natural language processing, and Gun for decentralized
|
|
11
|
-
* data storage, HoloSphere provides a robust platform for developers to build innovative stygmergic applications.
|
|
12
|
-
*
|
|
13
|
-
* Key Features:
|
|
14
|
-
* - Geospatial indexing using H3
|
|
15
|
-
* - Natural language processing with OpenAI
|
|
16
|
-
* - Decentralized data storage with Gun
|
|
17
|
-
* - JSON schema validation with Ajv
|
|
18
|
-
* - Federation capabilities for distributed systems
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*/
|
|
22
|
+
// Define the version constant
|
|
23
|
+
const HOLOSPHERE_VERSION = '1.1.12';
|
|
22
24
|
|
|
23
25
|
class HoloSphere {
|
|
24
26
|
/**
|
|
25
27
|
* Initializes a new instance of the HoloSphere class.
|
|
26
28
|
* @param {string} appname - The name of the application.
|
|
27
|
-
* @param {boolean} strict - Whether to enforce strict schema validation.
|
|
28
|
-
* @param {string|null} openaikey - The OpenAI API key.
|
|
29
|
-
* @param {
|
|
29
|
+
* @param {boolean} [strict=false] - Whether to enforce strict schema validation.
|
|
30
|
+
* @param {string|null} [openaikey=null] - The OpenAI API key.
|
|
31
|
+
* @param {object} [gunOptions={}] - Optional Gun constructor options (e.g., peers, localStorage, radisk).
|
|
30
32
|
*/
|
|
31
|
-
constructor(appname, strict = false, openaikey = null) {
|
|
32
|
-
console.log('HoloSphere
|
|
33
|
+
constructor(appname, strict = false, openaikey = null, gunOptions = {}) {
|
|
34
|
+
console.log('HoloSphere v' + HOLOSPHERE_VERSION);
|
|
33
35
|
this.appname = appname
|
|
34
36
|
this.strict = strict;
|
|
35
37
|
this.validator = new Ajv2019({
|
|
@@ -39,11 +41,19 @@ class HoloSphere {
|
|
|
39
41
|
});
|
|
40
42
|
|
|
41
43
|
|
|
42
|
-
//
|
|
43
|
-
|
|
44
|
-
peers: ['https://gun.holons.io/gun'],
|
|
45
|
-
axe: false
|
|
46
|
-
|
|
44
|
+
// Define default Gun options
|
|
45
|
+
const defaultGunOptions = {
|
|
46
|
+
peers: ['https://gun.holons.io/gun','https://59.src.eco/gun'],
|
|
47
|
+
axe: false
|
|
48
|
+
// Add other potential defaults here if needed
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Merge provided options with defaults
|
|
52
|
+
const finalGunOptions = { ...defaultGunOptions, ...gunOptions };
|
|
53
|
+
console.log("Initializing Gun with options:", finalGunOptions);
|
|
54
|
+
|
|
55
|
+
// Use provided Gun instance or create new one with final options
|
|
56
|
+
this.gun = Gun(finalGunOptions); // Pass the merged options
|
|
47
57
|
|
|
48
58
|
|
|
49
59
|
if (openaikey != null) {
|
|
@@ -59,6 +69,10 @@ class HoloSphere {
|
|
|
59
69
|
this.schemaCache = new Map();
|
|
60
70
|
}
|
|
61
71
|
|
|
72
|
+
getGun() {
|
|
73
|
+
return this.gun;
|
|
74
|
+
}
|
|
75
|
+
|
|
62
76
|
// ================================ SCHEMA FUNCTIONS ================================
|
|
63
77
|
|
|
64
78
|
/**
|
|
@@ -68,64 +82,8 @@ class HoloSphere {
|
|
|
68
82
|
* @returns {Promise} - Resolves when the schema is set.
|
|
69
83
|
*/
|
|
70
84
|
async setSchema(lens, schema) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Basic schema validation
|
|
76
|
-
if (!schema.type || typeof schema.type !== 'string') {
|
|
77
|
-
throw new Error('setSchema: Schema must have a type field');
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const metaSchema = {
|
|
81
|
-
type: 'object',
|
|
82
|
-
required: ['type', 'properties'],
|
|
83
|
-
properties: {
|
|
84
|
-
type: { type: 'string' },
|
|
85
|
-
properties: {
|
|
86
|
-
type: 'object',
|
|
87
|
-
additionalProperties: {
|
|
88
|
-
type: 'object',
|
|
89
|
-
required: ['type'],
|
|
90
|
-
properties: {
|
|
91
|
-
type: { type: 'string' }
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
},
|
|
95
|
-
required: {
|
|
96
|
-
type: 'array',
|
|
97
|
-
items: { type: 'string' }
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
const valid = this.validator.validate(metaSchema, schema);
|
|
103
|
-
if (!valid) {
|
|
104
|
-
throw new Error(`Invalid schema structure: ${JSON.stringify(this.validator.errors)}`);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if (!schema.properties || typeof schema.properties !== 'object') {
|
|
108
|
-
throw new Error('Schema must have properties in strict mode');
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (!schema.required || !Array.isArray(schema.required) || schema.required.length === 0) {
|
|
112
|
-
throw new Error('Schema must have required fields in strict mode');
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Store schema in global table with lens as key
|
|
116
|
-
await this.putGlobal('schemas', {
|
|
117
|
-
id: lens,
|
|
118
|
-
schema: schema,
|
|
119
|
-
timestamp: Date.now()
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
// Update the cache with the new schema
|
|
123
|
-
this.schemaCache.set(lens, {
|
|
124
|
-
schema,
|
|
125
|
-
timestamp: Date.now()
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
return true;
|
|
85
|
+
// Delegate to the external function
|
|
86
|
+
return SchemaOps.setSchema(this, lens, schema);
|
|
129
87
|
}
|
|
130
88
|
|
|
131
89
|
/**
|
|
@@ -137,37 +95,8 @@ class HoloSphere {
|
|
|
137
95
|
* @returns {Promise<object|null>} - The retrieved schema or null if not found.
|
|
138
96
|
*/
|
|
139
97
|
async getSchema(lens, options = {}) {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const { useCache = true, maxCacheAge = 3600000 } = options;
|
|
145
|
-
|
|
146
|
-
// Check cache first if enabled
|
|
147
|
-
if (useCache && this.schemaCache.has(lens)) {
|
|
148
|
-
const cached = this.schemaCache.get(lens);
|
|
149
|
-
const cacheAge = Date.now() - cached.timestamp;
|
|
150
|
-
|
|
151
|
-
// Use cache if it's fresh enough
|
|
152
|
-
if (cacheAge < maxCacheAge) {
|
|
153
|
-
return cached.schema;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Cache miss or expired, fetch from storage
|
|
158
|
-
const schemaData = await this.getGlobal('schemas', lens);
|
|
159
|
-
|
|
160
|
-
if (!schemaData || !schemaData.schema) {
|
|
161
|
-
return null;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Update cache with fetched schema
|
|
165
|
-
this.schemaCache.set(lens, {
|
|
166
|
-
schema: schemaData.schema,
|
|
167
|
-
timestamp: Date.now()
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
return schemaData.schema;
|
|
98
|
+
// Delegate to the external function
|
|
99
|
+
return SchemaOps.getSchema(this, lens, options);
|
|
171
100
|
}
|
|
172
101
|
|
|
173
102
|
/**
|
|
@@ -176,14 +105,8 @@ class HoloSphere {
|
|
|
176
105
|
* @returns {boolean} - Returns true if successful
|
|
177
106
|
*/
|
|
178
107
|
clearSchemaCache(lens = null) {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
return this.schemaCache.delete(lens);
|
|
182
|
-
} else {
|
|
183
|
-
// Clear entire cache
|
|
184
|
-
this.schemaCache.clear();
|
|
185
|
-
return true;
|
|
186
|
-
}
|
|
108
|
+
// Delegate to the external function
|
|
109
|
+
return SchemaOps.clearSchemaCache(this, lens);
|
|
187
110
|
}
|
|
188
111
|
|
|
189
112
|
// ================================ CONTENT FUNCTIONS ================================
|
|
@@ -201,110 +124,8 @@ class HoloSphere {
|
|
|
201
124
|
* @returns {Promise<boolean>} - Returns true if successful, false if there was an error
|
|
202
125
|
*/
|
|
203
126
|
async put(holon, lens, data, password = null, options = {}) {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
if (!data.id) {
|
|
209
|
-
data.id = this.generateId();
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// Check if this is a reference we're storing
|
|
213
|
-
const isRef = this.isReference(data);
|
|
214
|
-
|
|
215
|
-
// Get and validate schema only in strict mode for non-references
|
|
216
|
-
if (this.strict && !isRef) {
|
|
217
|
-
const schema = await this.getSchema(lens);
|
|
218
|
-
if (!schema) {
|
|
219
|
-
throw new Error('Schema required in strict mode');
|
|
220
|
-
}
|
|
221
|
-
const dataToValidate = JSON.parse(JSON.stringify(data));
|
|
222
|
-
const valid = this.validator.validate(schema, dataToValidate);
|
|
223
|
-
|
|
224
|
-
if (!valid) {
|
|
225
|
-
const errorMsg = `Schema validation failed: ${JSON.stringify(this.validator.errors)}`;
|
|
226
|
-
throw new Error(errorMsg);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
try {
|
|
231
|
-
let user = null;
|
|
232
|
-
if (password) {
|
|
233
|
-
user = this.gun.user();
|
|
234
|
-
await new Promise((resolve, reject) => {
|
|
235
|
-
user.auth(this.userName(holon), password, (ack) => {
|
|
236
|
-
if (ack.err) reject(new Error(ack.err));
|
|
237
|
-
else resolve();
|
|
238
|
-
});
|
|
239
|
-
});
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
return new Promise((resolve, reject) => {
|
|
243
|
-
try {
|
|
244
|
-
const payload = JSON.stringify(data);
|
|
245
|
-
|
|
246
|
-
const putCallback = async (ack) => {
|
|
247
|
-
if (ack.err) {
|
|
248
|
-
reject(new Error(ack.err));
|
|
249
|
-
} else {
|
|
250
|
-
// Only notify subscribers for actual data, not references
|
|
251
|
-
if (!isRef) {
|
|
252
|
-
this.notifySubscribers({
|
|
253
|
-
holon,
|
|
254
|
-
lens,
|
|
255
|
-
...data
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// Auto-propagate to federation by default (if not a reference)
|
|
260
|
-
const shouldPropagate = options.autoPropagate !== false && !isRef;
|
|
261
|
-
let propagationResult = null;
|
|
262
|
-
|
|
263
|
-
if (shouldPropagate) {
|
|
264
|
-
try {
|
|
265
|
-
// Default to using references
|
|
266
|
-
const propagationOptions = {
|
|
267
|
-
useReferences: true,
|
|
268
|
-
...options.propagationOptions
|
|
269
|
-
};
|
|
270
|
-
|
|
271
|
-
propagationResult = await this.propagate(
|
|
272
|
-
holon,
|
|
273
|
-
lens,
|
|
274
|
-
data,
|
|
275
|
-
propagationOptions
|
|
276
|
-
);
|
|
277
|
-
|
|
278
|
-
// Still resolve with true even if propagation had errors
|
|
279
|
-
if (propagationResult.errors > 0) {
|
|
280
|
-
console.warn('Auto-propagation had errors:', propagationResult);
|
|
281
|
-
}
|
|
282
|
-
} catch (propError) {
|
|
283
|
-
console.warn('Error in auto-propagation:', propError);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
resolve({
|
|
288
|
-
success: true,
|
|
289
|
-
isReference: isRef,
|
|
290
|
-
propagationResult
|
|
291
|
-
});
|
|
292
|
-
}
|
|
293
|
-
};
|
|
294
|
-
|
|
295
|
-
const dataPath = password ?
|
|
296
|
-
user.get('private').get(lens).get(data.id) :
|
|
297
|
-
this.gun.get(this.appname).get(holon).get(lens).get(data.id);
|
|
298
|
-
|
|
299
|
-
dataPath.put(payload, putCallback);
|
|
300
|
-
} catch (error) {
|
|
301
|
-
reject(error);
|
|
302
|
-
}
|
|
303
|
-
});
|
|
304
|
-
} catch (error) {
|
|
305
|
-
console.error('Error in put:', error);
|
|
306
|
-
throw error;
|
|
307
|
-
}
|
|
127
|
+
// Delegate to the external function
|
|
128
|
+
return ContentOps.put(this, holon, lens, data, password, options);
|
|
308
129
|
}
|
|
309
130
|
|
|
310
131
|
/**
|
|
@@ -318,137 +139,8 @@ class HoloSphere {
|
|
|
318
139
|
* @returns {Promise<object|null>} - The retrieved content or null if not found.
|
|
319
140
|
*/
|
|
320
141
|
async get(holon, lens, key, password = null, options = {}) {
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
return null;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
const { resolveReferences = true } = options;
|
|
327
|
-
|
|
328
|
-
// Only check schema in strict mode
|
|
329
|
-
let schema;
|
|
330
|
-
if (this.strict) {
|
|
331
|
-
schema = await this.getSchema(lens);
|
|
332
|
-
if (!schema) {
|
|
333
|
-
throw new Error('Schema required in strict mode');
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
try {
|
|
338
|
-
let user = null;
|
|
339
|
-
if (password) {
|
|
340
|
-
user = this.gun.user();
|
|
341
|
-
await new Promise((resolve, reject) => {
|
|
342
|
-
user.auth(this.userName(holon), password, (ack) => {
|
|
343
|
-
if (ack.err) reject(new Error(ack.err));
|
|
344
|
-
else resolve();
|
|
345
|
-
});
|
|
346
|
-
});
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
return new Promise((resolve) => {
|
|
350
|
-
const handleData = async (data) => {
|
|
351
|
-
if (!data) {
|
|
352
|
-
resolve(null);
|
|
353
|
-
return;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
try {
|
|
357
|
-
const parsed = await this.parse(data);
|
|
358
|
-
|
|
359
|
-
if (!parsed) {
|
|
360
|
-
resolve(null);
|
|
361
|
-
return;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
// Check if this is a reference that needs to be resolved
|
|
365
|
-
if (resolveReferences && this.isReference(parsed)) {
|
|
366
|
-
const resolved = await this.resolveReference(parsed, {
|
|
367
|
-
followReferences: true // Always follow nested references when resolving
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
if (schema && resolved._federation) {
|
|
371
|
-
// Skip schema validation for resolved references
|
|
372
|
-
resolve(resolved);
|
|
373
|
-
return;
|
|
374
|
-
} else if (resolved !== parsed) {
|
|
375
|
-
// Reference was resolved successfully
|
|
376
|
-
resolve(resolved);
|
|
377
|
-
return;
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
// Perform schema validation if needed
|
|
382
|
-
if (schema) {
|
|
383
|
-
const valid = this.validator.validate(schema, parsed);
|
|
384
|
-
if (!valid) {
|
|
385
|
-
console.error('get: Invalid data according to schema:', this.validator.errors);
|
|
386
|
-
if (this.strict) {
|
|
387
|
-
resolve(null);
|
|
388
|
-
return;
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
resolve(parsed);
|
|
394
|
-
} catch (error) {
|
|
395
|
-
console.error('Error parsing data:', error);
|
|
396
|
-
resolve(null);
|
|
397
|
-
}
|
|
398
|
-
};
|
|
399
|
-
|
|
400
|
-
const dataPath = password ?
|
|
401
|
-
user.get('private').get(lens).get(key) :
|
|
402
|
-
this.gun.get(this.appname).get(holon).get(lens).get(key);
|
|
403
|
-
|
|
404
|
-
dataPath.once(handleData);
|
|
405
|
-
});
|
|
406
|
-
} catch (error) {
|
|
407
|
-
console.error('Error in get:', error);
|
|
408
|
-
return null;
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
/**
|
|
413
|
-
* Retrieves a node directly using its soul path
|
|
414
|
-
* @param {string} soul - The soul path of the node
|
|
415
|
-
* @returns {Promise<any>} - The retrieved node or null if not found.
|
|
416
|
-
*/
|
|
417
|
-
async getNodeBySoul(soul) {
|
|
418
|
-
if (!soul) {
|
|
419
|
-
throw new Error('getNodeBySoul: Missing soul parameter');
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
console.log(`getNodeBySoul: Accessing soul ${soul}`);
|
|
423
|
-
|
|
424
|
-
return new Promise((resolve, reject) => {
|
|
425
|
-
try {
|
|
426
|
-
const ref = this.getNodeRef(soul);
|
|
427
|
-
ref.once((data) => {
|
|
428
|
-
console.log(`getNodeBySoul: Retrieved data:`, data);
|
|
429
|
-
if (!data) {
|
|
430
|
-
resolve(null);
|
|
431
|
-
return;
|
|
432
|
-
}
|
|
433
|
-
resolve(data); // Return the data directly
|
|
434
|
-
});
|
|
435
|
-
} catch (error) {
|
|
436
|
-
console.error(`getNodeBySoul error:`, error);
|
|
437
|
-
reject(error);
|
|
438
|
-
}
|
|
439
|
-
});
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
/**
|
|
443
|
-
* Propagates data to federated holons
|
|
444
|
-
* @param {string} holon - The holon identifier
|
|
445
|
-
* @param {string} lens - The lens identifier
|
|
446
|
-
* @param {object} data - The data to propagate
|
|
447
|
-
* @param {object} [options] - Propagation options
|
|
448
|
-
* @returns {Promise<object>} - Result with success count and errors
|
|
449
|
-
*/
|
|
450
|
-
async propagate(holon, lens, data, options = {}) {
|
|
451
|
-
return Federation.propagate(this, holon, lens, data, options);
|
|
142
|
+
// Delegate to the external function
|
|
143
|
+
return ContentOps.get(this, holon, lens, key, password, options);
|
|
452
144
|
}
|
|
453
145
|
|
|
454
146
|
/**
|
|
@@ -459,102 +151,8 @@ class HoloSphere {
|
|
|
459
151
|
* @returns {Promise<Array<object>>} - The retrieved content.
|
|
460
152
|
*/
|
|
461
153
|
async getAll(holon, lens, password = null) {
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
const schema = await this.getSchema(lens);
|
|
467
|
-
if (!schema && this.strict) {
|
|
468
|
-
throw new Error('getAll: Schema required in strict mode');
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
try {
|
|
472
|
-
let user = null;
|
|
473
|
-
if (password) {
|
|
474
|
-
user = this.gun.user();
|
|
475
|
-
await new Promise((resolve, reject) => {
|
|
476
|
-
user.auth(this.userName(holon), password, (ack) => {
|
|
477
|
-
if (ack.err) reject(new Error(ack.err));
|
|
478
|
-
else resolve();
|
|
479
|
-
});
|
|
480
|
-
});
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
return new Promise((resolve) => {
|
|
484
|
-
const output = new Map();
|
|
485
|
-
|
|
486
|
-
const processData = async (data, key) => {
|
|
487
|
-
if (!data || key === '_') return;
|
|
488
|
-
|
|
489
|
-
try {
|
|
490
|
-
const parsed = await this.parse(data);
|
|
491
|
-
if (!parsed || !parsed.id) return;
|
|
492
|
-
|
|
493
|
-
// Check if this is a reference that needs to be resolved
|
|
494
|
-
if (this.isReference(parsed)) {
|
|
495
|
-
const resolved = await this.resolveReference(parsed, {
|
|
496
|
-
followReferences: true // Always follow references
|
|
497
|
-
});
|
|
498
|
-
|
|
499
|
-
if (resolved !== parsed) {
|
|
500
|
-
// Reference was resolved successfully
|
|
501
|
-
if (schema) {
|
|
502
|
-
const valid = this.validator.validate(schema, resolved);
|
|
503
|
-
if (valid || !this.strict) {
|
|
504
|
-
output.set(resolved.id, resolved);
|
|
505
|
-
}
|
|
506
|
-
} else {
|
|
507
|
-
output.set(resolved.id, resolved);
|
|
508
|
-
}
|
|
509
|
-
return;
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
if (schema) {
|
|
514
|
-
const valid = this.validator.validate(schema, parsed);
|
|
515
|
-
if (valid || !this.strict) {
|
|
516
|
-
output.set(parsed.id, parsed);
|
|
517
|
-
}
|
|
518
|
-
} else {
|
|
519
|
-
output.set(parsed.id, parsed);
|
|
520
|
-
}
|
|
521
|
-
} catch (error) {
|
|
522
|
-
console.error('Error processing data:', error);
|
|
523
|
-
}
|
|
524
|
-
};
|
|
525
|
-
|
|
526
|
-
const handleData = async (data) => {
|
|
527
|
-
if (!data) {
|
|
528
|
-
resolve([]);
|
|
529
|
-
return;
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
const initialPromises = [];
|
|
533
|
-
Object.keys(data)
|
|
534
|
-
.filter(key => key !== '_')
|
|
535
|
-
.forEach(key => {
|
|
536
|
-
initialPromises.push(processData(data[key], key));
|
|
537
|
-
});
|
|
538
|
-
|
|
539
|
-
try {
|
|
540
|
-
await Promise.all(initialPromises);
|
|
541
|
-
resolve(Array.from(output.values()));
|
|
542
|
-
} catch (error) {
|
|
543
|
-
console.error('Error in getAll:', error);
|
|
544
|
-
resolve([]);
|
|
545
|
-
}
|
|
546
|
-
};
|
|
547
|
-
|
|
548
|
-
const dataPath = password ?
|
|
549
|
-
user.get('private').get(lens) :
|
|
550
|
-
this.gun.get(this.appname).get(holon).get(lens);
|
|
551
|
-
|
|
552
|
-
dataPath.once(handleData);
|
|
553
|
-
});
|
|
554
|
-
} catch (error) {
|
|
555
|
-
console.error('Error in getAll:', error);
|
|
556
|
-
return [];
|
|
557
|
-
}
|
|
154
|
+
// Delegate to the external function
|
|
155
|
+
return ContentOps.getAll(this, holon, lens, password);
|
|
558
156
|
}
|
|
559
157
|
|
|
560
158
|
/**
|
|
@@ -563,56 +161,8 @@ class HoloSphere {
|
|
|
563
161
|
* @returns {Promise<object>} - The parsed data.
|
|
564
162
|
*/
|
|
565
163
|
async parse(rawData) {
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
if (!rawData) {
|
|
569
|
-
throw new Error('parse: No data provided');
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
try {
|
|
573
|
-
|
|
574
|
-
if (typeof rawData === 'string') {
|
|
575
|
-
parsedData = await JSON.parse(rawData);
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
if (rawData.soul) {
|
|
580
|
-
const data = await this.getNodeRef(rawData.soul).once();
|
|
581
|
-
if (!data) {
|
|
582
|
-
throw new Error('Referenced data not found');
|
|
583
|
-
}
|
|
584
|
-
return JSON.parse(data);
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
if (typeof rawData === 'object' && rawData !== null) {
|
|
589
|
-
if (rawData._ && rawData._["#"]) {
|
|
590
|
-
const pathParts = rawData._['#'].split('/');
|
|
591
|
-
if (pathParts.length < 4) {
|
|
592
|
-
throw new Error('Invalid reference format');
|
|
593
|
-
}
|
|
594
|
-
parsedData = await this.get(pathParts[1], pathParts[2], pathParts[3]);
|
|
595
|
-
if (!parsedData) {
|
|
596
|
-
throw new Error('Referenced data not found');
|
|
597
|
-
}
|
|
598
|
-
} else if (rawData._ && rawData._['>']) {
|
|
599
|
-
const nodeValue = Object.values(rawData).find(v => typeof v !== 'object' && v !== '_');
|
|
600
|
-
if (!nodeValue) {
|
|
601
|
-
throw new Error('Invalid node data');
|
|
602
|
-
}
|
|
603
|
-
parsedData = JSON.parse(nodeValue);
|
|
604
|
-
} else {
|
|
605
|
-
parsedData = rawData;
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
return parsedData;
|
|
610
|
-
|
|
611
|
-
} catch (error) {
|
|
612
|
-
console.log("Parsing not a JSON, returning raw data", rawData);
|
|
613
|
-
return rawData;
|
|
614
|
-
//throw new Error(`Parse error: ${error.message}`);
|
|
615
|
-
}
|
|
164
|
+
// Delegate to the external function
|
|
165
|
+
return ContentOps.parse(this, rawData);
|
|
616
166
|
}
|
|
617
167
|
|
|
618
168
|
/**
|
|
@@ -624,39 +174,8 @@ class HoloSphere {
|
|
|
624
174
|
* @returns {Promise<boolean>} - Returns true if successful
|
|
625
175
|
*/
|
|
626
176
|
async delete(holon, lens, key, password = null) {
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
try {
|
|
632
|
-
let user = null;
|
|
633
|
-
if (password) {
|
|
634
|
-
user = this.gun.user();
|
|
635
|
-
await new Promise((resolve, reject) => {
|
|
636
|
-
user.auth(this.userName(holon), password, (ack) => {
|
|
637
|
-
if (ack.err) reject(new Error(ack.err));
|
|
638
|
-
else resolve();
|
|
639
|
-
});
|
|
640
|
-
});
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
return new Promise((resolve, reject) => {
|
|
644
|
-
const dataPath = password ?
|
|
645
|
-
user.get('private').get(lens).get(key) :
|
|
646
|
-
this.gun.get(this.appname).get(holon).get(lens).get(key);
|
|
647
|
-
|
|
648
|
-
dataPath.put(null, ack => {
|
|
649
|
-
if (ack.err) {
|
|
650
|
-
reject(new Error(ack.err));
|
|
651
|
-
} else {
|
|
652
|
-
resolve(true);
|
|
653
|
-
}
|
|
654
|
-
});
|
|
655
|
-
});
|
|
656
|
-
} catch (error) {
|
|
657
|
-
console.error('Error in delete:', error);
|
|
658
|
-
throw error;
|
|
659
|
-
}
|
|
177
|
+
// Delegate to the external function (renamed to deleteFunc in module)
|
|
178
|
+
return ContentOps.deleteFunc(this, holon, lens, key, password);
|
|
660
179
|
}
|
|
661
180
|
|
|
662
181
|
/**
|
|
@@ -667,71 +186,8 @@ class HoloSphere {
|
|
|
667
186
|
* @returns {Promise<boolean>} - Returns true if successful
|
|
668
187
|
*/
|
|
669
188
|
async deleteAll(holon, lens, password = null) {
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
return false;
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
try {
|
|
676
|
-
let user = null;
|
|
677
|
-
if (password) {
|
|
678
|
-
user = this.gun.user();
|
|
679
|
-
await new Promise((resolve, reject) => {
|
|
680
|
-
user.auth(this.userName(holon), password, (ack) => {
|
|
681
|
-
if (ack.err) reject(new Error(ack.err));
|
|
682
|
-
else resolve();
|
|
683
|
-
});
|
|
684
|
-
});
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
return new Promise((resolve) => {
|
|
688
|
-
let deletionPromises = [];
|
|
689
|
-
|
|
690
|
-
const dataPath = password ?
|
|
691
|
-
user.get('private').get(lens) :
|
|
692
|
-
this.gun.get(this.appname).get(holon).get(lens);
|
|
693
|
-
|
|
694
|
-
// First get all the data to find keys to delete
|
|
695
|
-
dataPath.once((data) => {
|
|
696
|
-
if (!data) {
|
|
697
|
-
resolve(true); // Nothing to delete
|
|
698
|
-
return;
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
// Get all keys except Gun's metadata key '_'
|
|
702
|
-
const keys = Object.keys(data).filter(key => key !== '_');
|
|
703
|
-
|
|
704
|
-
// Create deletion promises for each key
|
|
705
|
-
keys.forEach(key => {
|
|
706
|
-
deletionPromises.push(
|
|
707
|
-
new Promise((resolveDelete) => {
|
|
708
|
-
const deletePath = password ?
|
|
709
|
-
user.get('private').get(lens).get(key) :
|
|
710
|
-
this.gun.get(this.appname).get(holon).get(lens).get(key);
|
|
711
|
-
|
|
712
|
-
deletePath.put(null, ack => {
|
|
713
|
-
resolveDelete(!!ack.ok); // Convert to boolean
|
|
714
|
-
});
|
|
715
|
-
})
|
|
716
|
-
);
|
|
717
|
-
});
|
|
718
|
-
|
|
719
|
-
// Wait for all deletions to complete
|
|
720
|
-
Promise.all(deletionPromises)
|
|
721
|
-
.then(results => {
|
|
722
|
-
const allSuccessful = results.every(result => result === true);
|
|
723
|
-
resolve(allSuccessful);
|
|
724
|
-
})
|
|
725
|
-
.catch(error => {
|
|
726
|
-
console.error('Error in deleteAll:', error);
|
|
727
|
-
resolve(false);
|
|
728
|
-
});
|
|
729
|
-
});
|
|
730
|
-
});
|
|
731
|
-
} catch (error) {
|
|
732
|
-
console.error('Error in deleteAll:', error);
|
|
733
|
-
return false;
|
|
734
|
-
}
|
|
189
|
+
// Delegate to the external function
|
|
190
|
+
return ContentOps.deleteAll(this, holon, lens, password);
|
|
735
191
|
}
|
|
736
192
|
|
|
737
193
|
// ================================ NODE FUNCTIONS ================================
|
|
@@ -744,27 +200,8 @@ class HoloSphere {
|
|
|
744
200
|
* @param {object} data - The node to store.
|
|
745
201
|
*/
|
|
746
202
|
async putNode(holon, lens, data) {
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
return new Promise((resolve, reject) => {
|
|
752
|
-
try {
|
|
753
|
-
this.gun.get(this.appname)
|
|
754
|
-
.get(holon)
|
|
755
|
-
.get(lens)
|
|
756
|
-
.get('value') // Store at 'value' key
|
|
757
|
-
.put(data.value, ack => { // Store the value directly
|
|
758
|
-
if (ack.err) {
|
|
759
|
-
reject(new Error(ack.err));
|
|
760
|
-
} else {
|
|
761
|
-
resolve(true);
|
|
762
|
-
}
|
|
763
|
-
});
|
|
764
|
-
} catch (error) {
|
|
765
|
-
reject(error);
|
|
766
|
-
}
|
|
767
|
-
});
|
|
203
|
+
// Delegate to the external function
|
|
204
|
+
return NodeOps.putNode(this, holon, lens, data);
|
|
768
205
|
}
|
|
769
206
|
|
|
770
207
|
/**
|
|
@@ -775,50 +212,28 @@ class HoloSphere {
|
|
|
775
212
|
* @returns {Promise<any>} - The retrieved node or null if not found.
|
|
776
213
|
*/
|
|
777
214
|
async getNode(holon, lens, key) {
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
return new Promise((resolve, reject) => {
|
|
783
|
-
try {
|
|
784
|
-
this.gun.get(this.appname)
|
|
785
|
-
.get(holon)
|
|
786
|
-
.get(lens)
|
|
787
|
-
.get(key)
|
|
788
|
-
.once((data) => {
|
|
789
|
-
if (!data) {
|
|
790
|
-
resolve(null);
|
|
791
|
-
return;
|
|
792
|
-
}
|
|
793
|
-
resolve(data); // Return the data directly
|
|
794
|
-
});
|
|
795
|
-
} catch (error) {
|
|
796
|
-
reject(error);
|
|
797
|
-
}
|
|
798
|
-
});
|
|
215
|
+
// Delegate to the external function
|
|
216
|
+
return NodeOps.getNode(this, holon, lens, key);
|
|
799
217
|
}
|
|
800
218
|
|
|
219
|
+
/**
|
|
220
|
+
* Retrieves a Gun node reference using its soul path
|
|
221
|
+
* @param {string} soul - The soul path of the node
|
|
222
|
+
* @returns {Gun.ChainReference} - The Gun node reference
|
|
223
|
+
*/
|
|
801
224
|
getNodeRef(soul) {
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
const parts = soul.split('/').filter(part => {
|
|
807
|
-
if (!part.trim() || /[<>:"/\\|?*]/.test(part)) {
|
|
808
|
-
throw new Error('getNodeRef: Invalid path segment');
|
|
809
|
-
}
|
|
810
|
-
return part.trim();
|
|
811
|
-
});
|
|
812
|
-
|
|
813
|
-
if (parts.length === 0) {
|
|
814
|
-
throw new Error('getNodeRef: Invalid soul format');
|
|
815
|
-
}
|
|
225
|
+
// Delegate to the external function
|
|
226
|
+
return NodeOps.getNodeRef(this, soul);
|
|
227
|
+
}
|
|
816
228
|
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
229
|
+
/**
|
|
230
|
+
* Retrieves a node directly using its soul path
|
|
231
|
+
* @param {string} soul - The soul path of the node
|
|
232
|
+
* @returns {Promise<any>} - The retrieved node or null if not found.
|
|
233
|
+
*/
|
|
234
|
+
async getNodeBySoul(soul) {
|
|
235
|
+
// Delegate to the external function
|
|
236
|
+
return NodeOps.getNodeBySoul(this, soul);
|
|
822
237
|
}
|
|
823
238
|
|
|
824
239
|
/**
|
|
@@ -829,22 +244,8 @@ class HoloSphere {
|
|
|
829
244
|
* @returns {Promise<boolean>} - Returns true if successful
|
|
830
245
|
*/
|
|
831
246
|
async deleteNode(holon, lens, key) {
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
}
|
|
835
|
-
return new Promise((resolve, reject) => {
|
|
836
|
-
this.gun.get(this.appname)
|
|
837
|
-
.get(holon)
|
|
838
|
-
.get(lens)
|
|
839
|
-
.get(key)
|
|
840
|
-
.put(null, ack => {
|
|
841
|
-
if (ack.err) {
|
|
842
|
-
reject(new Error(ack.err));
|
|
843
|
-
} else {
|
|
844
|
-
resolve(true);
|
|
845
|
-
}
|
|
846
|
-
});
|
|
847
|
-
});
|
|
247
|
+
// Delegate to the external function
|
|
248
|
+
return NodeOps.deleteNode(this, holon, lens, key);
|
|
848
249
|
}
|
|
849
250
|
|
|
850
251
|
// ================================ GLOBAL FUNCTIONS ================================
|
|
@@ -856,57 +257,8 @@ class HoloSphere {
|
|
|
856
257
|
* @returns {Promise<void>}
|
|
857
258
|
*/
|
|
858
259
|
async putGlobal(tableName, data, password = null) {
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
throw new Error('Table name and data are required');
|
|
862
|
-
}
|
|
863
|
-
|
|
864
|
-
let user = null;
|
|
865
|
-
if (password) {
|
|
866
|
-
user = this.gun.user();
|
|
867
|
-
await new Promise((resolve, reject) => {
|
|
868
|
-
user.auth(this.userName(tableName), password, (ack) => {
|
|
869
|
-
if (ack.err) reject(new Error(ack.err));
|
|
870
|
-
else resolve();
|
|
871
|
-
});
|
|
872
|
-
});
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
return new Promise((resolve, reject) => {
|
|
876
|
-
try {
|
|
877
|
-
const payload = JSON.stringify(data);
|
|
878
|
-
|
|
879
|
-
const dataPath = password ?
|
|
880
|
-
user.get('private').get(tableName) :
|
|
881
|
-
this.gun.get(this.appname).get(tableName);
|
|
882
|
-
|
|
883
|
-
if (data.id) {
|
|
884
|
-
// Store at the specific key path
|
|
885
|
-
dataPath.get(data.id).put(payload, ack => {
|
|
886
|
-
|
|
887
|
-
if (ack.err) {
|
|
888
|
-
reject(new Error(ack.err));
|
|
889
|
-
} else {
|
|
890
|
-
resolve();
|
|
891
|
-
}
|
|
892
|
-
});
|
|
893
|
-
} else {
|
|
894
|
-
dataPath.put(payload, ack => {
|
|
895
|
-
if (ack.err) {
|
|
896
|
-
reject(new Error(ack.err));
|
|
897
|
-
} else {
|
|
898
|
-
resolve();
|
|
899
|
-
}
|
|
900
|
-
});
|
|
901
|
-
}
|
|
902
|
-
} catch (error) {
|
|
903
|
-
reject(error);
|
|
904
|
-
}
|
|
905
|
-
});
|
|
906
|
-
} catch (error) {
|
|
907
|
-
console.error('Error in putGlobal:', error);
|
|
908
|
-
throw error;
|
|
909
|
-
}
|
|
260
|
+
// Delegate to the external function
|
|
261
|
+
return GlobalOps.putGlobal(this, tableName, data, password);
|
|
910
262
|
}
|
|
911
263
|
|
|
912
264
|
/**
|
|
@@ -917,64 +269,8 @@ class HoloSphere {
|
|
|
917
269
|
* @returns {Promise<object|null>} - The parsed data for the key or null if not found.
|
|
918
270
|
*/
|
|
919
271
|
async getGlobal(tableName, key, password = null) {
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
if (password) {
|
|
923
|
-
user = this.gun.user();
|
|
924
|
-
await new Promise((resolve, reject) => {
|
|
925
|
-
user.auth(this.userName(tableName), password, (ack) => {
|
|
926
|
-
if (ack.err) reject(new Error(ack.err));
|
|
927
|
-
else resolve();
|
|
928
|
-
});
|
|
929
|
-
});
|
|
930
|
-
}
|
|
931
|
-
|
|
932
|
-
return new Promise((resolve) => {
|
|
933
|
-
const handleData = async (data) => {
|
|
934
|
-
if (!data) {
|
|
935
|
-
resolve(null);
|
|
936
|
-
return;
|
|
937
|
-
}
|
|
938
|
-
|
|
939
|
-
try {
|
|
940
|
-
// The data should be a stringified JSON from putGlobal
|
|
941
|
-
const parsed = await this.parse(data);
|
|
942
|
-
|
|
943
|
-
if (!parsed) {
|
|
944
|
-
resolve(null);
|
|
945
|
-
return;
|
|
946
|
-
}
|
|
947
|
-
|
|
948
|
-
// Check if this is a reference that needs to be resolved
|
|
949
|
-
if (this.isReference(parsed)) {
|
|
950
|
-
const resolved = await this.resolveReference(parsed, {
|
|
951
|
-
followReferences: true // Always follow references
|
|
952
|
-
});
|
|
953
|
-
|
|
954
|
-
if (resolved !== parsed) {
|
|
955
|
-
// Reference was resolved successfully
|
|
956
|
-
resolve(resolved);
|
|
957
|
-
return;
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
resolve(parsed);
|
|
962
|
-
} catch (e) {
|
|
963
|
-
console.error('Error parsing data in getGlobal:', e);
|
|
964
|
-
resolve(null);
|
|
965
|
-
}
|
|
966
|
-
};
|
|
967
|
-
|
|
968
|
-
const dataPath = password ?
|
|
969
|
-
user.get('private').get(tableName) :
|
|
970
|
-
this.gun.get(this.appname).get(tableName);
|
|
971
|
-
|
|
972
|
-
dataPath.get(key).once(handleData);
|
|
973
|
-
});
|
|
974
|
-
} catch (error) {
|
|
975
|
-
console.error('Error in getGlobal:', error);
|
|
976
|
-
return null;
|
|
977
|
-
}
|
|
272
|
+
// Delegate to the external function
|
|
273
|
+
return GlobalOps.getGlobal(this, tableName, key, password);
|
|
978
274
|
}
|
|
979
275
|
|
|
980
276
|
/**
|
|
@@ -984,97 +280,8 @@ class HoloSphere {
|
|
|
984
280
|
* @returns {Promise<Array<object>>} - The parsed data from the table as an array.
|
|
985
281
|
*/
|
|
986
282
|
async getAllGlobal(tableName, password = null) {
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
}
|
|
990
|
-
|
|
991
|
-
try {
|
|
992
|
-
let user = null;
|
|
993
|
-
if (password) {
|
|
994
|
-
user = this.gun.user();
|
|
995
|
-
await new Promise((resolve, reject) => {
|
|
996
|
-
user.auth(this.userName(tableName), password, (ack) => {
|
|
997
|
-
if (ack.err) reject(new Error(ack.err));
|
|
998
|
-
else resolve();
|
|
999
|
-
});
|
|
1000
|
-
});
|
|
1001
|
-
}
|
|
1002
|
-
|
|
1003
|
-
return new Promise((resolve) => {
|
|
1004
|
-
let output = [];
|
|
1005
|
-
let isResolved = false;
|
|
1006
|
-
let timeout = setTimeout(() => {
|
|
1007
|
-
if (!isResolved) {
|
|
1008
|
-
isResolved = true;
|
|
1009
|
-
resolve(output);
|
|
1010
|
-
}
|
|
1011
|
-
}, 5000);
|
|
1012
|
-
|
|
1013
|
-
const handleData = async (data) => {
|
|
1014
|
-
if (!data) {
|
|
1015
|
-
clearTimeout(timeout);
|
|
1016
|
-
isResolved = true;
|
|
1017
|
-
resolve([]);
|
|
1018
|
-
return;
|
|
1019
|
-
}
|
|
1020
|
-
|
|
1021
|
-
const keys = Object.keys(data).filter(key => key !== '_');
|
|
1022
|
-
const promises = keys.map(key =>
|
|
1023
|
-
new Promise(async (resolveItem) => {
|
|
1024
|
-
const itemPath = password ?
|
|
1025
|
-
user.get('private').get(tableName).get(key) :
|
|
1026
|
-
this.gun.get(this.appname).get(tableName).get(key);
|
|
1027
|
-
|
|
1028
|
-
const itemData = await new Promise(resolveData => {
|
|
1029
|
-
itemPath.once(resolveData);
|
|
1030
|
-
});
|
|
1031
|
-
|
|
1032
|
-
if (itemData) {
|
|
1033
|
-
try {
|
|
1034
|
-
const parsed = await this.parse(itemData);
|
|
1035
|
-
if (parsed) {
|
|
1036
|
-
// Check if this is a reference that needs to be resolved
|
|
1037
|
-
if (this.isReference(parsed)) {
|
|
1038
|
-
const resolved = await this.resolveReference(parsed, {
|
|
1039
|
-
followReferences: true // Always follow references
|
|
1040
|
-
});
|
|
1041
|
-
|
|
1042
|
-
if (resolved !== parsed) {
|
|
1043
|
-
// Reference was resolved successfully
|
|
1044
|
-
output.push(resolved);
|
|
1045
|
-
} else {
|
|
1046
|
-
output.push(parsed);
|
|
1047
|
-
}
|
|
1048
|
-
} else {
|
|
1049
|
-
output.push(parsed);
|
|
1050
|
-
}
|
|
1051
|
-
}
|
|
1052
|
-
} catch (error) {
|
|
1053
|
-
console.error('Error parsing data:', error);
|
|
1054
|
-
}
|
|
1055
|
-
}
|
|
1056
|
-
resolveItem();
|
|
1057
|
-
})
|
|
1058
|
-
);
|
|
1059
|
-
|
|
1060
|
-
await Promise.all(promises);
|
|
1061
|
-
clearTimeout(timeout);
|
|
1062
|
-
if (!isResolved) {
|
|
1063
|
-
isResolved = true;
|
|
1064
|
-
resolve(output);
|
|
1065
|
-
}
|
|
1066
|
-
};
|
|
1067
|
-
|
|
1068
|
-
const dataPath = password ?
|
|
1069
|
-
user.get('private').get(tableName) :
|
|
1070
|
-
this.gun.get(this.appname).get(tableName);
|
|
1071
|
-
|
|
1072
|
-
dataPath.once(handleData);
|
|
1073
|
-
});
|
|
1074
|
-
} catch (error) {
|
|
1075
|
-
console.error('Error in getAllGlobal:', error);
|
|
1076
|
-
return [];
|
|
1077
|
-
}
|
|
283
|
+
// Delegate to the external function
|
|
284
|
+
return GlobalOps.getAllGlobal(this, tableName, password);
|
|
1078
285
|
}
|
|
1079
286
|
|
|
1080
287
|
/**
|
|
@@ -1085,55 +292,8 @@ class HoloSphere {
|
|
|
1085
292
|
* @returns {Promise<boolean>}
|
|
1086
293
|
*/
|
|
1087
294
|
async deleteGlobal(tableName, key, password = null) {
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
}
|
|
1091
|
-
|
|
1092
|
-
try {
|
|
1093
|
-
console.log('deleteGlobal - Starting deletion:', { tableName, key, hasPassword: !!password });
|
|
1094
|
-
|
|
1095
|
-
let user = null;
|
|
1096
|
-
if (password) {
|
|
1097
|
-
user = this.gun.user();
|
|
1098
|
-
await new Promise((resolve, reject) => {
|
|
1099
|
-
user.auth(this.userName(tableName), password, (ack) => {
|
|
1100
|
-
if (ack.err) reject(new Error(ack.err));
|
|
1101
|
-
else resolve();
|
|
1102
|
-
});
|
|
1103
|
-
});
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
|
-
return new Promise((resolve, reject) => {
|
|
1107
|
-
const dataPath = password ?
|
|
1108
|
-
user.get('private').get(tableName) :
|
|
1109
|
-
this.gun.get(this.appname).get(tableName);
|
|
1110
|
-
|
|
1111
|
-
console.log('deleteGlobal - Constructed base path:', dataPath._.back);
|
|
1112
|
-
|
|
1113
|
-
// First verify the data exists
|
|
1114
|
-
dataPath.get(key).once((data) => {
|
|
1115
|
-
console.log('deleteGlobal - Data before deletion:', data);
|
|
1116
|
-
|
|
1117
|
-
// Now perform the deletion
|
|
1118
|
-
dataPath.get(key).put(null, ack => {
|
|
1119
|
-
console.log('deleteGlobal - Deletion acknowledgment:', ack);
|
|
1120
|
-
if (ack.err) {
|
|
1121
|
-
console.error('deleteGlobal - Deletion error:', ack.err);
|
|
1122
|
-
reject(new Error(ack.err));
|
|
1123
|
-
} else {
|
|
1124
|
-
// Verify deletion
|
|
1125
|
-
dataPath.get(key).once((deletedData) => {
|
|
1126
|
-
console.log('deleteGlobal - Data after deletion:', deletedData);
|
|
1127
|
-
resolve(true);
|
|
1128
|
-
});
|
|
1129
|
-
}
|
|
1130
|
-
});
|
|
1131
|
-
});
|
|
1132
|
-
});
|
|
1133
|
-
} catch (error) {
|
|
1134
|
-
console.error('Error in deleteGlobal:', error);
|
|
1135
|
-
throw error;
|
|
1136
|
-
}
|
|
295
|
+
// Delegate to the external function
|
|
296
|
+
return GlobalOps.deleteGlobal(this, tableName, key, password);
|
|
1137
297
|
}
|
|
1138
298
|
|
|
1139
299
|
/**
|
|
@@ -1143,99 +303,22 @@ class HoloSphere {
|
|
|
1143
303
|
* @returns {Promise<boolean>}
|
|
1144
304
|
*/
|
|
1145
305
|
async deleteAllGlobal(tableName, password = null) {
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
}
|
|
1149
|
-
|
|
1150
|
-
try {
|
|
1151
|
-
let user = null;
|
|
1152
|
-
if (password) {
|
|
1153
|
-
user = this.gun.user();
|
|
1154
|
-
await new Promise((resolve, reject) => {
|
|
1155
|
-
user.auth(this.userName(tableName), password, (ack) => {
|
|
1156
|
-
if (ack.err) reject(new Error(ack.err));
|
|
1157
|
-
else resolve();
|
|
1158
|
-
});
|
|
1159
|
-
});
|
|
1160
|
-
}
|
|
1161
|
-
|
|
1162
|
-
return new Promise((resolve, reject) => {
|
|
1163
|
-
try {
|
|
1164
|
-
const deletions = new Set();
|
|
1165
|
-
let timeout = setTimeout(() => {
|
|
1166
|
-
if (deletions.size === 0) {
|
|
1167
|
-
resolve(true); // No data to delete
|
|
1168
|
-
}
|
|
1169
|
-
}, 5000);
|
|
1170
|
-
|
|
1171
|
-
const dataPath = password ?
|
|
1172
|
-
user.get('private').get(tableName) :
|
|
1173
|
-
this.gun.get(this.appname).get(tableName);
|
|
1174
|
-
|
|
1175
|
-
dataPath.once(async (data) => {
|
|
1176
|
-
if (!data) {
|
|
1177
|
-
clearTimeout(timeout);
|
|
1178
|
-
resolve(true);
|
|
1179
|
-
return;
|
|
1180
|
-
}
|
|
1181
|
-
|
|
1182
|
-
const keys = Object.keys(data).filter(key => key !== '_');
|
|
1183
|
-
const promises = keys.map(key =>
|
|
1184
|
-
new Promise((resolveDelete, rejectDelete) => {
|
|
1185
|
-
const deletePath = password ?
|
|
1186
|
-
user.get('private').get(tableName).get(key) :
|
|
1187
|
-
this.gun.get(this.appname).get(tableName).get(key);
|
|
1188
|
-
|
|
1189
|
-
deletePath.put(null, ack => {
|
|
1190
|
-
if (ack.err) {
|
|
1191
|
-
console.error(`Failed to delete ${key}:`, ack.err);
|
|
1192
|
-
rejectDelete(new Error(ack.err));
|
|
1193
|
-
} else {
|
|
1194
|
-
resolveDelete();
|
|
1195
|
-
}
|
|
1196
|
-
});
|
|
1197
|
-
})
|
|
1198
|
-
);
|
|
1199
|
-
|
|
1200
|
-
try {
|
|
1201
|
-
await Promise.all(promises);
|
|
1202
|
-
// Finally delete the table itself
|
|
1203
|
-
dataPath.put(null);
|
|
1204
|
-
clearTimeout(timeout);
|
|
1205
|
-
resolve(true);
|
|
1206
|
-
} catch (error) {
|
|
1207
|
-
reject(error);
|
|
1208
|
-
}
|
|
1209
|
-
});
|
|
1210
|
-
} catch (error) {
|
|
1211
|
-
reject(error);
|
|
1212
|
-
}
|
|
1213
|
-
});
|
|
1214
|
-
} catch (error) {
|
|
1215
|
-
console.error('Error in deleteAllGlobal:', error);
|
|
1216
|
-
throw error;
|
|
1217
|
-
}
|
|
306
|
+
// Delegate to the external function
|
|
307
|
+
return GlobalOps.deleteAllGlobal(this, tableName, password);
|
|
1218
308
|
}
|
|
1219
309
|
|
|
1220
310
|
// ================================ REFERENCE FUNCTIONS ================================
|
|
1221
311
|
|
|
1222
312
|
/**
|
|
1223
|
-
* Creates a soul
|
|
313
|
+
* Creates a soul hologram object for a data item
|
|
1224
314
|
* @param {string} holon - The holon where the original data is stored
|
|
1225
315
|
* @param {string} lens - The lens where the original data is stored
|
|
1226
|
-
* @param {object} data - The data to create a
|
|
1227
|
-
* @returns {object} - A
|
|
316
|
+
* @param {object} data - The data to create a hologram for
|
|
317
|
+
* @returns {object} - A hologram object with id and soul
|
|
1228
318
|
*/
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
}
|
|
1233
|
-
|
|
1234
|
-
const soul = `${this.appname}/${holon}/${lens}/${data.id}`;
|
|
1235
|
-
return {
|
|
1236
|
-
id: data.id,
|
|
1237
|
-
soul: soul
|
|
1238
|
-
};
|
|
319
|
+
createHologram(holon, lens, data) {
|
|
320
|
+
// Delegate to the external function
|
|
321
|
+
return HologramOps.createHologram(this, holon, lens, data);
|
|
1239
322
|
}
|
|
1240
323
|
|
|
1241
324
|
/**
|
|
@@ -1244,130 +327,31 @@ class HoloSphere {
|
|
|
1244
327
|
* @returns {object|null} - The parsed components or null if invalid format
|
|
1245
328
|
*/
|
|
1246
329
|
parseSoulPath(soul) {
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
}
|
|
1250
|
-
|
|
1251
|
-
const soulParts = soul.split('/');
|
|
1252
|
-
if (soulParts.length < 4) {
|
|
1253
|
-
return null;
|
|
1254
|
-
}
|
|
1255
|
-
|
|
1256
|
-
return {
|
|
1257
|
-
appname: soulParts[0],
|
|
1258
|
-
holon: soulParts[1],
|
|
1259
|
-
lens: soulParts[2],
|
|
1260
|
-
key: soulParts[3]
|
|
1261
|
-
};
|
|
330
|
+
// Delegate to the external function (doesn't need instance)
|
|
331
|
+
return HologramOps.parseSoulPath(soul);
|
|
1262
332
|
}
|
|
1263
333
|
|
|
1264
334
|
/**
|
|
1265
|
-
* Checks if an object is a
|
|
335
|
+
* Checks if an object is a hologram
|
|
1266
336
|
* @param {object} data - The data to check
|
|
1267
|
-
* @returns {boolean} - True if the object is a
|
|
337
|
+
* @returns {boolean} - True if the object is a hologram
|
|
1268
338
|
*/
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
}
|
|
1273
|
-
|
|
1274
|
-
// Check for direct soul reference
|
|
1275
|
-
if (data.soul && typeof data.soul === 'string' && data.id) {
|
|
1276
|
-
return true;
|
|
1277
|
-
}
|
|
1278
|
-
|
|
1279
|
-
// Check for legacy federation reference
|
|
1280
|
-
if (data._federation && data._federation.isReference) {
|
|
1281
|
-
return true;
|
|
1282
|
-
}
|
|
1283
|
-
|
|
1284
|
-
return false;
|
|
339
|
+
isHologram(data) {
|
|
340
|
+
// Delegate to the external function (doesn't need instance)
|
|
341
|
+
return HologramOps.isHologram(data);
|
|
1285
342
|
}
|
|
1286
343
|
|
|
1287
344
|
/**
|
|
1288
|
-
* Resolves a
|
|
1289
|
-
* @param {object}
|
|
345
|
+
* Resolves a hologram to its actual data
|
|
346
|
+
* @param {object} hologram - The hologram to resolve
|
|
1290
347
|
* @param {object} [options] - Optional parameters
|
|
1291
|
-
* @param {boolean} [options.
|
|
1292
|
-
* @
|
|
348
|
+
* @param {boolean} [options.followHolograms=true] - Whether to follow nested holograms
|
|
349
|
+
* @param {Set<string>} [options.visited] - Internal use: Tracks visited souls to prevent loops
|
|
350
|
+
* @returns {Promise<object|null>} - The resolved data, null if resolution failed due to target not found, or the original hologram for circular/invalid cases.
|
|
1293
351
|
*/
|
|
1294
|
-
async
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
}
|
|
1298
|
-
|
|
1299
|
-
const { followReferences = true } = options;
|
|
1300
|
-
|
|
1301
|
-
try {
|
|
1302
|
-
// Handle direct soul reference
|
|
1303
|
-
if (reference.soul) {
|
|
1304
|
-
const soulInfo = this.parseSoulPath(reference.soul);
|
|
1305
|
-
if (!soulInfo) {
|
|
1306
|
-
console.warn(`Invalid soul format: ${reference.soul}`);
|
|
1307
|
-
return reference;
|
|
1308
|
-
}
|
|
1309
|
-
|
|
1310
|
-
console.log(`Resolving reference with soul: ${reference.soul}`);
|
|
1311
|
-
|
|
1312
|
-
// Get original data using the extracted path components
|
|
1313
|
-
const originalData = await this.get(
|
|
1314
|
-
soulInfo.holon,
|
|
1315
|
-
soulInfo.lens,
|
|
1316
|
-
soulInfo.key,
|
|
1317
|
-
null,
|
|
1318
|
-
{ resolveReferences: followReferences } // Control recursion
|
|
1319
|
-
);
|
|
1320
|
-
|
|
1321
|
-
if (originalData) {
|
|
1322
|
-
console.log(`Original data found through soul path resolution`);
|
|
1323
|
-
return {
|
|
1324
|
-
...originalData,
|
|
1325
|
-
_federation: {
|
|
1326
|
-
isReference: true,
|
|
1327
|
-
resolved: true,
|
|
1328
|
-
soul: reference.soul,
|
|
1329
|
-
timestamp: Date.now()
|
|
1330
|
-
}
|
|
1331
|
-
};
|
|
1332
|
-
} else {
|
|
1333
|
-
console.warn(`Could not resolve reference: original data not found at extracted path`);
|
|
1334
|
-
return reference; // Return original reference if resolution fails
|
|
1335
|
-
}
|
|
1336
|
-
}
|
|
1337
|
-
|
|
1338
|
-
// Handle legacy federation reference
|
|
1339
|
-
else if (reference._federation && reference._federation.isReference) {
|
|
1340
|
-
const fedRef = reference._federation;
|
|
1341
|
-
console.log(`Resolving legacy federation reference from ${fedRef.origin}`);
|
|
1342
|
-
|
|
1343
|
-
const originalData = await this.get(
|
|
1344
|
-
fedRef.origin,
|
|
1345
|
-
fedRef.lens,
|
|
1346
|
-
reference.id || fedRef.key,
|
|
1347
|
-
null,
|
|
1348
|
-
{ resolveReferences: followReferences }
|
|
1349
|
-
);
|
|
1350
|
-
|
|
1351
|
-
if (originalData) {
|
|
1352
|
-
return {
|
|
1353
|
-
...originalData,
|
|
1354
|
-
_federation: {
|
|
1355
|
-
...fedRef,
|
|
1356
|
-
resolved: true,
|
|
1357
|
-
timestamp: Date.now()
|
|
1358
|
-
}
|
|
1359
|
-
};
|
|
1360
|
-
} else {
|
|
1361
|
-
console.warn(`Could not resolve legacy reference: original data not found`);
|
|
1362
|
-
return reference;
|
|
1363
|
-
}
|
|
1364
|
-
}
|
|
1365
|
-
|
|
1366
|
-
return reference;
|
|
1367
|
-
} catch (error) {
|
|
1368
|
-
console.error(`Error resolving reference: ${error.message}`, error);
|
|
1369
|
-
return reference;
|
|
1370
|
-
}
|
|
352
|
+
async resolveHologram(hologram, options = {}) {
|
|
353
|
+
// Delegate to the external function
|
|
354
|
+
return HologramOps.resolveHologram(this, hologram, options);
|
|
1371
355
|
}
|
|
1372
356
|
|
|
1373
357
|
// ================================ COMPUTE FUNCTIONS ================================
|
|
@@ -1380,26 +364,8 @@ class HoloSphere {
|
|
|
1380
364
|
* @param {string} [password] - Optional password for private holons
|
|
1381
365
|
*/
|
|
1382
366
|
async computeHierarchy(holon, lens, options, maxLevels = 15, password = null) {
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
const results = [];
|
|
1386
|
-
|
|
1387
|
-
while (currentRes > 0 && maxLevels > 0) {
|
|
1388
|
-
try {
|
|
1389
|
-
const result = await this.compute(currentHolon, lens, options, password);
|
|
1390
|
-
if (result) {
|
|
1391
|
-
results.push(result);
|
|
1392
|
-
}
|
|
1393
|
-
currentHolon = h3.cellToParent(currentHolon, currentRes - 1);
|
|
1394
|
-
currentRes--;
|
|
1395
|
-
maxLevels--;
|
|
1396
|
-
} catch (error) {
|
|
1397
|
-
console.error('Error in compute hierarchy:', error);
|
|
1398
|
-
break;
|
|
1399
|
-
}
|
|
1400
|
-
}
|
|
1401
|
-
|
|
1402
|
-
return results;
|
|
367
|
+
// Delegate to the external function
|
|
368
|
+
return ComputeOps.computeHierarchy(this, holon, lens, options, maxLevels, password);
|
|
1403
369
|
}
|
|
1404
370
|
|
|
1405
371
|
/**
|
|
@@ -1414,132 +380,8 @@ class HoloSphere {
|
|
|
1414
380
|
* @throws {Error} If parameters are invalid or missing
|
|
1415
381
|
*/
|
|
1416
382
|
async compute(holon, lens, options, password = null) {
|
|
1417
|
-
//
|
|
1418
|
-
|
|
1419
|
-
throw new Error('compute: Missing required parameters');
|
|
1420
|
-
}
|
|
1421
|
-
|
|
1422
|
-
// Convert string operation to options object
|
|
1423
|
-
if (typeof options === 'string') {
|
|
1424
|
-
options = { operation: options };
|
|
1425
|
-
}
|
|
1426
|
-
|
|
1427
|
-
if (!options?.operation) {
|
|
1428
|
-
throw new Error('compute: Missing required parameters');
|
|
1429
|
-
}
|
|
1430
|
-
|
|
1431
|
-
// Validate holon format and resolution first
|
|
1432
|
-
let res;
|
|
1433
|
-
try {
|
|
1434
|
-
res = h3.getResolution(holon);
|
|
1435
|
-
} catch (error) {
|
|
1436
|
-
throw new Error('compute: Invalid holon format');
|
|
1437
|
-
}
|
|
1438
|
-
|
|
1439
|
-
if (res < 1 || res > 15) {
|
|
1440
|
-
throw new Error('compute: Invalid holon resolution (must be between 1 and 15)');
|
|
1441
|
-
}
|
|
1442
|
-
|
|
1443
|
-
const {
|
|
1444
|
-
operation,
|
|
1445
|
-
fields = [],
|
|
1446
|
-
targetField,
|
|
1447
|
-
depth,
|
|
1448
|
-
maxDepth
|
|
1449
|
-
} = options;
|
|
1450
|
-
|
|
1451
|
-
// Validate depth parameters if provided
|
|
1452
|
-
if (depth !== undefined && depth < 0) {
|
|
1453
|
-
throw new Error('compute: Invalid depth parameter');
|
|
1454
|
-
}
|
|
1455
|
-
|
|
1456
|
-
if (maxDepth !== undefined && (maxDepth < 1 || maxDepth > 15)) {
|
|
1457
|
-
throw new Error('compute: Invalid maxDepth parameter (must be between 1 and 15)');
|
|
1458
|
-
}
|
|
1459
|
-
|
|
1460
|
-
// Validate operation
|
|
1461
|
-
const validOperations = ['summarize', 'aggregate', 'concatenate'];
|
|
1462
|
-
if (!validOperations.includes(operation)) {
|
|
1463
|
-
throw new Error(`compute: Invalid operation (must be one of ${validOperations.join(', ')})`);
|
|
1464
|
-
}
|
|
1465
|
-
|
|
1466
|
-
const parent = h3.cellToParent(holon, res - 1);
|
|
1467
|
-
const siblings = h3.cellToChildren(parent, res);
|
|
1468
|
-
|
|
1469
|
-
// Collect all content from siblings
|
|
1470
|
-
const contents = await Promise.all(
|
|
1471
|
-
siblings.map(sibling => this.getAll(sibling, lens, password))
|
|
1472
|
-
);
|
|
1473
|
-
|
|
1474
|
-
const flatContents = contents.flat().filter(Boolean);
|
|
1475
|
-
|
|
1476
|
-
if (flatContents.length > 0) {
|
|
1477
|
-
try {
|
|
1478
|
-
let computed;
|
|
1479
|
-
switch (operation) {
|
|
1480
|
-
case 'summarize':
|
|
1481
|
-
// For summarize, concatenate specified fields or use entire content
|
|
1482
|
-
const textToSummarize = fields.length > 0
|
|
1483
|
-
? flatContents.map(item => fields.map(field => item[field]).filter(Boolean).join('\n')).join('\n')
|
|
1484
|
-
: JSON.stringify(flatContents);
|
|
1485
|
-
computed = await this.summarize(textToSummarize);
|
|
1486
|
-
break;
|
|
1487
|
-
|
|
1488
|
-
case 'aggregate':
|
|
1489
|
-
// For aggregate, sum numeric fields
|
|
1490
|
-
computed = fields.reduce((acc, field) => {
|
|
1491
|
-
acc[field] = flatContents.reduce((sum, item) => {
|
|
1492
|
-
return sum + (Number(item[field]) || 0);
|
|
1493
|
-
}, 0);
|
|
1494
|
-
return acc;
|
|
1495
|
-
}, {});
|
|
1496
|
-
break;
|
|
1497
|
-
|
|
1498
|
-
case 'concatenate':
|
|
1499
|
-
// For concatenate, combine arrays or strings
|
|
1500
|
-
computed = fields.reduce((acc, field) => {
|
|
1501
|
-
acc[field] = flatContents.reduce((combined, item) => {
|
|
1502
|
-
const value = item[field];
|
|
1503
|
-
if (Array.isArray(value)) {
|
|
1504
|
-
return [...combined, ...value];
|
|
1505
|
-
} else if (value) {
|
|
1506
|
-
return [...combined, value];
|
|
1507
|
-
}
|
|
1508
|
-
return combined;
|
|
1509
|
-
}, []);
|
|
1510
|
-
// Remove duplicates if array
|
|
1511
|
-
acc[field] = Array.from(new Set(acc[field]));
|
|
1512
|
-
return acc;
|
|
1513
|
-
}, {});
|
|
1514
|
-
break;
|
|
1515
|
-
}
|
|
1516
|
-
|
|
1517
|
-
if (computed) {
|
|
1518
|
-
const resultId = `${parent}_${operation}`;
|
|
1519
|
-
const result = {
|
|
1520
|
-
id: resultId,
|
|
1521
|
-
timestamp: Date.now()
|
|
1522
|
-
};
|
|
1523
|
-
|
|
1524
|
-
// Store result in targetField if specified, otherwise at root level
|
|
1525
|
-
if (targetField) {
|
|
1526
|
-
result[targetField] = computed;
|
|
1527
|
-
} else if (typeof computed === 'object') {
|
|
1528
|
-
Object.assign(result, computed);
|
|
1529
|
-
} else {
|
|
1530
|
-
result.value = computed;
|
|
1531
|
-
}
|
|
1532
|
-
|
|
1533
|
-
await this.put(parent, lens, result, password);
|
|
1534
|
-
return result;
|
|
1535
|
-
}
|
|
1536
|
-
} catch (error) {
|
|
1537
|
-
console.warn('Error in compute operation:', error);
|
|
1538
|
-
throw error;
|
|
1539
|
-
}
|
|
1540
|
-
}
|
|
1541
|
-
|
|
1542
|
-
return null;
|
|
383
|
+
// Delegate to the external function
|
|
384
|
+
return ComputeOps.compute(this, holon, lens, options, password);
|
|
1543
385
|
}
|
|
1544
386
|
|
|
1545
387
|
/**
|
|
@@ -1548,32 +390,8 @@ class HoloSphere {
|
|
|
1548
390
|
* @returns {Promise<string>} - The summarized text.
|
|
1549
391
|
*/
|
|
1550
392
|
async summarize(history) {
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
}
|
|
1554
|
-
|
|
1555
|
-
try {
|
|
1556
|
-
const response = await this.openai.chat.completions.create({
|
|
1557
|
-
model: "gpt-4",
|
|
1558
|
-
messages: [
|
|
1559
|
-
{
|
|
1560
|
-
role: "system",
|
|
1561
|
-
content: "You are a helpful assistant that summarizes text concisely while preserving key information. Keep summaries clear and focused."
|
|
1562
|
-
},
|
|
1563
|
-
{
|
|
1564
|
-
role: "user",
|
|
1565
|
-
content: history
|
|
1566
|
-
}
|
|
1567
|
-
],
|
|
1568
|
-
temperature: 0.7,
|
|
1569
|
-
max_tokens: 500
|
|
1570
|
-
});
|
|
1571
|
-
|
|
1572
|
-
return response.choices[0].message.content.trim();
|
|
1573
|
-
} catch (error) {
|
|
1574
|
-
console.error('Error in summarize:', error);
|
|
1575
|
-
throw new Error('Failed to generate summary');
|
|
1576
|
-
}
|
|
393
|
+
// Delegate to the external function
|
|
394
|
+
return ComputeOps.summarize(this, history);
|
|
1577
395
|
}
|
|
1578
396
|
|
|
1579
397
|
/**
|
|
@@ -1585,33 +403,8 @@ class HoloSphere {
|
|
|
1585
403
|
* @returns {Promise<object>} - The original content.
|
|
1586
404
|
*/
|
|
1587
405
|
async upcast(holon, lens, content, maxLevels = 15) {
|
|
1588
|
-
//
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
let res = h3.getResolution(holon);
|
|
1592
|
-
|
|
1593
|
-
// If already at the highest level (res 0) or reached max levels, we're done
|
|
1594
|
-
if (res === 0 || maxLevels <= 0) {
|
|
1595
|
-
return content;
|
|
1596
|
-
}
|
|
1597
|
-
|
|
1598
|
-
// Get the parent cell
|
|
1599
|
-
let parent = h3.cellToParent(holon, res - 1);
|
|
1600
|
-
|
|
1601
|
-
// Create a reference to store in the parent
|
|
1602
|
-
const reference = this.createReference(holon, lens, content);
|
|
1603
|
-
|
|
1604
|
-
// Store the reference in the parent cell
|
|
1605
|
-
await this.put(parent, lens, reference, null, {
|
|
1606
|
-
autoPropagate: false
|
|
1607
|
-
});
|
|
1608
|
-
|
|
1609
|
-
// Continue upcasting with the parent
|
|
1610
|
-
if (res > 1 && maxLevels > 1) {
|
|
1611
|
-
return this.upcast(parent, lens, reference, maxLevels - 1);
|
|
1612
|
-
}
|
|
1613
|
-
|
|
1614
|
-
return content;
|
|
406
|
+
// Delegate to the external function
|
|
407
|
+
return ComputeOps.upcast(this, holon, lens, content, maxLevels);
|
|
1615
408
|
}
|
|
1616
409
|
|
|
1617
410
|
/**
|
|
@@ -1621,19 +414,21 @@ class HoloSphere {
|
|
|
1621
414
|
* @returns {Promise<object>} - The updated parent information.
|
|
1622
415
|
*/
|
|
1623
416
|
async updateParent(id, report) {
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
let parent = h3.cellToParent(id, res - 1)
|
|
1627
|
-
let parentInfo = await this.getCellInfo(parent)
|
|
1628
|
-
parentInfo.wisdom[id] = report
|
|
1629
|
-
//update summary
|
|
1630
|
-
let summary = await this.summarize(Object.values(parentInfo.wisdom).join('\n'))
|
|
1631
|
-
parentInfo.summary = summary
|
|
1632
|
-
|
|
1633
|
-
await this.db.put('cell', parentInfo)
|
|
1634
|
-
return parentInfo
|
|
417
|
+
// Delegate to the external function
|
|
418
|
+
return ComputeOps.updateParent(this, id, report);
|
|
1635
419
|
}
|
|
1636
420
|
|
|
421
|
+
/**
|
|
422
|
+
* Propagates data to federated holons
|
|
423
|
+
* @param {string} holon - The holon identifier
|
|
424
|
+
* @param {string} lens - The lens identifier
|
|
425
|
+
* @param {object} data - The data to propagate
|
|
426
|
+
* @param {object} [options] - Propagation options
|
|
427
|
+
* @returns {Promise<object>} - Result with success count and errors
|
|
428
|
+
*/
|
|
429
|
+
async propagate(holon, lens, data, options = {}) {
|
|
430
|
+
return Federation.propagate(this, holon, lens, data, options);
|
|
431
|
+
}
|
|
1637
432
|
|
|
1638
433
|
/**
|
|
1639
434
|
* Converts latitude and longitude to a holon identifier.
|
|
@@ -1643,7 +438,8 @@ class HoloSphere {
|
|
|
1643
438
|
* @returns {Promise<string>} - The resulting holon identifier.
|
|
1644
439
|
*/
|
|
1645
440
|
async getHolon(lat, lng, resolution) {
|
|
1646
|
-
|
|
441
|
+
// Delegate to the external function
|
|
442
|
+
return Utils.getHolon(lat, lng, resolution);
|
|
1647
443
|
}
|
|
1648
444
|
|
|
1649
445
|
/**
|
|
@@ -1653,13 +449,8 @@ class HoloSphere {
|
|
|
1653
449
|
* @returns {Array<string>} - List of holon identifiers.
|
|
1654
450
|
*/
|
|
1655
451
|
getScalespace(lat, lng) {
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
list.push(cell)
|
|
1659
|
-
for (let i = 13; i >= 0; i--) {
|
|
1660
|
-
list.push(h3.cellToParent(cell, i))
|
|
1661
|
-
}
|
|
1662
|
-
return list
|
|
452
|
+
// Delegate to the external function
|
|
453
|
+
return Utils.getScalespace(lat, lng);
|
|
1663
454
|
}
|
|
1664
455
|
|
|
1665
456
|
/**
|
|
@@ -1668,12 +459,8 @@ class HoloSphere {
|
|
|
1668
459
|
* @returns {Array<string>} - List of holon identifiers.
|
|
1669
460
|
*/
|
|
1670
461
|
getHolonScalespace(holon) {
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
for (let i = res; i >= 0; i--) {
|
|
1674
|
-
list.push(h3.cellToParent(holon, i))
|
|
1675
|
-
}
|
|
1676
|
-
return list
|
|
462
|
+
// Delegate to the external function
|
|
463
|
+
return Utils.getHolonScalespace(holon);
|
|
1677
464
|
}
|
|
1678
465
|
|
|
1679
466
|
/**
|
|
@@ -1684,89 +471,8 @@ class HoloSphere {
|
|
|
1684
471
|
* @returns {Promise<object>} - Subscription object with unsubscribe method
|
|
1685
472
|
*/
|
|
1686
473
|
async subscribe(holon, lens, callback) {
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
}
|
|
1690
|
-
|
|
1691
|
-
if (!callback || typeof callback !== 'function') {
|
|
1692
|
-
throw new Error('subscribe: Callback must be a function');
|
|
1693
|
-
}
|
|
1694
|
-
|
|
1695
|
-
const subscriptionId = this.generateId();
|
|
1696
|
-
|
|
1697
|
-
try {
|
|
1698
|
-
// Create the subscription
|
|
1699
|
-
const gunSubscription = this.gun.get(this.appname).get(holon).get(lens).map().on(async (data, key) => {
|
|
1700
|
-
// Check if subscription is still active before processing
|
|
1701
|
-
if (!this.subscriptions[subscriptionId]?.active) {
|
|
1702
|
-
return;
|
|
1703
|
-
}
|
|
1704
|
-
|
|
1705
|
-
if (data) {
|
|
1706
|
-
try {
|
|
1707
|
-
let parsed = await this.parse(data);
|
|
1708
|
-
|
|
1709
|
-
// Check if the parsed data is a reference that needs resolution
|
|
1710
|
-
if (parsed && this.isReference(parsed)) {
|
|
1711
|
-
const resolved = await this.resolveReference(parsed, {
|
|
1712
|
-
followReferences: true // Always follow references
|
|
1713
|
-
});
|
|
1714
|
-
|
|
1715
|
-
if (resolved !== parsed) {
|
|
1716
|
-
// Reference was resolved successfully
|
|
1717
|
-
// Check again if subscription is still active
|
|
1718
|
-
if (this.subscriptions[subscriptionId]?.active) {
|
|
1719
|
-
callback(resolved, key);
|
|
1720
|
-
}
|
|
1721
|
-
return;
|
|
1722
|
-
}
|
|
1723
|
-
}
|
|
1724
|
-
|
|
1725
|
-
// Check again if subscription is still active before final callback
|
|
1726
|
-
if (this.subscriptions[subscriptionId]?.active) {
|
|
1727
|
-
callback(parsed, key);
|
|
1728
|
-
}
|
|
1729
|
-
} catch (error) {
|
|
1730
|
-
console.error('Error in subscribe:', error);
|
|
1731
|
-
}
|
|
1732
|
-
}
|
|
1733
|
-
});
|
|
1734
|
-
|
|
1735
|
-
// Store the subscription with its ID
|
|
1736
|
-
this.subscriptions[subscriptionId] = {
|
|
1737
|
-
id: subscriptionId,
|
|
1738
|
-
holon,
|
|
1739
|
-
lens,
|
|
1740
|
-
active: true,
|
|
1741
|
-
callback,
|
|
1742
|
-
gunSubscription
|
|
1743
|
-
};
|
|
1744
|
-
|
|
1745
|
-
// Return an object with unsubscribe method
|
|
1746
|
-
return {
|
|
1747
|
-
unsubscribe: () => {
|
|
1748
|
-
try {
|
|
1749
|
-
// Mark as inactive first to prevent any new callbacks
|
|
1750
|
-
if (this.subscriptions[subscriptionId]) {
|
|
1751
|
-
this.subscriptions[subscriptionId].active = false;
|
|
1752
|
-
}
|
|
1753
|
-
|
|
1754
|
-
// Turn off the Gun subscription using the stored reference
|
|
1755
|
-
if (this.subscriptions[subscriptionId]?.gunSubscription) {
|
|
1756
|
-
this.subscriptions[subscriptionId].gunSubscription.off();
|
|
1757
|
-
}
|
|
1758
|
-
|
|
1759
|
-
// Remove from subscriptions
|
|
1760
|
-
delete this.subscriptions[subscriptionId];
|
|
1761
|
-
} catch (error) {
|
|
1762
|
-
console.error('Error in unsubscribe:', error);
|
|
1763
|
-
}
|
|
1764
|
-
}
|
|
1765
|
-
};
|
|
1766
|
-
} catch (error) {
|
|
1767
|
-
console.error('Error creating subscription:', error);
|
|
1768
|
-
throw error;
|
|
1769
|
-
}
|
|
474
|
+
// Delegate to the external function
|
|
475
|
+
return Utils.subscribe(this, holon, lens, callback);
|
|
1770
476
|
}
|
|
1771
477
|
|
|
1772
478
|
/**
|
|
@@ -1775,32 +481,14 @@ class HoloSphere {
|
|
|
1775
481
|
* @private
|
|
1776
482
|
*/
|
|
1777
483
|
notifySubscribers(data) {
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
}
|
|
1781
|
-
|
|
1782
|
-
try {
|
|
1783
|
-
Object.values(this.subscriptions).forEach(subscription => {
|
|
1784
|
-
if (subscription.active &&
|
|
1785
|
-
subscription.holon === data.holon &&
|
|
1786
|
-
subscription.lens === data.lens) {
|
|
1787
|
-
try {
|
|
1788
|
-
if (subscription.callback && typeof subscription.callback === 'function') {
|
|
1789
|
-
subscription.callback(data);
|
|
1790
|
-
}
|
|
1791
|
-
} catch (error) {
|
|
1792
|
-
console.warn('Error in subscription callback:', error);
|
|
1793
|
-
}
|
|
1794
|
-
}
|
|
1795
|
-
});
|
|
1796
|
-
} catch (error) {
|
|
1797
|
-
console.warn('Error notifying subscribers:', error);
|
|
1798
|
-
}
|
|
484
|
+
// Delegate to the external function
|
|
485
|
+
return Utils.notifySubscribers(this, data);
|
|
1799
486
|
}
|
|
1800
487
|
|
|
1801
488
|
// Add ID generation method
|
|
1802
489
|
generateId() {
|
|
1803
|
-
|
|
490
|
+
// Delegate to the external function
|
|
491
|
+
return Utils.generateId();
|
|
1804
492
|
}
|
|
1805
493
|
|
|
1806
494
|
// ================================ FEDERATION FUNCTIONS ================================
|
|
@@ -1809,13 +497,16 @@ class HoloSphere {
|
|
|
1809
497
|
* Creates a federation relationship between two holons
|
|
1810
498
|
* @param {string} holonId1 - The first holon ID
|
|
1811
499
|
* @param {string} holonId2 - The second holon ID
|
|
1812
|
-
* @param {string} password1 -
|
|
500
|
+
* @param {string} [password1] - Optional password for the first holon
|
|
1813
501
|
* @param {string} [password2] - Optional password for the second holon
|
|
1814
502
|
* @param {boolean} [bidirectional=true] - Whether to set up bidirectional notifications automatically
|
|
503
|
+
* @param {object} [lensConfig] - Optional lens-specific configuration
|
|
504
|
+
* @param {string[]} [lensConfig.federate] - List of lenses to federate (default: all)
|
|
505
|
+
* @param {string[]} [lensConfig.notify] - List of lenses to notify (default: all)
|
|
1815
506
|
* @returns {Promise<boolean>} - True if federation was created successfully
|
|
1816
507
|
*/
|
|
1817
|
-
async federate(holonId1, holonId2, password1, password2 = null, bidirectional = true) {
|
|
1818
|
-
return Federation.federate(this, holonId1, holonId2, password1, password2, bidirectional);
|
|
508
|
+
async federate(holonId1, holonId2, password1 = null, password2 = null, bidirectional = true, lensConfig = {}) {
|
|
509
|
+
return Federation.federate(this, holonId1, holonId2, password1, password2, bidirectional, lensConfig);
|
|
1819
510
|
}
|
|
1820
511
|
|
|
1821
512
|
/**
|
|
@@ -1841,6 +532,17 @@ class HoloSphere {
|
|
|
1841
532
|
async getFederation(holonId, password = null) {
|
|
1842
533
|
return Federation.getFederation(this, holonId, password);
|
|
1843
534
|
}
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* Retrieves the lens-specific configuration for a federation link between two holons.
|
|
538
|
+
* @param {string} holonId - The ID of the source holon.
|
|
539
|
+
* @param {string} targetHolonId - The ID of the target holon in the federation link.
|
|
540
|
+
* @param {string} [password] - Optional password for the source holon.
|
|
541
|
+
* @returns {Promise<object|null>} - An object with 'federate' and 'notify' arrays, or null if not found.
|
|
542
|
+
*/
|
|
543
|
+
async getFederatedConfig(holonId, targetHolonId, password = null) {
|
|
544
|
+
return Federation.getFederatedConfig(this, holonId, targetHolonId, password);
|
|
545
|
+
}
|
|
1844
546
|
|
|
1845
547
|
/**
|
|
1846
548
|
* Removes a federation relationship between holons
|
|
@@ -1936,111 +638,8 @@ class HoloSphere {
|
|
|
1936
638
|
* @returns {Promise<void>}
|
|
1937
639
|
*/
|
|
1938
640
|
async close() {
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
// Unsubscribe from all subscriptions
|
|
1942
|
-
const subscriptionIds = Object.keys(this.subscriptions);
|
|
1943
|
-
for (const id of subscriptionIds) {
|
|
1944
|
-
try {
|
|
1945
|
-
const subscription = this.subscriptions[id];
|
|
1946
|
-
if (subscription && subscription.active) {
|
|
1947
|
-
// Turn off the Gun subscription using the stored reference
|
|
1948
|
-
if (subscription.gunSubscription) {
|
|
1949
|
-
subscription.gunSubscription.off();
|
|
1950
|
-
}
|
|
1951
|
-
|
|
1952
|
-
// Mark as inactive
|
|
1953
|
-
subscription.active = false;
|
|
1954
|
-
}
|
|
1955
|
-
} catch (error) {
|
|
1956
|
-
console.warn(`Error cleaning up subscription ${id}:`, error);
|
|
1957
|
-
}
|
|
1958
|
-
}
|
|
1959
|
-
|
|
1960
|
-
// Clear subscriptions
|
|
1961
|
-
this.subscriptions = {};
|
|
1962
|
-
|
|
1963
|
-
// Clear schema cache
|
|
1964
|
-
this.clearSchemaCache();
|
|
1965
|
-
|
|
1966
|
-
// Close Gun connections
|
|
1967
|
-
if (this.gun.back) {
|
|
1968
|
-
try {
|
|
1969
|
-
// Clean up mesh connections
|
|
1970
|
-
const mesh = this.gun.back('opt.mesh');
|
|
1971
|
-
if (mesh) {
|
|
1972
|
-
// Clean up mesh.hear
|
|
1973
|
-
if (mesh.hear) {
|
|
1974
|
-
try {
|
|
1975
|
-
// Safely clear mesh.hear without modifying function properties
|
|
1976
|
-
const hearKeys = Object.keys(mesh.hear);
|
|
1977
|
-
for (const key of hearKeys) {
|
|
1978
|
-
// Check if it's an array before trying to clear it
|
|
1979
|
-
if (Array.isArray(mesh.hear[key])) {
|
|
1980
|
-
mesh.hear[key] = [];
|
|
1981
|
-
}
|
|
1982
|
-
}
|
|
1983
|
-
|
|
1984
|
-
// Create a new empty object for mesh.hear
|
|
1985
|
-
// Only if mesh.hear is not a function
|
|
1986
|
-
if (typeof mesh.hear !== 'function') {
|
|
1987
|
-
mesh.hear = {};
|
|
1988
|
-
}
|
|
1989
|
-
} catch (meshError) {
|
|
1990
|
-
console.warn('Error cleaning up Gun mesh hear:', meshError);
|
|
1991
|
-
}
|
|
1992
|
-
}
|
|
1993
|
-
|
|
1994
|
-
// Close any open sockets in the mesh
|
|
1995
|
-
if (mesh.way) {
|
|
1996
|
-
try {
|
|
1997
|
-
Object.values(mesh.way).forEach(connection => {
|
|
1998
|
-
if (connection && connection.wire && connection.wire.close) {
|
|
1999
|
-
connection.wire.close();
|
|
2000
|
-
}
|
|
2001
|
-
});
|
|
2002
|
-
} catch (sockError) {
|
|
2003
|
-
console.warn('Error closing mesh sockets:', sockError);
|
|
2004
|
-
}
|
|
2005
|
-
}
|
|
2006
|
-
|
|
2007
|
-
// Clear the peers list
|
|
2008
|
-
if (mesh.opt && mesh.opt.peers) {
|
|
2009
|
-
mesh.opt.peers = {};
|
|
2010
|
-
}
|
|
2011
|
-
}
|
|
2012
|
-
|
|
2013
|
-
// Attempt to clean up any TCP connections
|
|
2014
|
-
if (this.gun.back('opt.web')) {
|
|
2015
|
-
try {
|
|
2016
|
-
const server = this.gun.back('opt.web');
|
|
2017
|
-
if (server && server.close) {
|
|
2018
|
-
server.close();
|
|
2019
|
-
}
|
|
2020
|
-
} catch (webError) {
|
|
2021
|
-
console.warn('Error closing web server:', webError);
|
|
2022
|
-
}
|
|
2023
|
-
}
|
|
2024
|
-
} catch (error) {
|
|
2025
|
-
console.warn('Error accessing Gun mesh:', error);
|
|
2026
|
-
}
|
|
2027
|
-
}
|
|
2028
|
-
|
|
2029
|
-
// Clear all Gun instance listeners
|
|
2030
|
-
try {
|
|
2031
|
-
this.gun.off();
|
|
2032
|
-
} catch (error) {
|
|
2033
|
-
console.warn('Error turning off Gun listeners:', error);
|
|
2034
|
-
}
|
|
2035
|
-
|
|
2036
|
-
// Wait a moment for cleanup to complete
|
|
2037
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
2038
|
-
}
|
|
2039
|
-
|
|
2040
|
-
console.log('HoloSphere instance closed successfully');
|
|
2041
|
-
} catch (error) {
|
|
2042
|
-
console.error('Error closing HoloSphere instance:', error);
|
|
2043
|
-
}
|
|
641
|
+
// Delegate to the external function
|
|
642
|
+
return Utils.close(this);
|
|
2044
643
|
}
|
|
2045
644
|
|
|
2046
645
|
/**
|
|
@@ -2050,8 +649,16 @@ class HoloSphere {
|
|
|
2050
649
|
* @returns {string} - Namespaced username
|
|
2051
650
|
*/
|
|
2052
651
|
userName(holonId) {
|
|
2053
|
-
|
|
2054
|
-
return
|
|
652
|
+
// Delegate to the external function
|
|
653
|
+
return Utils.userName(this, holonId);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* Returns the current version of the HoloSphere library.
|
|
658
|
+
* @returns {string} The library version.
|
|
659
|
+
*/
|
|
660
|
+
getVersion() {
|
|
661
|
+
return HOLOSPHERE_VERSION;
|
|
2055
662
|
}
|
|
2056
663
|
}
|
|
2057
664
|
|