holosphere 1.1.11 → 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/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
@@ -330,14 +331,24 @@ export async function unfederate(holosphere, spaceId1, spaceId2, password1 = nul
330
331
  // For now, we'll let it proceed to attempt metadata cleanup, but a throw here might be valid.
331
332
  }
332
333
 
333
- if (!fedInfo1 || !fedInfo1.federation) {
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.`);
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.`);
336
337
  } else {
337
- // Update first space federation info
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
+
338
346
  fedInfo1.federation = fedInfo1.federation.filter(id => id !== spaceId2);
347
+ fedInfo1.notify = fedInfo1.notify.filter(id => id !== spaceId2);
339
348
  fedInfo1.timestamp = Date.now();
340
349
 
350
+ console.log(`Unfederate: Removed ${spaceId2} from ${spaceId1}: federation ${originalFederationLength} -> ${fedInfo1.federation.length}, notify ${originalNotifyLength} -> ${fedInfo1.notify.length}`);
351
+
341
352
  try {
342
353
  await holosphere.putGlobal('federation', fedInfo1, password1);
343
354
  } catch (error) {
@@ -743,6 +754,8 @@ export async function getFederated(holosphere, holon, lens, options = {}) {
743
754
  * @param {boolean} [options.useHolograms=true] - Use holograms for propagation (default: true)
744
755
  * @param {string[]} [options.targetSpaces] - Specific target spaces to propagate to (defaults to all federated spaces)
745
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)
746
759
  * @returns {Promise<object>} - Result with success count and errors
747
760
  */
748
761
  export async function propagate(holosphere, holon, lens, data, options = {}) {
@@ -750,147 +763,252 @@ export async function propagate(holosphere, holon, lens, data, options = {}) {
750
763
  throw new Error('propagate: Missing required parameters');
751
764
  }
752
765
  // Default propagation options
753
- const { useHolograms = true, targetSpaces = null, password = null } = options;
766
+ const {
767
+ useHolograms = true,
768
+ targetSpaces = null,
769
+ password = null,
770
+ propagateToParents = true,
771
+ maxParentLevels = 15
772
+ } = options;
754
773
 
755
774
  const result = {
756
775
  success: 0,
757
776
  errors: 0,
758
777
  skipped: 0,
759
- messages: []
778
+ messages: [],
779
+ parentPropagation: {
780
+ success: 0,
781
+ errors: 0,
782
+ skipped: 0,
783
+ messages: []
784
+ }
760
785
  };
761
786
 
762
787
  try {
788
+ // ================================ FEDERATION PROPAGATION ================================
789
+
763
790
  // Get federation info for this holon using getFederation
764
791
  const fedInfo = await getFederation(holosphere, holon, password);
765
792
 
766
- // If no federation info or no federation list, return with message
767
- if (!fedInfo || !fedInfo.federation || fedInfo.federation.length === 0) {
768
- return {
769
- ...result,
770
- message: `No federation found for ${holon}`
771
- };
772
- }
773
-
774
- // If no notification list or it's empty, return with message
775
- if (!fedInfo.notify || fedInfo.notify.length === 0) {
776
- return {
777
- ...result,
778
- message: `No notification targets found for ${holon}`
779
- };
780
- }
781
-
782
- // Filter federation spaces to those in notify list
783
- let spaces = fedInfo.notify;
784
-
785
- // Further filter by targetSpaces if provided
786
- if (targetSpaces && Array.isArray(targetSpaces) && targetSpaces.length > 0) {
787
- spaces = spaces.filter(space => targetSpaces.includes(space));
788
- }
789
-
790
- if (spaces.length === 0) {
791
- return {
792
- ...result,
793
- message: 'No valid target spaces found after filtering'
794
- };
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);
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;
810
797
 
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++;
798
+ // Further filter by targetSpaces if provided
799
+ if (targetSpaces && Array.isArray(targetSpaces) && targetSpaces.length > 0) {
800
+ spaces = spaces.filter(space => targetSpaces.includes(space));
817
801
  }
818
802
 
819
- return shouldPropagate;
820
- });
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
+ }
821
812
 
822
- if (spaces.length === 0) {
823
- // If no specific skip messages were added and spaces is empty, add a general one.
824
- if (result.skipped === 0 && fedInfo.notify && fedInfo.notify.length > 0) {
825
- result.messages.push('No target spaces were configured for federation of this specific lens, out of available notification partners.');
826
- } else if (fedInfo.notify && fedInfo.notify.length === 0) {
827
- result.messages.push('No notification partners available to filter for lens propagation.');
828
- }
829
- return {
830
- ...result,
831
- message: result.messages.join('; ') || 'No valid target spaces for propagation after lens filtering.'
832
- };
833
- }
834
-
835
- // Check if data is already a hologram
836
- const isAlreadyHologram = holosphere.isHologram(data);
813
+ // Ensure .federate is an array before calling .includes
814
+ const federateLenses = Array.isArray(spaceConfig.federate) ? spaceConfig.federate : [];
837
815
 
838
- // If data is already a hologram, don't re-wrap it
839
- if (isAlreadyHologram && useHolograms) {
840
- }
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
+ });
841
828
 
