holosphere 1.1.20 → 2.0.0-alpha1

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.
Files changed (147) hide show
  1. package/.env.example +36 -0
  2. package/.eslintrc.json +16 -0
  3. package/.prettierrc.json +7 -0
  4. package/LICENSE +162 -38
  5. package/README.md +483 -367
  6. package/bin/holosphere-activitypub.js +158 -0
  7. package/cleanup-test-data.js +204 -0
  8. package/examples/demo.html +1333 -0
  9. package/examples/example-bot.js +197 -0
  10. package/package.json +47 -87
  11. package/scripts/check-bundle-size.js +54 -0
  12. package/scripts/check-quest-ids.js +77 -0
  13. package/scripts/import-holons.js +578 -0
  14. package/scripts/publish-to-relay.js +101 -0
  15. package/scripts/read-example.js +186 -0
  16. package/scripts/relay-diagnostic.js +59 -0
  17. package/scripts/relay-example.js +179 -0
  18. package/scripts/resync-to-relay.js +245 -0
  19. package/scripts/revert-import.js +196 -0
  20. package/scripts/test-hybrid-mode.js +108 -0
  21. package/scripts/test-local-storage.js +63 -0
  22. package/scripts/test-nostr-direct.js +55 -0
  23. package/scripts/test-read-data.js +45 -0
  24. package/scripts/test-write-read.js +63 -0
  25. package/scripts/verify-import.js +95 -0
  26. package/scripts/verify-relay-data.js +139 -0
  27. package/src/ai/aggregation.js +319 -0
  28. package/src/ai/breakdown.js +511 -0
  29. package/src/ai/classifier.js +217 -0
  30. package/src/ai/council.js +228 -0
  31. package/src/ai/embeddings.js +279 -0
  32. package/src/ai/federation-ai.js +324 -0
  33. package/src/ai/h3-ai.js +955 -0
  34. package/src/ai/index.js +112 -0
  35. package/src/ai/json-ops.js +225 -0
  36. package/src/ai/llm-service.js +205 -0
  37. package/src/ai/nl-query.js +223 -0
  38. package/src/ai/relationships.js +353 -0
  39. package/src/ai/schema-extractor.js +218 -0
  40. package/src/ai/spatial.js +293 -0
  41. package/src/ai/tts.js +194 -0
  42. package/src/content/social-protocols.js +168 -0
  43. package/src/core/holosphere.js +273 -0
  44. package/src/crypto/secp256k1.js +259 -0
  45. package/src/federation/discovery.js +334 -0
  46. package/src/federation/hologram.js +1042 -0
  47. package/src/federation/registry.js +386 -0
  48. package/src/hierarchical/upcast.js +110 -0
  49. package/src/index.js +2669 -0
  50. package/src/schema/validator.js +91 -0
  51. package/src/spatial/h3-operations.js +110 -0
  52. package/src/storage/backend-factory.js +125 -0
  53. package/src/storage/backend-interface.js +142 -0
  54. package/src/storage/backends/activitypub/server.js +653 -0
  55. package/src/storage/backends/activitypub-backend.js +272 -0
  56. package/src/storage/backends/gundb-backend.js +233 -0
  57. package/src/storage/backends/nostr-backend.js +136 -0
  58. package/src/storage/filesystem-storage-browser.js +41 -0
  59. package/src/storage/filesystem-storage.js +138 -0
  60. package/src/storage/global-tables.js +81 -0
  61. package/src/storage/gun-async.js +281 -0
  62. package/src/storage/gun-wrapper.js +221 -0
  63. package/src/storage/indexeddb-storage.js +122 -0
  64. package/src/storage/key-storage-simple.js +76 -0
  65. package/src/storage/key-storage.js +136 -0
  66. package/src/storage/memory-storage.js +59 -0
  67. package/src/storage/migration.js +338 -0
  68. package/src/storage/nostr-async.js +811 -0
  69. package/src/storage/nostr-client.js +939 -0
  70. package/src/storage/nostr-wrapper.js +211 -0
  71. package/src/storage/outbox-queue.js +208 -0
  72. package/src/storage/persistent-storage.js +109 -0
  73. package/src/storage/sync-service.js +164 -0
  74. package/src/subscriptions/manager.js +142 -0
  75. package/test-ai-real-api.js +202 -0
  76. package/tests/unit/ai/aggregation.test.js +295 -0
  77. package/tests/unit/ai/breakdown.test.js +446 -0
  78. package/tests/unit/ai/classifier.test.js +294 -0
  79. package/tests/unit/ai/council.test.js +262 -0
  80. package/tests/unit/ai/embeddings.test.js +384 -0
  81. package/tests/unit/ai/federation-ai.test.js +344 -0
  82. package/tests/unit/ai/h3-ai.test.js +458 -0
  83. package/tests/unit/ai/index.test.js +304 -0
  84. package/tests/unit/ai/json-ops.test.js +307 -0
  85. package/tests/unit/ai/llm-service.test.js +390 -0
  86. package/tests/unit/ai/nl-query.test.js +383 -0
  87. package/tests/unit/ai/relationships.test.js +311 -0
  88. package/tests/unit/ai/schema-extractor.test.js +384 -0
  89. package/tests/unit/ai/spatial.test.js +279 -0
  90. package/tests/unit/ai/tts.test.js +279 -0
  91. package/tests/unit/content.test.js +332 -0
  92. package/tests/unit/contract/core.test.js +88 -0
  93. package/tests/unit/contract/crypto.test.js +198 -0
  94. package/tests/unit/contract/data.test.js +223 -0
  95. package/tests/unit/contract/federation.test.js +181 -0
  96. package/tests/unit/contract/hierarchical.test.js +113 -0
  97. package/tests/unit/contract/schema.test.js +114 -0
  98. package/tests/unit/contract/social.test.js +217 -0
  99. package/tests/unit/contract/spatial.test.js +110 -0
  100. package/tests/unit/contract/subscriptions.test.js +128 -0
  101. package/tests/unit/contract/utils.test.js +159 -0
  102. package/tests/unit/core.test.js +152 -0
  103. package/tests/unit/crypto.test.js +328 -0
  104. package/tests/unit/federation.test.js +234 -0
  105. package/tests/unit/gun-async.test.js +252 -0
  106. package/tests/unit/hierarchical.test.js +399 -0
  107. package/tests/unit/integration/scenario-01-geographic-storage.test.js +74 -0
  108. package/tests/unit/integration/scenario-02-federation.test.js +76 -0
  109. package/tests/unit/integration/scenario-03-subscriptions.test.js +102 -0
  110. package/tests/unit/integration/scenario-04-validation.test.js +129 -0
  111. package/tests/unit/integration/scenario-05-hierarchy.test.js +125 -0
  112. package/tests/unit/integration/scenario-06-social.test.js +135 -0
  113. package/tests/unit/integration/scenario-07-persistence.test.js +130 -0
  114. package/tests/unit/integration/scenario-08-authorization.test.js +161 -0
  115. package/tests/unit/integration/scenario-09-cross-dimensional.test.js +139 -0
  116. package/tests/unit/integration/scenario-10-cross-holosphere-capabilities.test.js +357 -0
  117. package/tests/unit/integration/scenario-11-cross-holosphere-federation.test.js +410 -0
  118. package/tests/unit/integration/scenario-12-capability-federated-read.test.js +719 -0
  119. package/tests/unit/performance/benchmark.test.js +85 -0
  120. package/tests/unit/schema.test.js +213 -0
  121. package/tests/unit/spatial.test.js +158 -0
  122. package/tests/unit/storage.test.js +195 -0
  123. package/tests/unit/subscriptions.test.js +328 -0
  124. package/tests/unit/test-data-permanence-debug.js +197 -0
  125. package/tests/unit/test-data-permanence.js +340 -0
  126. package/tests/unit/test-key-persistence-fixed.js +148 -0
  127. package/tests/unit/test-key-persistence.js +172 -0
  128. package/tests/unit/test-relay-permanence.js +376 -0
  129. package/tests/unit/test-second-node.js +95 -0
  130. package/tests/unit/test-simple-write.js +89 -0
  131. package/vite.config.js +49 -0
  132. package/vitest.config.js +20 -0
  133. package/FEDERATION.md +0 -213
  134. package/compute.js +0 -298
  135. package/content.js +0 -980
  136. package/federation.js +0 -1234
  137. package/global.js +0 -736
  138. package/hexlib.js +0 -335
  139. package/hologram.js +0 -183
  140. package/holosphere-bundle.esm.js +0 -33256
  141. package/holosphere-bundle.js +0 -33287
  142. package/holosphere-bundle.min.js +0 -39
  143. package/holosphere.d.ts +0 -601
  144. package/holosphere.js +0 -719
  145. package/node.js +0 -246
  146. package/schema.js +0 -139
  147. package/utils.js +0 -302
