holosphere 1.1.19 → 2.0.0-alpha0

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