holosphere 1.1.9 → 1.1.11
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 +797 -0
- package/examples/federation.js +98 -90
- package/examples/{references.js → holograms.js} +49 -51
- package/federation.js +307 -197
- package/global.js +560 -0
- package/hologram.js +156 -0
- package/holosphere.d.ts +94 -7
- package/holosphere.js +211 -1464
- package/node.js +155 -0
- package/package.json +2 -5
- package/schema.js +132 -0
- package/test/auth.test.js +85 -51
- package/test/delete.test.js +15 -11
- package/test/federation.test.js +179 -0
- package/test/hologram.test.js +316 -0
- package/test/holosphere.test.js +189 -5
- package/test/subscription.test.js +364 -0
- package/utils.js +290 -0
package/holosphere.js
CHANGED
|
@@ -1,35 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module holosphere
|
|
3
|
+
* @version 1.1.11
|
|
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.11';
|
|
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) {
|
|
@@ -54,6 +64,13 @@ class HoloSphere {
|
|
|
54
64
|
|
|
55
65
|
// Initialize subscriptions
|
|
56
66
|
this.subscriptions = {};
|
|
67
|
+
|
|
68
|
+
// Initialize schema cache
|
|
69
|
+
this.schemaCache = new Map();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
getGun() {
|
|
73
|
+
return this.gun;
|
|
57
74
|
}
|
|
58
75
|
|
|
59
76
|
// ================================ SCHEMA FUNCTIONS ================================
|
|
@@ -65,76 +82,31 @@ class HoloSphere {
|
|
|
65
82
|
* @returns {Promise} - Resolves when the schema is set.
|
|
66
83
|
*/
|
|
67
84
|
async setSchema(lens, schema) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Basic schema validation
|
|
73
|
-
if (!schema.type || typeof schema.type !== 'string') {
|
|
74
|
-
throw new Error('setSchema: Schema must have a type field');
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const metaSchema = {
|
|
78
|
-
type: 'object',
|
|
79
|
-
required: ['type', 'properties'],
|
|
80
|
-
properties: {
|
|
81
|
-
type: { type: 'string' },
|
|
82
|
-
properties: {
|
|
83
|
-
type: 'object',
|
|
84
|
-
additionalProperties: {
|
|
85
|
-
type: 'object',
|
|
86
|
-
required: ['type'],
|
|
87
|
-
properties: {
|
|
88
|
-
type: { type: 'string' }
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
},
|
|
92
|
-
required: {
|
|
93
|
-
type: 'array',
|
|
94
|
-
items: { type: 'string' }
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
const valid = this.validator.validate(metaSchema, schema);
|
|
100
|
-
if (!valid) {
|
|
101
|
-
throw new Error(`Invalid schema structure: ${JSON.stringify(this.validator.errors)}`);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (!schema.properties || typeof schema.properties !== 'object') {
|
|
105
|
-
throw new Error('Schema must have properties in strict mode');
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (!schema.required || !Array.isArray(schema.required) || schema.required.length === 0) {
|
|
109
|
-
throw new Error('Schema must have required fields in strict mode');
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Store schema in global table with lens as key
|
|
113
|
-
await this.putGlobal('schemas', {
|
|
114
|
-
id: lens,
|
|
115
|
-
schema: schema,
|
|
116
|
-
timestamp: Date.now()
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
return true;
|
|
85
|
+
// Delegate to the external function
|
|
86
|
+
return SchemaOps.setSchema(this, lens, schema);
|
|
120
87
|
}
|
|
121
88
|
|
|
122
89
|
/**
|
|
123
90
|
* Retrieves the JSON schema for a specific lens.
|
|
124
91
|
* @param {string} lens - The lens identifier.
|
|
92
|
+
* @param {object} [options] - Additional options
|
|
93
|
+
* @param {boolean} [options.useCache=true] - Whether to use the schema cache
|
|
94
|
+
* @param {number} [options.maxCacheAge=3600000] - Maximum cache age in milliseconds (default: 1 hour)
|
|
125
95
|
* @returns {Promise<object|null>} - The retrieved schema or null if not found.
|
|
126
96
|
*/
|
|
127
|
-
async getSchema(lens) {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
const schemaData = await this.getGlobal('schemas', lens);
|
|
133
|
-
if (!schemaData || !schemaData.schema) {
|
|
134
|
-
return null;
|
|
135
|
-
}
|
|
97
|
+
async getSchema(lens, options = {}) {
|
|
98
|
+
// Delegate to the external function
|
|
99
|
+
return SchemaOps.getSchema(this, lens, options);
|
|
100
|
+
}
|
|
136
101
|
|
|
137
|
-
|
|
102
|
+
/**
|
|
103
|
+
* Clears the schema cache or a specific schema from the cache.
|
|
104
|
+
* @param {string} [lens] - Optional lens to clear from cache. If not provided, clears entire cache.
|
|
105
|
+
* @returns {boolean} - Returns true if successful
|
|
106
|
+
*/
|
|
107
|
+
clearSchemaCache(lens = null) {
|
|
108
|
+
// Delegate to the external function
|
|
109
|
+
return SchemaOps.clearSchemaCache(this, lens);
|
|
138
110
|
}
|
|
139
111
|
|
|
140
112
|
// ================================ CONTENT FUNCTIONS ================================
|
|
@@ -152,162 +124,8 @@ class HoloSphere {
|
|
|
152
124
|
* @returns {Promise<boolean>} - Returns true if successful, false if there was an error
|
|
153
125
|
*/
|
|
154
126
|
async put(holon, lens, data, password = null, options = {}) {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
if (!data.id) {
|
|
160
|
-
data.id = this.generateId();
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Get and validate schema only in strict mode
|
|
164
|
-
if (this.strict) {
|
|
165
|
-
const schema = await this.getSchema(lens);
|
|
166
|
-
if (!schema) {
|
|
167
|
-
throw new Error('Schema required in strict mode');
|
|
168
|
-
}
|
|
169
|
-
const dataToValidate = JSON.parse(JSON.stringify(data));
|
|
170
|
-
const valid = this.validator.validate(schema, dataToValidate);
|
|
171
|
-
|
|
172
|
-
if (!valid) {
|
|
173
|
-
const errorMsg = `Schema validation failed: ${JSON.stringify(this.validator.errors)}`;
|
|
174
|
-
throw new Error(errorMsg);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
try {
|
|
179
|
-
const user = this.gun.user();
|
|
180
|
-
|
|
181
|
-
if (password) {
|
|
182
|
-
try {
|
|
183
|
-
await new Promise((resolve, reject) => {
|
|
184
|
-
user.auth(this.userName(holon), password, (ack) => {
|
|
185
|
-
if (ack.err) reject(new Error(ack.err));
|
|
186
|
-
else resolve();
|
|
187
|
-
});
|
|
188
|
-
});
|
|
189
|
-
} catch (loginError) {
|
|
190
|
-
// If authentication fails, try to create user and then authenticate
|
|
191
|
-
try {
|
|
192
|
-
await new Promise((resolve, reject) => {
|
|
193
|
-
user.create(this.userName(holon), password, (ack) => {
|
|
194
|
-
if (ack.err) {
|
|
195
|
-
// Don't reject if the user is already being created or already exists
|
|
196
|
-
if (ack.err.includes('already being created') ||
|
|
197
|
-
ack.err.includes('already created')) {
|
|
198
|
-
console.warn(`User creation note: ${ack.err}, continuing...`);
|
|
199
|
-
// Try to authenticate again
|
|
200
|
-
user.auth(this.userName(holon), password, (authAck) => {
|
|
201
|
-
if (authAck.err) {
|
|
202
|
-
if (authAck.err.includes('already being created') ||
|
|
203
|
-
authAck.err.includes('already created')) {
|
|
204
|
-
console.warn(`Auth note: ${authAck.err}, continuing...`);
|
|
205
|
-
resolve(); // Continue anyway
|
|
206
|
-
} else {
|
|
207
|
-
reject(new Error(authAck.err));
|
|
208
|
-
}
|
|
209
|
-
} else {
|
|
210
|
-
resolve();
|
|
211
|
-
}
|
|
212
|
-
});
|
|
213
|
-
} else {
|
|
214
|
-
reject(new Error(ack.err));
|
|
215
|
-
}
|
|
216
|
-
} else {
|
|
217
|
-
user.auth(this.userName(holon), password, (authAck) => {
|
|
218
|
-
if (authAck.err) reject(new Error(authAck.err));
|
|
219
|
-
else resolve();
|
|
220
|
-
});
|
|
221
|
-
}
|
|
222
|
-
});
|
|
223
|
-
});
|
|
224
|
-
} catch (createError) {
|
|
225
|
-
// Try one last authentication
|
|
226
|
-
try {
|
|
227
|
-
await new Promise((resolve, reject) => {
|
|
228
|
-
setTimeout(() => {
|
|
229
|
-
user.auth(this.userName(holon), password, (ack) => {
|
|
230
|
-
if (ack.err) {
|
|
231
|
-
// Continue even if auth fails at this point
|
|
232
|
-
console.warn(`Final auth attempt note: ${ack.err}, continuing with limited functionality`);
|
|
233
|
-
resolve();
|
|
234
|
-
} else {
|
|
235
|
-
resolve();
|
|
236
|
-
}
|
|
237
|
-
});
|
|
238
|
-
}, 100); // Short delay before retry
|
|
239
|
-
});
|
|
240
|
-
} catch (finalAuthError) {
|
|
241
|
-
console.warn('All authentication attempts failed, continuing with limited functionality');
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
return new Promise((resolve, reject) => {
|
|
248
|
-
try {
|
|
249
|
-
const payload = JSON.stringify(data);
|
|
250
|
-
|
|
251
|
-
const putCallback = async (ack) => {
|
|
252
|
-
if (ack.err) {
|
|
253
|
-
reject(new Error(ack.err));
|
|
254
|
-
} else {
|
|
255
|
-
this.notifySubscribers({
|
|
256
|
-
holon,
|
|
257
|
-
lens,
|
|
258
|
-
...data
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
// Auto-propagate to federation by default
|
|
262
|
-
const shouldPropagate = options.autoPropagate !== false;
|
|
263
|
-
let propagationResult = null;
|
|
264
|
-
|
|
265
|
-
if (shouldPropagate) {
|
|
266
|
-
try {
|
|
267
|
-
// Default to using references
|
|
268
|
-
const propagationOptions = {
|
|
269
|
-
useReferences: true,
|
|
270
|
-
...options.propagationOptions
|
|
271
|
-
};
|
|
272
|
-
|
|
273
|
-
propagationResult = await this.propagate(
|
|
274
|
-
holon,
|
|
275
|
-
lens,
|
|
276
|
-
data,
|
|
277
|
-
propagationOptions
|
|
278
|
-
);
|
|
279
|
-
|
|
280
|
-
// Still resolve with true even if propagation had errors
|
|
281
|
-
if (propagationResult.errors > 0) {
|
|
282
|
-
console.warn('Auto-propagation had errors:', propagationResult);
|
|
283
|
-
}
|
|
284
|
-
} catch (propError) {
|
|
285
|
-
console.warn('Error in auto-propagation:', propError);
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
resolve({
|
|
290
|
-
success: true,
|
|
291
|
-
propagationResult
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
|
-
};
|
|
295
|
-
|
|
296
|
-
if (password) {
|
|
297
|
-
// For private data, use the authenticated user's holon
|
|
298
|
-
user.get('private').get(lens).get(data.id).put(payload, putCallback);
|
|
299
|
-
} else {
|
|
300
|
-
// For public data, use the regular path
|
|
301
|
-
this.gun.get(this.appname).get(holon).get(lens).get(data.id).put(payload, putCallback);
|
|
302
|
-
}
|
|
303
|
-
} catch (error) {
|
|
304
|
-
reject(error);
|
|
305
|
-
}
|
|
306
|
-
});
|
|
307
|
-
} catch (error) {
|
|
308
|
-
console.error('Error in put:', error);
|
|
309
|
-
throw error;
|
|
310
|
-
}
|
|
127
|
+
// Delegate to the external function
|
|
128
|
+
return ContentOps.put(this, holon, lens, data, password, options);
|
|
311
129
|
}
|
|
312
130
|
|
|
313
131
|
/**
|
|
@@ -321,215 +139,8 @@ class HoloSphere {
|
|
|
321
139
|
* @returns {Promise<object|null>} - The retrieved content or null if not found.
|
|
322
140
|
*/
|
|
323
141
|
async get(holon, lens, key, password = null, options = {}) {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
return null;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
const { resolveReferences = true } = options;
|
|
330
|
-
|
|
331
|
-
// Only check schema in strict mode
|
|
332
|
-
let schema;
|
|
333
|
-
if (this.strict) {
|
|
334
|
-
schema = await this.getSchema(lens);
|
|
335
|
-
if (!schema) {
|
|
336
|
-
throw new Error('Schema required in strict mode');
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
try {
|
|
341
|
-
const user = this.gun.user();
|
|
342
|
-
|
|
343
|
-
if (password) {
|
|
344
|
-
try {
|
|
345
|
-
await new Promise((resolve, reject) => {
|
|
346
|
-
user.auth(this.userName(holon), password, (ack) => {
|
|
347
|
-
if (ack.err) reject(new Error(ack.err));
|
|
348
|
-
else resolve();
|
|
349
|
-
});
|
|
350
|
-
});
|
|
351
|
-
} catch (loginError) {
|
|
352
|
-
// If authentication fails, try to create user and then authenticate
|
|
353
|
-
await new Promise((resolve, reject) => {
|
|
354
|
-
user.create(this.userName(holon), password, (ack) => {
|
|
355
|
-
if (ack.err) reject(new Error(ack.err));
|
|
356
|
-
else {
|
|
357
|
-
user.auth(this.userName(holon), password, (authAck) => {
|
|
358
|
-
if (authAck.err) reject(new Error(authAck.err));
|
|
359
|
-
else resolve();
|
|
360
|
-
});
|
|
361
|
-
}
|
|
362
|
-
});
|
|
363
|
-
});
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
return new Promise((resolve) => {
|
|
368
|
-
const handleData = async (data) => {
|
|
369
|
-
if (!data) {
|
|
370
|
-
resolve(null);
|
|
371
|
-
return;
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
try {
|
|
375
|
-
const parsed = await this.parse(data);
|
|
376
|
-
|
|
377
|
-
if (!parsed) {
|
|
378
|
-
resolve(null);
|
|
379
|
-
return;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
// Check if this is a reference that needs to be resolved
|
|
383
|
-
if (resolveReferences !== false && parsed) {
|
|
384
|
-
// Check if this is a simple reference (id + soul)
|
|
385
|
-
if (parsed.soul) {
|
|
386
|
-
console.log(`Resolving simple reference with soul: ${parsed.soul}`);
|
|
387
|
-
try {
|
|
388
|
-
// For direct soul resolution, we need to parse the soul to get the right path
|
|
389
|
-
const soulParts = parsed.soul.split('/');
|
|
390
|
-
if (soulParts.length >= 4) { // Expected format: appname/holon/lens/key
|
|
391
|
-
const originHolon = soulParts[1];
|
|
392
|
-
const originLens = soulParts[2];
|
|
393
|
-
const originKey = soulParts[3];
|
|
394
|
-
|
|
395
|
-
console.log(`Extracting from soul - holon: ${originHolon}, lens: ${originLens}, key: ${originKey}`);
|
|
396
|
-
|
|
397
|
-
// Get original data using the extracted path components
|
|
398
|
-
const originalData = await this.get(
|
|
399
|
-
originHolon,
|
|
400
|
-
originLens,
|
|
401
|
-
originKey,
|
|
402
|
-
null,
|
|
403
|
-
{ resolveReferences: false } // Prevent infinite recursion
|
|
404
|
-
);
|
|
405
|
-
|
|
406
|
-
if (originalData) {
|
|
407
|
-
console.log(`Original data found through soul path resolution:`, originalData);
|
|
408
|
-
resolve({
|
|
409
|
-
...originalData,
|
|
410
|
-
_federation: {
|
|
411
|
-
isReference: true,
|
|
412
|
-
resolved: true,
|
|
413
|
-
soul: parsed.soul,
|
|
414
|
-
timestamp: Date.now()
|
|
415
|
-
}
|
|
416
|
-
});
|
|
417
|
-
return;
|
|
418
|
-
} else {
|
|
419
|
-
console.warn(`Could not resolve reference: original data not found at extracted path`);
|
|
420
|
-
}
|
|
421
|
-
} else {
|
|
422
|
-
console.warn(`Soul doesn't match expected format: ${parsed.soul}`);
|
|
423
|
-
}
|
|
424
|
-
} catch (error) {
|
|
425
|
-
console.warn(`Error resolving reference by soul: ${error.message}`);
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
// Legacy federation reference
|
|
429
|
-
else if (parsed._federation && parsed._federation.isReference) {
|
|
430
|
-
console.log(`Resolving legacy federation reference from ${parsed._federation.origin}`);
|
|
431
|
-
try {
|
|
432
|
-
const reference = parsed._federation;
|
|
433
|
-
const originalData = await this.get(
|
|
434
|
-
reference.origin,
|
|
435
|
-
reference.lens,
|
|
436
|
-
key,
|
|
437
|
-
null,
|
|
438
|
-
{ resolveReferences: false } // Prevent infinite recursion
|
|
439
|
-
);
|
|
440
|
-
|
|
441
|
-
if (originalData) {
|
|
442
|
-
return {
|
|
443
|
-
...originalData,
|
|
444
|
-
_federation: {
|
|
445
|
-
...reference,
|
|
446
|
-
resolved: true,
|
|
447
|
-
timestamp: Date.now()
|
|
448
|
-
}
|
|
449
|
-
};
|
|
450
|
-
} else {
|
|
451
|
-
console.warn(`Could not resolve legacy reference: original data not found`);
|
|
452
|
-
return parsed; // Return the reference if we can't resolve it
|
|
453
|
-
}
|
|
454
|
-
} catch (error) {
|
|
455
|
-
console.warn(`Error resolving legacy reference: ${error.message}`);
|
|
456
|
-
return parsed;
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
if (schema) {
|
|
462
|
-
const valid = this.validator.validate(schema, parsed);
|
|
463
|
-
if (!valid) {
|
|
464
|
-
console.error('get: Invalid data according to schema:', this.validator.errors);
|
|
465
|
-
if (this.strict) {
|
|
466
|
-
resolve(null);
|
|
467
|
-
return;
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
resolve(parsed);
|
|
473
|
-
} catch (error) {
|
|
474
|
-
console.error('Error parsing data:', error);
|
|
475
|
-
resolve(null);
|
|
476
|
-
}
|
|
477
|
-
};
|
|
478
|
-
|
|
479
|
-
if (password) {
|
|
480
|
-
// For private data, use the authenticated user's holon
|
|
481
|
-
user.get('private').get(lens).get(key).once(handleData);
|
|
482
|
-
} else {
|
|
483
|
-
// For public data, use the regular path
|
|
484
|
-
this.gun.get(this.appname).get(holon).get(lens).get(key).once(handleData);
|
|
485
|
-
}
|
|
486
|
-
});
|
|
487
|
-
} catch (error) {
|
|
488
|
-
console.error('Error in get:', error);
|
|
489
|
-
return null;
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
/**
|
|
494
|
-
* Retrieves a node directly using its soul path
|
|
495
|
-
* @param {string} soul - The soul path of the node
|
|
496
|
-
* @returns {Promise<any>} - The retrieved node or null if not found.
|
|
497
|
-
*/
|
|
498
|
-
async getNodeBySoul(soul) {
|
|
499
|
-
if (!soul) {
|
|
500
|
-
throw new Error('getNodeBySoul: Missing soul parameter');
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
console.log(`getNodeBySoul: Accessing soul ${soul}`);
|
|
504
|
-
|
|
505
|
-
return new Promise((resolve) => {
|
|
506
|
-
try {
|
|
507
|
-
const ref = this.getNodeRef(soul);
|
|
508
|
-
ref.once((data) => {
|
|
509
|
-
console.log(`getNodeBySoul: Retrieved data:`, data);
|
|
510
|
-
if (!data) {
|
|
511
|
-
resolve(null);
|
|
512
|
-
return;
|
|
513
|
-
}
|
|
514
|
-
resolve(data); // Return the data directly
|
|
515
|
-
});
|
|
516
|
-
} catch (error) {
|
|
517
|
-
console.error(`getNodeBySoul error:`, error);
|
|
518
|
-
resolve(null);
|
|
519
|
-
}
|
|
520
|
-
});
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
/**
|
|
524
|
-
* Propagates data to federated holons
|
|
525
|
-
* @param {string} holon - The holon identifier
|
|
526
|
-
* @param {string} lens - The lens identifier
|
|
527
|
-
* @param {object} data - The data to propagate
|
|
528
|
-
* @param {object} [options] - Propagation options
|
|
529
|
-
* @returns {Promise<object>} - Result with success count and errors
|
|
530
|
-
*/
|
|
531
|
-
async propagate(holon, lens, data, options = {}) {
|
|
532
|
-
return Federation.propagate(this, holon, lens, data, options);
|
|
142
|
+
// Delegate to the external function
|
|
143
|
+
return ContentOps.get(this, holon, lens, key, password, options);
|
|
533
144
|
}
|
|
534
145
|
|
|
535
146
|
/**
|
|
@@ -540,75 +151,8 @@ class HoloSphere {
|
|
|
540
151
|
* @returns {Promise<Array<object>>} - The retrieved content.
|
|
541
152
|
*/
|
|
542
153
|
async getAll(holon, lens, password = null) {
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
const schema = await this.getSchema(lens);
|
|
548
|
-
if (!schema && this.strict) {
|
|
549
|
-
throw new Error('getAll: Schema required in strict mode');
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
try {
|
|
553
|
-
const user = this.gun.user();
|
|
554
|
-
|
|
555
|
-
return new Promise((resolve) => {
|
|
556
|
-
const output = new Map();
|
|
557
|
-
|
|
558
|
-
const processData = async (data, key) => {
|
|
559
|
-
if (!data || key === '_') return;
|
|
560
|
-
|
|
561
|
-
try {
|
|
562
|
-
const parsed = await this.parse(data);
|
|
563
|
-
if (!parsed || !parsed.id) return;
|
|
564
|
-
|
|
565
|
-
if (schema) {
|
|
566
|
-
const valid = this.validator.validate(schema, parsed);
|
|
567
|
-
if (valid || !this.strict) {
|
|
568
|
-
output.set(parsed.id, parsed);
|
|
569
|
-
}
|
|
570
|
-
} else {
|
|
571
|
-
output.set(parsed.id, parsed);
|
|
572
|
-
}
|
|
573
|
-
} catch (error) {
|
|
574
|
-
console.error('Error processing data:', error);
|
|
575
|
-
}
|
|
576
|
-
};
|
|
577
|
-
|
|
578
|
-
const handleData = async (data) => {
|
|
579
|
-
if (!data) {
|
|
580
|
-
resolve([]);
|
|
581
|
-
return;
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
const initialPromises = [];
|
|
585
|
-
Object.keys(data)
|
|
586
|
-
.filter(key => key !== '_')
|
|
587
|
-
.forEach(key => {
|
|
588
|
-
initialPromises.push(processData(data[key], key));
|
|
589
|
-
});
|
|
590
|
-
|
|
591
|
-
try {
|
|
592
|
-
await Promise.all(initialPromises);
|
|
593
|
-
resolve(Array.from(output.values()));
|
|
594
|
-
} catch (error) {
|
|
595
|
-
console.error('Error in getAll:', error);
|
|
596
|
-
resolve([]);
|
|
597
|
-
}
|
|
598
|
-
};
|
|
599
|
-
|
|
600
|
-
if (password) {
|
|
601
|
-
// For private data, use the authenticated user's holon
|
|
602
|
-
user.get('private').get(lens).once(handleData);
|
|
603
|
-
} else {
|
|
604
|
-
// For public data, use the regular path
|
|
605
|
-
this.gun.get(this.appname).get(holon).get(lens).once(handleData);
|
|
606
|
-
}
|
|
607
|
-
});
|
|
608
|
-
} catch (error) {
|
|
609
|
-
console.error('Error in getAll:', error);
|
|
610
|
-
return [];
|
|
611
|
-
}
|
|
154
|
+
// Delegate to the external function
|
|
155
|
+
return ContentOps.getAll(this, holon, lens, password);
|
|
612
156
|
}
|
|
613
157
|
|
|
614
158
|
/**
|
|
@@ -617,56 +161,8 @@ class HoloSphere {
|
|
|
617
161
|
* @returns {Promise<object>} - The parsed data.
|
|
618
162
|
*/
|
|
619
163
|
async parse(rawData) {
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
if (!rawData) {
|
|
623
|
-
throw new Error('parse: No data provided');
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
try {
|
|
627
|
-
|
|
628
|
-
if (typeof rawData === 'string') {
|
|
629
|
-
parsedData = await JSON.parse(rawData);
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
if (rawData.soul) {
|
|
634
|
-
const data = await this.getNodeRef(rawData.soul).once();
|
|
635
|
-
if (!data) {
|
|
636
|
-
throw new Error('Referenced data not found');
|
|
637
|
-
}
|
|
638
|
-
return JSON.parse(data);
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
if (typeof rawData === 'object' && rawData !== null) {
|
|
643
|
-
if (rawData._ && rawData._["#"]) {
|
|
644
|
-
const pathParts = rawData._['#'].split('/');
|
|
645
|
-
if (pathParts.length < 4) {
|
|
646
|
-
throw new Error('Invalid reference format');
|
|
647
|
-
}
|
|
648
|
-
parsedData = await this.get(pathParts[1], pathParts[2], pathParts[3]);
|
|
649
|
-
if (!parsedData) {
|
|
650
|
-
throw new Error('Referenced data not found');
|
|
651
|
-
}
|
|
652
|
-
} else if (rawData._ && rawData._['>']) {
|
|
653
|
-
const nodeValue = Object.values(rawData).find(v => typeof v !== 'object' && v !== '_');
|
|
654
|
-
if (!nodeValue) {
|
|
655
|
-
throw new Error('Invalid node data');
|
|
656
|
-
}
|
|
657
|
-
parsedData = JSON.parse(nodeValue);
|
|
658
|
-
} else {
|
|
659
|
-
parsedData = rawData;
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
return parsedData;
|
|
664
|
-
|
|
665
|
-
} catch (error) {
|
|
666
|
-
console.log("Parsing not a JSON, returning raw data", rawData);
|
|
667
|
-
return rawData;
|
|
668
|
-
//throw new Error(`Parse error: ${error.message}`);
|
|
669
|
-
}
|
|
164
|
+
// Delegate to the external function
|
|
165
|
+
return ContentOps.parse(this, rawData);
|
|
670
166
|
}
|
|
671
167
|
|
|
672
168
|
/**
|
|
@@ -678,40 +174,8 @@ class HoloSphere {
|
|
|
678
174
|
* @returns {Promise<boolean>} - Returns true if successful
|
|
679
175
|
*/
|
|
680
176
|
async delete(holon, lens, key, password = null) {
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
try {
|
|
686
|
-
// Get the appropriate holon
|
|
687
|
-
const user = this.gun.user();
|
|
688
|
-
|
|
689
|
-
// Delete data from holon
|
|
690
|
-
return new Promise((resolve, reject) => {
|
|
691
|
-
if (password) {
|
|
692
|
-
// For private data, use the authenticated user's holon
|
|
693
|
-
user.get('private').get(lens).get(key).put(null, ack => {
|
|
694
|
-
if (ack.err) {
|
|
695
|
-
reject(new Error(ack.err));
|
|
696
|
-
} else {
|
|
697
|
-
resolve(true);
|
|
698
|
-
}
|
|
699
|
-
});
|
|
700
|
-
} else {
|
|
701
|
-
// For public data, use the regular path
|
|
702
|
-
this.gun.get(this.appname).get(holon).get(lens).get(key).put(null, ack => {
|
|
703
|
-
if (ack.err) {
|
|
704
|
-
reject(new Error(ack.err));
|
|
705
|
-
} else {
|
|
706
|
-
resolve(true);
|
|
707
|
-
}
|
|
708
|
-
});
|
|
709
|
-
}
|
|
710
|
-
});
|
|
711
|
-
} catch (error) {
|
|
712
|
-
console.error('Error in delete:', error);
|
|
713
|
-
throw error;
|
|
714
|
-
}
|
|
177
|
+
// Delegate to the external function (renamed to deleteFunc in module)
|
|
178
|
+
return ContentOps.deleteFunc(this, holon, lens, key, password);
|
|
715
179
|
}
|
|
716
180
|
|
|
717
181
|
/**
|
|
@@ -722,63 +186,8 @@ class HoloSphere {
|
|
|
722
186
|
* @returns {Promise<boolean>} - Returns true if successful
|
|
723
187
|
*/
|
|
724
188
|
async deleteAll(holon, lens, password = null) {
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
return false;
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
try {
|
|
731
|
-
// Get the appropriate holon
|
|
732
|
-
const user = this.gun.user();
|
|
733
|
-
|
|
734
|
-
return new Promise((resolve) => {
|
|
735
|
-
let deletionPromises = [];
|
|
736
|
-
|
|
737
|
-
const dataPath = password ?
|
|
738
|
-
user.get('private').get(lens) :
|
|
739
|
-
this.gun.get(this.appname).get(holon).get(lens);
|
|
740
|
-
|
|
741
|
-
// First get all the data to find keys to delete
|
|
742
|
-
dataPath.once((data) => {
|
|
743
|
-
if (!data) {
|
|
744
|
-
resolve(true); // Nothing to delete
|
|
745
|
-
return;
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
// Get all keys except Gun's metadata key '_'
|
|
749
|
-
const keys = Object.keys(data).filter(key => key !== '_');
|
|
750
|
-
|
|
751
|
-
// Create deletion promises for each key
|
|
752
|
-
keys.forEach(key => {
|
|
753
|
-
deletionPromises.push(
|
|
754
|
-
new Promise((resolveDelete) => {
|
|
755
|
-
const deletePath = password ?
|
|
756
|
-
user.get('private').get(lens).get(key) :
|
|
757
|
-
this.gun.get(this.appname).get(holon).get(lens).get(key);
|
|
758
|
-
|
|
759
|
-
deletePath.put(null, ack => {
|
|
760
|
-
resolveDelete(!!ack.ok); // Convert to boolean
|
|
761
|
-
});
|
|
762
|
-
})
|
|
763
|
-
);
|
|
764
|
-
});
|
|
765
|
-
|
|
766
|
-
// Wait for all deletions to complete
|
|
767
|
-
Promise.all(deletionPromises)
|
|
768
|
-
.then(results => {
|
|
769
|
-
const allSuccessful = results.every(result => result === true);
|
|
770
|
-
resolve(allSuccessful);
|
|
771
|
-
})
|
|
772
|
-
.catch(error => {
|
|
773
|
-
console.error('Error in deleteAll:', error);
|
|
774
|
-
resolve(false);
|
|
775
|
-
});
|
|
776
|
-
});
|
|
777
|
-
});
|
|
778
|
-
} catch (error) {
|
|
779
|
-
console.error('Error in deleteAll:', error);
|
|
780
|
-
return false;
|
|
781
|
-
}
|
|
189
|
+
// Delegate to the external function
|
|
190
|
+
return ContentOps.deleteAll(this, holon, lens, password);
|
|
782
191
|
}
|
|
783
192
|
|
|
784
193
|
// ================================ NODE FUNCTIONS ================================
|
|
@@ -791,27 +200,8 @@ class HoloSphere {
|
|
|
791
200
|
* @param {object} data - The node to store.
|
|
792
201
|
*/
|
|
793
202
|
async putNode(holon, lens, data) {
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
return new Promise((resolve, reject) => {
|
|
799
|
-
try {
|
|
800
|
-
this.gun.get(this.appname)
|
|
801
|
-
.get(holon)
|
|
802
|
-
.get(lens)
|
|
803
|
-
.get('value') // Store at 'value' key
|
|
804
|
-
.put(data.value, ack => { // Store the value directly
|
|
805
|
-
if (ack.err) {
|
|
806
|
-
reject(new Error(ack.err));
|
|
807
|
-
} else {
|
|
808
|
-
resolve(true);
|
|
809
|
-
}
|
|
810
|
-
});
|
|
811
|
-
} catch (error) {
|
|
812
|
-
reject(error);
|
|
813
|
-
}
|
|
814
|
-
});
|
|
203
|
+
// Delegate to the external function
|
|
204
|
+
return NodeOps.putNode(this, holon, lens, data);
|
|
815
205
|
}
|
|
816
206
|
|
|
817
207
|
/**
|
|
@@ -822,46 +212,28 @@ class HoloSphere {
|
|
|
822
212
|
* @returns {Promise<any>} - The retrieved node or null if not found.
|
|
823
213
|
*/
|
|
824
214
|
async getNode(holon, lens, key) {
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
return new Promise((resolve) => {
|
|
830
|
-
this.gun.get(this.appname)
|
|
831
|
-
.get(holon)
|
|
832
|
-
.get(lens)
|
|
833
|
-
.get(key)
|
|
834
|
-
.once((data) => {
|
|
835
|
-
if (!data) {
|
|
836
|
-
resolve(null);
|
|
837
|
-
return;
|
|
838
|
-
}
|
|
839
|
-
resolve(data); // Return the data directly
|
|
840
|
-
});
|
|
841
|
-
});
|
|
215
|
+
// Delegate to the external function
|
|
216
|
+
return NodeOps.getNode(this, holon, lens, key);
|
|
842
217
|
}
|
|
843
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
|
+
*/
|
|
844
224
|
getNodeRef(soul) {
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
const parts = soul.split('/').filter(part => {
|
|
850
|
-
if (!part.trim() || /[<>:"/\\|?*]/.test(part)) {
|
|
851
|
-
throw new Error('getNodeRef: Invalid path segment');
|
|
852
|
-
}
|
|
853
|
-
return part.trim();
|
|
854
|
-
});
|
|
855
|
-
|
|
856
|
-
if (parts.length === 0) {
|
|
857
|
-
throw new Error('getNodeRef: Invalid soul format');
|
|
858
|
-
}
|
|
225
|
+
// Delegate to the external function
|
|
226
|
+
return NodeOps.getNodeRef(this, soul);
|
|
227
|
+
}
|
|
859
228
|
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
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);
|
|
865
237
|
}
|
|
866
238
|
|
|
867
239
|
/**
|
|
@@ -872,22 +244,8 @@ class HoloSphere {
|
|
|
872
244
|
* @returns {Promise<boolean>} - Returns true if successful
|
|
873
245
|
*/
|
|
874
246
|
async deleteNode(holon, lens, key) {
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
}
|
|
878
|
-
return new Promise((resolve, reject) => {
|
|
879
|
-
this.gun.get(this.appname)
|
|
880
|
-
.get(holon)
|
|
881
|
-
.get(lens)
|
|
882
|
-
.get(key)
|
|
883
|
-
.put(null, ack => {
|
|
884
|
-
if (ack.err) {
|
|
885
|
-
reject(new Error(ack.err));
|
|
886
|
-
} else {
|
|
887
|
-
resolve(true);
|
|
888
|
-
}
|
|
889
|
-
});
|
|
890
|
-
});
|
|
247
|
+
// Delegate to the external function
|
|
248
|
+
return NodeOps.deleteNode(this, holon, lens, key);
|
|
891
249
|
}
|
|
892
250
|
|
|
893
251
|
// ================================ GLOBAL FUNCTIONS ================================
|
|
@@ -899,122 +257,8 @@ class HoloSphere {
|
|
|
899
257
|
* @returns {Promise<void>}
|
|
900
258
|
*/
|
|
901
259
|
async putGlobal(tableName, data, password = null) {
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
throw new Error('Table name and data are required');
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
const user = this.gun.user();
|
|
908
|
-
|
|
909
|
-
if (password) {
|
|
910
|
-
try {
|
|
911
|
-
// Try to authenticate first
|
|
912
|
-
await new Promise((resolve, reject) => {
|
|
913
|
-
user.auth(this.userName(tableName), password, (ack) => {
|
|
914
|
-
if (ack.err) {
|
|
915
|
-
// Handle wrong username/password gracefully
|
|
916
|
-
if (ack.err.includes('Wrong user or password') ||
|
|
917
|
-
ack.err.includes('No user')) {
|
|
918
|
-
console.warn(`Authentication failed for ${tableName}: ${ack.err}`);
|
|
919
|
-
// Will try to create user next
|
|
920
|
-
reject(new Error(ack.err));
|
|
921
|
-
} else {
|
|
922
|
-
reject(new Error(ack.err));
|
|
923
|
-
}
|
|
924
|
-
} else {
|
|
925
|
-
resolve();
|
|
926
|
-
}
|
|
927
|
-
});
|
|
928
|
-
});
|
|
929
|
-
} catch (authError) {
|
|
930
|
-
// If authentication fails, try to create user
|
|
931
|
-
try {
|
|
932
|
-
await new Promise((resolve, reject) => {
|
|
933
|
-
user.create(this.userName(tableName), password, (ack) => {
|
|
934
|
-
// Handle "User already created!" error gracefully
|
|
935
|
-
if (ack.err && !ack.err.includes('already created')) {
|
|
936
|
-
reject(new Error(ack.err));
|
|
937
|
-
} else {
|
|
938
|
-
// Whether user was created or already existed, try to authenticate
|
|
939
|
-
user.auth(this.userName(tableName), password, (authAck) => {
|
|
940
|
-
if (authAck.err) {
|
|
941
|
-
console.warn(`Authentication failed after creation for ${tableName}: ${authAck.err}`);
|
|
942
|
-
reject(new Error(authAck.err));
|
|
943
|
-
} else {
|
|
944
|
-
resolve();
|
|
945
|
-
}
|
|
946
|
-
});
|
|
947
|
-
}
|
|
948
|
-
});
|
|
949
|
-
});
|
|
950
|
-
} catch (createError) {
|
|
951
|
-
// If both auth and create fail, try one last auth attempt
|
|
952
|
-
await new Promise((resolve, reject) => {
|
|
953
|
-
user.auth(this.userName(tableName), password, (ack) => {
|
|
954
|
-
if (ack.err) {
|
|
955
|
-
console.warn(`Final authentication attempt failed for ${tableName}: ${ack.err}`);
|
|
956
|
-
// Continue with operation even if auth fails
|
|
957
|
-
resolve();
|
|
958
|
-
} else {
|
|
959
|
-
resolve();
|
|
960
|
-
}
|
|
961
|
-
});
|
|
962
|
-
});
|
|
963
|
-
}
|
|
964
|
-
}
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
return new Promise((resolve, reject) => {
|
|
968
|
-
const payload = JSON.stringify(data);
|
|
969
|
-
|
|
970
|
-
if (password) {
|
|
971
|
-
// For private data, use the authenticated user's holon
|
|
972
|
-
const path = user.get('private').get(tableName);
|
|
973
|
-
|
|
974
|
-
if (data.id) {
|
|
975
|
-
path.get(data.id).put(payload, ack => {
|
|
976
|
-
if (ack.err) {
|
|
977
|
-
reject(new Error(ack.err));
|
|
978
|
-
} else {
|
|
979
|
-
resolve();
|
|
980
|
-
}
|
|
981
|
-
});
|
|
982
|
-
} else {
|
|
983
|
-
path.put(payload, ack => {
|
|
984
|
-
if (ack.err) {
|
|
985
|
-
reject(new Error(ack.err));
|
|
986
|
-
} else {
|
|
987
|
-
resolve();
|
|
988
|
-
}
|
|
989
|
-
});
|
|
990
|
-
}
|
|
991
|
-
} else {
|
|
992
|
-
// For public data, use the regular path
|
|
993
|
-
const path = this.gun.get(this.appname).get(tableName);
|
|
994
|
-
|
|
995
|
-
if (data.id) {
|
|
996
|
-
path.get(data.id).put(payload, ack => {
|
|
997
|
-
if (ack.err) {
|
|
998
|
-
reject(new Error(ack.err));
|
|
999
|
-
} else {
|
|
1000
|
-
resolve();
|
|
1001
|
-
}
|
|
1002
|
-
});
|
|
1003
|
-
} else {
|
|
1004
|
-
path.put(payload, ack => {
|
|
1005
|
-
if (ack.err) {
|
|
1006
|
-
reject(new Error(ack.err));
|
|
1007
|
-
} else {
|
|
1008
|
-
resolve();
|
|
1009
|
-
}
|
|
1010
|
-
});
|
|
1011
|
-
}
|
|
1012
|
-
}
|
|
1013
|
-
});
|
|
1014
|
-
} catch (error) {
|
|
1015
|
-
console.error('Error in putGlobal:', error);
|
|
1016
|
-
throw error;
|
|
1017
|
-
}
|
|
260
|
+
// Delegate to the external function
|
|
261
|
+
return GlobalOps.putGlobal(this, tableName, data, password);
|
|
1018
262
|
}
|
|
1019
263
|
|
|
1020
264
|
/**
|
|
@@ -1025,77 +269,8 @@ class HoloSphere {
|
|
|
1025
269
|
* @returns {Promise<object|null>} - The parsed data for the key or null if not found.
|
|
1026
270
|
*/
|
|
1027
271
|
async getGlobal(tableName, key, password = null) {
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
if (password) {
|
|
1032
|
-
try {
|
|
1033
|
-
await new Promise((resolve, reject) => {
|
|
1034
|
-
user.auth(this.userName(tableName), password, (ack) => {
|
|
1035
|
-
if (ack.err) {
|
|
1036
|
-
// Handle wrong username/password gracefully
|
|
1037
|
-
if (ack.err.includes('Wrong user or password') ||
|
|
1038
|
-
ack.err.includes('No user')) {
|
|
1039
|
-
console.warn(`Authentication failed for ${tableName}: ${ack.err}`);
|
|
1040
|
-
// Will try to create user next
|
|
1041
|
-
reject(new Error(ack.err));
|
|
1042
|
-
} else {
|
|
1043
|
-
reject(new Error(ack.err));
|
|
1044
|
-
}
|
|
1045
|
-
} else {
|
|
1046
|
-
resolve();
|
|
1047
|
-
}
|
|
1048
|
-
});
|
|
1049
|
-
});
|
|
1050
|
-
} catch (loginError) {
|
|
1051
|
-
// If authentication fails, try to create user and then authenticate
|
|
1052
|
-
await new Promise((resolve, reject) => {
|
|
1053
|
-
user.create(this.userName(tableName), password, (ack) => {
|
|
1054
|
-
// Handle "User already created!" error gracefully
|
|
1055
|
-
if (ack.err && !ack.err.includes('already created')) {
|
|
1056
|
-
reject(new Error(ack.err));
|
|
1057
|
-
} else {
|
|
1058
|
-
user.auth(this.userName(tableName), password, (authAck) => {
|
|
1059
|
-
if (authAck.err) {
|
|
1060
|
-
console.warn(`Authentication failed after creation for ${tableName}: ${authAck.err}`);
|
|
1061
|
-
// Continue with operation even if auth fails
|
|
1062
|
-
resolve();
|
|
1063
|
-
} else {
|
|
1064
|
-
resolve();
|
|
1065
|
-
}
|
|
1066
|
-
});
|
|
1067
|
-
}
|
|
1068
|
-
});
|
|
1069
|
-
});
|
|
1070
|
-
}
|
|
1071
|
-
}
|
|
1072
|
-
|
|
1073
|
-
return new Promise((resolve) => {
|
|
1074
|
-
const handleData = (data) => {
|
|
1075
|
-
if (!data) {
|
|
1076
|
-
resolve(null);
|
|
1077
|
-
return;
|
|
1078
|
-
}
|
|
1079
|
-
try {
|
|
1080
|
-
const parsed = this.parse(data);
|
|
1081
|
-
resolve(parsed);
|
|
1082
|
-
} catch (e) {
|
|
1083
|
-
resolve(null);
|
|
1084
|
-
}
|
|
1085
|
-
};
|
|
1086
|
-
|
|
1087
|
-
if (password) {
|
|
1088
|
-
// For private data, use the authenticated user's holon
|
|
1089
|
-
user.get('private').get(tableName).get(key).once(handleData);
|
|
1090
|
-
} else {
|
|
1091
|
-
// For public data, use the regular path
|
|
1092
|
-
this.gun.get(this.appname).get(tableName).get(key).once(handleData);
|
|
1093
|
-
}
|
|
1094
|
-
});
|
|
1095
|
-
} catch (error) {
|
|
1096
|
-
console.error('Error in getGlobal:', error);
|
|
1097
|
-
return null;
|
|
1098
|
-
}
|
|
272
|
+
// Delegate to the external function
|
|
273
|
+
return GlobalOps.getGlobal(this, tableName, key, password);
|
|
1099
274
|
}
|
|
1100
275
|
|
|
1101
276
|
/**
|
|
@@ -1105,75 +280,8 @@ class HoloSphere {
|
|
|
1105
280
|
* @returns {Promise<Array<object>>} - The parsed data from the table as an array.
|
|
1106
281
|
*/
|
|
1107
282
|
async getAllGlobal(tableName, password = null) {
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
}
|
|
1111
|
-
|
|
1112
|
-
try {
|
|
1113
|
-
// Get the appropriate holon
|
|
1114
|
-
const user = this.gun.user();
|
|
1115
|
-
|
|
1116
|
-
return new Promise((resolve) => {
|
|
1117
|
-
let output = [];
|
|
1118
|
-
let isResolved = false;
|
|
1119
|
-
let timeout = setTimeout(() => {
|
|
1120
|
-
if (!isResolved) {
|
|
1121
|
-
isResolved = true;
|
|
1122
|
-
resolve(output);
|
|
1123
|
-
}
|
|
1124
|
-
}, 5000);
|
|
1125
|
-
|
|
1126
|
-
const handleData = async (data) => {
|
|
1127
|
-
if (!data) {
|
|
1128
|
-
clearTimeout(timeout);
|
|
1129
|
-
isResolved = true;
|
|
1130
|
-
resolve([]);
|
|
1131
|
-
return;
|
|
1132
|
-
}
|
|
1133
|
-
|
|
1134
|
-
const keys = Object.keys(data).filter(key => key !== '_');
|
|
1135
|
-
const promises = keys.map(key =>
|
|
1136
|
-
new Promise(async (resolveItem) => {
|
|
1137
|
-
const itemPath = password ?
|
|
1138
|
-
user.get('private').get(tableName).get(key) :
|
|
1139
|
-
this.gun.get(this.appname).get(tableName).get(key);
|
|
1140
|
-
|
|
1141
|
-
const itemData = await new Promise(resolveData => {
|
|
1142
|
-
itemPath.once(resolveData);
|
|
1143
|
-
});
|
|
1144
|
-
|
|
1145
|
-
if (itemData) {
|
|
1146
|
-
try {
|
|
1147
|
-
const parsed = await this.parse(itemData);
|
|
1148
|
-
if (parsed) output.push(parsed);
|
|
1149
|
-
} catch (error) {
|
|
1150
|
-
console.error('Error parsing data:', error);
|
|
1151
|
-
}
|
|
1152
|
-
}
|
|
1153
|
-
resolveItem();
|
|
1154
|
-
})
|
|
1155
|
-
);
|
|
1156
|
-
|
|
1157
|
-
await Promise.all(promises);
|
|
1158
|
-
clearTimeout(timeout);
|
|
1159
|
-
if (!isResolved) {
|
|
1160
|
-
isResolved = true;
|
|
1161
|
-
resolve(output);
|
|
1162
|
-
}
|
|
1163
|
-
};
|
|
1164
|
-
|
|
1165
|
-
if (password) {
|
|
1166
|
-
// For private data, use the authenticated user's holon
|
|
1167
|
-
user.get('private').get(tableName).once(handleData);
|
|
1168
|
-
} else {
|
|
1169
|
-
// For public data, use the regular path
|
|
1170
|
-
this.gun.get(this.appname).get(tableName).once(handleData);
|
|
1171
|
-
}
|
|
1172
|
-
});
|
|
1173
|
-
} catch (error) {
|
|
1174
|
-
console.error('Error in getAllGlobal:', error);
|
|
1175
|
-
return [];
|
|
1176
|
-
}
|
|
283
|
+
// Delegate to the external function
|
|
284
|
+
return GlobalOps.getAllGlobal(this, tableName, password);
|
|
1177
285
|
}
|
|
1178
286
|
|
|
1179
287
|
/**
|
|
@@ -1184,39 +292,8 @@ class HoloSphere {
|
|
|
1184
292
|
* @returns {Promise<boolean>}
|
|
1185
293
|
*/
|
|
1186
294
|
async deleteGlobal(tableName, key, password = null) {
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
}
|
|
1190
|
-
|
|
1191
|
-
try {
|
|
1192
|
-
// Get the appropriate holon
|
|
1193
|
-
const user = this.gun.user();
|
|
1194
|
-
|
|
1195
|
-
return new Promise((resolve, reject) => {
|
|
1196
|
-
if (password) {
|
|
1197
|
-
// For private data, use the authenticated user's holon
|
|
1198
|
-
user.get('private').get(tableName).get(key).put(null, ack => {
|
|
1199
|
-
if (ack.err) {
|
|
1200
|
-
reject(new Error(ack.err));
|
|
1201
|
-
} else {
|
|
1202
|
-
resolve(true);
|
|
1203
|
-
}
|
|
1204
|
-
});
|
|
1205
|
-
} else {
|
|
1206
|
-
// For public data, use the regular path
|
|
1207
|
-
this.gun.get(this.appname).get(tableName).get(key).put(null, ack => {
|
|
1208
|
-
if (ack.err) {
|
|
1209
|
-
reject(new Error(ack.err));
|
|
1210
|
-
} else {
|
|
1211
|
-
resolve(true);
|
|
1212
|
-
}
|
|
1213
|
-
});
|
|
1214
|
-
}
|
|
1215
|
-
});
|
|
1216
|
-
} catch (error) {
|
|
1217
|
-
console.error('Error in deleteGlobal:', error);
|
|
1218
|
-
throw error;
|
|
1219
|
-
}
|
|
295
|
+
// Delegate to the external function
|
|
296
|
+
return GlobalOps.deleteGlobal(this, tableName, key, password);
|
|
1220
297
|
}
|
|
1221
298
|
|
|
1222
299
|
/**
|
|
@@ -1226,68 +303,55 @@ class HoloSphere {
|
|
|
1226
303
|
* @returns {Promise<boolean>}
|
|
1227
304
|
*/
|
|
1228
305
|
async deleteAllGlobal(tableName, password = null) {
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
try {
|
|
1234
|
-
// Get the appropriate holon
|
|
1235
|
-
const user = this.gun.user();
|
|
1236
|
-
|
|
1237
|
-
return new Promise((resolve, reject) => {
|
|
1238
|
-
try {
|
|
1239
|
-
const deletions = new Set();
|
|
1240
|
-
let timeout = setTimeout(() => {
|
|
1241
|
-
if (deletions.size === 0) {
|
|
1242
|
-
resolve(true); // No data to delete
|
|
1243
|
-
}
|
|
1244
|
-
}, 5000);
|
|
1245
|
-
|
|
1246
|
-
const dataPath = password ?
|
|
1247
|
-
user.get('private').get(tableName) :
|
|
1248
|
-
this.gun.get(this.appname).get(tableName);
|
|
1249
|
-
|
|
1250
|
-
dataPath.once(async (data) => {
|
|
1251
|
-
if (!data) {
|
|
1252
|
-
clearTimeout(timeout);
|
|
1253
|
-
resolve(true);
|
|
1254
|
-
return;
|
|
1255
|
-
}
|
|
1256
|
-
|
|
1257
|
-
const keys = Object.keys(data).filter(key => key !== '_');
|
|
1258
|
-
const promises = keys.map(key =>
|
|
1259
|
-
new Promise((resolveDelete) => {
|
|
1260
|
-
const deletePath = password ?
|
|
1261
|
-
user.get('private').get(tableName).get(key) :
|
|
1262
|
-
this.gun.get(this.appname).get(tableName).get(key);
|
|
306
|
+
// Delegate to the external function
|
|
307
|
+
return GlobalOps.deleteAllGlobal(this, tableName, password);
|
|
308
|
+
}
|
|
1263
309
|
|
|
1264
|
-
|
|
1265
|
-
if (ack.err) {
|
|
1266
|
-
console.error(`Failed to delete ${key}:`, ack.err);
|
|
1267
|
-
}
|
|
1268
|
-
resolveDelete();
|
|
1269
|
-
});
|
|
1270
|
-
})
|
|
1271
|
-
);
|
|
310
|
+
// ================================ REFERENCE FUNCTIONS ================================
|
|
1272
311
|
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
312
|
+
/**
|
|
313
|
+
* Creates a soul hologram object for a data item
|
|
314
|
+
* @param {string} holon - The holon where the original data is stored
|
|
315
|
+
* @param {string} lens - The lens where the original data is stored
|
|
316
|
+
* @param {object} data - The data to create a hologram for
|
|
317
|
+
* @returns {object} - A hologram object with id and soul
|
|
318
|
+
*/
|
|
319
|
+
createHologram(holon, lens, data) {
|
|
320
|
+
// Delegate to the external function
|
|
321
|
+
return HologramOps.createHologram(this, holon, lens, data);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Parses a soul path into its components
|
|
326
|
+
* @param {string} soul - The soul path to parse
|
|
327
|
+
* @returns {object|null} - The parsed components or null if invalid format
|
|
328
|
+
*/
|
|
329
|
+
parseSoulPath(soul) {
|
|
330
|
+
// Delegate to the external function (doesn't need instance)
|
|
331
|
+
return HologramOps.parseSoulPath(soul);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Checks if an object is a hologram
|
|
336
|
+
* @param {object} data - The data to check
|
|
337
|
+
* @returns {boolean} - True if the object is a hologram
|
|
338
|
+
*/
|
|
339
|
+
isHologram(data) {
|
|
340
|
+
// Delegate to the external function (doesn't need instance)
|
|
341
|
+
return HologramOps.isHologram(data);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Resolves a hologram to its actual data
|
|
346
|
+
* @param {object} hologram - The hologram to resolve
|
|
347
|
+
* @param {object} [options] - Optional parameters
|
|
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.
|
|
351
|
+
*/
|
|
352
|
+
async resolveHologram(hologram, options = {}) {
|
|
353
|
+
// Delegate to the external function
|
|
354
|
+
return HologramOps.resolveHologram(this, hologram, options);
|
|
1291
355
|
}
|
|
1292
356
|
|
|
1293
357
|
// ================================ COMPUTE FUNCTIONS ================================
|
|
@@ -1300,26 +364,8 @@ class HoloSphere {
|
|
|
1300
364
|
* @param {string} [password] - Optional password for private holons
|
|
1301
365
|
*/
|
|
1302
366
|
async computeHierarchy(holon, lens, options, maxLevels = 15, password = null) {
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
const results = [];
|
|
1306
|
-
|
|
1307
|
-
while (currentRes > 0 && maxLevels > 0) {
|
|
1308
|
-
try {
|
|
1309
|
-
const result = await this.compute(currentHolon, lens, options, password);
|
|
1310
|
-
if (result) {
|
|
1311
|
-
results.push(result);
|
|
1312
|
-
}
|
|
1313
|
-
currentHolon = h3.cellToParent(currentHolon, currentRes - 1);
|
|
1314
|
-
currentRes--;
|
|
1315
|
-
maxLevels--;
|
|
1316
|
-
} catch (error) {
|
|
1317
|
-
console.error('Error in compute hierarchy:', error);
|
|
1318
|
-
break;
|
|
1319
|
-
}
|
|
1320
|
-
}
|
|
1321
|
-
|
|
1322
|
-
return results;
|
|
367
|
+
// Delegate to the external function
|
|
368
|
+
return ComputeOps.computeHierarchy(this, holon, lens, options, maxLevels, password);
|
|
1323
369
|
}
|
|
1324
370
|
|
|
1325
371
|
/**
|
|
@@ -1334,132 +380,8 @@ class HoloSphere {
|
|
|
1334
380
|
* @throws {Error} If parameters are invalid or missing
|
|
1335
381
|
*/
|
|
1336
382
|
async compute(holon, lens, options, password = null) {
|
|
1337
|
-
//
|
|
1338
|
-
|
|
1339
|
-
throw new Error('compute: Missing required parameters');
|
|
1340
|
-
}
|
|
1341
|
-
|
|
1342
|
-
// Convert string operation to options object
|
|
1343
|
-
if (typeof options === 'string') {
|
|
1344
|
-
options = { operation: options };
|
|
1345
|
-
}
|
|
1346
|
-
|
|
1347
|
-
if (!options?.operation) {
|
|
1348
|
-
throw new Error('compute: Missing required parameters');
|
|
1349
|
-
}
|
|
1350
|
-
|
|
1351
|
-
// Validate holon format and resolution first
|
|
1352
|
-
let res;
|
|
1353
|
-
try {
|
|
1354
|
-
res = h3.getResolution(holon);
|
|
1355
|
-
} catch (error) {
|
|
1356
|
-
throw new Error('compute: Invalid holon format');
|
|
1357
|
-
}
|
|
1358
|
-
|
|
1359
|
-
if (res < 1 || res > 15) {
|
|
1360
|
-
throw new Error('compute: Invalid holon resolution (must be between 1 and 15)');
|
|
1361
|
-
}
|
|
1362
|
-
|
|
1363
|
-
const {
|
|
1364
|
-
operation,
|
|
1365
|
-
fields = [],
|
|
1366
|
-
targetField,
|
|
1367
|
-
depth,
|
|
1368
|
-
maxDepth
|
|
1369
|
-
} = options;
|
|
1370
|
-
|
|
1371
|
-
// Validate depth parameters if provided
|
|
1372
|
-
if (depth !== undefined && depth < 0) {
|
|
1373
|
-
throw new Error('compute: Invalid depth parameter');
|
|
1374
|
-
}
|
|
1375
|
-
|
|
1376
|
-
if (maxDepth !== undefined && (maxDepth < 1 || maxDepth > 15)) {
|
|
1377
|
-
throw new Error('compute: Invalid maxDepth parameter (must be between 1 and 15)');
|
|
1378
|
-
}
|
|
1379
|
-
|
|
1380
|
-
// Validate operation
|
|
1381
|
-
const validOperations = ['summarize', 'aggregate', 'concatenate'];
|
|
1382
|
-
if (!validOperations.includes(operation)) {
|
|
1383
|
-
throw new Error(`compute: Invalid operation (must be one of ${validOperations.join(', ')})`);
|
|
1384
|
-
}
|
|
1385
|
-
|
|
1386
|
-
const parent = h3.cellToParent(holon, res - 1);
|
|
1387
|
-
const siblings = h3.cellToChildren(parent, res);
|
|
1388
|
-
|
|
1389
|
-
// Collect all content from siblings
|
|
1390
|
-
const contents = await Promise.all(
|
|
1391
|
-
siblings.map(sibling => this.getAll(sibling, lens, password))
|
|
1392
|
-
);
|
|
1393
|
-
|
|
1394
|
-
const flatContents = contents.flat().filter(Boolean);
|
|
1395
|
-
|
|
1396
|
-
if (flatContents.length > 0) {
|
|
1397
|
-
try {
|
|
1398
|
-
let computed;
|
|
1399
|
-
switch (operation) {
|
|
1400
|
-
case 'summarize':
|
|
1401
|
-
// For summarize, concatenate specified fields or use entire content
|
|
1402
|
-
const textToSummarize = fields.length > 0
|
|
1403
|
-
? flatContents.map(item => fields.map(field => item[field]).filter(Boolean).join('\n')).join('\n')
|
|
1404
|
-
: JSON.stringify(flatContents);
|
|
1405
|
-
computed = await this.summarize(textToSummarize);
|
|
1406
|
-
break;
|
|
1407
|
-
|
|
1408
|
-
case 'aggregate':
|
|
1409
|
-
// For aggregate, sum numeric fields
|
|
1410
|
-
computed = fields.reduce((acc, field) => {
|
|
1411
|
-
acc[field] = flatContents.reduce((sum, item) => {
|
|
1412
|
-
return sum + (Number(item[field]) || 0);
|
|
1413
|
-
}, 0);
|
|
1414
|
-
return acc;
|
|
1415
|
-
}, {});
|
|
1416
|
-
break;
|
|
1417
|
-
|
|
1418
|
-
case 'concatenate':
|
|
1419
|
-
// For concatenate, combine arrays or strings
|
|
1420
|
-
computed = fields.reduce((acc, field) => {
|
|
1421
|
-
acc[field] = flatContents.reduce((combined, item) => {
|
|
1422
|
-
const value = item[field];
|
|
1423
|
-
if (Array.isArray(value)) {
|
|
1424
|
-
return [...combined, ...value];
|
|
1425
|
-
} else if (value) {
|
|
1426
|
-
return [...combined, value];
|
|
1427
|
-
}
|
|
1428
|
-
return combined;
|
|
1429
|
-
}, []);
|
|
1430
|
-
// Remove duplicates if array
|
|
1431
|
-
acc[field] = Array.from(new Set(acc[field]));
|
|
1432
|
-
return acc;
|
|
1433
|
-
}, {});
|
|
1434
|
-
break;
|
|
1435
|
-
}
|
|
1436
|
-
|
|
1437
|
-
if (computed) {
|
|
1438
|
-
const resultId = `${parent}_${operation}`;
|
|
1439
|
-
const result = {
|
|
1440
|
-
id: resultId,
|
|
1441
|
-
timestamp: Date.now()
|
|
1442
|
-
};
|
|
1443
|
-
|
|
1444
|
-
// Store result in targetField if specified, otherwise at root level
|
|
1445
|
-
if (targetField) {
|
|
1446
|
-
result[targetField] = computed;
|
|
1447
|
-
} else if (typeof computed === 'object') {
|
|
1448
|
-
Object.assign(result, computed);
|
|
1449
|
-
} else {
|
|
1450
|
-
result.value = computed;
|
|
1451
|
-
}
|
|
1452
|
-
|
|
1453
|
-
await this.put(parent, lens, result, password);
|
|
1454
|
-
return result;
|
|
1455
|
-
}
|
|
1456
|
-
} catch (error) {
|
|
1457
|
-
console.warn('Error in compute operation:', error);
|
|
1458
|
-
throw error;
|
|
1459
|
-
}
|
|
1460
|
-
}
|
|
1461
|
-
|
|
1462
|
-
return null;
|
|
383
|
+
// Delegate to the external function
|
|
384
|
+
return ComputeOps.compute(this, holon, lens, options, password);
|
|
1463
385
|
}
|
|
1464
386
|
|
|
1465
387
|
/**
|
|
@@ -1468,37 +390,12 @@ class HoloSphere {
|
|
|
1468
390
|
* @returns {Promise<string>} - The summarized text.
|
|
1469
391
|
*/
|
|
1470
392
|
async summarize(history) {
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
}
|
|
1474
|
-
|
|
1475
|
-
try {
|
|
1476
|
-
const response = await this.openai.chat.completions.create({
|
|
1477
|
-
model: "gpt-4",
|
|
1478
|
-
messages: [
|
|
1479
|
-
{
|
|
1480
|
-
role: "system",
|
|
1481
|
-
content: "You are a helpful assistant that summarizes text concisely while preserving key information. Keep summaries clear and focused."
|
|
1482
|
-
},
|
|
1483
|
-
{
|
|
1484
|
-
role: "user",
|
|
1485
|
-
content: history
|
|
1486
|
-
}
|
|
1487
|
-
],
|
|
1488
|
-
temperature: 0.7,
|
|
1489
|
-
max_tokens: 500
|
|
1490
|
-
});
|
|
1491
|
-
|
|
1492
|
-
return response.choices[0].message.content.trim();
|
|
1493
|
-
} catch (error) {
|
|
1494
|
-
console.error('Error in summarize:', error);
|
|
1495
|
-
throw new Error('Failed to generate summary');
|
|
1496
|
-
}
|
|
393
|
+
// Delegate to the external function
|
|
394
|
+
return ComputeOps.summarize(this, history);
|
|
1497
395
|
}
|
|
1498
396
|
|
|
1499
397
|
/**
|
|
1500
|
-
* Upcasts content to parent holonagons recursively using
|
|
1501
|
-
* This is the modern implementation that uses federation references instead of duplicating data.
|
|
398
|
+
* Upcasts content to parent holonagons recursively using references.
|
|
1502
399
|
* @param {string} holon - The current holon identifier.
|
|
1503
400
|
* @param {string} lens - The lens under which to upcast.
|
|
1504
401
|
* @param {object} content - The content to upcast.
|
|
@@ -1506,41 +403,8 @@ class HoloSphere {
|
|
|
1506
403
|
* @returns {Promise<object>} - The original content.
|
|
1507
404
|
*/
|
|
1508
405
|
async upcast(holon, lens, content, maxLevels = 15) {
|
|
1509
|
-
//
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
let res = h3.getResolution(holon);
|
|
1513
|
-
|
|
1514
|
-
// If already at the highest level (res 0) or reached max levels, we're done
|
|
1515
|
-
if (res === 0 || maxLevels <= 0) {
|
|
1516
|
-
return content;
|
|
1517
|
-
}
|
|
1518
|
-
|
|
1519
|
-
// Get the parent cell
|
|
1520
|
-
let parent = h3.cellToParent(holon, res - 1);
|
|
1521
|
-
|
|
1522
|
-
// Create federation relationship if it doesn't exist
|
|
1523
|
-
await this.federate(holon, parent);
|
|
1524
|
-
|
|
1525
|
-
// Create a soul reference to store in the parent
|
|
1526
|
-
const soul = `${this.appname}/${holon}/${lens}/${content.id}`;
|
|
1527
|
-
const reference = {
|
|
1528
|
-
id: content.id,
|
|
1529
|
-
soul: soul
|
|
1530
|
-
};
|
|
1531
|
-
|
|
1532
|
-
// Store the reference in the parent cell
|
|
1533
|
-
// We use { autoPropagate: false } to prevent circular propagation
|
|
1534
|
-
await this.put(parent, lens, reference, null, {
|
|
1535
|
-
autoPropagate: false
|
|
1536
|
-
});
|
|
1537
|
-
|
|
1538
|
-
// Continue upcasting with the parent
|
|
1539
|
-
if (res > 1 && maxLevels > 1) {
|
|
1540
|
-
return this.upcast(parent, lens, reference, maxLevels - 1);
|
|
1541
|
-
}
|
|
1542
|
-
|
|
1543
|
-
return content;
|
|
406
|
+
// Delegate to the external function
|
|
407
|
+
return ComputeOps.upcast(this, holon, lens, content, maxLevels);
|
|
1544
408
|
}
|
|
1545
409
|
|
|
1546
410
|
/**
|
|
@@ -1550,19 +414,21 @@ class HoloSphere {
|
|
|
1550
414
|
* @returns {Promise<object>} - The updated parent information.
|
|
1551
415
|
*/
|
|
1552
416
|
async updateParent(id, report) {
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
let parent = h3.cellToParent(id, res - 1)
|
|
1556
|
-
let parentInfo = await this.getCellInfo(parent)
|
|
1557
|
-
parentInfo.wisdom[id] = report
|
|
1558
|
-
//update summary
|
|
1559
|
-
let summary = await this.summarize(Object.values(parentInfo.wisdom).join('\n'))
|
|
1560
|
-
parentInfo.summary = summary
|
|
1561
|
-
|
|
1562
|
-
await this.db.put('cell', parentInfo)
|
|
1563
|
-
return parentInfo
|
|
417
|
+
// Delegate to the external function
|
|
418
|
+
return ComputeOps.updateParent(this, id, report);
|
|
1564
419
|
}
|
|
1565
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
|
+
}
|
|
1566
432
|
|
|
1567
433
|
/**
|
|
1568
434
|
* Converts latitude and longitude to a holon identifier.
|
|
@@ -1572,7 +438,8 @@ class HoloSphere {
|
|
|
1572
438
|
* @returns {Promise<string>} - The resulting holon identifier.
|
|
1573
439
|
*/
|
|
1574
440
|
async getHolon(lat, lng, resolution) {
|
|
1575
|
-
|
|
441
|
+
// Delegate to the external function
|
|
442
|
+
return Utils.getHolon(lat, lng, resolution);
|
|
1576
443
|
}
|
|
1577
444
|
|
|
1578
445
|
/**
|
|
@@ -1582,13 +449,8 @@ class HoloSphere {
|
|
|
1582
449
|
* @returns {Array<string>} - List of holon identifiers.
|
|
1583
450
|
*/
|
|
1584
451
|
getScalespace(lat, lng) {
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
list.push(cell)
|
|
1588
|
-
for (let i = 13; i >= 0; i--) {
|
|
1589
|
-
list.push(h3.cellToParent(cell, i))
|
|
1590
|
-
}
|
|
1591
|
-
return list
|
|
452
|
+
// Delegate to the external function
|
|
453
|
+
return Utils.getScalespace(lat, lng);
|
|
1592
454
|
}
|
|
1593
455
|
|
|
1594
456
|
/**
|
|
@@ -1597,12 +459,8 @@ class HoloSphere {
|
|
|
1597
459
|
* @returns {Array<string>} - List of holon identifiers.
|
|
1598
460
|
*/
|
|
1599
461
|
getHolonScalespace(holon) {
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
for (let i = res; i >= 0; i--) {
|
|
1603
|
-
list.push(h3.cellToParent(holon, i))
|
|
1604
|
-
}
|
|
1605
|
-
return list
|
|
462
|
+
// Delegate to the external function
|
|
463
|
+
return Utils.getHolonScalespace(holon);
|
|
1606
464
|
}
|
|
1607
465
|
|
|
1608
466
|
/**
|
|
@@ -1613,90 +471,24 @@ class HoloSphere {
|
|
|
1613
471
|
* @returns {Promise<object>} - Subscription object with unsubscribe method
|
|
1614
472
|
*/
|
|
1615
473
|
async subscribe(holon, lens, callback) {
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
}
|
|
1619
|
-
|
|
1620
|
-
const subscriptionId = this.generateId();
|
|
1621
|
-
|
|
1622
|
-
try {
|
|
1623
|
-
// Create the subscription
|
|
1624
|
-
const gunSubscription = this.gun.get(this.appname).get(holon).get(lens).map().on(async (data, key) => {
|
|
1625
|
-
if (data) {
|
|
1626
|
-
try {
|
|
1627
|
-
let parsed = await this.parse(data);
|
|
1628
|
-
callback(parsed, key);
|
|
1629
|
-
} catch (error) {
|
|
1630
|
-
console.error('Error in subscribe:', error);
|
|
1631
|
-
}
|
|
1632
|
-
}
|
|
1633
|
-
});
|
|
1634
|
-
|
|
1635
|
-
// Store the subscription with its ID
|
|
1636
|
-
this.subscriptions[subscriptionId] = {
|
|
1637
|
-
id: subscriptionId,
|
|
1638
|
-
holon,
|
|
1639
|
-
lens,
|
|
1640
|
-
active: true,
|
|
1641
|
-
gunSubscription
|
|
1642
|
-
};
|
|
1643
|
-
|
|
1644
|
-
// Return an object with unsubscribe method
|
|
1645
|
-
return {
|
|
1646
|
-
unsubscribe: () => {
|
|
1647
|
-
try {
|
|
1648
|
-
// Turn off the Gun subscription
|
|
1649
|
-
this.gun.get(this.appname).get(holon).get(lens).map().off();
|
|
1650
|
-
|
|
1651
|
-
// Mark as inactive and remove from subscriptions
|
|
1652
|
-
if (this.subscriptions[subscriptionId]) {
|
|
1653
|
-
this.subscriptions[subscriptionId].active = false;
|
|
1654
|
-
delete this.subscriptions[subscriptionId];
|
|
1655
|
-
}
|
|
1656
|
-
} catch (error) {
|
|
1657
|
-
console.error('Error in unsubscribe:', error);
|
|
1658
|
-
}
|
|
1659
|
-
}
|
|
1660
|
-
};
|
|
1661
|
-
} catch (error) {
|
|
1662
|
-
console.error('Error creating subscription:', error);
|
|
1663
|
-
throw error;
|
|
1664
|
-
}
|
|
474
|
+
// Delegate to the external function
|
|
475
|
+
return Utils.subscribe(this, holon, lens, callback);
|
|
1665
476
|
}
|
|
1666
477
|
|
|
1667
|
-
|
|
1668
478
|
/**
|
|
1669
479
|
* Notifies subscribers about data changes
|
|
1670
480
|
* @param {object} data - The data to notify about
|
|
1671
481
|
* @private
|
|
1672
482
|
*/
|
|
1673
483
|
notifySubscribers(data) {
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
}
|
|
1677
|
-
|
|
1678
|
-
try {
|
|
1679
|
-
Object.values(this.subscriptions).forEach(subscription => {
|
|
1680
|
-
if (subscription.active &&
|
|
1681
|
-
subscription.holon === data.holon &&
|
|
1682
|
-
subscription.lens === data.lens) {
|
|
1683
|
-
try {
|
|
1684
|
-
if (subscription.callback && typeof subscription.callback === 'function') {
|
|
1685
|
-
subscription.callback(data);
|
|
1686
|
-
}
|
|
1687
|
-
} catch (error) {
|
|
1688
|
-
console.warn('Error in subscription callback:', error);
|
|
1689
|
-
}
|
|
1690
|
-
}
|
|
1691
|
-
});
|
|
1692
|
-
} catch (error) {
|
|
1693
|
-
console.warn('Error notifying subscribers:', error);
|
|
1694
|
-
}
|
|
484
|
+
// Delegate to the external function
|
|
485
|
+
return Utils.notifySubscribers(this, data);
|
|
1695
486
|
}
|
|
1696
487
|
|
|
1697
488
|
// Add ID generation method
|
|
1698
489
|
generateId() {
|
|
1699
|
-
|
|
490
|
+
// Delegate to the external function
|
|
491
|
+
return Utils.generateId();
|
|
1700
492
|
}
|
|
1701
493
|
|
|
1702
494
|
// ================================ FEDERATION FUNCTIONS ================================
|
|
@@ -1705,13 +497,16 @@ class HoloSphere {
|
|
|
1705
497
|
* Creates a federation relationship between two holons
|
|
1706
498
|
* @param {string} holonId1 - The first holon ID
|
|
1707
499
|
* @param {string} holonId2 - The second holon ID
|
|
1708
|
-
* @param {string} password1 -
|
|
500
|
+
* @param {string} [password1] - Optional password for the first holon
|
|
1709
501
|
* @param {string} [password2] - Optional password for the second holon
|
|
1710
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)
|
|
1711
506
|
* @returns {Promise<boolean>} - True if federation was created successfully
|
|
1712
507
|
*/
|
|
1713
|
-
async federate(holonId1, holonId2, password1, password2 = null, bidirectional = true) {
|
|
1714
|
-
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);
|
|
1715
510
|
}
|
|
1716
511
|
|
|
1717
512
|
/**
|
|
@@ -1737,6 +532,17 @@ class HoloSphere {
|
|
|
1737
532
|
async getFederation(holonId, password = null) {
|
|
1738
533
|
return Federation.getFederation(this, holonId, password);
|
|
1739
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
|
+
}
|
|
1740
546
|
|
|
1741
547
|
/**
|
|
1742
548
|
* Removes a federation relationship between holons
|
|
@@ -1832,75 +638,8 @@ class HoloSphere {
|
|
|
1832
638
|
* @returns {Promise<void>}
|
|
1833
639
|
*/
|
|
1834
640
|
async close() {
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
// Unsubscribe from all subscriptions
|
|
1838
|
-
const subscriptionIds = Object.keys(this.subscriptions);
|
|
1839
|
-
for (const id of subscriptionIds) {
|
|
1840
|
-
try {
|
|
1841
|
-
const subscription = this.subscriptions[id];
|
|
1842
|
-
if (subscription && subscription.active) {
|
|
1843
|
-
// Turn off the Gun subscription
|
|
1844
|
-
this.gun.get(this.appname)
|
|
1845
|
-
.get(subscription.holon)
|
|
1846
|
-
.get(subscription.lens)
|
|
1847
|
-
.map().off();
|
|
1848
|
-
|
|
1849
|
-
// Mark as inactive
|
|
1850
|
-
subscription.active = false;
|
|
1851
|
-
}
|
|
1852
|
-
} catch (error) {
|
|
1853
|
-
console.warn(`Error cleaning up subscription ${id}:`, error);
|
|
1854
|
-
}
|
|
1855
|
-
}
|
|
1856
|
-
|
|
1857
|
-
// Clear subscriptions
|
|
1858
|
-
this.subscriptions = {};
|
|
1859
|
-
|
|
1860
|
-
// Close Gun connections
|
|
1861
|
-
if (this.gun.back) {
|
|
1862
|
-
try {
|
|
1863
|
-
const mesh = this.gun.back('opt.mesh');
|
|
1864
|
-
if (mesh && mesh.hear) {
|
|
1865
|
-
try {
|
|
1866
|
-
// Safely clear mesh.hear without modifying function properties
|
|
1867
|
-
const hearKeys = Object.keys(mesh.hear);
|
|
1868
|
-
for (const key of hearKeys) {
|
|
1869
|
-
// Check if it's an array before trying to clear it
|
|
1870
|
-
if (Array.isArray(mesh.hear[key])) {
|
|
1871
|
-
mesh.hear[key] = [];
|
|
1872
|
-
}
|
|
1873
|
-
}
|
|
1874
|
-
|
|
1875
|
-
// Create a new empty object for mesh.hear
|
|
1876
|
-
// Only if mesh.hear is not a function
|
|
1877
|
-
if (typeof mesh.hear !== 'function') {
|
|
1878
|
-
mesh.hear = {};
|
|
1879
|
-
}
|
|
1880
|
-
} catch (meshError) {
|
|
1881
|
-
console.warn('Error cleaning up Gun mesh hear:', meshError);
|
|
1882
|
-
}
|
|
1883
|
-
}
|
|
1884
|
-
} catch (error) {
|
|
1885
|
-
console.warn('Error accessing Gun mesh:', error);
|
|
1886
|
-
}
|
|
1887
|
-
}
|
|
1888
|
-
|
|
1889
|
-
// Clear all Gun instance listeners
|
|
1890
|
-
try {
|
|
1891
|
-
this.gun.off();
|
|
1892
|
-
} catch (error) {
|
|
1893
|
-
console.warn('Error turning off Gun listeners:', error);
|
|
1894
|
-
}
|
|
1895
|
-
|
|
1896
|
-
// Wait a moment for cleanup to complete
|
|
1897
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
1898
|
-
}
|
|
1899
|
-
|
|
1900
|
-
console.log('HoloSphere instance closed successfully');
|
|
1901
|
-
} catch (error) {
|
|
1902
|
-
console.error('Error closing HoloSphere instance:', error);
|
|
1903
|
-
}
|
|
641
|
+
// Delegate to the external function
|
|
642
|
+
return Utils.close(this);
|
|
1904
643
|
}
|
|
1905
644
|
|
|
1906
645
|
/**
|
|
@@ -1910,8 +649,16 @@ class HoloSphere {
|
|
|
1910
649
|
* @returns {string} - Namespaced username
|
|
1911
650
|
*/
|
|
1912
651
|
userName(holonId) {
|
|
1913
|
-
|
|
1914
|
-
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;
|
|
1915
662
|
}
|
|
1916
663
|
}
|
|
1917
664
|
|