holosphere 1.1.10 → 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 +272 -208
- package/global.js +560 -0
- package/hologram.js +156 -0
- package/holosphere.d.ts +94 -7
- package/holosphere.js +172 -1565
- package/node.js +155 -0
- package/package.json +2 -5
- package/schema.js +132 -0
- package/test/auth.test.js +55 -37
- package/test/delete.test.js +15 -12
- package/test/federation.test.js +179 -0
- package/test/hologram.test.js +316 -0
- package/test/subscription.test.js +105 -70
- package/utils.js +290 -0
- package/test/reference.test.js +0 -211
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
|
+
);
|
|
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.`);
|
|
498
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];
|
|
@@ -542,9 +595,7 @@ 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
|
-
console.warn(`Could not resolve reference: original data not found at extracted path`);
|
|
548
599
|
// Instead of leaving the original reference, create an error object
|
|
549
600
|
result[i] = {
|
|
550
601
|
id: item.id,
|
|
@@ -572,7 +623,6 @@ export async function getFederated(holosphere, holon, lens, options = {}) {
|
|
|
572
623
|
};
|
|
573
624
|
}
|
|
574
625
|
} catch (refError) {
|
|
575
|
-
console.warn(`Error resolving reference by soul in getFederated: ${refError.message}`);
|
|
576
626
|
// Instead of leaving the original reference, create an error object
|
|
577
627
|
result[i] = {
|
|
578
628
|
id: item.id,
|
|
@@ -615,12 +665,10 @@ export async function getFederated(holosphere, holon, lens, options = {}) {
|
|
|
615
665
|
timestamp: Date.now()
|
|
616
666
|
}
|
|
617
667
|
};
|
|
618
|
-
console.log(`Legacy reference resolved successfully, processed item:`, JSON.stringify(result[i]));
|
|
619
668
|
} else {
|
|
620
669
|
console.warn(`Could not resolve legacy reference: original data not found`);
|
|
621
670
|
}
|
|
622
671
|
} catch (refError) {
|
|
623
|
-
console.warn(`Error resolving legacy reference in getFederated: ${refError.message}`);
|
|
624
672
|
}
|
|
625
673
|
}
|
|
626
674
|
}
|
|
@@ -692,7 +740,7 @@ export async function getFederated(holosphere, holon, lens, options = {}) {
|
|
|
692
740
|
* @param {string} lens - The lens identifier
|
|
693
741
|
* @param {object} data - The data to propagate
|
|
694
742
|
* @param {object} [options] - Propagation options
|
|
695
|
-
* @param {boolean} [options.
|
|
743
|
+
* @param {boolean} [options.useHolograms=true] - Use holograms for propagation (default: true)
|
|
696
744
|
* @param {string[]} [options.targetSpaces] - Specific target spaces to propagate to (defaults to all federated spaces)
|
|
697
745
|
* @param {string} [options.password] - Password for accessing the source holon (if needed)
|
|
698
746
|
* @returns {Promise<object>} - Result with success count and errors
|
|
@@ -702,18 +750,13 @@ export async function propagate(holosphere, holon, lens, data, options = {}) {
|
|
|
702
750
|
throw new Error('propagate: Missing required parameters');
|
|
703
751
|
}
|
|
704
752
|
// Default propagation options
|
|
705
|
-
const {
|
|
706
|
-
useReferences = true,
|
|
707
|
-
targetSpaces = null,
|
|
708
|
-
password = null
|
|
709
|
-
} = options;
|
|
753
|
+
const { useHolograms = true, targetSpaces = null, password = null } = options;
|
|
710
754
|
|
|
711
755
|
const result = {
|
|
712
756
|
success: 0,
|
|
713
757
|
errors: 0,
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
referencesUsed: useReferences
|
|
758
|
+
skipped: 0,
|
|
759
|
+
messages: []
|
|
717
760
|
};
|
|
718
761
|
|
|
719
762
|
try {
|
|
@@ -750,69 +793,97 @@ export async function propagate(holosphere, holon, lens, data, options = {}) {
|
|
|
750
793
|
message: 'No valid target spaces found after filtering'
|
|
751
794
|
};
|
|
752
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
|
+
}
|
|
753
834
|
|
|
754
|
-
// Check if data is already a
|
|
755
|
-
const
|
|
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
|
+
}
|
|
756
845
|
|
|
757
846
|
// For each target space, propagate the data
|
|
758
847
|
const propagatePromises = spaces.map(async (targetSpace) => {
|
|
759
848
|
try {
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
//
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
// Store the reference in the target space without propagation
|
|
775
|
-
await holosphere.put(targetSpace, lens, reference, null, { autoPropagate: false });
|
|
776
|
-
|
|
777
|
-
result.success++;
|
|
778
|
-
return true;
|
|
779
|
-
}
|
|
780
|
-
// If already a reference, propagate it as is
|
|
781
|
-
else if (isAlreadyReference) {
|
|
782
|
-
// Add federation metadata if needed
|
|
783
|
-
const referenceToStore = {
|
|
784
|
-
...data,
|
|
785
|
-
_federation: data._federation || {
|
|
786
|
-
origin: holon,
|
|
787
|
-
lens: lens,
|
|
788
|
-
timestamp: Date.now()
|
|
789
|
-
}
|
|
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
|
|
790
863
|
};
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
// Otherwise, store a full copy without propagation
|
|
797
|
-
else {
|
|
798
|
-
const dataToStore = {
|
|
799
|
-
...data,
|
|
864
|
+
} else {
|
|
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,
|
|
800
869
|
_federation: {
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
timestamp: Date.now()
|
|
870
|
+
...(data._federation || {}), // Preserve existing _federation fields if any
|
|
871
|
+
...federationMeta // Add/overwrite with current propagation info
|
|
804
872
|
}
|
|
805
873
|
};
|
|
806
|
-
await holosphere.put(targetSpace, lens, dataToStore, null, { autoPropagate: false });
|
|
807
|
-
result.success++;
|
|
808
|
-
return true;
|
|
809
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;
|
|
810
884
|
} catch (error) {
|
|
811
885
|
result.errors++;
|
|
812
|
-
result.
|
|
813
|
-
space: targetSpace,
|
|
814
|
-
error: error.message
|
|
815
|
-
});
|
|
886
|
+
result.messages.push(`Error propagating ${data.id} to ${targetSpace}: ${error.message}`);
|
|
816
887
|
return false;
|
|
817
888
|
}
|
|
818
889
|
});
|
|
@@ -822,7 +893,6 @@ export async function propagate(holosphere, holon, lens, data, options = {}) {
|
|
|
822
893
|
result.propagated = result.success > 0;
|
|
823
894
|
return result;
|
|
824
895
|
} catch (error) {
|
|
825
|
-
console.error('Error in propagate:', error);
|
|
826
896
|
return {
|
|
827
897
|
...result,
|
|
828
898
|
error: error.message
|
|
@@ -894,7 +964,6 @@ export async function updateFederatedMessages(holosphere, originalChatId, messag
|
|
|
894
964
|
try {
|
|
895
965
|
await updateCallback(msg.chatId, msg.messageId);
|
|
896
966
|
} catch (error) {
|
|
897
|
-
console.warn(`Failed to update federated message in chat ${msg.chatId}:`, error);
|
|
898
967
|
}
|
|
899
968
|
}
|
|
900
969
|
}
|
|
@@ -981,13 +1050,11 @@ export async function resetFederation(holosphere, spaceId, password = null, opti
|
|
|
981
1050
|
|
|
982
1051
|
// Save partner's updated federation info
|
|
983
1052
|
await holosphere.putGlobal('federation', partnerFedInfo);
|
|
984
|
-
console.log(`Updated federation info for partner ${partnerSpace}`);
|
|
985
1053
|
result.partnersNotified++;
|
|
986
1054
|
return true;
|
|
987
1055
|
}
|
|
988
1056
|
return false;
|
|
989
1057
|
} catch (error) {
|
|
990
|
-
console.warn(`Could not update federation info for partner ${partnerSpace}: ${error.message}`);
|
|
991
1058
|
result.errors.push({
|
|
992
1059
|
partner: partnerSpace,
|
|
993
1060
|
error: error.message
|
|
@@ -1013,10 +1080,8 @@ export async function resetFederation(holosphere, spaceId, password = null, opti
|
|
|
1013
1080
|
meta.status = 'inactive';
|
|
1014
1081
|
meta.endedAt = Date.now();
|
|
1015
1082
|
await holosphere.putGlobal('federationMeta', meta);
|
|
1016
|
-
console.log(`Updated federation metadata for ${spaceId} and ${partnerSpace}`);
|
|
1017
1083
|
}
|
|
1018
1084
|
} catch (error) {
|
|
1019
|
-
console.warn(`Could not update federation metadata for ${partnerSpace}: ${error.message}`);
|
|
1020
1085
|
}
|
|
1021
1086
|
}
|
|
1022
1087
|
}
|
|
@@ -1024,7 +1089,6 @@ export async function resetFederation(holosphere, spaceId, password = null, opti
|
|
|
1024
1089
|
result.success = true;
|
|
1025
1090
|
return result;
|
|
1026
1091
|
} catch (error) {
|
|
1027
|
-
console.error(`Federation reset failed: ${error.message}`);
|
|
1028
1092
|
return {
|
|
1029
1093
|
...result,
|
|
1030
1094
|
success: false,
|