holosphere 1.1.10 → 1.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/content.js ADDED
@@ -0,0 +1,797 @@
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<boolean>} - Returns true if successful, false if there was an error
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
+ console.log(`Redirecting put for data (ID: ${data.id}). Original target ${targetHolon}/${targetLens}/${targetKey} contained hologram (ID: ${existingItemAtPath.id}, Soul: ${existingItemAtPath.soul}). New target is ${soulInfo.holon}/${soulInfo.lens}/${soulInfo.key}.`);
54
+ targetHolon = soulInfo.holon; // Redirect holon
55
+ targetLens = soulInfo.lens; // Redirect lens
56
+ targetKey = soulInfo.key; // Redirect key (important!)
57
+ // data.id should ideally match soulInfo.key if this is consistent.
58
+ // If data.id is different, it means we are writing data with one ID to a path derived from another ID's soul.
59
+ if (data.id !== targetKey) {
60
+ 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}'.`);
61
+ // It's crucial that the actual GunDB path uses targetKey.
62
+ // The 'data' object itself retains its original 'data.id' unless explicitly changed.
63
+ }
64
+
65
+ } else {
66
+ 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.`);
67
+ }
68
+ }
69
+ } catch (error) {
70
+ // If 'get' fails (e.g., item not found, auth error), proceed with original target.
71
+ // A "not found" error is expected if the path is new.
72
+ if (error.message && error.message.includes('RESOLVED_NULL')) {
73
+ // This is fine, means nothing was at the path.
74
+ } else {
75
+ console.warn(`Error checking for existing hologram at ${targetHolon}/${targetLens}/${targetKey}: ${error.message}. Proceeding with original target.`);
76
+ }
77
+ }
78
+ // --- End: Target Path Hologram Redirection Logic ---
79
+
80
+ // The data being stored is 'data'. Its 'id' property is 'data.id'.
81
+ // The final storage path key is 'targetKey'.
82
+
83
+ // Check if the data *being put* is a hologram (this variable is used later for schema and propagation)
84
+ const isHologram = holoInstance.isHologram(data);
85
+
86
+ // Get and validate schema only in strict mode for non-holograms (data being put)
87
+ if (holoInstance.strict && !isHologram) {
88
+ const schema = await holoInstance.getSchema(targetLens); // Use targetLens for schema
89
+ if (!schema) {
90
+ throw new Error('Schema required in strict mode');
91
+ }
92
+ const dataToValidate = JSON.parse(JSON.stringify(data)); // Validate the actual data
93
+ const valid = holoInstance.validator.validate(schema, dataToValidate);
94
+
95
+ if (!valid) {
96
+ const errorMsg = `Schema validation failed: ${JSON.stringify(holoInstance.validator.errors)}`;
97
+ throw new Error(errorMsg);
98
+ }
99
+ }
100
+
101
+ try {
102
+ let user = null;
103
+ if (password) {
104
+ user = holoInstance.gun.user();
105
+ await new Promise((resolve, reject) => {
106
+ const userNameString = holoInstance.userName(targetHolon); // Use targetHolon for put
107
+ user.auth(userNameString, password, (authAck) => {
108
+ if (authAck.err) {
109
+ console.log(`Initial auth failed for ${userNameString} during put, attempting to create...`);
110
+ user.create(userNameString, password, (createAck) => {
111
+ if (createAck.err) {
112
+ if (createAck.err.includes("already created")) {
113
+ console.log(`User ${userNameString} already existed during put, re-attempting auth with fresh user object.`);
114
+ const freshUser = holoInstance.gun.user(); // Get a new user object
115
+ freshUser.auth(userNameString, password, (secondAuthAck) => {
116
+ if (secondAuthAck.err) {
117
+ reject(new Error(`Failed to auth with fresh user object after create attempt (user existed) for ${userNameString} during put: ${secondAuthAck.err}`));
118
+ } else {
119
+ resolve();
120
+ }
121
+ });
122
+ } else {
123
+ reject(new Error(`Failed to create user ${userNameString} during put: ${createAck.err}`));
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
+ // Remove isHologram field before storing - NO LONGER NEEDED
146
+ // if (data && data.isHologram !== undefined) {
147
+ // delete data.isHologram;
148
+ // }
149
+ const payload = JSON.stringify(data); // The data being stored
150
+
151
+ const putCallback = async (ack) => {
152
+ if (ack.err) {
153
+ reject(new Error(ack.err));
154
+ } else {
155
+ // --- Start: Hologram Tracking Logic (for data *being put*, if it's a hologram) ---
156
+ if (isHologram) {
157
+ try {
158
+ const storedDataSoulInfo = holoInstance.parseSoulPath(data.soul);
159
+ if (storedDataSoulInfo) {
160
+ const targetNodeRef = holoInstance.getNodeRef(data.soul); // Target of the data *being put*
161
+ // Soul of the hologram that was *actually stored* at targetHolon/targetLens/targetKey
162
+ const storedHologramInstanceSoul = `${holoInstance.appname}/${targetHolon}/${targetLens}/${targetKey}`;
163
+
164
+ targetNodeRef.get('_holograms').get(storedHologramInstanceSoul).put(true);
165
+
166
+ console.log(`Data (ID: ${data.id}) being put is a hologram. Added its instance soul ${storedHologramInstanceSoul} to its target ${data.soul}'s _holograms set.`);
167
+ } else {
168
+ console.warn(`Data (ID: ${data.id}) being put is a hologram, but could not parse its soul ${data.soul} for tracking.`);
169
+ }
170
+ } catch (trackingError) {
171
+ console.warn(`Error updating _holograms set for the target of the data being put (data ID: ${data.id}, soul: ${data.soul}):`, trackingError);
172
+ }
173
+ }
174
+ // --- End: Hologram Tracking Logic ---
175
+
176
+ // Only notify subscribers for actual data, not holograms
177
+ if (!isHologram) {
178
+ holoInstance.notifySubscribers({
179
+ holon: targetHolon, // Notify with final target
180
+ lens: targetLens,
181
+ ...data // The data that was put
182
+ });
183
+ }
184
+
185
+ // Auto-propagate to federation by default (if data *being put* is not a hologram)
186
+ const shouldPropagate = options.autoPropagate !== false && !isHologram;
187
+ let propagationResult = null;
188
+
189
+ if (shouldPropagate) {
190
+ try {
191
+ const propagationOptions = {
192
+ useHolograms: true,
193
+ ...options.propagationOptions
194
+ };
195
+
196
+ propagationResult = await holoInstance.propagate(
197
+ targetHolon, // Propagate from final target
198
+ targetLens,
199
+ data, // The data that was put
200
+ propagationOptions
201
+ );
202
+
203
+ if (propagationResult && propagationResult.errors > 0) {
204
+ console.warn('Auto-propagation had errors:', propagationResult);
205
+ }
206
+ } catch (propError) {
207
+ console.warn('Error in auto-propagation:', propError);
208
+ }
209
+ }
210
+
211
+ resolve({
212
+ success: true,
213
+ isHologramAtPath: isHologram, // whether the data *put* was a hologram
214
+ pathHolon: targetHolon,
215
+ pathLens: targetLens,
216
+ pathKey: targetKey,
217
+ propagationResult
218
+ });
219
+ }
220
+ };
221
+
222
+ // Use targetHolon, targetLens, and targetKey for the actual storage path
223
+ const dataPath = password ?
224
+ user.get('private').get(targetLens).get(targetKey) :
225
+ holoInstance.gun.get(holoInstance.appname).get(targetHolon).get(targetLens).get(targetKey);
226
+
227
+ dataPath.put(payload, putCallback);
228
+ } catch (error) {
229
+ reject(error);
230
+ }
231
+ });
232
+ } catch (error) {
233
+ console.error('Error in put:', error);
234
+ throw error;
235
+ }
236
+ }
237
+
238
+ /**
239
+ * Retrieves content from the specified holon and lens.
240
+ * @param {HoloSphere} holoInstance - The HoloSphere instance.
241
+ * @param {string} holon - The holon identifier.
242
+ * @param {string} lens - The lens from which to retrieve content.
243
+ * @param {string} key - The specific key to retrieve.
244
+ * @param {string} [password] - Optional password for private holon.
245
+ * @param {object} [options] - Additional options
246
+ * @param {boolean} [options.resolveHolograms=true] - Whether to automatically resolve holograms
247
+ * @param {object} [options.validationOptions] - Options passed to the schema validator
248
+ * @returns {Promise<object|null>} - The retrieved content or null if not found.
249
+ */
250
+ export async function get(holoInstance, holon, lens, key, password = null, options = {}) {
251
+ if (!holon || !lens || !key) {
252
+ console.error('get: Missing required parameters');
253
+ return null;
254
+ }
255
+
256
+ // Destructure options, including visited
257
+ const { resolveHolograms = true, validationOptions = {}, visited } = options;
258
+
259
+ // Get schema for validation if in strict mode
260
+ let schema = null;
261
+ if (holoInstance.strict) {
262
+ schema = await holoInstance.getSchema(lens);
263
+ if (!schema) {
264
+ throw new Error('Schema required in strict mode');
265
+ }
266
+ }
267
+
268
+ try {
269
+ let user = null;
270
+ if (password) {
271
+ user = holoInstance.gun.user();
272
+ await new Promise((resolve, reject) => {
273
+ const userNameString = holoInstance.userName(holon); // Use holon for get
274
+ user.auth(userNameString, password, (authAck) => {
275
+ if (authAck.err) {
276
+ // If auth fails, reject immediately. Do not attempt to create user.
277
+ reject(new Error(`Authentication failed for ${userNameString} during get: ${authAck.err}`));
278
+ } else {
279
+ resolve(); // Auth successful
280
+ }
281
+ });
282
+ });
283
+ }
284
+
285
+ return new Promise((resolve) => {
286
+ const handleData = async (data) => {
287
+ let parsed = null; // Declare parsed here to make it available in catch
288
+ if (!data) {
289
+ resolve(null);
290
+ return;
291
+ }
292
+
293
+ try {
294
+ parsed = await holoInstance.parse(data); // Assign to the outer scoped parsed
295
+
296
+ if (!parsed) {
297
+ resolve(null);
298
+ return;
299
+ }
300
+
301
+ // Check if this is a hologram that needs to be resolved
302
+ if (resolveHolograms && holoInstance.isHologram(parsed)) {
303
+ const resolvedValue = await holoInstance.resolveHologram(parsed, {
304
+ followHolograms: resolveHolograms,
305
+ visited: visited
306
+ });
307
+
308
+ console.log(`### get/handleData received resolved value:`, resolvedValue);
309
+
310
+ if (resolvedValue === null) {
311
+ // This means resolveHologram determined the target doesn't exist or a sub-resolution failed to null.
312
+ console.warn(`Hologram at ${holon}/${lens}/${key} could not be fully resolved (target not found or sub-problem). Resolving null.`);
313
+ resolve(null);
314
+ return; // Important to return after resolving
315
+ }
316
+ // If resolveHologram encountered a circular ref, it would throw, not return.
317
+ // If it returned the hologram itself (if we ever revert to that), this logic would need adjustment.
318
+ // For now, assume resolvedValue is either the resolved data or we've returned null above.
319
+
320
+ if (resolvedValue !== parsed) {
321
+ console.log(`### get/handleData using resolved data:`, resolvedValue);
322
+ parsed = resolvedValue;
323
+ }
324
+ }
325
+
326
+ // Perform schema validation if needed
327
+ if (schema) {
328
+ const valid = holoInstance.validator.validate(schema, parsed);
329
+ if (!valid) {
330
+ console.error('get: Invalid data according to schema:', holoInstance.validator.errors);
331
+ if (holoInstance.strict) {
332
+ resolve(null);
333
+ return;
334
+ }
335
+ }
336
+ }
337
+
338
+ resolve(parsed);
339
+ } catch (error) {
340
+ if (error.message?.startsWith('CIRCULAR_REFERENCE')) {
341
+ console.warn(`Caught circular reference during get/handleData for key ${key}. Resolving null.`);
342
+ resolve(null);
343
+ } else {
344
+ console.error('Error processing data in get/handleData:', error);
345
+ resolve(null); // For other errors, resolve null
346
+ }
347
+ }
348
+ };
349
+
350
+ const dataPath = password ?
351
+ user.get('private').get(lens).get(key) :
352
+ holoInstance.gun.get(holoInstance.appname).get(holon).get(lens).get(key);
353
+
354
+ dataPath.once(handleData);
355
+ });
356
+ } catch (error) {
357
+ console.error('Error in get:', error);
358
+ return null;
359
+ }
360
+ }
361
+
362
+ /**
363
+ * Retrieves all content from the specified holon and lens.
364
+ * @param {HoloSphere} holoInstance - The HoloSphere instance.
365
+ * @param {string} holon - The holon identifier.
366
+ * @param {string} lens - The lens from which to retrieve content.
367
+ * @param {string} [password] - Optional password for private holon.
368
+ * @returns {Promise<Array<object>>} - The retrieved content.
369
+ */
370
+ export async function getAll(holoInstance, holon, lens, password = null) {
371
+ if (!holon || !lens) {
372
+ throw new Error('getAll: Missing required parameters');
373
+ }
374
+
375
+ const schema = await holoInstance.getSchema(lens);
376
+ if (!schema && holoInstance.strict) {
377
+ throw new Error('getAll: Schema required in strict mode');
378
+ }
379
+
380
+ try {
381
+ let user = null;
382
+ if (password) {
383
+ user = holoInstance.gun.user();
384
+ await new Promise((resolve, reject) => {
385
+ const userNameString = holoInstance.userName(holon); // Use holon for getAll
386
+ user.auth(userNameString, password, (authAck) => {
387
+ if (authAck.err) {
388
+ console.log(`Initial auth failed for ${userNameString} during getAll, attempting to create...`);
389
+ user.create(userNameString, password, (createAck) => {
390
+ if (createAck.err) {
391
+ if (createAck.err.includes("already created")) {
392
+ console.log(`User ${userNameString} already existed during getAll, re-attempting auth with fresh user object.`);
393
+ const freshUser = holoInstance.gun.user(); // Get a new user object
394
+ freshUser.auth(userNameString, password, (secondAuthAck) => {
395
+ if (secondAuthAck.err) {
396
+ reject(new Error(`Failed to auth with fresh user object after create attempt (user existed) for ${userNameString} during getAll: ${secondAuthAck.err}`));
397
+ } else {
398
+ resolve();
399
+ }
400
+ });
401
+ } else {
402
+ reject(new Error(`Failed to create user ${userNameString} during getAll: ${createAck.err}`));
403
+ }
404
+ } else {
405
+ console.log(`User ${userNameString} created successfully during getAll, attempting auth...`);
406
+ user.auth(userNameString, password, (secondAuthAck) => {
407
+ if (secondAuthAck.err) {
408
+ reject(new Error(`Failed to auth after create for ${userNameString} during getAll: ${secondAuthAck.err}`));
409
+ } else {
410
+ resolve();
411
+ }
412
+ });
413
+ }
414
+ });
415
+ } else {
416
+ resolve(); // Auth successful
417
+ }
418
+ });
419
+ });
420
+ }
421
+
422
+ return new Promise((resolve) => {
423
+ const output = new Map();
424
+
425
+ const processData = async (data, key) => {
426
+ if (!data || key === '_') return;
427
+
428
+ try {
429
+ const parsed = await holoInstance.parse(data); // Use instance's parse
430
+ if (!parsed || !parsed.id) return;
431
+
432
+ // Check if this is a hologram that needs to be resolved
433
+ if (holoInstance.isHologram(parsed)) {
434
+ const resolved = await holoInstance.resolveHologram(parsed, {
435
+ followHolograms: true
436
+ });
437
+
438
+ if (resolved !== parsed) {
439
+ // Hologram was resolved successfully
440
+ if (schema) {
441
+ const valid = holoInstance.validator.validate(schema, resolved);
442
+ if (valid || !holoInstance.strict) {
443
+ output.set(resolved.id, resolved);
444
+ }
445
+ } else {
446
+ output.set(resolved.id, resolved);
447
+ }
448
+ return;
449
+ }
450
+ }
451
+
452
+ if (schema) {
453
+ const valid = holoInstance.validator.validate(schema, parsed);
454
+ if (valid || !holoInstance.strict) {
455
+ output.set(parsed.id, parsed);
456
+ }
457
+ } else {
458
+ output.set(parsed.id, parsed);
459
+ }
460
+ } catch (error) {
461
+ console.error('Error processing data:', error);
462
+ }
463
+ };
464
+
465
+ const handleData = async (data) => {
466
+ if (!data) {
467
+ resolve([]);
468
+ return;
469
+ }
470
+
471
+ const initialPromises = [];
472
+ Object.keys(data)
473
+ .filter(key => key !== '_')
474
+ .forEach(key => {
475
+ initialPromises.push(processData(data[key], key));
476
+ });
477
+
478
+ try {
479
+ await Promise.all(initialPromises);
480
+ resolve(Array.from(output.values()));
481
+ } catch (error) {
482
+ console.error('Error in getAll:', error);
483
+ resolve([]);
484
+ }
485
+ };
486
+
487
+ const dataPath = password ?
488
+ user.get('private').get(lens) :
489
+ holoInstance.gun.get(holoInstance.appname).get(holon).get(lens);
490
+
491
+ dataPath.once(handleData);
492
+ });
493
+ } catch (error) {
494
+ console.error('Error in getAll:', error);
495
+ return [];
496
+ }
497
+ }
498
+
499
+ /**
500
+ * Parses data from GunDB, handling various data formats and references.
501
+ * @param {HoloSphere} holoInstance - The HoloSphere instance.
502
+ * @param {*} data - The data to parse, could be a string, object, or GunDB reference.
503
+ * @returns {Promise<object>} - The parsed data.
504
+ */
505
+ export async function parse(holoInstance, rawData) {
506
+ if (rawData === null || rawData === undefined) {
507
+ console.warn('Parse received null or undefined data.');
508
+ return null;
509
+ }
510
+
511
+ // 1. Handle string data (attempt JSON parse)
512
+ if (typeof rawData === 'string') {
513
+ try {
514
+ return JSON.parse(rawData);
515
+ } catch (error) {
516
+ // It's a string, but not valid JSON. Return null.
517
+ console.warn("Data was a string but not valid JSON, returning null:", rawData);
518
+ return null;
519
+ }
520
+ }
521
+
522
+ // 2. Handle object data
523
+ if (typeof rawData === 'object' && rawData !== null) {
524
+ // Check for GunDB soul link (less common now?)
525
+ if (rawData.soul && typeof rawData.soul === 'string' && rawData.id) {
526
+ // This looks like a Hologram object based on structure.
527
+ // Return it as is; resolution happens later if needed.
528
+ return rawData;
529
+ } else if (holoInstance.isHologram(rawData)) {
530
+ // Explicitly check using isHologram (might be redundant if structure check above is reliable)
531
+ return rawData;
532
+ } else if (rawData._) {
533
+ // Handle potential GunDB metadata remnants (attempt cleanup)
534
+ console.warn('Parsing raw Gun object with metadata (_) - attempting cleanup:', rawData);
535
+ const potentialData = Object.keys(rawData).reduce((acc, k) => {
536
+ if (k !== '_') {
537
+ acc[k] = rawData[k];
538
+ }
539
+ return acc;
540
+ }, {});
541
+ if (Object.keys(potentialData).length === 0) {
542
+ console.warn('Raw Gun object had only metadata (_), returning null.');
543
+ return null;
544
+ }
545
+ return potentialData; // Return cleaned-up object
546
+ } else {
547
+ // Assume it's a regular plain object
548
+ return rawData;
549
+ }
550
+ }
551
+
552
+ // 3. Handle other unexpected types
553
+ console.warn("Parsing encountered unexpected data type, returning null:", typeof rawData, rawData);
554
+ return null;
555
+ }
556
+
557
+ /**
558
+ * Deletes a specific key from a given holon and lens.
559
+ * If the deleted data was a hologram, this function also attempts to update the
560
+ * target data node's `_holograms` set by marking the deleted hologram's soul as 'DELETED'.
561
+ * @param {HoloSphere} holoInstance - The HoloSphere instance.
562
+ * @param {string} holon - The holon identifier.
563
+ * @param {string} lens - The lens from which to delete the key.
564
+ * @param {string} key - The specific key to delete.
565
+ * @param {string} [password] - Optional password for private holon.
566
+ * @returns {Promise<boolean>} - Returns true if successful
567
+ */
568
+ export async function deleteFunc(holoInstance, holon, lens, key, password = null) { // Renamed to deleteFunc to avoid keyword conflict
569
+ if (!holon || !lens || !key) {
570
+ throw new Error('delete: Missing required parameters');
571
+ }
572
+
573
+ try {
574
+ let user = null;
575
+ if (password) {
576
+ user = holoInstance.gun.user();
577
+ await new Promise((resolve, reject) => {
578
+ const userNameString = holoInstance.userName(holon); // Use holon for deleteFunc
579
+ user.auth(userNameString, password, (authAck) => {
580
+ if (authAck.err) {
581
+ console.log(`Initial auth failed for ${userNameString} during deleteFunc, attempting to create...`);
582
+ user.create(userNameString, password, (createAck) => {
583
+ if (createAck.err) {
584
+ if (createAck.err.includes("already created")) {
585
+ console.log(`User ${userNameString} already existed during deleteFunc, re-attempting auth with fresh user object.`);
586
+ const freshUser = holoInstance.gun.user(); // Get a new user object
587
+ freshUser.auth(userNameString, password, (secondAuthAck) => {
588
+ if (secondAuthAck.err) {
589
+ reject(new Error(`Failed to auth with fresh user object after create attempt (user existed) for ${userNameString} during deleteFunc: ${secondAuthAck.err}`));
590
+ } else {
591
+ resolve();
592
+ }
593
+ });
594
+ } else {
595
+ reject(new Error(`Failed to create user ${userNameString} during deleteFunc: ${createAck.err}`));
596
+ }
597
+ } else {
598
+ console.log(`User ${userNameString} created successfully during deleteFunc, attempting auth...`);
599
+ user.auth(userNameString, password, (secondAuthAck) => {
600
+ if (secondAuthAck.err) {
601
+ reject(new Error(`Failed to auth after create for ${userNameString} during deleteFunc: ${secondAuthAck.err}`));
602
+ } else {
603
+ resolve();
604
+ }
605
+ });
606
+ }
607
+ });
608
+ } else {
609
+ resolve(); // Auth successful
610
+ }
611
+ });
612
+ });
613
+ }
614
+
615
+ const dataPath = password ?
616
+ user.get('private').get(lens).get(key) :
617
+ holoInstance.gun.get(holoInstance.appname).get(holon).get(lens).get(key);
618
+
619
+ // --- Start: Hologram Tracking Removal ---
620
+ let trackingRemovalPromise = Promise.resolve(); // Default to resolved promise
621
+
622
+ // 1. Get the data first to check if it's a hologram
623
+ const rawDataToDelete = await new Promise((resolve) => dataPath.once(resolve));
624
+ let dataToDelete = null;
625
+ try {
626
+ if (typeof rawDataToDelete === 'string') {
627
+ dataToDelete = JSON.parse(rawDataToDelete);
628
+ } else {
629
+ // Handle cases where it might already be an object (though likely string)
630
+ dataToDelete = rawDataToDelete;
631
+ }
632
+ } catch(e) {
633
+ console.warn("[deleteFunc] Could not JSON parse data for deletion check:", rawDataToDelete, e);
634
+ dataToDelete = null; // Ensure it's null if parsing fails
635
+ }
636
+
637
+ // 2. If it is a hologram, try to remove its reference from the target
638
+ const isDataHologram = dataToDelete && holoInstance.isHologram(dataToDelete);
639
+
640
+ if (isDataHologram) {
641
+ try {
642
+ const targetSoul = dataToDelete.soul;
643
+ const targetSoulInfo = holoInstance.parseSoulPath(targetSoul);
644
+
645
+ if (targetSoulInfo) {
646
+ const targetNodeRef = holoInstance.getNodeRef(targetSoul);
647
+ const deletedHologramSoul = `${holoInstance.appname}/${holon}/${lens}/${key}`;
648
+
649
+ // Create a promise that resolves when the put('DELETED') ack is received
650
+ trackingRemovalPromise = new Promise((resolveTrack) => { // No reject needed, just warn on error
651
+ targetNodeRef.get('_holograms').get(deletedHologramSoul).put('DELETED', (ack) => { // <-- Use 'DELETED' string
652
+ if (ack.err) {
653
+ // Keep this warning for actual errors
654
+ console.warn(`[deleteFunc] Error ack for marking hologram ${deletedHologramSoul} as DELETED in target ${targetSoul}:`, ack.err);
655
+ }
656
+ // Ack received log removed
657
+ resolveTrack(); // Resolve regardless of ack error to not block main delete
658
+ });
659
+ });
660
+ } else {
661
+ // Keep this warning
662
+ console.warn(`Could not parse target soul ${targetSoul} for hologram tracking removal.`);
663
+ }
664
+ } catch (trackingError) {
665
+ // Keep this warning
666
+ console.warn(`Error initiating hologram reference removal from target ${dataToDelete.soul}:`, trackingError);
667
+ // Ensure trackingRemovalPromise remains resolved if setup fails
668
+ trackingRemovalPromise = Promise.resolve();
669
+ }
670
+ }
671
+ // --- End: Hologram Tracking Removal ---
672
+
673
+ // 3. Wait for the tracking removal attempt to be acknowledged
674
+ await trackingRemovalPromise;
675
+ // Log removed
676
+
677
+ // 4. Proceed with the actual deletion of the hologram node itself
678
+ return new Promise((resolve, reject) => {
679
+ dataPath.put(null, ack => {
680
+ if (ack.err) {
681
+ reject(new Error(ack.err));
682
+ } else {
683
+ resolve(true);
684
+ }
685
+ });
686
+ });
687
+ } catch (error) {
688
+ console.error('Error in delete:', error);
689
+ throw error;
690
+ }
691
+ }
692
+
693
+ /**
694
+ * Deletes all keys from a given holon and lens.
695
+ * @param {HoloSphere} holoInstance - The HoloSphere instance.
696
+ * @param {string} holon - The holon identifier.
697
+ * @param {string} lens - The lens from which to delete all keys.
698
+ * @param {string} [password] - Optional password for private holon.
699
+ * @returns {Promise<boolean>} - Returns true if successful
700
+ */
701
+ export async function deleteAll(holoInstance, holon, lens, password = null) {
702
+ if (!holon || !lens) {
703
+ console.error('deleteAll: Missing holon or lens parameter');
704
+ return false;
705
+ }
706
+
707
+ try {
708
+ let user = null;
709
+ if (password) {
710
+ user = holoInstance.gun.user();
711
+ await new Promise((resolve, reject) => {
712
+ const userNameString = holoInstance.userName(holon); // Use holon for deleteAll
713
+ user.auth(userNameString, password, (authAck) => {
714
+ if (authAck.err) {
715
+ console.log(`Initial auth failed for ${userNameString} during deleteAll, attempting to create...`);
716
+ user.create(userNameString, password, (createAck) => {
717
+ if (createAck.err) {
718
+ if (createAck.err.includes("already created")) {
719
+ console.log(`User ${userNameString} already existed during deleteAll, re-attempting auth with fresh user object.`);
720
+ const freshUser = holoInstance.gun.user(); // Get a new user object
721
+ freshUser.auth(userNameString, password, (secondAuthAck) => {
722
+ if (secondAuthAck.err) {
723
+ reject(new Error(`Failed to auth with fresh user object after create attempt (user existed) for ${userNameString} during deleteAll: ${secondAuthAck.err}`));
724
+ } else {
725
+ resolve();
726
+ }
727
+ });
728
+ } else {
729
+ reject(new Error(`Failed to create user ${userNameString} during deleteAll: ${createAck.err}`));
730
+ }
731
+ } else {
732
+ console.log(`User ${userNameString} created successfully during deleteAll, attempting auth...`);
733
+ user.auth(userNameString, password, (secondAuthAck) => {
734
+ if (secondAuthAck.err) {
735
+ reject(new Error(`Failed to auth after create for ${userNameString} during deleteAll: ${secondAuthAck.err}`));
736
+ } else {
737
+ resolve();
738
+ }
739
+ });
740
+ }
741
+ });
742
+ } else {
743
+ resolve(); // Auth successful
744
+ }
745
+ });
746
+ });
747
+ }
748
+
749
+ return new Promise((resolve) => {
750
+ let deletionPromises = [];
751
+
752
+ const dataPath = password ?
753
+ user.get('private').get(lens) :
754
+ holoInstance.gun.get(holoInstance.appname).get(holon).get(lens);
755
+
756
+ // First get all the data to find keys to delete
757
+ dataPath.once((data) => {
758
+ if (!data) {
759
+ resolve(true); // Nothing to delete
760
+ return;
761
+ }
762
+
763
+ // Get all keys except Gun's metadata key '_'
764
+ const keys = Object.keys(data).filter(key => key !== '_');
765
+
766
+ // Create deletion promises for each key
767
+ keys.forEach(key => {
768
+ deletionPromises.push(
769
+ new Promise((resolveDelete) => {
770
+ const deletePath = password ?
771
+ user.get('private').get(lens).get(key) :
772
+ holoInstance.gun.get(holoInstance.appname).get(holon).get(lens).get(key);
773
+
774
+ deletePath.put(null, ack => {
775
+ resolveDelete(!!ack.ok); // Convert to boolean
776
+ });
777
+ })
778
+ );
779
+ });
780
+
781
+ // Wait for all deletions to complete
782
+ Promise.all(deletionPromises)
783
+ .then(results => {
784
+ const allSuccessful = results.every(result => result === true);
785
+ resolve(allSuccessful);
786
+ })
787
+ .catch(error => {
788
+ console.error('Error in deleteAll:', error);
789
+ resolve(false);
790
+ });
791
+ });
792
+ });
793
+ } catch (error) {
794
+ console.error('Error in deleteAll:', error);
795
+ return false;
796
+ }
797
+ }