package/content.js DELETED
@@ -1,980 +0,0 @@
1
- // holo_content.js
2
-
3
- /**
4
- * Stores content in the specified holon and lens.
5
- * If the target path already contains a hologram, the put operation will be
6
- * redirected to store the new data at the location specified in the existing
7
- * hologram's soul.
8
- * If the stored data (after potential redirection) is a hologram, this function
9
- * also attempts to update the target data node's `_holograms` set.
10
- * @param {HoloSphere} holoInstance - The HoloSphere instance.
11
- * @param {string} holon - The initial holon identifier.
12
- * @param {string} lens - The initial lens under which to store the content.
13
- * @param {object} data - The data to store.
14
- * @param {string} [password] - Optional password for private holon.
15
- * @param {object} [options] - Additional options
16
- * @param {boolean} [options.autoPropagate=true] - Whether to automatically propagate to federated holons (default: true)
17
- * @param {object} [options.propagationOptions] - Options to pass to propagate
18
- * @param {boolean} [options.propagationOptions.useHolograms=true] - Whether to use holograms instead of duplicating data
19
- * @param {boolean} [options.disableHologramRedirection=false] - Whether to disable hologram redirection
20
- * @returns {Promise<object>} - Returns an object with success status, path info, propagation result, and list of updated holograms
21
- */
22
- export async function put(holoInstance, holon, lens, data, password = null, options = {}) {
23
- if (!data) { // Check data first as it's used for id generation
24
- throw new Error('put: Missing required data parameter');
25
- }
26
- if (!holon || !lens) {
27
- throw new Error('put: Missing required holon or lens parameters:', holon, lens);
28
- }
29
-
30
- const { disableHologramRedirection = false } = options; // Extract new option
31
-
32
- let targetHolon = holon;
33
- let targetLens = lens;
34
- let targetKey = data.id; // Use data.id as the key
35
-
36
- if (!targetKey) {
37
- targetKey = holoInstance.generateId();
38
- data.id = targetKey; // Assign the generated ID back to the data
39
- }
40
-
41
- // --- Start: Target Path Hologram Redirection Logic ---
42
- try {
43
- // Get the item at the original target path, WITHOUT resolving holograms
44
- const existingItemAtPath = await get(holoInstance, targetHolon, targetLens, targetKey, password, { resolveHolograms: false });
45
-
46
- if (!disableHologramRedirection && existingItemAtPath && holoInstance.isHologram(existingItemAtPath)) {
47
- const soulInfo = holoInstance.parseSoulPath(existingItemAtPath.soul);
48
- if (soulInfo) {
49
- // Optional: Check if soulInfo.appname matches holoInstance.appname
50
- if (soulInfo.appname !== holoInstance.appname) {
51
- console.warn(`Existing hologram at ${targetHolon}/${targetLens}/${targetKey} has appname (${soulInfo.appname}) in its soul ${existingItemAtPath.soul} which does not match current HoloSphere instance appname (${holoInstance.appname}). Redirecting put to soul's holon/lens within this instance.`);
52
- }
53
- targetHolon = soulInfo.holon; // Redirect holon
54
- targetLens = soulInfo.lens; // Redirect lens
55
- targetKey = soulInfo.key; // Redirect key (important!)
56
- // data.id should ideally match soulInfo.key if this is consistent.
57
- // If data.id is different, it means we are writing data with one ID to a path derived from another ID's soul.
58
- if (data.id !== targetKey) {
59
- console.warn(`Data ID ('${data.id}') differs from redirected target key ('${targetKey}') derived from existing hologram's soul. Data will be stored under key '${targetKey}'.`);
60
- // It's crucial that the actual GunDB path uses targetKey.
61
- // The 'data' object itself retains its original 'data.id' unless explicitly changed.
62
- }
63
- } else {
64
- console.warn(`Existing item at ${targetHolon}/${targetLens}/${targetKey} (ID: ${existingItemAtPath.id}) is a hologram, but its soul ('${existingItemAtPath.soul}') is invalid. Proceeding with original target.`);
65
- }
66
- }
67
- } catch (error) {
68
- // If 'get' fails (e.g., item not found, auth error), proceed with original target.
69
- // A "not found" error is expected if the path is new.
70
- if (error.message && error.message.includes('RESOLVED_NULL')) {
71
- // This is fine, means nothing was at the path.
72
- } else {
73
- console.warn(`Error checking for existing hologram at ${targetHolon}/${targetLens}/${targetKey}: ${error.message}. Proceeding with original target.`);
74
- }
75
- }
76
- // --- End: Target Path Hologram Redirection Logic ---
77
-
78
- // The data being stored is 'data'. Its 'id' property is 'data.id'.
79
- // The final storage path key is 'targetKey'.
80
-
81
- // Check if the data *being put* is a hologram (this variable is used later for schema and propagation)
82
- const isHologram = holoInstance.isHologram(data);
83
-
84
- // Get and validate schema only in strict mode for non-holograms (data being put)
85
- if (holoInstance.strict && !isHologram) {
86
- const schema = await holoInstance.getSchema(targetLens); // Use targetLens for schema
87
- if (!schema) {
88
- throw new Error('Schema required in strict mode');
89
- }
90
- const dataToValidate = JSON.parse(JSON.stringify(data)); // Validate the actual data
91
- const valid = holoInstance.validator.validate(schema, dataToValidate);
92
-
93
- if (!valid) {
94
- const errorMsg = `Schema validation failed: ${JSON.stringify(holoInstance.validator.errors)}`;
95
- throw new Error(errorMsg);
96
- }
97
- }
98
-
99
- try {
100
- let user = null;
101
- if (password) {
102
- user = holoInstance.gun.user();
103
- await new Promise((resolve, reject) => {
104
- const userNameString = holoInstance.userName(targetHolon); // Use targetHolon for put
105
- user.auth(userNameString, password, (authAck) => {
106
- if (authAck.err) {
107
- console.log(`Initial auth failed for ${userNameString} during put, attempting to create...`);
108
- user.create(userNameString, password, (createAck) => {
109
- if (createAck.err) {
110
- if (createAck.err.includes("already created") || createAck.err.includes("already being created")) {
111
- console.log(`User ${userNameString} already existed or being created during put, re-attempting auth with fresh user object.`);
112
- const freshUser = holoInstance.gun.user(); // Get a new user object
113
- freshUser.auth(userNameString, password, (secondAuthAck) => {
114
- if (secondAuthAck.err) {
115
- console.log(`Auth still failed after user existed check: ${secondAuthAck.err}. Resolving anyway for test operations.`);
116
- resolve(); // Resolve anyway to allow test operations
117
- } else {
118
- resolve();
119
- }
120
- });
121
- } else {
122
- console.log(`Create user error (resolving anyway for operations): ${createAck.err}`);
123
- resolve(); // Resolve anyway to allow test operations
124
- }
125
- } else {
126
- console.log(`User ${userNameString} created successfully during put, attempting auth...`);
127
- user.auth(userNameString, password, (secondAuthAck) => {
128
- if (secondAuthAck.err) {
129
- reject(new Error(`Failed to auth after create for ${userNameString} during put: ${secondAuthAck.err}`));
130
- } else {
131
- resolve();
132
- }
133
- });
134
- }
135
- });
136
- } else {
137
- resolve(); // Auth successful
138
- }
139
- });
140
- });
141
- }
142
-
143
- return new Promise((resolve, reject) => {
144
- try {
145
- // Create a copy of data without the _meta field if it exists
146
- let dataToStore = { ...data };
147
- if (dataToStore._meta !== undefined) {
148
- delete dataToStore._meta;
149
- }
150
- const payload = JSON.stringify(dataToStore); // The data being stored
151
-
152
- const putCallback = async (ack) => {
153
- if (ack.err) {
154
- reject(new Error(ack.err));
155
- } else {
156
- // --- Start: Hologram Tracking Logic (for data *being put*, if it's a hologram) ---
157
- if (isHologram) {
158
- try {
159
- const storedDataSoulInfo = holoInstance.parseSoulPath(data.soul);
160
- if (storedDataSoulInfo) {
161
- const targetNodeRef = holoInstance.getNodeRef(data.soul); // Target of the data *being put*
162
- // Soul of the hologram that was *actually stored* at targetHolon/targetLens/targetKey
163
- const storedHologramInstanceSoul = `${holoInstance.appname}/${targetHolon}/${targetLens}/${targetKey}`;
164
-
165
- targetNodeRef.get('_holograms').get(storedHologramInstanceSoul).put(true);
166
- } else {
167
- console.warn(`Data (ID: ${data.id}) being put is a hologram, but could not parse its soul ${data.soul} for tracking.`);
168
- }
169
- } catch (trackingError) {
170
- console.warn(`Error updating _holograms set for the target of the data being put (data ID: ${data.id}, soul: ${data.soul}):`, trackingError);
171
- }
172
- }
173
- // --- End: Hologram Tracking Logic ---
174
-
175
- // --- Start: Active Hologram Update Logic (for actual data being stored) ---
176
- let updatedHolograms = [];
177
- if (!isHologram && !options.isHologramUpdate) {
178
- try {
179
- const currentDataSoul = `${holoInstance.appname}/${targetHolon}/${targetLens}/${targetKey}`;
180
- const currentNodeRef = holoInstance.getNodeRef(currentDataSoul);
181
-
182
- // Get the _holograms set for this data
183
- await new Promise((resolveHologramUpdate) => {
184
- currentNodeRef.get('_holograms').once(async (hologramsSet) => {
185
- if (hologramsSet) {
186
- const hologramSouls = Object.keys(hologramsSet).filter(k =>
187
- k !== '_' && hologramsSet[k] === true // Only active holograms (deleted ones are null/removed)
188
- );
189
-
190
- if (hologramSouls.length > 0) {
191
- // Update each active hologram with an 'updated' timestamp
192
- const updatePromises = hologramSouls.map(async (hologramSoul) => {
193
- try {
194
- const hologramSoulInfo = holoInstance.parseSoulPath(hologramSoul);
195
- if (hologramSoulInfo) {
196
- // Get the current hologram data
197
- const currentHologram = await holoInstance.get(
198
- hologramSoulInfo.holon,
199
- hologramSoulInfo.lens,
200
- hologramSoulInfo.key,
201
- null,
202
- { resolveHolograms: false }
203
- );
204
-
205
- if (currentHologram) {
206
- // Update the hologram with an 'updated' timestamp
207
- const updatedHologram = {
208
- ...currentHologram,
209
- updated: Date.now()
210
- };
211
-
212
- await holoInstance.put(
213
- hologramSoulInfo.holon,
214
- hologramSoulInfo.lens,
215
- updatedHologram,
216
- null,
217
- {
218
- autoPropagate: false, // Don't auto-propagate hologram updates
219
- disableHologramRedirection: true, // Prevent redirection when updating holograms
220
- isHologramUpdate: true // Prevent recursive hologram updates
221
- }
222
- );
223
-
224
- // Add to the list of updated holograms
225
- updatedHolograms.push({
226
- soul: hologramSoul,
227
- holon: hologramSoulInfo.holon,
228
- lens: hologramSoulInfo.lens,
229
- key: hologramSoulInfo.key,
230
- id: hologramSoulInfo.key,
231
- timestamp: updatedHologram.updated
232
- });
233
- }
234
- }
235
- } catch (hologramUpdateError) {
236
- console.warn(`Error updating hologram ${hologramSoul}:`, hologramUpdateError);
237
- }
238
- });
239
-
240
- await Promise.all(updatePromises);
241
- }
242
- }
243
- resolveHologramUpdate(); // Resolve the promise to continue with the main put logic
244
- });
245
- });
246
- } catch (hologramUpdateError) {
247
- console.warn(`Error checking for active holograms to update:`, hologramUpdateError);
248
- }
249
- }
250
- // --- End: Active Hologram Update Logic ---
251
-
252
- // Only notify subscribers for actual data, not holograms
253
- if (!isHologram) {
254
- holoInstance.notifySubscribers({
255
- holon: targetHolon, // Notify with final target
256
- lens: targetLens,
257
- ...data // The data that was put
258
- });
259
- }
260
-
261
- // Auto-propagate to federation by default (if data *being put* is not a hologram)
262
- const shouldPropagate = options.autoPropagate !== false && !isHologram;
263
- let propagationResult = null;
264
-
265
- if (shouldPropagate) {
266
- try {
267
- const propagationOptions = {
268
- useHolograms: true,
269
- ...options.propagationOptions
270
- };
271
-
272
- propagationResult = await holoInstance.propagate(
273
- targetHolon, // Propagate from final target
274
- targetLens,
275
- data, // The data that was put
276
- propagationOptions
277
- );
278
-
279
- if (propagationResult && propagationResult.errors > 0) {
280
- console.warn('Auto-propagation had errors:', propagationResult);
281
- }
282
- } catch (propError) {
283
- console.warn('Error in auto-propagation:', propError);
284
- }
285
- }
286
-
287
- resolve({
288
- success: true,
289
- isHologramAtPath: isHologram, // whether the data *put* was a hologram
290
- pathHolon: targetHolon,
291
- pathLens: targetLens,
292
- pathKey: targetKey,
293
- propagationResult,
294
- updatedHolograms: updatedHolograms // List of holograms that were updated
295
- });
296
- }
297
- };
298
-
299
- // Use targetHolon, targetLens, and targetKey for the actual storage path
300
- const dataPath = password ?
301
- user.get('private').get(targetLens).get(targetKey) :
302
- holoInstance.gun.get(holoInstance.appname).get(targetHolon).get(targetLens).get(targetKey);
303
-
304
- dataPath.put(payload, putCallback);
305
- } catch (error) {
306
- reject(error);
307
- }
308
- });
309
- } catch (error) {
310
- console.error('Error in put:', error);
311
- throw error;
312
- }
313
- }
314
-
315
- /**
316
- * Retrieves content from the specified holon and lens.
317
- * @param {HoloSphere} holoInstance - The HoloSphere instance.
318
- * @param {string} holon - The holon identifier.
319
- * @param {string} lens - The lens from which to retrieve content.
320
- * @param {string} key - The specific key to retrieve.
321
- * @param {string} [password] - Optional password for private holon.
322
- * @param {object} [options] - Additional options
323
- * @param {boolean} [options.resolveHolograms=true] - Whether to automatically resolve holograms
324
- * @param {object} [options.validationOptions] - Options passed to the schema validator
325
- * @returns {Promise<object|null>} - The retrieved content or null if not found.
326
- */
327
- export async function get(holoInstance, holon, lens, key, password = null, options = {}) {
328
- if (!holon || !lens || !key) {
329
- console.error('get: Missing required parameters');
330
- return null;
331
- }
332
-
333
- // Destructure options, including visited
334
- const { resolveHolograms = true, validationOptions = {}, visited } = options;
335
-
336
- // Get schema for validation if in strict mode
337
- let schema = null;
338
- if (holoInstance.strict) {
339
- schema = await holoInstance.getSchema(lens);
340
- if (!schema) {
341
- throw new Error('Schema required in strict mode');
342
- }
343
- }
344
-
345
- try {
346
- let user = null;
347
- if (password) {
348
- user = holoInstance.gun.user();
349
- await new Promise((resolve, reject) => {
350
- const userNameString = holoInstance.userName(holon); // Use holon for get
351
- user.auth(userNameString, password, (authAck) => {
352
- if (authAck.err) {
353
- // If auth fails, reject immediately. Do not attempt to create user.
354
- reject(new Error(`Authentication failed for ${userNameString} during get: ${authAck.err}`));
355
- } else {
356
- resolve(); // Auth successful
357
- }
358
- });
359
- });
360
- }
361
-
362
- return new Promise((resolve) => {
363
- const handleData = async (data) => {
364
- let parsed = null; // Declare parsed here to make it available in catch
365
- if (!data) {
366
- resolve(null);
367
- return;
368
- }
369
-
370
- try {
371
- parsed = await holoInstance.parse(data); // Assign to the outer scoped parsed
372
-
373
- if (!parsed) {
374
- resolve(null);
375
- return;
376
- }
377
-
378
- // Check if this is a hologram that needs to be resolved
379
- if (resolveHolograms && holoInstance.isHologram(parsed)) {
380
- const resolvedValue = await holoInstance.resolveHologram(parsed, {
381
- followHolograms: resolveHolograms,
382
- visited: visited,
383
- maxDepth: options.maxDepth || 10,
384
- currentDepth: options.currentDepth || 0
385
- });
386
-
387
- if (resolvedValue === null) {
388
- // This means resolveHologram determined the target doesn't exist or encountered an error
389
- console.warn(`Broken hologram detected at ${holon}/${lens}/${key}. Removing it...`);
390
-
391
- try {
392
- // Delete the broken hologram
393
- await holoInstance.delete(holon, lens, key, password);
394
- console.log(`Successfully removed broken hologram from ${holon}/${lens}/${key}`);
395
- } catch (cleanupError) {
396
- console.error(`Failed to remove broken hologram at ${holon}/${lens}/${key}:`, cleanupError);
397
- }
398
-
399
- resolve(null);
400
- return;
401
- }
402
- // If resolveHologram encountered a circular ref, it would throw, not return.
403
- // If it returned the hologram itself (if we ever revert to that), this logic would need adjustment.
404
- // For now, assume resolvedValue is either the resolved data or we've returned null above.
405
-
406
- if (resolvedValue !== parsed) {
407
- parsed = resolvedValue;
408
- }
409
- }
410
-
411
- // Perform schema validation if needed
412
- if (schema) {
413
- const valid = holoInstance.validator.validate(schema, parsed);
414
- if (!valid) {
415
- console.error('get: Invalid data according to schema:', holoInstance.validator.errors);
416
- if (holoInstance.strict) {
417
- resolve(null);
418
- return;
419
- }
420
- }
421
- }
422
-
423
- resolve(parsed);
424
- } catch (error) {
425
- if (error.message?.startsWith('CIRCULAR_REFERENCE')) {
426
- console.warn(`Caught circular reference during get/handleData for key ${key}. Resolving null.`);
427
- resolve(null);
428
- } else {
429
- console.error('Error processing data in get/handleData:', error);
430
- resolve(null); // For other errors, resolve null
431
- }
432
- }
433
- };
434
-
435
- const dataPath = password ?
436
- user.get('private').get(lens).get(key) :
437
- holoInstance.gun.get(holoInstance.appname).get(holon).get(lens).get(key);
438
-
439
- dataPath.once(handleData);
440
- });
441
- } catch (error) {
442
- console.error('Error in get:', error);
443
- return null;
444
- }
445
- }
446
-
447
- /**
448
- * Retrieves all content from the specified holon and lens.
449
- * @param {HoloSphere} holoInstance - The HoloSphere instance.
450
- * @param {string} holon - The holon identifier.
451
- * @param {string} lens - The lens from which to retrieve content.
452
- * @param {string} [password] - Optional password for private holon.
453
- * @returns {Promise<Array<object>>} - The retrieved content.
454
- */
455
- export async function getAll(holoInstance, holon, lens, password = null) {
456
- if (!holon || !lens) {
457
- throw new Error('getAll: Missing required parameters');
458
- }
459
-
460
- const schema = await holoInstance.getSchema(lens);
461
- if (!schema && holoInstance.strict) {
462
- throw new Error('getAll: Schema required in strict mode');
463
- }
464
-
465
- try {
466
- let user = null;
467
- if (password) {
468
- user = holoInstance.gun.user();
469
- await new Promise((resolve, reject) => {
470
- const userNameString = holoInstance.userName(holon); // Use holon for getAll
471
- user.auth(userNameString, password, (authAck) => {
472
- if (authAck.err) {
473
- console.log(`Initial auth failed for ${userNameString} during getAll, attempting to create...`);
474
- user.create(userNameString, password, (createAck) => {
475
- if (createAck.err) {
476
- if (createAck.err.includes("already created") || createAck.err.includes("already being created")) {
477
- console.log(`User ${userNameString} already existed or being created during getAll, re-attempting auth with fresh user object.`);
478
- const freshUser = holoInstance.gun.user(); // Get a new user object
479
- freshUser.auth(userNameString, password, (secondAuthAck) => {
480
- if (secondAuthAck.err) {
481
- console.log(`Auth still failed after user existed check: ${secondAuthAck.err}. Resolving anyway for test operations.`);
482
- resolve(); // Resolve anyway to allow test operations
483
- } else {
484
- resolve();
485
- }
486
- });
487
- } else {
488
- console.log(`Create user error (resolving anyway for operations): ${createAck.err}`);
489
- resolve(); // Resolve anyway to allow test operations
490
- }
491
- } else {
492
- console.log(`User ${userNameString} created successfully during getAll, attempting auth...`);
493
- user.auth(userNameString, password, (secondAuthAck) => {
494
- if (secondAuthAck.err) {
495
- reject(new Error(`Failed to auth after create for ${userNameString} during getAll: ${secondAuthAck.err}`));
496
- } else {
497
- resolve();
498
- }
499
- });
500
- }
501
- });
502
- } else {
503
- resolve(); // Auth successful
504
- }
505
- });
506
- });
507
- }
508
-
509
- return new Promise((resolve) => {
510
- const output = new Map();
511
-
512
- const processData = async (data, key) => {
513
- if (!data || key === '_') return;
514
-
515
- try {
516
- const parsed = await holoInstance.parse(data); // Use instance's parse
517
- if (!parsed || !parsed.id) return;
518
-
519
- // Check if this is a hologram that needs to be resolved
520
- if (holoInstance.isHologram(parsed)) {
521
- try {
522
- const resolved = await holoInstance.resolveHologram(parsed, {
523
- followHolograms: true,
524
- maxDepth: 10,
525
- currentDepth: 0
526
- });
527
-
528
- if (resolved === null) {
529
- console.warn(`Broken hologram detected in getAll for key ${key}. Removing it...`);
530
-
531
- try {
532
- // Delete the broken hologram
533
- await holoInstance.delete(holon, lens, key, password);
534
- console.log(`Successfully removed broken hologram from ${holon}/${lens}/${key}`);
535
- } catch (cleanupError) {
536
- console.error(`Failed to remove broken hologram at ${holon}/${lens}/${key}:`, cleanupError);
537
- }
538
- return; // Skip adding this item to output
539
- }
540
-
541
- if (resolved && resolved !== parsed) {
542
- // Hologram was resolved successfully
543
- if (schema) {
544
- const valid = holoInstance.validator.validate(schema, resolved);
545
- if (valid || !holoInstance.strict) {
546
- output.set(resolved.id, resolved);
547
- }
548
- } else {
549
- output.set(resolved.id, resolved);
550
- }
551
- return;
552
- }
553
- } catch (hologramError) {
554
- console.error(`Error resolving hologram for key ${key}:`, hologramError);
555
- return; // Skip this item
556
- }
557
- }
558
-
559
- if (schema) {
560
- const valid = holoInstance.validator.validate(schema, parsed);
561
- if (valid || !holoInstance.strict) {
562
- output.set(parsed.id, parsed);
563
- }
564
- } else {
565
- output.set(parsed.id, parsed);
566
- }
567
- } catch (error) {
568
- console.error('Error processing data:', error);
569
- }
570
- };
571
-
572
- const handleData = async (data) => {
573
- if (!data) {
574
- resolve([]);
575
- return;
576
- }
577
-
578
- const initialPromises = [];
579
- Object.keys(data)
580
- .filter(key => key !== '_')
581
- .forEach(key => {
582
- initialPromises.push(processData(data[key], key));
583
- });
584
-
585
- try {
586
- await Promise.all(initialPromises);
587
- resolve(Array.from(output.values()));
588
- } catch (error) {
589
- console.error('Error in getAll:', error);
590
- resolve([]);
591
- }
592
- };
593
-
594
- const dataPath = password ?
595
- user.get('private').get(lens) :
596
- holoInstance.gun.get(holoInstance.appname).get(holon).get(lens);
597
-
598
- dataPath.once(handleData);
599
- });
600
- } catch (error) {
601
- console.error('Error in getAll:', error);
602
- return [];
603
- }
604
- }
605
-
606
- /**
607
- * Parses data from GunDB, handling various data formats and references.
608
- * @param {HoloSphere} holoInstance - The HoloSphere instance.
609
- * @param {*} data - The data to parse, could be a string, object, or GunDB reference.
610
- * @returns {Promise<object>} - The parsed data.
611
- */
612
- export async function parse(holoInstance, rawData) {
613
- if (rawData === null || rawData === undefined) {
614
- console.warn('Parse received null or undefined data.');
615
- return null;
616
- }
617
-
618
- // 1. Handle string data (attempt JSON parse)
619
- if (typeof rawData === 'string') {
620
- try {
621
- return JSON.parse(rawData);
622
- } catch (error) {
623
- // It's a string, but not valid JSON. Return null.
624
- console.warn("Data was a string but not valid JSON, returning null:", rawData);
625
- return null;
626
- }
627
- }
628
-
629
- // 2. Handle object data
630
- if (typeof rawData === 'object' && rawData !== null) {
631
- // Check for GunDB soul link (less common now?)
632
- if (rawData.soul && typeof rawData.soul === 'string' && rawData.id) {
633
- // This looks like a Hologram object based on structure.
634
- // Return it as is; resolution happens later if needed.
635
- return rawData;
636
- } else if (holoInstance.isHologram(rawData)) {
637
- // Explicitly check using isHologram (might be redundant if structure check above is reliable)
638
- return rawData;
639
- } else if (rawData._) {
640
- // Handle potential GunDB metadata remnants (attempt cleanup)
641
- console.warn('Parsing raw Gun object with metadata (_) - attempting cleanup:', rawData);
642
- const potentialData = Object.keys(rawData).reduce((acc, k) => {
643
- if (k !== '_') {
644
- acc[k] = rawData[k];
645
- }
646
- return acc;
647
- }, {});
648
- if (Object.keys(potentialData).length === 0) {
649
- console.warn('Raw Gun object had only metadata (_), returning null.');
650
- return null;
651
- }
652
- return potentialData; // Return cleaned-up object
653
- } else {
654
- // Assume it's a regular plain object
655
- return rawData;
656
- }
657
- }
658
-
659
- // 3. Handle other unexpected types
660
- console.warn("Parsing encountered unexpected data type, returning null:", typeof rawData, rawData);
661
- return null;
662
- }
663
-
664
- /**
665
- * Deletes a specific key from a given holon and lens.
666
- * If the deleted data was a hologram, this function also attempts to update the
667
- * target data node's `_holograms` set by marking the deleted hologram's soul as 'DELETED'.
668
- * @param {HoloSphere} holoInstance - The HoloSphere instance.
669
- * @param {string} holon - The holon identifier.
670
- * @param {string} lens - The lens from which to delete the key.
671
- * @param {string} key - The specific key to delete.
672
- * @param {string} [password] - Optional password for private holon.
673
- * @returns {Promise<boolean>} - Returns true if successful
674
- */
675
- export async function deleteFunc(holoInstance, holon, lens, key, password = null) { // Renamed to deleteFunc to avoid keyword conflict
676
- if (!holon || !lens || !key) {
677
- throw new Error('delete: Missing required parameters');
678
- }
679
-
680
- try {
681
- let user = null;
682
- if (password) {
683
- user = holoInstance.gun.user();
684
- await new Promise((resolve, reject) => {
685
- const userNameString = holoInstance.userName(holon); // Use holon for deleteFunc
686
- user.auth(userNameString, password, (authAck) => {
687
- if (authAck.err) {
688
- console.log(`Initial auth failed for ${userNameString} during deleteFunc, attempting to create...`);
689
- user.create(userNameString, password, (createAck) => {
690
- if (createAck.err) {
691
- if (createAck.err.includes("already created")) {
692
- console.log(`User ${userNameString} already existed during deleteFunc, re-attempting auth with fresh user object.`);
693
- const freshUser = holoInstance.gun.user(); // Get a new user object
694
- freshUser.auth(userNameString, password, (secondAuthAck) => {
695
- if (secondAuthAck.err) {
696
- reject(new Error(`Failed to auth with fresh user object after create attempt (user existed) for ${userNameString} during deleteFunc: ${secondAuthAck.err}`));
697
- } else {
698
- resolve();
699
- }
700
- });
701
- } else {
702
- reject(new Error(`Failed to create user ${userNameString} during deleteFunc: ${createAck.err}`));
703
- }
704
- } else {
705
- console.log(`User ${userNameString} created successfully during deleteFunc, attempting auth...`);
706
- user.auth(userNameString, password, (secondAuthAck) => {
707
- if (secondAuthAck.err) {
708
- reject(new Error(`Failed to auth after create for ${userNameString} during deleteFunc: ${secondAuthAck.err}`));
709
- } else {
710
- resolve();
711
- }
712
- });
713
- }
714
- });
715
- } else {
716
- resolve(); // Auth successful
717
- }
718
- });
719
- });
720
- }
721
-
722
- const dataPath = password ?
723
- user.get('private').get(lens).get(key) :
724
- holoInstance.gun.get(holoInstance.appname).get(holon).get(lens).get(key);
725
-
726
- // --- Start: Hologram Tracking Removal ---
727
- let trackingRemovalPromise = Promise.resolve(); // Default to resolved promise
728
-
729
- // 1. Get the data first to check if it's a hologram
730
- const rawDataToDelete = await new Promise((resolve) => dataPath.once(resolve));
731
- let dataToDelete = null;
732
- try {
733
- if (typeof rawDataToDelete === 'string') {
734
- dataToDelete = JSON.parse(rawDataToDelete);
735
- } else {
736
- // Handle cases where it might already be an object (though likely string)
737
- dataToDelete = rawDataToDelete;
738
- }
739
- } catch(e) {
740
- console.warn("[deleteFunc] Could not JSON parse data for deletion check:", rawDataToDelete, e);
741
- dataToDelete = null; // Ensure it's null if parsing fails
742
- }
743
-
744
- // 2. If it is a hologram, try to remove its reference from the target
745
- const isDataHologram = dataToDelete && holoInstance.isHologram(dataToDelete);
746
-
747
- if (isDataHologram) {
748
- try {
749
- const targetSoul = dataToDelete.soul;
750
- const targetSoulInfo = holoInstance.parseSoulPath(targetSoul);
751
-
752
- if (targetSoulInfo) {
753
- const targetNodeRef = holoInstance.getNodeRef(targetSoul);
754
- const deletedHologramSoul = `${holoInstance.appname}/${holon}/${lens}/${key}`;
755
-
756
- // Create a promise that resolves when the hologram is removed from the list
757
- trackingRemovalPromise = new Promise((resolveTrack) => { // No reject needed, just warn on error
758
- targetNodeRef.get('_holograms').get(deletedHologramSoul).put(null, (ack) => { // Remove the hologram entry completely
759
- if (ack.err) {
760
- console.warn(`[deleteFunc] Error removing hologram ${deletedHologramSoul} from target ${targetSoul}:`, ack.err);
761
- }
762
- resolveTrack(); // Resolve regardless of ack error to not block main delete
763
- });
764
- });
765
- } else {
766
- // Keep this warning
767
- console.warn(`Could not parse target soul ${targetSoul} for hologram tracking removal.`);
768
- }
769
- } catch (trackingError) {
770
- // Keep this warning
771
- console.warn(`Error initiating hologram reference removal from target ${dataToDelete.soul}:`, trackingError);
772
- // Ensure trackingRemovalPromise remains resolved if setup fails
773
- trackingRemovalPromise = Promise.resolve();
774
- }
775
- }
776
- // --- End: Hologram Tracking Removal ---
777
-
778
- // 3. Wait for the tracking removal attempt to be acknowledged
779
- await trackingRemovalPromise;
780
- // Log removed
781
-
782
- // 4. Proceed with the actual deletion of the hologram node itself
783
- return new Promise((resolve, reject) => {
784
- dataPath.put(null, ack => {
785
- if (ack.err) {
786
- reject(new Error(ack.err));
787
- } else {
788
- resolve(true);
789
- }
790
- });
791
- });
792
- } catch (error) {
793
- console.error('Error in delete:', error);
794
- throw error;
795
- }
796
- }
797
-
798
- /**
799
- * Deletes all keys from a given holon and lens.
800
- * @param {HoloSphere} holoInstance - The HoloSphere instance.
801
- * @param {string} holon - The holon identifier.
802
- * @param {string} lens - The lens from which to delete all keys.
803
- * @param {string} [password] - Optional password for private holon.
804
- * @returns {Promise<boolean>} - Returns true if successful
805
- */
806
- export async function deleteAll(holoInstance, holon, lens, password = null) {
807
- if (!holon || !lens) {
808
- console.error('deleteAll: Missing holon or lens parameter');
809
- return false;
810
- }
811
-
812
- try {
813
- let user = null;
814
- if (password) {
815
- user = holoInstance.gun.user();
816
- await new Promise((resolve, reject) => {
817
- const userNameString = holoInstance.userName(holon); // Use holon for deleteAll
818
- user.auth(userNameString, password, (authAck) => {
819
- if (authAck.err) {
820
- console.log(`Initial auth failed for ${userNameString} during deleteAll, attempting to create...`);
821
- user.create(userNameString, password, (createAck) => {
822
- if (createAck.err) {
823
- if (createAck.err.includes("already created") || createAck.err.includes("already being created")) {
824
- console.log(`User ${userNameString} already existed or being created during deleteAll, re-attempting auth with fresh user object.`);
825
- const freshUser = holoInstance.gun.user(); // Get a new user object
826
- freshUser.auth(userNameString, password, (secondAuthAck) => {
827
- if (secondAuthAck.err) {
828
- console.log(`Auth still failed after user existed check: ${secondAuthAck.err}. Resolving anyway for test operations.`);
829
- resolve(); // Resolve anyway to allow test operations
830
- } else {
831
- resolve();
832
- }
833
- });
834
- } else {
835
- console.log(`Create user error (resolving anyway for operations): ${createAck.err}`);
836
- resolve(); // Resolve anyway to allow test operations
837
- }
838
- } else {
839
- console.log(`User ${userNameString} created successfully during deleteAll, attempting auth...`);
840
- user.auth(userNameString, password, (secondAuthAck) => {
841
- if (secondAuthAck.err) {
842
- reject(new Error(`Failed to auth after create for ${userNameString} during deleteAll: ${secondAuthAck.err}`));
843
- } else {
844
- resolve();
845
- }
846
- });
847
- }
848
- });
849
- } else {
850
- resolve(); // Auth successful
851
- }
852
- });
853
- });
854
- }
855
-
856
- return new Promise((resolve) => {
857
- let deletionPromises = [];
858
-
859
- const dataPath = password ?
860
- user.get('private').get(lens) :
861
- holoInstance.gun.get(holoInstance.appname).get(holon).get(lens);
862
-
863
- // First get all the data to find keys to delete
864
- dataPath.once(async (data) => {
865
- if (!data) {
866
- resolve(true); // Nothing to delete
867
- return;
868
- }
869
-
870
- // Get all keys except Gun's metadata key '_'
871
- const keys = Object.keys(data).filter(key => key !== '_');
872
-
873
- // Process each key to handle holograms properly
874
- for (const key of keys) {
875
- try {
876
- // Get the data to check if it's a hologram
877
- const itemPath = password ?
878
- user.get('private').get(lens).get(key) :
879
- holoInstance.gun.get(holoInstance.appname).get(holon).get(lens).get(key);
880
-
881
- const rawDataToDelete = await new Promise((resolveItem) => itemPath.once(resolveItem));
882
- let dataToDelete = null;
883
-
884
- try {
885
- if (typeof rawDataToDelete === 'string') {
886
- dataToDelete = JSON.parse(rawDataToDelete);
887
- } else {
888
- dataToDelete = rawDataToDelete;
889
- }
890
- } catch(e) {
891
- console.warn("[deleteAll] Could not JSON parse data for deletion check:", rawDataToDelete, e);
892
- dataToDelete = null;
893
- }
894
-
895
- // Check if it's a hologram and handle accordingly
896
- const isDataHologram = dataToDelete && holoInstance.isHologram(dataToDelete);
897
-
898
- if (isDataHologram) {
899
- // Handle hologram deletion - remove from target's _holograms list
900
- try {
901
- const targetSoul = dataToDelete.soul;
902
- const targetSoulInfo = holoInstance.parseSoulPath(targetSoul);
903
-
904
- if (targetSoulInfo) {
905
- const targetNodeRef = holoInstance.getNodeRef(targetSoul);
906
- const deletedHologramSoul = `${holoInstance.appname}/${holon}/${lens}/${key}`;
907
-
908
- // Remove the hologram from target's _holograms list
909
- await new Promise((resolveTrack) => {
910
- targetNodeRef.get('_holograms').get(deletedHologramSoul).put(null, (ack) => {
911
- if (ack.err) {
912
- console.warn(`[deleteAll] Error removing hologram ${deletedHologramSoul} from target ${targetSoul}:`, ack.err);
913
- }
914
- resolveTrack();
915
- });
916
- });
917
- } else {
918
- console.warn(`Could not parse target soul ${targetSoul} for hologram tracking removal during deleteAll.`);
919
- }
920
- } catch (trackingError) {
921
- console.warn(`Error removing hologram reference from target ${dataToDelete.soul} during deleteAll:`, trackingError);
922
- }
923
- }
924
-
925
- // Create deletion promise for this key (whether it's a hologram or not)
926
- deletionPromises.push(
927
- new Promise((resolveDelete) => {
928
- const deletePath = password ?
929
- user.get('private').get(lens).get(key) :
930
- holoInstance.gun.get(holoInstance.appname).get(holon).get(lens).get(key);
931
-
932
- deletePath.put(null, ack => {
933
- resolveDelete(!!ack.ok); // Convert to boolean
934
- });
935
- })
936
- );
937
- } catch (error) {
938
- console.warn(`Error processing key ${key} during deleteAll:`, error);
939
- // Still try to delete the item even if hologram processing failed
940
- deletionPromises.push(
941
- new Promise((resolveDelete) => {
942
- const deletePath = password ?
943
- user.get('private').get(lens).get(key) :
944
- holoInstance.gun.get(holoInstance.appname).get(holon).get(lens).get(key);
945
-
946
- deletePath.put(null, ack => {
947
- resolveDelete(!!ack.ok);
948
- });
949
- })
950
- );
951
- }
952
- }
953
-
954
- // Wait for all deletions to complete
955
- Promise.all(deletionPromises)
956
- .then(results => {
957
- const allSuccessful = results.every(result => result === true);
958
- resolve(allSuccessful);
959
- })
960
- .catch(error => {
961
- console.error('Error in deleteAll:', error);
962
- resolve(false);
963
- });
964
- });
965
- });
966
- } catch (error) {
967
- console.error('Error in deleteAll:', error);
968
- return false;
969
- }
970
- }
971
-
972
- // Export all content operations as default
973
- export default {
974
- put,
975
- get,
976
- getAll,
977
- parse,
978
- delete: deleteFunc,
979
- deleteAll
980
- };