842
- // If propagating a non-hologram and useHolograms is false, add warning
843
- if (!isAlreadyHologram && !useHolograms) {
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.`);
844
887
  }
845
888
 
846
- // For each target space, propagate the data
847
- const propagatePromises = spaces.map(async (targetSpace) => {
889
+ // ================================ PARENT PROPAGATION ================================
890
+
891
+ // Check if we should propagate to parent hexagons
892
+ if (propagateToParents) {
893
+ console.log(`[Federation] Starting parent propagation for holon: ${holon}`);
848
894
  try {
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
863
- };
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,
869
- _federation: {
870
- ...(data._federation || {}), // Preserve existing _federation fields if any
871
- ...federationMeta // Add/overwrite with current propagation info
872
- }
873
- };
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++;
874
905
  }
875
906
 
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
- });
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;
913
+
914
+ console.log(`[Federation] Getting parent hexagons for ${holon} (resolution ${holonResolution}) up to ${maxParentLevels} levels`);
915
+
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;
929
+ }
930
+ }
931
+
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
+ };
881
953
 
882
- result.success++;
883
- return true;
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
+ }
999
+ }
884
1000
  } catch (error) {
885
- result.errors++;
886
- result.messages.push(`Error propagating ${data.id} to ${targetSpace}: ${error.message}`);
887
- 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}`);
888
1004
  }
889
- });
1005
+ } else {
1006
+ console.log(`[Federation] Parent propagation disabled for holon: ${holon}`);
1007
+ }
890
1008
 
891
- await Promise.all(propagatePromises);
1009
+ // ================================ END PARENT PROPAGATION ================================
892
1010
 
893
- result.propagated = result.success > 0;
1011
+ result.propagated = result.success > 0 || result.parentPropagation.success > 0;
894
1012
  return result;
