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/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 = null;
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 and notify lists if not already present
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
- // // Always add to notify list for the first space (primary direction)
61
- // if (!fedInfo1.notify.includes(spaceId2)) {
62
- // fedInfo1.notify.push(spaceId2);
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
- // Add nEnsure arrays exist
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 not already present
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.warn(`Could not get federation info for ${spaceId1}: ${error.message}`);
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
- console.warn(`Federation not found for space ${spaceId1}`);
280
- // Continue anyway to clean up any potential metadata
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.warn(`Could not update federation info for ${spaceId1}: ${error.message}`);
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 if password provided
295
- if (password2) {
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.warn(`Could not get federation info for ${spaceId2}: ${error.message}`);
357
+ console.error(`Error getting fedInfo2 for ${spaceId2} during unfederate: ${error.message}`);
301
358
  }
302
359
 
303
- if (fedInfo2 && fedInfo2.notify) {
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.warn(`Could not update federation info for ${spaceId2}: ${error.message}`);
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(`Could not update federation metadata: ${error.message}`);
389
+ console.warn(`Failed to update federationMeta during unfederate: ${error.message}`);
332
390
  }
333
391
 
334
392
  return true;
335
393
  } catch (error) {
336
- console.error(`Federation removal failed: ${error.message}`);
337
- throw error;
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 {boolean} options.aggregate Whether to aggregate results by ID (default: false)
395
- * @param {string} options.idField The field to use as ID (default: '_id')
396
- * @param {string[]} options.sumFields Fields to sum during aggregation (default: [])
397
- * @param {string[]} options.concatArrays Array fields to concatenate during aggregation (default: [])
398
- * @param {boolean} options.removeDuplicates Whether to remove duplicates in concatenated arrays (default: true)
399
- * @param {Function} options.mergeStrategy Custom merge function for aggregation (default: null)
400
- * @param {boolean} options.includeLocal Whether to include local data (default: true)
401
- * @param {boolean} options.includeFederated Whether to include federated data (default: true)
402
- * @param {boolean} options.resolveReferences Whether to resolve federation references (default: true)
403
- * @param {number} options.maxFederatedSpaces Maximum number of federated spaces to query (default: -1 for all)
404
- * @param {number} options.timeout Timeout in milliseconds for federated queries (default: 10000)
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
- console.log(`getFederated called with options:`, JSON.stringify(options));
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, // Default to 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
- console.log(`Federation info retrieved:`, JSON.stringify(fedInfo));
438
-
439
- // Initialize result array and track processed IDs to avoid duplicates
440
- const result = [];
441
- const processedIds = new Set();
442
- const references = new Map(); // To keep track of references for resolution
443
-
444
- // Process each federated space first to prioritize federation data
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
- console.log(`Will process ${federatedSpaces.length} federated spaces: ${JSON.stringify(federatedSpaces)}`);
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
- // Now get local data if requested
487
- if (includeLocal) {
488
- const localData = await holosphere.getAll(holon, lens);
489
- console.log(`Got ${localData.length} local items from holon ${holon}`);
490
-
491
- // Add each local item to results, but only if not already processed
492
- for (const item of localData) {
493
- if (item && item[idField] && !processedIds.has(item[idField])) {
494
- result.push(item);
495
- processedIds.add(item[idField]);
496
- } else if (item && item[idField]) {
497
- console.log(`Local item ${item[idField]} already in result from federation, skipping`);
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.useReferences=true] - Whether to use references instead of duplicating data
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
- errorDetails: [],
715
- propagated: false,
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 reference
755
- const isAlreadyReference = holosphere.isReference(data);
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
- // If using references and data isn't already a reference, create a reference
761
- if (useReferences && !isAlreadyReference) {
762
- // Create a reference object using the dedicated utility
763
- const reference = holosphere.createReference(holon, lens, data);
764
-
765
- // Add federation metadata
766
- reference._federation = {
767
- origin: holon,
768
- lens: lens,
769
- timestamp: Date.now()
770
- };
771
-
772
- console.log(`Using reference: ${reference.soul} for data: ${data.id}`);
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
- await holosphere.put(targetSpace, lens, referenceToStore, null, { autoPropagate: false });
793
- result.success++;
794
- return true;
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
- origin: holon,
802
- lens: lens,
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.errorDetails.push({
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,