holosphere 1.1.10 → 1.1.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursor/rules/futura.mdc +55 -0
- package/FEDERATION.md +17 -17
- package/compute.js +289 -0
- package/content.js +946 -0
- package/examples/federation.js +98 -90
- package/examples/hologram-updates-example.js +106 -0
- package/examples/{references.js → holograms.js} +49 -51
- package/federation.js +427 -245
- package/global.js +725 -0
- package/hologram.js +156 -0
- package/holosphere.d.ts +109 -7
- package/holosphere.js +172 -1565
- package/node.js +240 -0
- package/package.json +2 -5
- package/schema.js +132 -0
- package/test/auth.test.js +55 -37
- package/test/delete.test.js +15 -12
- package/test/federation.test.js +179 -0
- package/test/hologram-deletion.test.js +197 -0
- package/test/hologram-updates-return.test.js +166 -0
- package/test/hologram-updates.test.js +143 -0
- package/test/hologram.test.js +316 -0
- package/test/meta-strip.test.js +159 -0
- package/test/parent-propagation.test.js +138 -0
- package/test/subscription.test.js +105 -70
- package/utils.js +290 -0
- package/test/reference.test.js +0 -211
package/federation.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Provides methods for creating, managing, and using federated spaces
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import * as h3 from 'h3-js';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Creates a federation relationship between two spaces
|
|
@@ -14,10 +15,12 @@
|
|
|
14
15
|
* @param {string} [password1] - Optional password for the first space
|
|
15
16
|
* @param {string} [password2] - Optional password for the second space
|
|
16
17
|
* @param {boolean} [bidirectional=true] - Whether to set up bidirectional notifications (default: true)
|
|
18
|
+
* @param {object} [lensConfig] - Optional lens-specific configuration
|
|
19
|
+
* @param {string[]} [lensConfig.federate] - List of lenses to federate (default: all)
|
|
20
|
+
* @param {string[]} [lensConfig.notify] - List of lenses to notify (default: all)
|
|
17
21
|
* @returns {Promise<boolean>} - True if federation was created successfully
|
|
18
22
|
*/
|
|
19
|
-
export async function federate(holosphere, spaceId1, spaceId2, password1 = null, password2 = null, bidirectional = true) {
|
|
20
|
-
console.log('FEDERATING', spaceId1, spaceId2, password1, password2, bidirectional)
|
|
23
|
+
export async function federate(holosphere, spaceId1, spaceId2, password1 = null, password2 = null, bidirectional = true, lensConfig = {}) {
|
|
21
24
|
if (!spaceId1 || !spaceId2) {
|
|
22
25
|
throw new Error('federate: Missing required space IDs');
|
|
23
26
|
}
|
|
@@ -27,40 +30,59 @@ export async function federate(holosphere, spaceId1, spaceId2, password1 = null,
|
|
|
27
30
|
throw new Error('Cannot federate a space with itself');
|
|
28
31
|
}
|
|
29
32
|
|
|
33
|
+
// Validate lens configuration
|
|
34
|
+
const { federate = [], notify = [] } = lensConfig;
|
|
35
|
+
if (!Array.isArray(federate) || !Array.isArray(notify)) {
|
|
36
|
+
throw new Error('federate: lensConfig.federate and lensConfig.notify must be arrays');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Use the provided lens configurations directly
|
|
40
|
+
const federateLenses = federate;
|
|
41
|
+
const notifyLenses = notify;
|
|
42
|
+
|
|
30
43
|
try {
|
|
31
44
|
// Get or create federation info for first space (A)
|
|
32
|
-
let fedInfo1
|
|
45
|
+
let fedInfo1 = null;
|
|
33
46
|
|
|
34
47
|
try {
|
|
35
48
|
fedInfo1 = await holosphere.getGlobal('federation', spaceId1, password1);
|
|
36
49
|
} 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
50
|
}
|
|
51
|
+
|
|
41
52
|
if (fedInfo1 == null) {
|
|
42
53
|
fedInfo1 = {
|
|
43
54
|
id: spaceId1,
|
|
44
55
|
name: spaceId1,
|
|
45
56
|
federation: [],
|
|
46
57
|
notify: [],
|
|
58
|
+
lensConfig: {}, // New field for lens-specific settings
|
|
47
59
|
timestamp: Date.now()
|
|
48
60
|
};
|
|
49
61
|
}
|
|
50
|
-
|
|
51
62
|
|
|
52
|
-
// Ensure arrays exist
|
|
63
|
+
// Ensure arrays and lensConfig exist
|
|
53
64
|
if (!fedInfo1.federation) fedInfo1.federation = [];
|
|
54
65
|
if (!fedInfo1.notify) fedInfo1.notify = [];
|
|
66
|
+
if (!fedInfo1.lensConfig) fedInfo1.lensConfig = {};
|
|
55
67
|
|
|
56
|
-
// Add space2 to space1's federation
|
|
68
|
+
// Add space2 to space1's federation list if not already present
|
|
57
69
|
if (!fedInfo1.federation.includes(spaceId2)) {
|
|
58
70
|
fedInfo1.federation.push(spaceId2);
|
|
59
71
|
}
|
|
60
|
-
|
|
61
|
-
//
|
|
62
|
-
|
|
63
|
-
|
|
72
|
+
|
|
73
|
+
// Add space2 to space1's notify list if not already present
|
|
74
|
+
if (!fedInfo1.notify.includes(spaceId2)) {
|
|
75
|
+
fedInfo1.notify.push(spaceId2);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Store lens configuration for space2
|
|
79
|
+
const newLensConfigsForSpace1 = { ...(fedInfo1.lensConfig || {}) }; // Shallow copy existing lensConfigs for space1
|
|
80
|
+
newLensConfigsForSpace1[spaceId2] = { // Add/update config for the target spaceId2
|
|
81
|
+
federate: [...federateLenses], // federateLenses & notifyLenses are from the main lensConfig parameter
|
|
82
|
+
notify: [...notifyLenses],
|
|
83
|
+
timestamp: Date.now()
|
|
84
|
+
};
|
|
85
|
+
fedInfo1.lensConfig = newLensConfigsForSpace1; // Assign the new/modified object back to fedInfo1.lensConfig
|
|
64
86
|
|
|
65
87
|
// Update timestamp
|
|
66
88
|
fedInfo1.timestamp = Date.now();
|
|
@@ -68,42 +90,50 @@ export async function federate(holosphere, spaceId1, spaceId2, password1 = null,
|
|
|
68
90
|
// Save updated federation info for space1
|
|
69
91
|
try {
|
|
70
92
|
await holosphere.putGlobal('federation', fedInfo1, password1);
|
|
71
|
-
console.log(`Updated federation info for ${spaceId1}`);
|
|
72
93
|
} catch (error) {
|
|
73
|
-
console.warn(`Could not update federation info for ${spaceId1}: ${error.message}`);
|
|
74
94
|
throw new Error(`Failed to create federation: ${error.message}`);
|
|
75
95
|
}
|
|
76
|
-
|
|
96
|
+
|
|
77
97
|
// If bidirectional is true, handle space2 (B) as well
|
|
78
|
-
//if (bidirectional && password2) {
|
|
79
98
|
{
|
|
80
99
|
let fedInfo2 = null;
|
|
81
100
|
try {
|
|
82
101
|
fedInfo2 = await holosphere.getGlobal('federation', spaceId2, password2);
|
|
83
102
|
} 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
103
|
}
|
|
104
|
+
|
|
88
105
|
if (fedInfo2 == null) {
|
|
89
106
|
fedInfo2 = {
|
|
90
107
|
id: spaceId2,
|
|
91
108
|
name: spaceId2,
|
|
92
109
|
federation: [],
|
|
93
110
|
notify: [],
|
|
111
|
+
lensConfig: {}, // New field for lens-specific settings
|
|
94
112
|
timestamp: Date.now()
|
|
95
113
|
};
|
|
96
114
|
}
|
|
97
|
-
|
|
98
|
-
//
|
|
99
|
-
|
|
115
|
+
|
|
116
|
+
// Ensure arrays and lensConfig exist
|
|
117
|
+
if (!fedInfo2.federation) fedInfo2.federation = [];
|
|
100
118
|
if (!fedInfo2.notify) fedInfo2.notify = [];
|
|
119
|
+
if (!fedInfo2.lensConfig) fedInfo2.lensConfig = {};
|
|
120
|
+
|
|
121
|
+
// Add space1 to space2's federation list if bidirectional
|
|
122
|
+
if (bidirectional && !fedInfo2.federation.includes(spaceId1)) {
|
|
123
|
+
fedInfo2.federation.push(spaceId1);
|
|
124
|
+
}
|
|
101
125
|
|
|
102
|
-
// Add space1 to space2's
|
|
126
|
+
// Add space1 to space2's notify list if not already present
|
|
103
127
|
if (!fedInfo2.notify.includes(spaceId1)) {
|
|
104
128
|
fedInfo2.notify.push(spaceId1);
|
|
105
129
|
}
|
|
106
|
-
|
|
130
|
+
|
|
131
|
+
// Store lens configuration for space1
|
|
132
|
+
fedInfo2.lensConfig[spaceId1] = {
|
|
133
|
+
federate: bidirectional ? [...federateLenses] : [], // Create a copy of the array
|
|
134
|
+
notify: bidirectional ? [...notifyLenses] : [], // Create a copy of the array
|
|
135
|
+
timestamp: Date.now()
|
|
136
|
+
};
|
|
107
137
|
|
|
108
138
|
// Update timestamp
|
|
109
139
|
fedInfo2.timestamp = Date.now();
|
|
@@ -111,10 +141,7 @@ export async function federate(holosphere, spaceId1, spaceId2, password1 = null,
|
|
|
111
141
|
// Save updated federation info for space2
|
|
112
142
|
try {
|
|
113
143
|
await holosphere.putGlobal('federation', fedInfo2, password2);
|
|
114
|
-
console.log(`Updated federation info for ${spaceId2}`);
|
|
115
144
|
} 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
145
|
}
|
|
119
146
|
}
|
|
120
147
|
|
|
@@ -125,19 +152,20 @@ export async function federate(holosphere, spaceId1, spaceId2, password1 = null,
|
|
|
125
152
|
space2: spaceId2,
|
|
126
153
|
created: Date.now(),
|
|
127
154
|
status: 'active',
|
|
128
|
-
bidirectional: bidirectional
|
|
155
|
+
bidirectional: bidirectional,
|
|
156
|
+
lensConfig: {
|
|
157
|
+
federate: [...federateLenses], // Create a copy of the array
|
|
158
|
+
notify: [...notifyLenses] // Create a copy of the array
|
|
159
|
+
}
|
|
129
160
|
};
|
|
130
|
-
|
|
161
|
+
|
|
131
162
|
try {
|
|
132
163
|
await holosphere.putGlobal('federationMeta', federationMeta);
|
|
133
|
-
console.log(`Created federation metadata for ${spaceId1} and ${spaceId2}`);
|
|
134
164
|
} catch (error) {
|
|
135
|
-
console.warn(`Could not create federation metadata: ${error.message}`);
|
|
136
165
|
}
|
|
137
166
|
|
|
138
167
|
return true;
|
|
139
168
|
} catch (error) {
|
|
140
|
-
console.error(`Federation creation failed: ${error.message}`);
|
|
141
169
|
throw error;
|
|
142
170
|
}
|
|
143
171
|
}
|
|
@@ -203,7 +231,6 @@ export async function subscribeFederation(holosphere, spaceId, password = null,
|
|
|
203
231
|
// Execute callback with the data
|
|
204
232
|
await callback(data, federatedSpace, lens);
|
|
205
233
|
} catch (error) {
|
|
206
|
-
console.warn('Federation notification error:', error);
|
|
207
234
|
}
|
|
208
235
|
});
|
|
209
236
|
|
|
@@ -211,7 +238,6 @@ export async function subscribeFederation(holosphere, spaceId, password = null,
|
|
|
211
238
|
subscriptions.push(sub);
|
|
212
239
|
}
|
|
213
240
|
} catch (error) {
|
|
214
|
-
console.warn(`Error creating subscription for ${federatedSpace}/${lens}:`, error);
|
|
215
241
|
}
|
|
216
242
|
}
|
|
217
243
|
}
|
|
@@ -226,7 +252,6 @@ export async function subscribeFederation(holosphere, spaceId, password = null,
|
|
|
226
252
|
sub.unsubscribe();
|
|
227
253
|
}
|
|
228
254
|
} catch (error) {
|
|
229
|
-
console.warn('Error unsubscribing:', error);
|
|
230
255
|
}
|
|
231
256
|
});
|
|
232
257
|
// Clear the subscriptions array
|
|
@@ -252,6 +277,35 @@ export async function getFederation(holosphere, spaceId, password = null) {
|
|
|
252
277
|
return await holosphere.getGlobal('federation', spaceId, password);
|
|
253
278
|
}
|
|
254
279
|
|
|
280
|
+
/**
|
|
281
|
+
* Retrieves the lens-specific configuration for a federation link between two spaces.
|
|
282
|
+
* @param {object} holosphere - The HoloSphere instance
|
|
283
|
+
* @param {string} spaceId - The ID of the source space.
|
|
284
|
+
* @param {string} targetSpaceId - The ID of the target space in the federation link.
|
|
285
|
+
* @param {string} [password] - Optional password for the source space.
|
|
286
|
+
* @returns {Promise<object|null>} - An object with 'federate' and 'notify' arrays, or null if not found.
|
|
287
|
+
*/
|
|
288
|
+
export async function getFederatedConfig(holosphere, spaceId, targetSpaceId, password = null) {
|
|
289
|
+
if (!holosphere || !spaceId || !targetSpaceId) {
|
|
290
|
+
throw new Error('getFederatedConfig: Missing required parameters');
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
try {
|
|
294
|
+
const fedInfo = await getFederation(holosphere, spaceId, password);
|
|
295
|
+
|
|
296
|
+
if (fedInfo && fedInfo.lensConfig && fedInfo.lensConfig[targetSpaceId]) {
|
|
297
|
+
return {
|
|
298
|
+
federate: fedInfo.lensConfig[targetSpaceId].federate || [],
|
|
299
|
+
notify: fedInfo.lensConfig[targetSpaceId].notify || []
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
return null; // Or return an empty config: { federate: [], notify: [] }
|
|
303
|
+
} catch (error) {
|
|
304
|
+
console.error(`Error getting federated config for ${spaceId} -> ${targetSpaceId}: ${error.message}`);
|
|
305
|
+
throw error;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
255
309
|
/**
|
|
256
310
|
* Removes a federation relationship between spaces
|
|
257
311
|
* @param {object} holosphere - The HoloSphere instance
|
|
@@ -272,43 +326,59 @@ export async function unfederate(holosphere, spaceId1, spaceId2, password1 = nul
|
|
|
272
326
|
try {
|
|
273
327
|
fedInfo1 = await holosphere.getGlobal('federation', spaceId1, password1);
|
|
274
328
|
} catch (error) {
|
|
275
|
-
console.
|
|
329
|
+
console.error(`Error getting fedInfo1 for ${spaceId1} during unfederate: ${error.message}`);
|
|
330
|
+
// If we can't get fedInfo1, we can't modify it. Decide if this is a critical failure.
|
|
331
|
+
// For now, we'll let it proceed to attempt metadata cleanup, but a throw here might be valid.
|
|
276
332
|
}
|
|
277
333
|
|
|
278
|
-
if (!fedInfo1
|
|
279
|
-
|
|
280
|
-
|
|
334
|
+
if (!fedInfo1) {
|
|
335
|
+
// If fedInfo1 doesn't exist, log and proceed to metadata cleanup.
|
|
336
|
+
console.warn(`No federation info found for ${spaceId1}. Skipping its update.`);
|
|
281
337
|
} else {
|
|
282
|
-
//
|
|
338
|
+
// Ensure arrays exist
|
|
339
|
+
if (!fedInfo1.federation) fedInfo1.federation = [];
|
|
340
|
+
if (!fedInfo1.notify) fedInfo1.notify = [];
|
|
341
|
+
|
|
342
|
+
// Update first space federation info - remove from both federation and notify arrays
|
|
343
|
+
const originalFederationLength = fedInfo1.federation.length;
|
|
344
|
+
const originalNotifyLength = fedInfo1.notify.length;
|
|
345
|
+
|
|
283
346
|
fedInfo1.federation = fedInfo1.federation.filter(id => id !== spaceId2);
|
|
347
|
+
fedInfo1.notify = fedInfo1.notify.filter(id => id !== spaceId2);
|
|
284
348
|
fedInfo1.timestamp = Date.now();
|
|
285
349
|
|
|
350
|
+
console.log(`Unfederate: Removed ${spaceId2} from ${spaceId1}: federation ${originalFederationLength} -> ${fedInfo1.federation.length}, notify ${originalNotifyLength} -> ${fedInfo1.notify.length}`);
|
|
351
|
+
|
|
286
352
|
try {
|
|
287
353
|
await holosphere.putGlobal('federation', fedInfo1, password1);
|
|
288
|
-
console.log(`Updated federation info for ${spaceId1}`);
|
|
289
354
|
} catch (error) {
|
|
290
|
-
console.
|
|
355
|
+
console.error(`Failed to update fedInfo1 for ${spaceId1} during unfederate: ${error.message}`);
|
|
356
|
+
throw error; // RE-THROW to signal failure
|
|
291
357
|
}
|
|
292
358
|
}
|
|
293
359
|
|
|
294
|
-
// Update second space federation info
|
|
295
|
-
|
|
360
|
+
// Update second space federation info (remove spaceId1 from spaceId2's notify list)
|
|
361
|
+
// This part is usually for full bidirectional unfederation cleanup.
|
|
362
|
+
// The original code only did this if password2 was provided.
|
|
363
|
+
if (password2) { // Retaining original condition for this block
|
|
296
364
|
let fedInfo2 = null;
|
|
297
365
|
try {
|
|
298
366
|
fedInfo2 = await holosphere.getGlobal('federation', spaceId2, password2);
|
|
299
367
|
} catch (error) {
|
|
300
|
-
console.
|
|
368
|
+
console.error(`Error getting fedInfo2 for ${spaceId2} during unfederate: ${error.message}`);
|
|
301
369
|
}
|
|
302
370
|
|
|
303
|
-
if (fedInfo2
|
|
371
|
+
if (!fedInfo2 || !fedInfo2.notify) {
|
|
372
|
+
console.warn(`No notify array found for ${spaceId2} or fedInfo2 is null. Skipping its update.`);
|
|
373
|
+
} else {
|
|
304
374
|
fedInfo2.notify = fedInfo2.notify.filter(id => id !== spaceId1);
|
|
305
375
|
fedInfo2.timestamp = Date.now();
|
|
306
376
|
|
|
307
377
|
try {
|
|
308
378
|
await holosphere.putGlobal('federation', fedInfo2, password2);
|
|
309
|
-
console.log(`Updated federation info for ${spaceId2}`);
|
|
310
379
|
} catch (error) {
|
|
311
|
-
console.
|
|
380
|
+
console.error(`Failed to update fedInfo2 for ${spaceId2} during unfederate: ${error.message}`);
|
|
381
|
+
throw error; // RE-THROW to signal failure
|
|
312
382
|
}
|
|
313
383
|
}
|
|
314
384
|
}
|
|
@@ -324,17 +394,17 @@ export async function unfederate(holosphere, spaceId1, spaceId2, password1 = nul
|
|
|
324
394
|
if (meta) {
|
|
325
395
|
meta.status = 'inactive';
|
|
326
396
|
meta.endedAt = Date.now();
|
|
327
|
-
await holosphere.putGlobal('federationMeta', meta);
|
|
328
|
-
console.log(`Updated federation metadata for ${spaceId1} and ${spaceId2}`);
|
|
397
|
+
await holosphere.putGlobal('federationMeta', meta); // Not re-throwing here as it's metadata cleanup
|
|
329
398
|
}
|
|
330
399
|
} catch (error) {
|
|
331
|
-
console.warn(`
|
|
400
|
+
console.warn(`Failed to update federationMeta during unfederate: ${error.message}`);
|
|
332
401
|
}
|
|
333
402
|
|
|
334
403
|
return true;
|
|
335
404
|
} catch (error) {
|
|
336
|
-
|
|
337
|
-
|
|
405
|
+
// This will catch errors re-thrown from putGlobal or from getGlobal if they occur before specific catches.
|
|
406
|
+
console.error(`Critical error during unfederate operation for ${spaceId1}-${spaceId2}: ${error.message}`);
|
|
407
|
+
throw error; // Ensure the main operation failure is propagated
|
|
338
408
|
}
|
|
339
409
|
}
|
|
340
410
|
|
|
@@ -373,42 +443,42 @@ export async function removeNotify(holosphere, spaceId1, spaceId2, password1 = n
|
|
|
373
443
|
|
|
374
444
|
// Save updated federation info
|
|
375
445
|
await holosphere.putGlobal('federation', fedInfo, password1);
|
|
376
|
-
console.log(`Removed ${spaceId2} from ${spaceId1}'s notify list`);
|
|
377
446
|
return true;
|
|
378
447
|
} else {
|
|
379
|
-
console.log(`${spaceId2} not found in ${spaceId1}'s notify list`);
|
|
380
448
|
return false;
|
|
381
449
|
}
|
|
382
450
|
} catch (error) {
|
|
383
|
-
console.error(`Remove notification failed: ${error.message}`);
|
|
384
451
|
throw error;
|
|
385
452
|
}
|
|
386
453
|
}
|
|
387
454
|
|
|
388
455
|
/**
|
|
389
|
-
* Get and combine data from local and federated sources
|
|
456
|
+
* Get and combine data from local and federated sources.
|
|
457
|
+
* If `options.queryIds` is provided, fetches only those specific IDs using `get()`.
|
|
458
|
+
* Otherwise, falls back to fetching all data using `getAll()` (potentially inefficient).
|
|
459
|
+
*
|
|
390
460
|
* @param {HoloSphere} holosphere The HoloSphere instance
|
|
391
|
-
* @param {string} holon The local holon name
|
|
461
|
+
* @param {string} holon The local holon name (used as the space ID for federation info)
|
|
392
462
|
* @param {string} lens The lens to query
|
|
393
463
|
* @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.
|
|
464
|
+
* @param {string[]} [options.queryIds] Optional array of specific item IDs to fetch.
|
|
465
|
+
* @param {boolean} [options.aggregate=false] Whether to aggregate results by ID
|
|
466
|
+
* @param {string} [options.idField='id'] The field to use as ID
|
|
467
|
+
* @param {string[]} [options.sumFields=[]] Fields to sum during aggregation
|
|
468
|
+
* @param {string[]} [options.concatArrays=[]] Array fields to concatenate during aggregation
|
|
469
|
+
* @param {boolean} [options.removeDuplicates=true] Whether to remove duplicates in concatenated arrays
|
|
470
|
+
* @param {Function} [options.mergeStrategy=null] Custom merge function for aggregation
|
|
471
|
+
* @param {boolean} [options.includeLocal=true] Whether to include local data
|
|
472
|
+
* @param {boolean} [options.includeFederated=true] Whether to include federated data
|
|
473
|
+
* @param {boolean} [options.resolveReferences=true] Whether to resolve federation references
|
|
474
|
+
* @param {number} [options.maxFederatedSpaces=-1] Maximum number of federated spaces to query (-1 for all)
|
|
475
|
+
* @param {number} [options.timeout=10000] Timeout in milliseconds for federated queries
|
|
405
476
|
* @returns {Promise<Array>} Combined array of local and federated data
|
|
406
477
|
*/
|
|
407
478
|
export async function getFederated(holosphere, holon, lens, options = {}) {
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
// Set default options
|
|
479
|
+
// Set default options and extract queryIds
|
|
411
480
|
const {
|
|
481
|
+
queryIds = null, // New option
|
|
412
482
|
aggregate = false,
|
|
413
483
|
idField = 'id',
|
|
414
484
|
sumFields = [],
|
|
@@ -417,91 +487,85 @@ export async function getFederated(holosphere, holon, lens, options = {}) {
|
|
|
417
487
|
mergeStrategy = null,
|
|
418
488
|
includeLocal = true,
|
|
419
489
|
includeFederated = true,
|
|
420
|
-
resolveReferences = true,
|
|
490
|
+
resolveReferences = true,
|
|
421
491
|
maxFederatedSpaces = -1,
|
|
422
492
|
timeout = 10000
|
|
423
493
|
} = options;
|
|
424
494
|
|
|
425
495
|
console.log(`resolveReferences option: ${resolveReferences}`);
|
|
426
|
-
|
|
496
|
+
console.log(`Querying specific IDs:`, queryIds ? queryIds.join(', ') : 'No (fetching all)');
|
|
497
|
+
|
|
427
498
|
// Validate required parameters
|
|
428
499
|
if (!holosphere || !holon || !lens) {
|
|
429
500
|
throw new Error('Missing required parameters: holosphere, holon, and lens are required');
|
|
430
501
|
}
|
|
431
502
|
|
|
432
|
-
// Get federation info for current space
|
|
433
|
-
// Use holon as the space ID
|
|
503
|
+
// Get federation info for current space (using holon as spaceId)
|
|
434
504
|
const spaceId = holon;
|
|
435
505
|
const fedInfo = await getFederation(holosphere, spaceId);
|
|
436
506
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
const
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
507
|
+
// Initialize result array and track processed IDs to avoid duplicates/redundant fetches
|
|
508
|
+
const fetchedItems = new Map(); // Use Map to store fetched items by ID
|
|
509
|
+
const processedIds = new Set(); // Track IDs added to the final result
|
|
510
|
+
|
|
511
|
+
const fetchPromises = [];
|
|
512
|
+
|
|
513
|
+
// Determine list of spaces to query (local + federated)
|
|
514
|
+
let spacesToQuery = [];
|
|
515
|
+
if (includeLocal) {
|
|
516
|
+
spacesToQuery.push(holon); // Add local holon first
|
|
517
|
+
}
|
|
445
518
|
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
519
|
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
|
-
}
|
|
520
|
+
spacesToQuery = spacesToQuery.concat(federatedSpaces);
|
|
484
521
|
}
|
|
485
|
-
|
|
486
|
-
//
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
522
|
+
|
|
523
|
+
// Fetch data from all relevant spaces
|
|
524
|
+
for (const currentSpace of spacesToQuery) {
|
|
525
|
+
if (queryIds && Array.isArray(queryIds)) {
|
|
526
|
+
// --- Fetch specific IDs using holosphere.get ---
|
|
527
|
+
console.log(`Fetching specific IDs from ${currentSpace}: ${queryIds.join(', ')}`);
|
|
528
|
+
for (const itemId of queryIds) {
|
|
529
|
+
if (fetchedItems.has(itemId)) continue; // Skip if already fetched
|
|
530
|
+
fetchPromises.push(
|
|
531
|
+
holosphere.get(currentSpace, lens, itemId)
|
|
532
|
+
.then(item => {
|
|
533
|
+
if (item) {
|
|
534
|
+
fetchedItems.set(itemId, item);
|
|
535
|
+
}
|
|
536
|
+
})
|
|
537
|
+
.catch(err => console.warn(`Error fetching item ${itemId} from ${currentSpace}: ${err.message}`))
|
|
538
|
+
);
|
|
498
539
|
}
|
|
540
|
+
} else {
|
|
541
|
+
// --- Fetch all data using holosphere.getAll (Fallback - inefficient) ---
|
|
542
|
+
if(currentSpace === holon && includeLocal) { // Only warn once for local
|
|
543
|
+
console.warn(`getFederated: No queryIds provided. Falling back to fetching ALL items from ${currentSpace} using getAll. This can be inefficient.`);
|
|
544
|
+
}
|
|
545
|
+
console.log(`Fetching ALL items from ${currentSpace}`);
|
|
546
|
+
fetchPromises.push(
|
|
547
|
+
holosphere.getAll(currentSpace, lens)
|
|
548
|
+
.then(items => {
|
|
549
|
+
for (const item of items) {
|
|
550
|
+
if (item && item[idField] && !fetchedItems.has(item[idField])) {
|
|
551
|
+
fetchedItems.set(item[idField], item);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
})
|
|
555
|
+
.catch(err => console.warn(`Error fetching all items from ${currentSpace}: ${err.message}`))
|
|
556
|
+
);
|
|
499
557
|
}
|
|
500
558
|
}
|
|
501
|
-
|
|
559
|
+
|
|
560
|
+
// Wait for all fetches to complete
|
|
561
|
+
await Promise.all(fetchPromises);
|
|
562
|
+
|
|
563
|
+
// Convert Map values to array for processing
|
|
564
|
+
const result = Array.from(fetchedItems.values());
|
|
565
|
+
|
|
502
566
|
// Now resolve references if needed
|
|
503
|
-
if (resolveReferences) {
|
|
504
|
-
console.log(`Resolving references for ${result.length} items`);
|
|
567
|
+
if (resolveReferences && result.length > 0) {
|
|
568
|
+
console.log(`Resolving references for ${result.length} fetched items`);
|
|
505
569
|
|
|
506
570
|
for (let i = 0; i < result.length; i++) {
|
|
507
571
|
const item = result[i];
|
|
@@ -542,9 +606,7 @@ export async function getFederated(holosphere, holon, lens, options = {}) {
|
|
|
542
606
|
timestamp: Date.now()
|
|
543
607
|
}
|
|
544
608
|
};
|
|
545
|
-
console.log(`Reference resolved successfully via soul path, processed item:`, JSON.stringify(result[i]));
|
|
546
609
|
} else {
|
|
547
|
-
console.warn(`Could not resolve reference: original data not found at extracted path`);
|
|
548
610
|
// Instead of leaving the original reference, create an error object
|
|
549
611
|
result[i] = {
|
|
550
612
|
id: item.id,
|
|
@@ -572,7 +634,6 @@ export async function getFederated(holosphere, holon, lens, options = {}) {
|
|
|
572
634
|
};
|
|
573
635
|
}
|
|
574
636
|
} catch (refError) {
|
|
575
|
-
console.warn(`Error resolving reference by soul in getFederated: ${refError.message}`);
|
|
576
637
|
// Instead of leaving the original reference, create an error object
|
|
577
638
|
result[i] = {
|
|
578
639
|
id: item.id,
|
|
@@ -615,12 +676,10 @@ export async function getFederated(holosphere, holon, lens, options = {}) {
|
|
|
615
676
|
timestamp: Date.now()
|
|
616
677
|
}
|
|
617
678
|
};
|
|
618
|
-
console.log(`Legacy reference resolved successfully, processed item:`, JSON.stringify(result[i]));
|
|
619
679
|
} else {
|
|
620
680
|
console.warn(`Could not resolve legacy reference: original data not found`);
|
|
621
681
|
}
|
|
622
682
|
} catch (refError) {
|
|
623
|
-
console.warn(`Error resolving legacy reference in getFederated: ${refError.message}`);
|
|
624
683
|
}
|
|
625
684
|
}
|
|
626
685
|
}
|
|
@@ -692,9 +751,11 @@ export async function getFederated(holosphere, holon, lens, options = {}) {
|
|
|
692
751
|
* @param {string} lens - The lens identifier
|
|
693
752
|
* @param {object} data - The data to propagate
|
|
694
753
|
* @param {object} [options] - Propagation options
|
|
695
|
-
* @param {boolean} [options.
|
|
754
|
+
* @param {boolean} [options.useHolograms=true] - Use holograms for propagation (default: true)
|
|
696
755
|
* @param {string[]} [options.targetSpaces] - Specific target spaces to propagate to (defaults to all federated spaces)
|
|
697
756
|
* @param {string} [options.password] - Password for accessing the source holon (if needed)
|
|
757
|
+
* @param {boolean} [options.propagateToParents=true] - Whether to automatically propagate to parent hexagons (default: true)
|
|
758
|
+
* @param {number} [options.maxParentLevels=15] - Maximum number of parent levels to propagate to (default: 15)
|
|
698
759
|
* @returns {Promise<object>} - Result with success count and errors
|
|
699
760
|
*/
|
|
700
761
|
export async function propagate(holosphere, holon, lens, data, options = {}) {
|
|
@@ -702,127 +763,254 @@ export async function propagate(holosphere, holon, lens, data, options = {}) {
|
|
|
702
763
|
throw new Error('propagate: Missing required parameters');
|
|
703
764
|
}
|
|
704
765
|
// Default propagation options
|
|
705
|
-
const {
|
|
706
|
-
|
|
707
|
-
targetSpaces = null,
|
|
708
|
-
password = null
|
|
766
|
+
const {
|
|
767
|
+
useHolograms = true,
|
|
768
|
+
targetSpaces = null,
|
|
769
|
+
password = null,
|
|
770
|
+
propagateToParents = true,
|
|
771
|
+
maxParentLevels = 15
|
|
709
772
|
} = options;
|
|
710
773
|
|
|
711
774
|
const result = {
|
|
712
775
|
success: 0,
|
|
713
776
|
errors: 0,
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
777
|
+
skipped: 0,
|
|
778
|
+
messages: [],
|
|
779
|
+
parentPropagation: {
|
|
780
|
+
success: 0,
|
|
781
|
+
errors: 0,
|
|
782
|
+
skipped: 0,
|
|
783
|
+
messages: []
|
|
784
|
+
}
|
|
717
785
|
};
|
|
718
786
|
|
|
719
787
|
try {
|
|
788
|
+
// ================================ FEDERATION PROPAGATION ================================
|
|
789
|
+
|
|
720
790
|
// Get federation info for this holon using getFederation
|
|
721
791
|
const fedInfo = await getFederation(holosphere, holon, password);
|
|
722
792
|
|
|
723
|
-
//
|
|
724
|
-
if (
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
793
|
+
// Only perform federation propagation if there's valid federation info
|
|
794
|
+
if (fedInfo && fedInfo.federation && fedInfo.federation.length > 0 && fedInfo.notify && fedInfo.notify.length > 0) {
|
|
795
|
+
// Filter federation spaces to those in notify list
|
|
796
|
+
let spaces = fedInfo.notify;
|
|
797
|
+
|
|
798
|
+
// Further filter by targetSpaces if provided
|
|
799
|
+
if (targetSpaces && Array.isArray(targetSpaces) && targetSpaces.length > 0) {
|
|
800
|
+
spaces = spaces.filter(space => targetSpaces.includes(space));
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
if (spaces.length > 0) {
|
|
804
|
+
// Filter spaces based on lens configuration
|
|
805
|
+
spaces = spaces.filter(targetSpace => {
|
|
806
|
+
const spaceConfig = fedInfo.lensConfig?.[targetSpace];
|
|
807
|
+
if (!spaceConfig) {
|
|
808
|
+
result.messages.push(`No lens configuration for target space ${targetSpace}. Skipping propagation of lens '${lens}'.`);
|
|
809
|
+
result.skipped++;
|
|
810
|
+
return false;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
// Ensure .federate is an array before calling .includes
|
|
814
|
+
const federateLenses = Array.isArray(spaceConfig.federate) ? spaceConfig.federate : [];
|
|
815
|
+
|
|
816
|
+
const shouldFederate = federateLenses.includes('*') || federateLenses.includes(lens);
|
|
817
|
+
|
|
818
|
+
// Propagation now only depends on the 'federate' list configuration for the lens
|
|
819
|
+
const shouldPropagate = shouldFederate;
|
|
820
|
+
|
|
821
|
+
if (!shouldPropagate) {
|
|
822
|
+
result.messages.push(`Propagation of lens '${lens}' to target space ${targetSpace} skipped: lens not in 'federate' configuration.`);
|
|
823
|
+
result.skipped++;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
return shouldPropagate;
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
if (spaces.length > 0) {
|
|
830
|
+
// Check if data is already a hologram
|
|
831
|
+
const isAlreadyHologram = holosphere.isHologram(data);
|
|
832
|
+
|
|
833
|
+
// For each target space, propagate the data
|
|
834
|
+
const propagatePromises = spaces.map(async (targetSpace) => {
|
|
835
|
+
try {
|
|
836
|
+
let payloadToPut;
|
|
837
|
+
const federationMeta = {
|
|
838
|
+
origin: holon, // The space from which this data is being propagated
|
|
839
|
+
sourceLens: lens, // The lens from which this data is being propagated
|
|
840
|
+
propagatedAt: Date.now(),
|
|
841
|
+
originalId: data.id
|
|
842
|
+
};
|
|
843
|
+
|
|
844
|
+
if (useHolograms && !isAlreadyHologram) {
|
|
845
|
+
// Create a new hologram referencing the original data
|
|
846
|
+
const newHologram = holosphere.createHologram(holon, lens, data);
|
|
847
|
+
payloadToPut = {
|
|
848
|
+
...newHologram, // This will be { id: data.id, soul: 'path/to/original' }
|
|
849
|
+
_federation: federationMeta
|
|
850
|
+
};
|
|
851
|
+
} else {
|
|
852
|
+
// Propagate existing data (could be a full object or an existing hologram)
|
|
853
|
+
// Make a shallow copy and update/add _federation metadata
|
|
854
|
+
payloadToPut = {
|
|
855
|
+
...data,
|
|
856
|
+
_federation: {
|
|
857
|
+
...(data._federation || {}), // Preserve existing _federation fields if any
|
|
858
|
+
...federationMeta // Add/overwrite with current propagation info
|
|
859
|
+
}
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
// Store in the target space with redirection disabled and no further auto-propagation
|
|
864
|
+
await holosphere.put(targetSpace, lens, payloadToPut, null, {
|
|
865
|
+
disableHologramRedirection: true,
|
|
866
|
+
autoPropagate: false
|
|
867
|
+
});
|
|
868
|
+
|
|
869
|
+
result.success++;
|
|
870
|
+
return true;
|
|
871
|
+
} catch (error) {
|
|
872
|
+
result.errors++;
|
|
873
|
+
result.messages.push(`Error propagating ${data.id} to ${targetSpace}: ${error.message}`);
|
|
874
|
+
return false;
|
|
875
|
+
}
|
|
876
|
+
});
|
|
877
|
+
|
|
878
|
+
await Promise.all(propagatePromises);
|
|
879
|
+
} else {
|
|
880
|
+
result.messages.push('No valid target spaces for federation propagation after lens filtering.');
|
|
881
|
+
}
|
|
882
|
+
} else {
|
|
883
|
+
result.messages.push('No valid target spaces found for federation propagation.');
|
|
884
|
+
}
|
|
885
|
+
} else {
|
|
886
|
+
result.messages.push(`No federation found for ${holon} or no notification targets available.`);
|
|
752
887
|
}
|
|
753
888
|
|
|
754
|
-
//
|
|
755
|
-
const isAlreadyReference = holosphere.isReference(data);
|
|
889
|
+
// ================================ PARENT PROPAGATION ================================
|
|
756
890
|
|
|
757
|
-
//
|
|
758
|
-
|
|
891
|
+
// Check if we should propagate to parent hexagons
|
|
892
|
+
if (propagateToParents) {
|
|
893
|
+
console.log(`[Federation] Starting parent propagation for holon: ${holon}`);
|
|
759
894
|
try {
|
|
760
|
-
//
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
895
|
+
// Check if the holon is a valid H3 hexagon
|
|
896
|
+
let holonResolution;
|
|
897
|
+
try {
|
|
898
|
+
holonResolution = h3.getResolution(holon);
|
|
899
|
+
console.log(`[Federation] Holon ${holon} is valid H3 hexagon with resolution: ${holonResolution}`);
|
|
900
|
+
} catch (error) {
|
|
901
|
+
// Not a valid H3 hexagon, skip parent propagation
|
|
902
|
+
console.log(`[Federation] Holon ${holon} is not a valid H3 hexagon: ${error.message}`);
|
|
903
|
+
result.parentPropagation.messages.push(`Holon ${holon} is not a valid H3 hexagon. Skipping parent propagation.`);
|
|
904
|
+
result.parentPropagation.skipped++;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
if (holonResolution !== undefined) {
|
|
908
|
+
// Get all parent hexagons up to the specified max levels
|
|
909
|
+
const parentHexagons = [];
|
|
910
|
+
let currentHolon = holon;
|
|
911
|
+
let currentRes = holonResolution;
|
|
912
|
+
let levelsProcessed = 0;
|
|
773
913
|
|
|
774
|
-
|
|
775
|
-
await holosphere.put(targetSpace, lens, reference, null, { autoPropagate: false });
|
|
914
|
+
console.log(`[Federation] Getting parent hexagons for ${holon} (resolution ${holonResolution}) up to ${maxParentLevels} levels`);
|
|
776
915
|
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
916
|
+
while (currentRes > 0 && levelsProcessed < maxParentLevels) {
|
|
917
|
+
try {
|
|
918
|
+
const parent = h3.cellToParent(currentHolon, currentRes - 1);
|
|
919
|
+
parentHexagons.push(parent);
|
|
920
|
+
console.log(`[Federation] Found parent hexagon: ${parent} (resolution ${currentRes - 1})`);
|
|
921
|
+
currentHolon = parent;
|
|
922
|
+
currentRes--;
|
|
923
|
+
levelsProcessed++;
|
|
924
|
+
} catch (error) {
|
|
925
|
+
console.error(`[Federation] Error getting parent for ${currentHolon}: ${error.message}`);
|
|
926
|
+
result.parentPropagation.messages.push(`Error getting parent for ${currentHolon}: ${error.message}`);
|
|
927
|
+
result.parentPropagation.errors++;
|
|
928
|
+
break;
|
|
789
929
|
}
|
|
790
|
-
}
|
|
930
|
+
}
|
|
791
931
|
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
932
|
+
if (parentHexagons.length > 0) {
|
|
933
|
+
console.log(`[Federation] Found ${parentHexagons.length} parent hexagons to propagate to: ${parentHexagons.join(', ')}`);
|
|
934
|
+
result.parentPropagation.messages.push(`Found ${parentHexagons.length} parent hexagons to propagate to: ${parentHexagons.join(', ')}`);
|
|
935
|
+
|
|
936
|
+
// Check if data is already a hologram (reuse from federation section)
|
|
937
|
+
const isAlreadyHologram = holosphere.isHologram(data);
|
|
938
|
+
console.log(`[Federation] Data is already hologram: ${isAlreadyHologram}`);
|
|
939
|
+
|
|
940
|
+
// Propagate to each parent hexagon
|
|
941
|
+
const parentPropagatePromises = parentHexagons.map(async (parentHexagon) => {
|
|
942
|
+
try {
|
|
943
|
+
console.log(`[Federation] Propagating to parent hexagon: ${parentHexagon}`);
|
|
944
|
+
let payloadToPut;
|
|
945
|
+
const parentFederationMeta = {
|
|
946
|
+
origin: holon, // The original holon from which this data is being propagated
|
|
947
|
+
sourceLens: lens, // The lens from which this data is being propagated
|
|
948
|
+
propagatedAt: Date.now(),
|
|
949
|
+
originalId: data.id,
|
|
950
|
+
propagationType: 'parent', // Indicate this is parent propagation
|
|
951
|
+
parentLevel: holonResolution - h3.getResolution(parentHexagon) // How many levels up
|
|
952
|
+
};
|
|
953
|
+
|
|
954
|
+
if (useHolograms && !isAlreadyHologram) {
|
|
955
|
+
// Create a new hologram referencing the original data
|
|
956
|
+
const newHologram = holosphere.createHologram(holon, lens, data);
|
|
957
|
+
console.log(`[Federation] Created hologram for parent propagation:`, newHologram);
|
|
958
|
+
payloadToPut = {
|
|
959
|
+
...newHologram, // This will be { id: data.id, soul: 'path/to/original' }
|
|
960
|
+
_federation: parentFederationMeta
|
|
961
|
+
};
|
|
962
|
+
} else {
|
|
963
|
+
// Propagate existing data (could be a full object or an existing hologram)
|
|
964
|
+
// Make a shallow copy and update/add _federation metadata
|
|
965
|
+
payloadToPut = {
|
|
966
|
+
...data,
|
|
967
|
+
_federation: {
|
|
968
|
+
...(data._federation || {}), // Preserve existing _federation fields if any
|
|
969
|
+
...parentFederationMeta // Add/overwrite with current propagation info
|
|
970
|
+
}
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
console.log(`[Federation] Storing in parent hexagon ${parentHexagon} with payload:`, payloadToPut);
|
|
975
|
+
|
|
976
|
+
// Store in the parent hexagon with redirection disabled and no further auto-propagation
|
|
977
|
+
await holosphere.put(parentHexagon, lens, payloadToPut, null, {
|
|
978
|
+
disableHologramRedirection: true,
|
|
979
|
+
autoPropagate: false
|
|
980
|
+
});
|
|
981
|
+
|
|
982
|
+
console.log(`[Federation] Successfully propagated to parent hexagon: ${parentHexagon}`);
|
|
983
|
+
result.parentPropagation.success++;
|
|
984
|
+
return true;
|
|
985
|
+
} catch (error) {
|
|
986
|
+
console.error(`[Federation] Error propagating ${data.id} to parent hexagon ${parentHexagon}: ${error.message}`);
|
|
987
|
+
result.parentPropagation.errors++;
|
|
988
|
+
result.parentPropagation.messages.push(`Error propagating ${data.id} to parent hexagon ${parentHexagon}: ${error.message}`);
|
|
989
|
+
return false;
|
|
990
|
+
}
|
|
991
|
+
});
|
|
992
|
+
|
|
993
|
+
await Promise.all(parentPropagatePromises);
|
|
994
|
+
} else {
|
|
995
|
+
console.log(`[Federation] No parent hexagons found for ${holon} (already at resolution 0 or max levels reached)`);
|
|
996
|
+
result.parentPropagation.messages.push(`No parent hexagons found for ${holon} (already at resolution 0 or max levels reached)`);
|
|
997
|
+
result.parentPropagation.skipped++;
|
|
998
|
+
}
|
|
809
999
|
}
|
|
810
1000
|
} catch (error) {
|
|
811
|
-
|
|
812
|
-
result.
|
|
813
|
-
|
|
814
|
-
error: error.message
|
|
815
|
-
});
|
|
816
|
-
return false;
|
|
1001
|
+
console.error(`[Federation] Error during parent propagation: ${error.message}`);
|
|
1002
|
+
result.parentPropagation.errors++;
|
|
1003
|
+
result.parentPropagation.messages.push(`Error during parent propagation: ${error.message}`);
|
|
817
1004
|
}
|
|
818
|
-
}
|
|
1005
|
+
} else {
|
|
1006
|
+
console.log(`[Federation] Parent propagation disabled for holon: ${holon}`);
|
|
1007
|
+
}
|
|
819
1008
|
|
|
820
|
-
|
|
1009
|
+
// ================================ END PARENT PROPAGATION ================================
|
|
821
1010
|
|
|
822
|
-
result.propagated = result.success > 0;
|
|
1011
|
+
result.propagated = result.success > 0 || result.parentPropagation.success > 0;
|
|
823
1012
|
return result;
|
|
824
1013
|
} catch (error) {
|
|
825
|
-
console.error('Error in propagate:', error);
|
|
826
1014
|
return {
|
|
827
1015
|
...result,
|
|
828
1016
|
error: error.message
|
|
@@ -894,7 +1082,6 @@ export async function updateFederatedMessages(holosphere, originalChatId, messag
|
|
|
894
1082
|
try {
|
|
895
1083
|
await updateCallback(msg.chatId, msg.messageId);
|
|
896
1084
|
} catch (error) {
|
|
897
|
-
console.warn(`Failed to update federated message in chat ${msg.chatId}:`, error);
|
|
898
1085
|
}
|
|
899
1086
|
}
|
|
900
1087
|
}
|
|
@@ -981,13 +1168,11 @@ export async function resetFederation(holosphere, spaceId, password = null, opti
|
|
|
981
1168
|
|
|
982
1169
|
// Save partner's updated federation info
|
|
983
1170
|
await holosphere.putGlobal('federation', partnerFedInfo);
|
|
984
|
-
console.log(`Updated federation info for partner ${partnerSpace}`);
|
|
985
1171
|
result.partnersNotified++;
|
|
986
1172
|
return true;
|
|
987
1173
|
}
|
|
988
1174
|
return false;
|
|
989
1175
|
} catch (error) {
|
|
990
|
-
console.warn(`Could not update federation info for partner ${partnerSpace}: ${error.message}`);
|
|
991
1176
|
result.errors.push({
|
|
992
1177
|
partner: partnerSpace,
|
|
993
1178
|
error: error.message
|
|
@@ -1013,10 +1198,8 @@ export async function resetFederation(holosphere, spaceId, password = null, opti
|
|
|
1013
1198
|
meta.status = 'inactive';
|
|
1014
1199
|
meta.endedAt = Date.now();
|
|
1015
1200
|
await holosphere.putGlobal('federationMeta', meta);
|
|
1016
|
-
console.log(`Updated federation metadata for ${spaceId} and ${partnerSpace}`);
|
|
1017
1201
|
}
|
|
1018
1202
|
} catch (error) {
|
|
1019
|
-
console.warn(`Could not update federation metadata for ${partnerSpace}: ${error.message}`);
|
|
1020
1203
|
}
|
|
1021
1204
|
}
|
|
1022
1205
|
}
|
|
@@ -1024,7 +1207,6 @@ export async function resetFederation(holosphere, spaceId, password = null, opti
|
|
|
1024
1207
|
result.success = true;
|
|
1025
1208
|
return result;
|
|
1026
1209
|
} catch (error) {
|
|
1027
|
-
console.error(`Federation reset failed: ${error.message}`);
|
|
1028
1210
|
return {
|
|
1029
1211
|
...result,
|
|
1030
1212
|
success: false,
|