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/federation.js
CHANGED
|
@@ -14,10 +14,12 @@
|
|
|
14
14
|
* @param {string} [password1] - Optional password for the first space
|
|
15
15
|
* @param {string} [password2] - Optional password for the second space
|
|
16
16
|
* @param {boolean} [bidirectional=true] - Whether to set up bidirectional notifications (default: true)
|
|
17
|
+
* @param {object} [lensConfig] - Optional lens-specific configuration
|
|
18
|
+
* @param {string[]} [lensConfig.federate] - List of lenses to federate (default: all)
|
|
19
|
+
* @param {string[]} [lensConfig.notify] - List of lenses to notify (default: all)
|
|
17
20
|
* @returns {Promise<boolean>} - True if federation was created successfully
|
|
18
21
|
*/
|
|
19
|
-
export async function federate(holosphere, spaceId1, spaceId2, password1 = null, password2 = null, bidirectional = true) {
|
|
20
|
-
console.log('FEDERATING', spaceId1, spaceId2, password1, password2, bidirectional)
|
|
22
|
+
export async function federate(holosphere, spaceId1, spaceId2, password1 = null, password2 = null, bidirectional = true, lensConfig = {}) {
|
|
21
23
|
if (!spaceId1 || !spaceId2) {
|
|
22
24
|
throw new Error('federate: Missing required space IDs');
|
|
23
25
|
}
|
|
@@ -27,40 +29,59 @@ export async function federate(holosphere, spaceId1, spaceId2, password1 = null,
|
|
|
27
29
|
throw new Error('Cannot federate a space with itself');
|
|
28
30
|
}
|
|
29
31
|
|
|
32
|
+
// Validate lens configuration
|
|
33
|
+
const { federate = [], notify = [] } = lensConfig;
|
|
34
|
+
if (!Array.isArray(federate) || !Array.isArray(notify)) {
|
|
35
|
+
throw new Error('federate: lensConfig.federate and lensConfig.notify must be arrays');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Use the provided lens configurations directly
|
|
39
|
+
const federateLenses = federate;
|
|
40
|
+
const notifyLenses = notify;
|
|
41
|
+
|
|
30
42
|
try {
|
|
31
43
|
// Get or create federation info for first space (A)
|
|
32
|
-
let fedInfo1
|
|
44
|
+
let fedInfo1 = null;
|
|
33
45
|
|
|
34
46
|
try {
|
|
35
47
|
fedInfo1 = await holosphere.getGlobal('federation', spaceId1, password1);
|
|
36
48
|
} catch (error) {
|
|
37
|
-
console.warn(`Could not get federation info for ${spaceId1}: ${error.message}`);
|
|
38
|
-
// Create new federation info if it doesn't exist
|
|
39
|
-
|
|
40
49
|
}
|
|
50
|
+
|
|
41
51
|
if (fedInfo1 == null) {
|
|
42
52
|
fedInfo1 = {
|
|
43
53
|
id: spaceId1,
|
|
44
54
|
name: spaceId1,
|
|
45
55
|
federation: [],
|
|
46
56
|
notify: [],
|
|
57
|
+
lensConfig: {}, // New field for lens-specific settings
|
|
47
58
|
timestamp: Date.now()
|
|
48
59
|
};
|
|
49
60
|
}
|
|
50
|
-
|
|
51
61
|
|
|
52
|
-
// Ensure arrays exist
|
|
62
|
+
// Ensure arrays and lensConfig exist
|
|
53
63
|
if (!fedInfo1.federation) fedInfo1.federation = [];
|
|
54
64
|
if (!fedInfo1.notify) fedInfo1.notify = [];
|
|
65
|
+
if (!fedInfo1.lensConfig) fedInfo1.lensConfig = {};
|
|
55
66
|
|
|
56
|
-
// Add space2 to space1's federation
|
|
67
|
+
// Add space2 to space1's federation list if not already present
|
|
57
68
|
if (!fedInfo1.federation.includes(spaceId2)) {
|
|
58
69
|
fedInfo1.federation.push(spaceId2);
|
|
59
70
|
}
|
|
60
|
-
|
|
61
|
-
//
|
|
62
|
-
|
|
63
|
-
|
|
71
|
+
|
|
72
|
+
// Add space2 to space1's notify list if not already present
|
|
73
|
+
if (!fedInfo1.notify.includes(spaceId2)) {
|
|
74
|
+
fedInfo1.notify.push(spaceId2);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Store lens configuration for space2
|
|
78
|
+
const newLensConfigsForSpace1 = { ...(fedInfo1.lensConfig || {}) }; // Shallow copy existing lensConfigs for space1
|
|
79
|
+
newLensConfigsForSpace1[spaceId2] = { // Add/update config for the target spaceId2
|
|
80
|
+
federate: [...federateLenses], // federateLenses & notifyLenses are from the main lensConfig parameter
|
|
81
|
+
notify: [...notifyLenses],
|
|
82
|
+
timestamp: Date.now()
|
|
83
|
+
};
|
|
84
|
+
fedInfo1.lensConfig = newLensConfigsForSpace1; // Assign the new/modified object back to fedInfo1.lensConfig
|
|
64
85
|
|
|
65
86
|
// Update timestamp
|
|
66
87
|
fedInfo1.timestamp = Date.now();
|
|
@@ -68,42 +89,50 @@ export async function federate(holosphere, spaceId1, spaceId2, password1 = null,
|
|
|
68
89
|
// Save updated federation info for space1
|
|
69
90
|
try {
|
|
70
91
|
await holosphere.putGlobal('federation', fedInfo1, password1);
|
|
71
|
-
console.log(`Updated federation info for ${spaceId1}`);
|
|
72
92
|
} catch (error) {
|
|
73
|
-
console.warn(`Could not update federation info for ${spaceId1}: ${error.message}`);
|
|
74
93
|
throw new Error(`Failed to create federation: ${error.message}`);
|
|
75
94
|
}
|
|
76
|
-
|
|
95
|
+
|
|
77
96
|
// If bidirectional is true, handle space2 (B) as well
|
|
78
|
-
//if (bidirectional && password2) {
|
|
79
97
|
{
|
|
80
98
|
let fedInfo2 = null;
|
|
81
99
|
try {
|
|
82
100
|
fedInfo2 = await holosphere.getGlobal('federation', spaceId2, password2);
|
|
83
101
|
} catch (error) {
|
|
84
|
-
console.warn(`Could not get federation info for ${spaceId2}: ${error.message}`);
|
|
85
|
-
// Create new federation info if it doesn't exist
|
|
86
|
-
|
|
87
102
|
}
|
|
103
|
+
|
|
88
104
|
if (fedInfo2 == null) {
|
|
89
105
|
fedInfo2 = {
|
|
90
106
|
id: spaceId2,
|
|
91
107
|
name: spaceId2,
|
|
92
108
|
federation: [],
|
|
93
109
|
notify: [],
|
|
110
|
+
lensConfig: {}, // New field for lens-specific settings
|
|
94
111
|
timestamp: Date.now()
|
|
95
112
|
};
|
|
96
113
|
}
|
|
97
|
-
|
|
98
|
-
//
|
|
99
|
-
|
|
114
|
+
|
|
115
|
+
// Ensure arrays and lensConfig exist
|
|
116
|
+
if (!fedInfo2.federation) fedInfo2.federation = [];
|
|
100
117
|
if (!fedInfo2.notify) fedInfo2.notify = [];
|
|
118
|
+
if (!fedInfo2.lensConfig) fedInfo2.lensConfig = {};
|
|
101
119
|
|
|
102
|
-
// Add space1 to space2's federation list if
|
|
120
|
+
// Add space1 to space2's federation list if bidirectional
|
|
121
|
+
if (bidirectional && !fedInfo2.federation.includes(spaceId1)) {
|
|
122
|
+
fedInfo2.federation.push(spaceId1);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Add space1 to space2's notify list if not already present
|
|
103
126
|
if (!fedInfo2.notify.includes(spaceId1)) {
|
|
104
127
|
fedInfo2.notify.push(spaceId1);
|
|
105
128
|
}
|
|
106
|
-
|
|
129
|
+
|
|
130
|
+
// Store lens configuration for space1
|
|
131
|
+
fedInfo2.lensConfig[spaceId1] = {
|
|
132
|
+
federate: bidirectional ? [...federateLenses] : [], // Create a copy of the array
|
|
133
|
+
notify: bidirectional ? [...notifyLenses] : [], // Create a copy of the array
|
|
134
|
+
timestamp: Date.now()
|
|
135
|
+
};
|
|
107
136
|
|
|
108
137
|
// Update timestamp
|
|
109
138
|
fedInfo2.timestamp = Date.now();
|
|
@@ -111,10 +140,7 @@ export async function federate(holosphere, spaceId1, spaceId2, password1 = null,
|
|
|
111
140
|
// Save updated federation info for space2
|
|
112
141
|
try {
|
|
113
142
|
await holosphere.putGlobal('federation', fedInfo2, password2);
|
|
114
|
-
console.log(`Updated federation info for ${spaceId2}`);
|
|
115
143
|
} catch (error) {
|
|
116
|
-
console.warn(`Could not update federation info for ${spaceId2}: ${error.message}`);
|
|
117
|
-
// Don't throw here as the main federation was successful
|
|
118
144
|
}
|
|
119
145
|
}
|
|
120
146
|
|
|
@@ -125,19 +151,20 @@ export async function federate(holosphere, spaceId1, spaceId2, password1 = null,
|
|
|
125
151
|
space2: spaceId2,
|
|
126
152
|
created: Date.now(),
|
|
127
153
|
status: 'active',
|
|
128
|
-
bidirectional: bidirectional
|
|
154
|
+
bidirectional: bidirectional,
|
|
155
|
+
lensConfig: {
|
|
156
|
+
federate: [...federateLenses], // Create a copy of the array
|
|
157
|
+
notify: [...notifyLenses] // Create a copy of the array
|
|
158
|
+
}
|
|
129
159
|
};
|
|
130
|
-
|
|
160
|
+
|
|
131
161
|
try {
|
|
132
162
|
await holosphere.putGlobal('federationMeta', federationMeta);
|
|
133
|
-
console.log(`Created federation metadata for ${spaceId1} and ${spaceId2}`);
|
|
134
163
|
} catch (error) {
|
|
135
|
-
console.warn(`Could not create federation metadata: ${error.message}`);
|
|
136
164
|
}
|
|
137
165
|
|
|
138
166
|
return true;
|
|
139
167
|
} catch (error) {
|
|
140
|
-
console.error(`Federation creation failed: ${error.message}`);
|
|
141
168
|
throw error;
|
|
142
169
|
}
|
|
143
170
|
}
|
|
@@ -203,7 +230,6 @@ export async function subscribeFederation(holosphere, spaceId, password = null,
|
|
|
203
230
|
// Execute callback with the data
|
|
204
231
|
await callback(data, federatedSpace, lens);
|
|
205
232
|
} catch (error) {
|
|
206
|
-
console.warn('Federation notification error:', error);
|
|
207
233
|
}
|
|
208
234
|
});
|
|
209
235
|
|
|
@@ -211,7 +237,6 @@ export async function subscribeFederation(holosphere, spaceId, password = null,
|
|
|
211
237
|
subscriptions.push(sub);
|
|
212
238
|
}
|
|
213
239
|
} catch (error) {
|
|
214
|
-
console.warn(`Error creating subscription for ${federatedSpace}/${lens}:`, error);
|
|
215
240
|
}
|
|
216
241
|
}
|
|
217
242
|
}
|
|
@@ -226,7 +251,6 @@ export async function subscribeFederation(holosphere, spaceId, password = null,
|
|
|
226
251
|
sub.unsubscribe();
|
|
227
252
|
}
|
|
228
253
|
} catch (error) {
|
|
229
|
-
console.warn('Error unsubscribing:', error);
|
|
230
254
|
}
|
|
231
255
|
});
|
|
232
256
|
// Clear the subscriptions array
|
|
@@ -252,6 +276,35 @@ export async function getFederation(holosphere, spaceId, password = null) {
|
|
|
252
276
|
return await holosphere.getGlobal('federation', spaceId, password);
|
|
253
277
|
}
|
|
254
278
|
|
|
279
|
+
/**
|
|
280
|
+
* Retrieves the lens-specific configuration for a federation link between two spaces.
|
|
281
|
+
* @param {object} holosphere - The HoloSphere instance
|
|
282
|
+
* @param {string} spaceId - The ID of the source space.
|
|
283
|
+
* @param {string} targetSpaceId - The ID of the target space in the federation link.
|
|
284
|
+
* @param {string} [password] - Optional password for the source space.
|
|
285
|
+
* @returns {Promise<object|null>} - An object with 'federate' and 'notify' arrays, or null if not found.
|
|
286
|
+
*/
|
|
287
|
+
export async function getFederatedConfig(holosphere, spaceId, targetSpaceId, password = null) {
|
|
288
|
+
if (!holosphere || !spaceId || !targetSpaceId) {
|
|
289
|
+
throw new Error('getFederatedConfig: Missing required parameters');
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
try {
|
|
293
|
+
const fedInfo = await getFederation(holosphere, spaceId, password);
|
|
294
|
+
|
|
295
|
+
if (fedInfo && fedInfo.lensConfig && fedInfo.lensConfig[targetSpaceId]) {
|
|
296
|
+
return {
|
|
297
|
+
federate: fedInfo.lensConfig[targetSpaceId].federate || [],
|
|
298
|
+
notify: fedInfo.lensConfig[targetSpaceId].notify || []
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
return null; // Or return an empty config: { federate: [], notify: [] }
|
|
302
|
+
} catch (error) {
|
|
303
|
+
console.error(`Error getting federated config for ${spaceId} -> ${targetSpaceId}: ${error.message}`);
|
|
304
|
+
throw error;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
255
308
|
/**
|
|
256
309
|
* Removes a federation relationship between spaces
|
|
257
310
|
* @param {object} holosphere - The HoloSphere instance
|
|
@@ -272,12 +325,14 @@ export async function unfederate(holosphere, spaceId1, spaceId2, password1 = nul
|
|
|
272
325
|
try {
|
|
273
326
|
fedInfo1 = await holosphere.getGlobal('federation', spaceId1, password1);
|
|
274
327
|
} catch (error) {
|
|
275
|
-
console.
|
|
328
|
+
console.error(`Error getting fedInfo1 for ${spaceId1} during unfederate: ${error.message}`);
|
|
329
|
+
// If we can't get fedInfo1, we can't modify it. Decide if this is a critical failure.
|
|
330
|
+
// For now, we'll let it proceed to attempt metadata cleanup, but a throw here might be valid.
|
|
276
331
|
}
|
|
277
332
|
|
|
278
333
|
if (!fedInfo1 || !fedInfo1.federation) {
|
|
279
|
-
|
|
280
|
-
|
|
334
|
+
// If fedInfo1 or its federation array doesn't exist, log and proceed to metadata cleanup.
|
|
335
|
+
console.warn(`No federation array found for ${spaceId1} or fedInfo1 is null. Skipping its update.`);
|
|
281
336
|
} else {
|
|
282
337
|
// Update first space federation info
|
|
283
338
|
fedInfo1.federation = fedInfo1.federation.filter(id => id !== spaceId2);
|
|
@@ -285,30 +340,34 @@ export async function unfederate(holosphere, spaceId1, spaceId2, password1 = nul
|
|
|
285
340
|
|
|
286
341
|
try {
|
|
287
342
|
await holosphere.putGlobal('federation', fedInfo1, password1);
|
|
288
|
-
console.log(`Updated federation info for ${spaceId1}`);
|
|
289
343
|
} catch (error) {
|
|
290
|
-
console.
|
|
344
|
+
console.error(`Failed to update fedInfo1 for ${spaceId1} during unfederate: ${error.message}`);
|
|
345
|
+
throw error; // RE-THROW to signal failure
|
|
291
346
|
}
|
|
292
347
|
}
|
|
293
348
|
|
|
294
|
-
// Update second space federation info
|
|
295
|
-
|
|
349
|
+
// Update second space federation info (remove spaceId1 from spaceId2's notify list)
|
|
350
|
+
// This part is usually for full bidirectional unfederation cleanup.
|
|
351
|
+
// The original code only did this if password2 was provided.
|
|
352
|
+
if (password2) { // Retaining original condition for this block
|
|
296
353
|
let fedInfo2 = null;
|
|
297
354
|
try {
|
|
298
355
|
fedInfo2 = await holosphere.getGlobal('federation', spaceId2, password2);
|
|
299
356
|
} catch (error) {
|
|
300
|
-
console.
|
|
357
|
+
console.error(`Error getting fedInfo2 for ${spaceId2} during unfederate: ${error.message}`);
|
|
301
358
|
}
|
|
302
359
|
|
|
303
|
-
if (fedInfo2
|
|
360
|
+
if (!fedInfo2 || !fedInfo2.notify) {
|
|
361
|
+
console.warn(`No notify array found for ${spaceId2} or fedInfo2 is null. Skipping its update.`);
|
|
362
|
+
} else {
|
|
304
363
|
fedInfo2.notify = fedInfo2.notify.filter(id => id !== spaceId1);
|
|
305
364
|
fedInfo2.timestamp = Date.now();
|
|
306
365
|
|
|
307
366
|
try {
|
|
308
367
|
await holosphere.putGlobal('federation', fedInfo2, password2);
|
|
309
|
-
console.log(`Updated federation info for ${spaceId2}`);
|
|
310
368
|
} catch (error) {
|
|
311
|
-
console.
|
|
369
|
+
console.error(`Failed to update fedInfo2 for ${spaceId2} during unfederate: ${error.message}`);
|
|
370
|
+
throw error; // RE-THROW to signal failure
|
|
312
371
|
}
|
|
313
372
|
}
|
|
314
373
|
}
|
|
@@ -324,17 +383,17 @@ export async function unfederate(holosphere, spaceId1, spaceId2, password1 = nul
|
|
|
324
383
|
if (meta) {
|
|
325
384
|
meta.status = 'inactive';
|
|
326
385
|
meta.endedAt = Date.now();
|
|
327
|
-
await holosphere.putGlobal('federationMeta', meta);
|
|
328
|
-
console.log(`Updated federation metadata for ${spaceId1} and ${spaceId2}`);
|
|
386
|
+
await holosphere.putGlobal('federationMeta', meta); // Not re-throwing here as it's metadata cleanup
|
|
329
387
|
}
|
|
330
388
|
} catch (error) {
|
|
331
|
-
console.warn(`
|
|
389
|
+
console.warn(`Failed to update federationMeta during unfederate: ${error.message}`);
|
|
332
390
|
}
|
|
333
391
|
|
|
334
392
|
return true;
|
|
335
393
|
} catch (error) {
|
|
336
|
-
|
|
337
|
-
|
|
394
|
+
// This will catch errors re-thrown from putGlobal or from getGlobal if they occur before specific catches.
|
|
395
|
+
console.error(`Critical error during unfederate operation for ${spaceId1}-${spaceId2}: ${error.message}`);
|
|
396
|
+
throw error; // Ensure the main operation failure is propagated
|
|
338
397
|
}
|
|
339
398
|
}
|
|
340
399
|
|
|
@@ -373,42 +432,42 @@ export async function removeNotify(holosphere, spaceId1, spaceId2, password1 = n
|
|
|
373
432
|
|
|
374
433
|
// Save updated federation info
|
|
375
434
|
await holosphere.putGlobal('federation', fedInfo, password1);
|
|
376
|
-
console.log(`Removed ${spaceId2} from ${spaceId1}'s notify list`);
|
|
377
435
|
return true;
|
|
378
436
|
} else {
|
|
379
|
-
console.log(`${spaceId2} not found in ${spaceId1}'s notify list`);
|
|
380
437
|
return false;
|
|
381
438
|
}
|
|
382
439
|
} catch (error) {
|
|
383
|
-
console.error(`Remove notification failed: ${error.message}`);
|
|
384
440
|
throw error;
|
|
385
441
|
}
|
|
386
442
|
}
|
|
387
443
|
|
|
388
444
|
/**
|
|
389
|
-
* Get and combine data from local and federated sources
|
|
445
|
+
* Get and combine data from local and federated sources.
|
|
446
|
+
* If `options.queryIds` is provided, fetches only those specific IDs using `get()`.
|
|
447
|
+
* Otherwise, falls back to fetching all data using `getAll()` (potentially inefficient).
|
|
448
|
+
*
|
|
390
449
|
* @param {HoloSphere} holosphere The HoloSphere instance
|
|
391
|
-
* @param {string} holon The local holon name
|
|
450
|
+
* @param {string} holon The local holon name (used as the space ID for federation info)
|
|
392
451
|
* @param {string} lens The lens to query
|
|
393
452
|
* @param {Object} options Options for data retrieval and aggregation
|
|
394
|
-
* @param {
|
|
395
|
-
* @param {
|
|
396
|
-
* @param {string
|
|
397
|
-
* @param {string[]} options.
|
|
398
|
-
* @param {
|
|
399
|
-
* @param {
|
|
400
|
-
* @param {
|
|
401
|
-
* @param {boolean} options.
|
|
402
|
-
* @param {boolean} options.
|
|
403
|
-
* @param {
|
|
404
|
-
* @param {number} options.
|
|
453
|
+
* @param {string[]} [options.queryIds] Optional array of specific item IDs to fetch.
|
|
454
|
+
* @param {boolean} [options.aggregate=false] Whether to aggregate results by ID
|
|
455
|
+
* @param {string} [options.idField='id'] The field to use as ID
|
|
456
|
+
* @param {string[]} [options.sumFields=[]] Fields to sum during aggregation
|
|
457
|
+
* @param {string[]} [options.concatArrays=[]] Array fields to concatenate during aggregation
|
|
458
|
+
* @param {boolean} [options.removeDuplicates=true] Whether to remove duplicates in concatenated arrays
|
|
459
|
+
* @param {Function} [options.mergeStrategy=null] Custom merge function for aggregation
|
|
460
|
+
* @param {boolean} [options.includeLocal=true] Whether to include local data
|
|
461
|
+
* @param {boolean} [options.includeFederated=true] Whether to include federated data
|
|
462
|
+
* @param {boolean} [options.resolveReferences=true] Whether to resolve federation references
|
|
463
|
+
* @param {number} [options.maxFederatedSpaces=-1] Maximum number of federated spaces to query (-1 for all)
|
|
464
|
+
* @param {number} [options.timeout=10000] Timeout in milliseconds for federated queries
|
|
405
465
|
* @returns {Promise<Array>} Combined array of local and federated data
|
|
406
466
|
*/
|
|
407
467
|
export async function getFederated(holosphere, holon, lens, options = {}) {
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
// Set default options
|
|
468
|
+
// Set default options and extract queryIds
|
|
411
469
|
const {
|
|
470
|
+
queryIds = null, // New option
|
|
412
471
|
aggregate = false,
|
|
413
472
|
idField = 'id',
|
|
414
473
|
sumFields = [],
|
|
@@ -417,91 +476,85 @@ export async function getFederated(holosphere, holon, lens, options = {}) {
|
|
|
417
476
|
mergeStrategy = null,
|
|
418
477
|
includeLocal = true,
|
|
419
478
|
includeFederated = true,
|
|
420
|
-
resolveReferences = true,
|
|
479
|
+
resolveReferences = true,
|
|
421
480
|
maxFederatedSpaces = -1,
|
|
422
481
|
timeout = 10000
|
|
423
482
|
} = options;
|
|
424
483
|
|
|
425
484
|
console.log(`resolveReferences option: ${resolveReferences}`);
|
|
426
|
-
|
|
485
|
+
console.log(`Querying specific IDs:`, queryIds ? queryIds.join(', ') : 'No (fetching all)');
|
|
486
|
+
|
|
427
487
|
// Validate required parameters
|
|
428
488
|
if (!holosphere || !holon || !lens) {
|
|
429
489
|
throw new Error('Missing required parameters: holosphere, holon, and lens are required');
|
|
430
490
|
}
|
|
431
491
|
|
|
432
|
-
// Get federation info for current space
|
|
433
|
-
// Use holon as the space ID
|
|
492
|
+
// Get federation info for current space (using holon as spaceId)
|
|
434
493
|
const spaceId = holon;
|
|
435
494
|
const fedInfo = await getFederation(holosphere, spaceId);
|
|
436
495
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
const
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
496
|
+
// Initialize result array and track processed IDs to avoid duplicates/redundant fetches
|
|
497
|
+
const fetchedItems = new Map(); // Use Map to store fetched items by ID
|
|
498
|
+
const processedIds = new Set(); // Track IDs added to the final result
|
|
499
|
+
|
|
500
|
+
const fetchPromises = [];
|
|
501
|
+
|
|
502
|
+
// Determine list of spaces to query (local + federated)
|
|
503
|
+
let spacesToQuery = [];
|
|
504
|
+
if (includeLocal) {
|
|
505
|
+
spacesToQuery.push(holon); // Add local holon first
|
|
506
|
+
}
|
|
445
507
|
if (includeFederated && fedInfo && fedInfo.federation && fedInfo.federation.length > 0) {
|
|
446
|
-
console.log(`Found ${fedInfo.federation.length} federated spaces`);
|
|
447
|
-
|
|
448
|
-
// Limit number of federated spaces to query
|
|
449
508
|
const federatedSpaces = maxFederatedSpaces === -1 ? fedInfo.federation : fedInfo.federation.slice(0, maxFederatedSpaces);
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
// Process federated spaces
|
|
453
|
-
for (const federatedSpace of federatedSpaces) {
|
|
454
|
-
try {
|
|
455
|
-
console.log(`=== PROCESSING FEDERATED SPACE: ${federatedSpace} ===`);
|
|
456
|
-
|
|
457
|
-
// Get all data for this lens from the federated space
|
|
458
|
-
const federatedItems = await holosphere.getAll(federatedSpace, lens);
|
|
459
|
-
console.log(`Got ${federatedItems.length} items from federated space ${federatedSpace}`);
|
|
460
|
-
console.log(`Federated items:`, JSON.stringify(federatedItems));
|
|
461
|
-
|
|
462
|
-
// Process each item
|
|
463
|
-
for (const item of federatedItems) {
|
|
464
|
-
if (!item) {
|
|
465
|
-
console.log('Item is null or undefined, skipping');
|
|
466
|
-
continue;
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
console.log(`Checking item for ID field '${idField}':`, item);
|
|
470
|
-
|
|
471
|
-
if (!item[idField]) {
|
|
472
|
-
console.log(`Item missing ID field '${idField}', available fields:`, Object.keys(item));
|
|
473
|
-
continue;
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
// For now, just add this item to results, we'll resolve references later
|
|
477
|
-
result.push(item);
|
|
478
|
-
processedIds.add(item[idField]);
|
|
479
|
-
}
|
|
480
|
-
} catch (error) {
|
|
481
|
-
console.warn(`Error processing federated space ${federatedSpace}: ${error.message}`);
|
|
482
|
-
}
|
|
483
|
-
}
|
|
509
|
+
spacesToQuery = spacesToQuery.concat(federatedSpaces);
|
|
484
510
|
}
|
|
485
|
-
|
|
486
|
-
//
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
511
|
+
|
|
512
|
+
// Fetch data from all relevant spaces
|
|
513
|
+
for (const currentSpace of spacesToQuery) {
|
|
514
|
+
if (queryIds && Array.isArray(queryIds)) {
|
|
515
|
+
// --- Fetch specific IDs using holosphere.get ---
|
|
516
|
+
console.log(`Fetching specific IDs from ${currentSpace}: ${queryIds.join(', ')}`);
|
|
517
|
+
for (const itemId of queryIds) {
|
|
518
|
+
if (fetchedItems.has(itemId)) continue; // Skip if already fetched
|
|
519
|
+
fetchPromises.push(
|
|
520
|
+
holosphere.get(currentSpace, lens, itemId)
|
|
521
|
+
.then(item => {
|
|
522
|
+
if (item) {
|
|
523
|
+
fetchedItems.set(itemId, item);
|
|
524
|
+
}
|
|
525
|
+
})
|
|
526
|
+
.catch(err => console.warn(`Error fetching item ${itemId} from ${currentSpace}: ${err.message}`))
|
|
527
|
+
);
|
|
498
528
|
}
|
|
529
|
+
} else {
|
|
530
|
+
// --- Fetch all data using holosphere.getAll (Fallback - inefficient) ---
|
|
531
|
+
if(currentSpace === holon && includeLocal) { // Only warn once for local
|
|
532
|
+
console.warn(`getFederated: No queryIds provided. Falling back to fetching ALL items from ${currentSpace} using getAll. This can be inefficient.`);
|
|
533
|
+
}
|
|
534
|
+
console.log(`Fetching ALL items from ${currentSpace}`);
|
|
535
|
+
fetchPromises.push(
|
|
536
|
+
holosphere.getAll(currentSpace, lens)
|
|
537
|
+
.then(items => {
|
|
538
|
+
for (const item of items) {
|
|
539
|
+
if (item && item[idField] && !fetchedItems.has(item[idField])) {
|
|
540
|
+
fetchedItems.set(item[idField], item);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
})
|
|
544
|
+
.catch(err => console.warn(`Error fetching all items from ${currentSpace}: ${err.message}`))
|
|
545
|
+
);
|
|
499
546
|
}
|
|
500
547
|
}
|
|
501
|
-
|
|
548
|
+
|
|
549
|
+
// Wait for all fetches to complete
|
|
550
|
+
await Promise.all(fetchPromises);
|
|
551
|
+
|
|
552
|
+
// Convert Map values to array for processing
|
|
553
|
+
const result = Array.from(fetchedItems.values());
|
|
554
|
+
|
|
502
555
|
// Now resolve references if needed
|
|
503
|
-
if (resolveReferences) {
|
|
504
|
-
console.log(`Resolving references for ${result.length} items`);
|
|
556
|
+
if (resolveReferences && result.length > 0) {
|
|
557
|
+
console.log(`Resolving references for ${result.length} fetched items`);
|
|
505
558
|
|
|
506
559
|
for (let i = 0; i < result.length; i++) {
|
|
507
560
|
const item = result[i];
|
|
@@ -520,13 +573,13 @@ export async function getFederated(holosphere, holon, lens, options = {}) {
|
|
|
520
573
|
|
|
521
574
|
console.log(`Extracting from soul - holon: ${originHolon}, lens: ${originLens}, key: ${originKey}`);
|
|
522
575
|
|
|
523
|
-
// Get original data using the extracted path
|
|
576
|
+
// Get original data using the extracted path - always resolve references
|
|
524
577
|
const originalData = await holosphere.get(
|
|
525
578
|
originHolon,
|
|
526
579
|
originLens,
|
|
527
580
|
originKey,
|
|
528
581
|
null,
|
|
529
|
-
{ resolveReferences:
|
|
582
|
+
{ resolveReferences: true } // Always resolve nested references
|
|
530
583
|
);
|
|
531
584
|
|
|
532
585
|
console.log(`Original data found via soul path:`, JSON.stringify(originalData));
|
|
@@ -542,15 +595,45 @@ export async function getFederated(holosphere, holon, lens, options = {}) {
|
|
|
542
595
|
timestamp: Date.now()
|
|
543
596
|
}
|
|
544
597
|
};
|
|
545
|
-
console.log(`Reference resolved successfully via soul path, processed item:`, JSON.stringify(result[i]));
|
|
546
598
|
} else {
|
|
547
|
-
|
|
599
|
+
// Instead of leaving the original reference, create an error object
|
|
600
|
+
result[i] = {
|
|
601
|
+
id: item.id,
|
|
602
|
+
_federation: {
|
|
603
|
+
isReference: true,
|
|
604
|
+
resolved: false,
|
|
605
|
+
soul: item.soul,
|
|
606
|
+
error: 'Referenced data not found',
|
|
607
|
+
timestamp: Date.now()
|
|
608
|
+
}
|
|
609
|
+
};
|
|
548
610
|
}
|
|
549
611
|
} else {
|
|
550
612
|
console.warn(`Soul doesn't match expected format: ${item.soul}`);
|
|
613
|
+
// Instead of leaving the original reference, create an error object
|
|
614
|
+
result[i] = {
|
|
615
|
+
id: item.id,
|
|
616
|
+
_federation: {
|
|
617
|
+
isReference: true,
|
|
618
|
+
resolved: false,
|
|
619
|
+
soul: item.soul,
|
|
620
|
+
error: 'Invalid soul format',
|
|
621
|
+
timestamp: Date.now()
|
|
622
|
+
}
|
|
623
|
+
};
|
|
551
624
|
}
|
|
552
625
|
} catch (refError) {
|
|
553
|
-
|
|
626
|
+
// Instead of leaving the original reference, create an error object
|
|
627
|
+
result[i] = {
|
|
628
|
+
id: item.id,
|
|
629
|
+
_federation: {
|
|
630
|
+
isReference: true,
|
|
631
|
+
resolved: false,
|
|
632
|
+
soul: item.soul,
|
|
633
|
+
error: refError.message || 'Error resolving reference',
|
|
634
|
+
timestamp: Date.now()
|
|
635
|
+
}
|
|
636
|
+
};
|
|
554
637
|
}
|
|
555
638
|
}
|
|
556
639
|
// For backward compatibility, check for old-style references
|
|
@@ -582,12 +665,10 @@ export async function getFederated(holosphere, holon, lens, options = {}) {
|
|
|
582
665
|
timestamp: Date.now()
|
|
583
666
|
}
|
|
584
667
|
};
|
|
585
|
-
console.log(`Legacy reference resolved successfully, processed item:`, JSON.stringify(result[i]));
|
|
586
668
|
} else {
|
|
587
669
|
console.warn(`Could not resolve legacy reference: original data not found`);
|
|
588
670
|
}
|
|
589
671
|
} catch (refError) {
|
|
590
|
-
console.warn(`Error resolving legacy reference in getFederated: ${refError.message}`);
|
|
591
672
|
}
|
|
592
673
|
}
|
|
593
674
|
}
|
|
@@ -659,7 +740,7 @@ export async function getFederated(holosphere, holon, lens, options = {}) {
|
|
|
659
740
|
* @param {string} lens - The lens identifier
|
|
660
741
|
* @param {object} data - The data to propagate
|
|
661
742
|
* @param {object} [options] - Propagation options
|
|
662
|
-
* @param {boolean} [options.
|
|
743
|
+
* @param {boolean} [options.useHolograms=true] - Use holograms for propagation (default: true)
|
|
663
744
|
* @param {string[]} [options.targetSpaces] - Specific target spaces to propagate to (defaults to all federated spaces)
|
|
664
745
|
* @param {string} [options.password] - Password for accessing the source holon (if needed)
|
|
665
746
|
* @returns {Promise<object>} - Result with success count and errors
|
|
@@ -669,18 +750,13 @@ export async function propagate(holosphere, holon, lens, data, options = {}) {
|
|
|
669
750
|
throw new Error('propagate: Missing required parameters');
|
|
670
751
|
}
|
|
671
752
|
// Default propagation options
|
|
672
|
-
const {
|
|
673
|
-
useReferences = true,
|
|
674
|
-
targetSpaces = null,
|
|
675
|
-
password = null
|
|
676
|
-
} = options;
|
|
753
|
+
const { useHolograms = true, targetSpaces = null, password = null } = options;
|
|
677
754
|
|
|
678
755
|
const result = {
|
|
679
756
|
success: 0,
|
|
680
757
|
errors: 0,
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
referencesUsed: useReferences
|
|
758
|
+
skipped: 0,
|
|
759
|
+
messages: []
|
|
684
760
|
};
|
|
685
761
|
|
|
686
762
|
try {
|
|
@@ -717,56 +793,97 @@ export async function propagate(holosphere, holon, lens, data, options = {}) {
|
|
|
717
793
|
message: 'No valid target spaces found after filtering'
|
|
718
794
|
};
|
|
719
795
|
}
|
|
796
|
+
|
|
797
|
+
// Filter spaces based on lens configuration
|
|
798
|
+
spaces = spaces.filter(targetSpace => {
|
|
799
|
+
const spaceConfig = fedInfo.lensConfig?.[targetSpace];
|
|
800
|
+
if (!spaceConfig) {
|
|
801
|
+
result.messages.push(`No lens configuration for target space ${targetSpace}. Skipping propagation of lens '${lens}'.`);
|
|
802
|
+
result.skipped++;
|
|
803
|
+
return false;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
// Ensure .federate is an array before calling .includes
|
|
807
|
+
const federateLenses = Array.isArray(spaceConfig.federate) ? spaceConfig.federate : [];
|
|
808
|
+
|
|
809
|
+
const shouldFederate = federateLenses.includes('*') || federateLenses.includes(lens);
|
|
810
|
+
|
|
811
|
+
// Propagation now only depends on the 'federate' list configuration for the lens
|
|
812
|
+
const shouldPropagate = shouldFederate;
|
|
813
|
+
|
|
814
|
+
if (!shouldPropagate) {
|
|
815
|
+
result.messages.push(`Propagation of lens '${lens}' to target space ${targetSpace} skipped: lens not in 'federate' configuration.`);
|
|
816
|
+
result.skipped++;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
return shouldPropagate;
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
if (spaces.length === 0) {
|
|
823
|
+
// If no specific skip messages were added and spaces is empty, add a general one.
|
|
824
|
+
if (result.skipped === 0 && fedInfo.notify && fedInfo.notify.length > 0) {
|
|
825
|
+
result.messages.push('No target spaces were configured for federation of this specific lens, out of available notification partners.');
|
|
826
|
+
} else if (fedInfo.notify && fedInfo.notify.length === 0) {
|
|
827
|
+
result.messages.push('No notification partners available to filter for lens propagation.');
|
|
828
|
+
}
|
|
829
|
+
return {
|
|
830
|
+
...result,
|
|
831
|
+
message: result.messages.join('; ') || 'No valid target spaces for propagation after lens filtering.'
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
// Check if data is already a hologram
|
|
836
|
+
const isAlreadyHologram = holosphere.isHologram(data);
|
|
837
|
+
|
|
838
|
+
// If data is already a hologram, don't re-wrap it
|
|
839
|
+
if (isAlreadyHologram && useHolograms) {
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
// If propagating a non-hologram and useHolograms is false, add warning
|
|
843
|
+
if (!isAlreadyHologram && !useHolograms) {
|
|
844
|
+
}
|
|
720
845
|
|
|
721
846
|
// For each target space, propagate the data
|
|
722
847
|
const propagatePromises = spaces.map(async (targetSpace) => {
|
|
723
848
|
try {
|
|
724
|
-
|
|
725
|
-
const
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
lens: lens,
|
|
739
|
-
timestamp: Date.now()
|
|
740
|
-
}
|
|
849
|
+
let payloadToPut;
|
|
850
|
+
const federationMeta = {
|
|
851
|
+
origin: holon, // The space from which this data is being propagated
|
|
852
|
+
sourceLens: lens, // The lens from which this data is being propagated
|
|
853
|
+
propagatedAt: Date.now(),
|
|
854
|
+
originalId: data.id
|
|
855
|
+
};
|
|
856
|
+
|
|
857
|
+
if (useHolograms && !isAlreadyHologram) {
|
|
858
|
+
// Create a new hologram referencing the original data
|
|
859
|
+
const newHologram = holosphere.createHologram(holon, lens, data);
|
|
860
|
+
payloadToPut = {
|
|
861
|
+
...newHologram, // This will be { id: data.id, soul: 'path/to/original' }
|
|
862
|
+
_federation: federationMeta
|
|
741
863
|
};
|
|
742
|
-
|
|
743
|
-
console.log(`Using soul reference: ${soul} for data: ${data.id}`);
|
|
744
|
-
|
|
745
|
-
// Store the reference in the target space without propagation
|
|
746
|
-
await holosphere.put(targetSpace, lens, reference, null, { autoPropagate: false });
|
|
747
|
-
|
|
748
|
-
result.success++;
|
|
749
|
-
return true;
|
|
750
864
|
} else {
|
|
751
|
-
//
|
|
752
|
-
|
|
753
|
-
|
|
865
|
+
// Propagate existing data (could be a full object or an existing hologram)
|
|
866
|
+
// Make a shallow copy and update/add _federation metadata
|
|
867
|
+
payloadToPut = {
|
|
868
|
+
...data,
|
|
754
869
|
_federation: {
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
timestamp: Date.now()
|
|
870
|
+
...(data._federation || {}), // Preserve existing _federation fields if any
|
|
871
|
+
...federationMeta // Add/overwrite with current propagation info
|
|
758
872
|
}
|
|
759
873
|
};
|
|
760
|
-
await holosphere.put(targetSpace, lens, dataToStore, null, { autoPropagate: false });
|
|
761
|
-
result.success++;
|
|
762
|
-
return true;
|
|
763
874
|
}
|
|
875
|
+
|
|
876
|
+
// Store in the target space with redirection disabled and no further auto-propagation
|
|
877
|
+
await holosphere.put(targetSpace, lens, payloadToPut, null, {
|
|
878
|
+
disableHologramRedirection: true,
|
|
879
|
+
autoPropagate: false
|
|
880
|
+
});
|
|
881
|
+
|
|
882
|
+
result.success++;
|
|
883
|
+
return true;
|
|
764
884
|
} catch (error) {
|
|
765
885
|
result.errors++;
|
|
766
|
-
result.
|
|
767
|
-
space: targetSpace,
|
|
768
|
-
error: error.message
|
|
769
|
-
});
|
|
886
|
+
result.messages.push(`Error propagating ${data.id} to ${targetSpace}: ${error.message}`);
|
|
770
887
|
return false;
|
|
771
888
|
}
|
|
772
889
|
});
|
|
@@ -776,7 +893,6 @@ export async function propagate(holosphere, holon, lens, data, options = {}) {
|
|
|
776
893
|
result.propagated = result.success > 0;
|
|
777
894
|
return result;
|
|
778
895
|
} catch (error) {
|
|
779
|
-
console.error('Error in propagate:', error);
|
|
780
896
|
return {
|
|
781
897
|
...result,
|
|
782
898
|
error: error.message
|
|
@@ -848,7 +964,6 @@ export async function updateFederatedMessages(holosphere, originalChatId, messag
|
|
|
848
964
|
try {
|
|
849
965
|
await updateCallback(msg.chatId, msg.messageId);
|
|
850
966
|
} catch (error) {
|
|
851
|
-
console.warn(`Failed to update federated message in chat ${msg.chatId}:`, error);
|
|
852
967
|
}
|
|
853
968
|
}
|
|
854
969
|
}
|
|
@@ -935,13 +1050,11 @@ export async function resetFederation(holosphere, spaceId, password = null, opti
|
|
|
935
1050
|
|
|
936
1051
|
// Save partner's updated federation info
|
|
937
1052
|
await holosphere.putGlobal('federation', partnerFedInfo);
|
|
938
|
-
console.log(`Updated federation info for partner ${partnerSpace}`);
|
|
939
1053
|
result.partnersNotified++;
|
|
940
1054
|
return true;
|
|
941
1055
|
}
|
|
942
1056
|
return false;
|
|
943
1057
|
} catch (error) {
|
|
944
|
-
console.warn(`Could not update federation info for partner ${partnerSpace}: ${error.message}`);
|
|
945
1058
|
result.errors.push({
|
|
946
1059
|
partner: partnerSpace,
|
|
947
1060
|
error: error.message
|
|
@@ -967,10 +1080,8 @@ export async function resetFederation(holosphere, spaceId, password = null, opti
|
|
|
967
1080
|
meta.status = 'inactive';
|
|
968
1081
|
meta.endedAt = Date.now();
|
|
969
1082
|
await holosphere.putGlobal('federationMeta', meta);
|
|
970
|
-
console.log(`Updated federation metadata for ${spaceId} and ${partnerSpace}`);
|
|
971
1083
|
}
|
|
972
1084
|
} catch (error) {
|
|
973
|
-
console.warn(`Could not update federation metadata for ${partnerSpace}: ${error.message}`);
|
|
974
1085
|
}
|
|
975
1086
|
}
|
|
976
1087
|
}
|
|
@@ -978,7 +1089,6 @@ export async function resetFederation(holosphere, spaceId, password = null, opti
|
|
|
978
1089
|
result.success = true;
|
|
979
1090
|
return result;
|
|
980
1091
|
} catch (error) {
|
|
981
|
-
console.error(`Federation reset failed: ${error.message}`);
|
|
982
1092
|
return {
|
|
983
1093
|
...result,
|
|
984
1094
|
success: false,
|