895
1013
  } catch (error) {
896
1014
  return {
package/global.js CHANGED
@@ -63,11 +63,15 @@ export async function putGlobal(holoInstance, tableName, data, password = null)
63
63
 
64
64
  return new Promise((resolve, reject) => {
65
65
  try {
66
- // Remove isHologram field before storing - NO LONGER NEEDED
67
- // if (data && data.isHologram !== undefined) {
68
- // delete data.isHologram;
69
- // }
70
- const payload = JSON.stringify(data);
66
+ // Create a copy of data without the _meta field if it exists
67
+ let dataToStore = { ...data };
68
+ if (dataToStore._meta !== undefined) {
69
+ delete dataToStore._meta;
70
+ }
71
+ const payload = JSON.stringify(dataToStore);
72
+
73
+ // Check if the data being stored is a hologram
74
+ const isHologram = holoInstance.isHologram(dataToStore);
71
75
 
72
76
  const dataPath = password ?
73
77
  user.get('private').get(tableName) :
@@ -79,6 +83,27 @@ export async function putGlobal(holoInstance, tableName, data, password = null)
79
83
  if (ack.err) {
80
84
  reject(new Error(ack.err));
81
85
  } else {
86
+ // --- Start: Hologram Tracking Logic (for data *being put*, if it's a hologram) ---
87
+ if (isHologram) {
88
+ try {
89
+ const storedDataSoulInfo = holoInstance.parseSoulPath(dataToStore.soul);
90
+ if (storedDataSoulInfo) {
91
+ const targetNodeRef = holoInstance.getNodeRef(dataToStore.soul); // Target of the data *being put*
92
+ // Soul of the hologram that was *actually stored* at tableName/data.id
93
+ const storedHologramInstanceSoul = `${holoInstance.appname}/${tableName}/${data.id}`;
94
+
95
+ targetNodeRef.get('_holograms').get(storedHologramInstanceSoul).put(true);
96
+
97
+ console.log(`Data (ID: ${data.id}) being put is a hologram. Added its instance soul ${storedHologramInstanceSoul} to its target ${dataToStore.soul}'s _holograms set.`);
98
+ } else {
99
+ console.warn(`Data (ID: ${data.id}) being put is a hologram, but could not parse its soul ${dataToStore.soul} for tracking.`);
100
+ }
101
+ } catch (trackingError) {
102
+ console.warn(`Error updating _holograms set for the target of the data being put (data ID: ${data.id}, soul: ${dataToStore.soul}):`, trackingError);
103
+ }
104
+ }
105
+ // --- End: Hologram Tracking Logic ---
106
+
82
107
  resolve();
83
108
  }
84
109
  });
@@ -87,6 +112,27 @@ export async function putGlobal(holoInstance, tableName, data, password = null)
87
112
  if (ack.err) {
88
113
  reject(new Error(ack.err));
89
114
  } else {
115
+ // --- Start: Hologram Tracking Logic (for data *being put*, if it's a hologram) ---
116
+ if (isHologram) {
117
+ try {
118
+ const storedDataSoulInfo = holoInstance.parseSoulPath(dataToStore.soul);
119
+ if (storedDataSoulInfo) {
120
+ const targetNodeRef = holoInstance.getNodeRef(dataToStore.soul); // Target of the data *being put*
121
+ // Soul of the hologram that was *actually stored* at tableName (without specific key)
122
+ const storedHologramInstanceSoul = `${holoInstance.appname}/${tableName}`;
123
+
124
+ targetNodeRef.get('_holograms').get(storedHologramInstanceSoul).put(true);
125
+
126
+ console.log(`Data being put is a hologram. Added its instance soul ${storedHologramInstanceSoul} to its target ${dataToStore.soul}'s _holograms set.`);
127
+ } else {
128
+ console.warn(`Data being put is a hologram, but could not parse its soul ${dataToStore.soul} for tracking.`);
129
+ }
130
+ } catch (trackingError) {
131
+ console.warn(`Error updating _holograms set for the target of the data being put (soul: ${dataToStore.soul}):`, trackingError);
132
+ }
133
+ }
134
+ // --- End: Hologram Tracking Logic ---
135
+
90
136
  resolve();
91
137
  }
92
138
  });
@@ -421,11 +467,66 @@ export async function deleteGlobal(holoInstance, tableName, key, password = null
421
467
  });
422
468
  }
423
469
 
424
- return new Promise((resolve, reject) => {
425
- const dataPath = password ?
426
- user.get('private').get(tableName).get(key) :
427
- holoInstance.gun.get(holoInstance.appname).get(tableName).get(key);
470
+ const dataPath = password ?
471
+ user.get('private').get(tableName).get(key) :
472
+ holoInstance.gun.get(holoInstance.appname).get(tableName).get(key);
473
+
474
+ // --- Start: Hologram Tracking Removal ---
475
+ let trackingRemovalPromise = Promise.resolve(); // Default to resolved promise
476
+
477
+ // 1. Get the data first to check if it's a hologram
478
+ const rawDataToDelete = await new Promise((resolve) => dataPath.once(resolve));
479
+ let dataToDelete = null;
480
+ try {
481
+ if (typeof rawDataToDelete === 'string') {
482
+ dataToDelete = JSON.parse(rawDataToDelete);
483
+ } else {
484
+ // Handle cases where it might already be an object (though likely string)
485
+ dataToDelete = rawDataToDelete;
486
+ }
487
+ } catch(e) {
488
+ console.warn("[deleteGlobal] Could not JSON parse data for deletion check:", rawDataToDelete, e);
489
+ dataToDelete = null; // Ensure it's null if parsing fails
490
+ }
491
+
492
+ // 2. If it is a hologram, try to remove its reference from the target
493
+ const isDataHologram = dataToDelete && holoInstance.isHologram(dataToDelete);
494
+
495
+ if (isDataHologram) {
496
+ try {
497
+ const targetSoul = dataToDelete.soul;
498
+ const targetSoulInfo = holoInstance.parseSoulPath(targetSoul);
499
+
500
+ if (targetSoulInfo) {
501
+ const targetNodeRef = holoInstance.getNodeRef(targetSoul);
502
+ const deletedHologramSoul = `${holoInstance.appname}/${tableName}/${key}`;
503
+
504
+ // Create a promise that resolves when the hologram is removed from the list
505
+ trackingRemovalPromise = new Promise((resolveTrack) => { // No reject needed, just warn on error
506
+ targetNodeRef.get('_holograms').get(deletedHologramSoul).put(null, (ack) => { // Remove the hologram entry completely
507
+ if (ack.err) {
508
+ console.warn(`[deleteGlobal] Error removing hologram ${deletedHologramSoul} from target ${targetSoul}:`, ack.err);
509
+ }
510
+ console.log(`Removed hologram ${deletedHologramSoul} from target ${targetSoul}'s _holograms list`);
511
+ resolveTrack(); // Resolve regardless of ack error to not block main delete
512
+ });
513
+ });
514
+ } else {
515
+ console.warn(`Could not parse target soul ${targetSoul} for hologram tracking removal during deleteGlobal.`);
516
+ }
517
+ } catch (trackingError) {
518
+ console.warn(`Error initiating hologram reference removal from target ${dataToDelete.soul} during deleteGlobal:`, trackingError);
519
+ // Ensure trackingRemovalPromise remains resolved if setup fails
520
+ trackingRemovalPromise = Promise.resolve();
521
+ }
522
+ }
523
+ // --- End: Hologram Tracking Removal ---
524
+
525
+ // 3. Wait for the tracking removal attempt to be acknowledged
526
+ await trackingRemovalPromise;
428
527
 
528
+ // 4. Proceed with the actual deletion of the hologram node itself
529
+ return new Promise((resolve, reject) => {
429
530
  // Request deletion
430
531
  dataPath.put(null, ack => {
431
532
  // console.log('deleteGlobal - Deletion acknowledgment:', ack); // Optional logging
@@ -522,6 +623,70 @@ export async function deleteAllGlobal(holoInstance, tableName, password = null)
522
623
  }
523
624
 
524
625
  const keys = Object.keys(data).filter(key => key !== '_');
626
+
627
+ // Process each key to handle holograms properly
628
+ for (const key of keys) {
629
+ try {
630
+ // Get the data to check if it's a hologram
631
+ const itemPath = password ?
632
+ user.get('private').get(tableName).get(key) :
633
+ holoInstance.gun.get(holoInstance.appname).get(tableName).get(key);
634
+
635
+ const rawDataToDelete = await new Promise((resolveItem) => itemPath.once(resolveItem));
636
+ let dataToDelete = null;
637
+
638
+ try {
639
+ if (typeof rawDataToDelete === 'string') {
640
+ dataToDelete = JSON.parse(rawDataToDelete);
641
+ } else {
642
+ dataToDelete = rawDataToDelete;
643
+ }
644
+ } catch(e) {
645
+ console.warn("[deleteAllGlobal] Could not JSON parse data for deletion check:", rawDataToDelete, e);
646
+ dataToDelete = null;
647
+ }
648
+
649
+ // Check if it's a hologram and handle accordingly
650
+ const isDataHologram = dataToDelete && holoInstance.isHologram(dataToDelete);
651
+
652
+ if (isDataHologram) {
653
+ // Handle hologram deletion - remove from target's _holograms list
654
+ try {
655
+ const targetSoul = dataToDelete.soul;
656
+ const targetSoulInfo = holoInstance.parseSoulPath(targetSoul);
657
+
658
+ if (targetSoulInfo) {
659
+ const targetNodeRef = holoInstance.getNodeRef(targetSoul);
660
+ const deletedHologramSoul = `${holoInstance.appname}/${tableName}/${key}`;
661
+
662
+ // Remove the hologram from target's _holograms list
663
+ await new Promise((resolveTrack) => {
664
+ targetNodeRef.get('_holograms').get(deletedHologramSoul).put(null, (ack) => {
665
+ if (ack.err) {
666
+ console.warn(`[deleteAllGlobal] Error removing hologram ${deletedHologramSoul} from target ${targetSoul}:`, ack.err);
667
+ }
668
+ console.log(`Removed hologram ${deletedHologramSoul} from target ${targetSoul}'s _holograms list`);
669
+ resolveTrack();
670
+ });
671
+ });
672
+ } else {
673
+ console.warn(`Could not parse target soul ${targetSoul} for hologram tracking removal during deleteAllGlobal.`);
674
+ }
675
+ } catch (trackingError) {
676
+ console.warn(`Error removing hologram reference from target ${dataToDelete.soul} during deleteAllGlobal:`, trackingError);
677
+ }
678
+ }
679
+
680
+ // Add to deletions set for tracking
681
+ deletions.add(key);
682
+ } catch (error) {
683
+ console.warn(`Error processing key ${key} during deleteAllGlobal:`, error);
684
+ // Still add to deletions set even if hologram processing failed
685
+ deletions.add(key);
686
+ }
687
+ }
688
+
689
+ // Now delete all the items
525
690
  const promises = keys.map(key =>
526
691
  new Promise((resolveDelete, rejectDelete) => {
527
692
  const deletePath = password ?