holosphere 2.0.0-alpha2 → 2.0.0-alpha5

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 (98) hide show
  1. package/dist/2019-D2OG2idw.js +6680 -0
  2. package/dist/2019-D2OG2idw.js.map +1 -0
  3. package/dist/2019-EION3wKo.cjs +8 -0
  4. package/dist/2019-EION3wKo.cjs.map +1 -0
  5. package/dist/_commonjsHelpers-C37NGDzP.cjs +2 -0
  6. package/dist/_commonjsHelpers-C37NGDzP.cjs.map +1 -0
  7. package/dist/_commonjsHelpers-CUmg6egw.js +7 -0
  8. package/dist/_commonjsHelpers-CUmg6egw.js.map +1 -0
  9. package/dist/browser-BSniCNqO.js +3058 -0
  10. package/dist/browser-BSniCNqO.js.map +1 -0
  11. package/dist/browser-Cq59Ij19.cjs +2 -0
  12. package/dist/browser-Cq59Ij19.cjs.map +1 -0
  13. package/dist/cjs/holosphere.cjs +1 -1
  14. package/dist/esm/holosphere.js +50 -53
  15. package/dist/index-BG8FStkt.cjs +12 -0
  16. package/dist/index-BG8FStkt.cjs.map +1 -0
  17. package/dist/index-Bbey4GkP.js +37869 -0
  18. package/dist/index-Bbey4GkP.js.map +1 -0
  19. package/dist/index-Cp3xctq8.js +15104 -0
  20. package/dist/index-Cp3xctq8.js.map +1 -0
  21. package/dist/index-hfVGRwSr.cjs +5 -0
  22. package/dist/index-hfVGRwSr.cjs.map +1 -0
  23. package/dist/indexeddb-storage-BD70pN7q.cjs +2 -0
  24. package/dist/indexeddb-storage-BD70pN7q.cjs.map +1 -0
  25. package/dist/{indexeddb-storage-CMW4qRQS.js → indexeddb-storage-Bjg84U5R.js} +49 -13
  26. package/dist/indexeddb-storage-Bjg84U5R.js.map +1 -0
  27. package/dist/{memory-storage-DQzcAZlf.js → memory-storage-CD0XFayE.js} +6 -2
  28. package/dist/memory-storage-CD0XFayE.js.map +1 -0
  29. package/dist/{memory-storage-DmePEP2q.cjs → memory-storage-DmMyJtOo.cjs} +2 -2
  30. package/dist/memory-storage-DmMyJtOo.cjs.map +1 -0
  31. package/dist/{secp256k1-vOXp40Fx.js → secp256k1-69sS9O-P.js} +2 -393
  32. package/dist/secp256k1-69sS9O-P.js.map +1 -0
  33. package/dist/secp256k1-TcN6vWGh.cjs +12 -0
  34. package/dist/secp256k1-TcN6vWGh.cjs.map +1 -0
  35. package/docs/CONTRACTS.md +797 -0
  36. package/examples/demo.html +47 -0
  37. package/package.json +10 -5
  38. package/src/contracts/abis/Appreciative.json +1280 -0
  39. package/src/contracts/abis/AppreciativeFactory.json +101 -0
  40. package/src/contracts/abis/Bundle.json +1435 -0
  41. package/src/contracts/abis/BundleFactory.json +106 -0
  42. package/src/contracts/abis/Holon.json +881 -0
  43. package/src/contracts/abis/Holons.json +330 -0
  44. package/src/contracts/abis/Managed.json +1262 -0
  45. package/src/contracts/abis/ManagedFactory.json +149 -0
  46. package/src/contracts/abis/Membrane.json +261 -0
  47. package/src/contracts/abis/Splitter.json +1624 -0
  48. package/src/contracts/abis/SplitterFactory.json +220 -0
  49. package/src/contracts/abis/TestToken.json +321 -0
  50. package/src/contracts/abis/Zoned.json +1461 -0
  51. package/src/contracts/abis/ZonedFactory.json +154 -0
  52. package/src/contracts/chain-manager.js +375 -0
  53. package/src/contracts/deployer.js +443 -0
  54. package/src/contracts/event-listener.js +507 -0
  55. package/src/contracts/holon-contracts.js +344 -0
  56. package/src/contracts/index.js +83 -0
  57. package/src/contracts/networks.js +224 -0
  58. package/src/contracts/operations.js +670 -0
  59. package/src/contracts/queries.js +589 -0
  60. package/src/core/holosphere.js +453 -1
  61. package/src/crypto/nostr-utils.js +263 -0
  62. package/src/federation/handshake.js +455 -0
  63. package/src/federation/hologram.js +1 -1
  64. package/src/hierarchical/upcast.js +6 -5
  65. package/src/index.js +463 -1939
  66. package/src/lib/ai-methods.js +308 -0
  67. package/src/lib/contract-methods.js +293 -0
  68. package/src/lib/errors.js +23 -0
  69. package/src/lib/federation-methods.js +238 -0
  70. package/src/lib/index.js +26 -0
  71. package/src/spatial/h3-operations.js +2 -2
  72. package/src/storage/backends/gundb-backend.js +377 -46
  73. package/src/storage/global-tables.js +28 -1
  74. package/src/storage/gun-auth.js +303 -0
  75. package/src/storage/gun-federation.js +776 -0
  76. package/src/storage/gun-references.js +198 -0
  77. package/src/storage/gun-schema.js +291 -0
  78. package/src/storage/gun-wrapper.js +347 -31
  79. package/src/storage/indexeddb-storage.js +49 -11
  80. package/src/storage/memory-storage.js +5 -0
  81. package/src/storage/nostr-async.js +194 -37
  82. package/src/storage/nostr-client.js +580 -51
  83. package/src/storage/persistent-storage.js +6 -1
  84. package/src/storage/unified-storage.js +119 -0
  85. package/src/subscriptions/manager.js +1 -1
  86. package/types/index.d.ts +133 -0
  87. package/dist/index-CDfIuXew.js +0 -15974
  88. package/dist/index-CDfIuXew.js.map +0 -1
  89. package/dist/index-ifOgtDvd.cjs +0 -3
  90. package/dist/index-ifOgtDvd.cjs.map +0 -1
  91. package/dist/indexeddb-storage-CMW4qRQS.js.map +0 -1
  92. package/dist/indexeddb-storage-DLZOgetM.cjs +0 -2
  93. package/dist/indexeddb-storage-DLZOgetM.cjs.map +0 -1
  94. package/dist/memory-storage-DQzcAZlf.js.map +0 -1
  95. package/dist/memory-storage-DmePEP2q.cjs.map +0 -1
  96. package/dist/secp256k1-CP0ZkpAx.cjs +0 -13
  97. package/dist/secp256k1-CP0ZkpAx.cjs.map +0 -1
  98. package/dist/secp256k1-vOXp40Fx.js.map +0 -1
@@ -5,10 +5,23 @@
5
5
 
6
6
  /**
7
7
  * Global subscription manager to prevent duplicate subscriptions
8
- * Maps: pathPrefix -> subscription object
8
+ * Maps: subscriptionKey -> subscription object
9
9
  */
10
10
  const globalSubscriptions = new Map();
11
11
 
12
+ /**
13
+ * Single-path subscription manager (for nostrSubscribe)
14
+ * Maps: subscriptionKey -> { subscription, callbacks: [] }
15
+ */
16
+ const singlePathSubscriptions = new Map();
17
+
18
+ /**
19
+ * Query deduplication for nostrGet - prevents duplicate relay queries
20
+ * Maps: queryKey -> { promise, timestamp, callbacks: [] }
21
+ */
22
+ const pendingQueries = new Map();
23
+ const QUERY_DEDUP_WINDOW = 2000; // 2 second window for deduplication
24
+
12
25
  /**
13
26
  * Write data as Nostr event (parameterized replaceable event)
14
27
  * @param {Object} client - NostrClient instance
@@ -34,6 +47,7 @@ export async function nostrPut(client, path, data, kind = 30000) {
34
47
  /**
35
48
  * Read data from Nostr (query by d-tag)
36
49
  * LOCAL-FIRST: Checks persistent storage first, never blocks on network
50
+ * Uses query deduplication to prevent duplicate relay queries within a time window
37
51
  * @param {Object} client - NostrClient instance
38
52
  * @param {string} path - Path identifier
39
53
  * @param {number} kind - Event kind (default: 30000)
@@ -77,7 +91,42 @@ export async function nostrGet(client, path, kind = 30000, options = {}) {
77
91
  }
78
92
  }
79
93
 
80
- // Fallback to relay query (existing logic)
94
+ // Query deduplication: Check if same query is already pending
95
+ const queryKey = `get:${client.publicKey}:${kind}:${path}:${authors.join(',')}`;
96
+ const pending = pendingQueries.get(queryKey);
97
+
98
+ if (pending && Date.now() - pending.timestamp < QUERY_DEDUP_WINDOW) {
99
+ // Reuse pending query result
100
+ return pending.promise;
101
+ }
102
+
103
+ // Create new query with deduplication
104
+ const queryPromise = _executeNostrGet(client, path, kind, authors, timeout, options);
105
+
106
+ pendingQueries.set(queryKey, {
107
+ promise: queryPromise,
108
+ timestamp: Date.now(),
109
+ });
110
+
111
+ // Clean up after promise resolves
112
+ queryPromise.finally(() => {
113
+ // Remove from pending after a short delay to allow coalescing
114
+ setTimeout(() => {
115
+ const current = pendingQueries.get(queryKey);
116
+ if (current && current.promise === queryPromise) {
117
+ pendingQueries.delete(queryKey);
118
+ }
119
+ }, QUERY_DEDUP_WINDOW);
120
+ });
121
+
122
+ return queryPromise;
123
+ }
124
+
125
+ /**
126
+ * Internal function to execute nostrGet query
127
+ * @private
128
+ */
129
+ async function _executeNostrGet(client, path, kind, authors, timeout, options) {
81
130
  const filter = {
82
131
  kinds: [kind],
83
132
  authors: authors, // Support multiple authors for cross-holosphere queries
@@ -96,13 +145,17 @@ export async function nostrGet(client, path, kind = 30000, options = {}) {
96
145
 
97
146
  try {
98
147
  const data = JSON.parse(event.content);
148
+ // Skip deleted items
149
+ if (data._deleted) {
150
+ return null;
151
+ }
99
152
  // Optionally include author information
100
153
  if (options.includeAuthor) {
101
154
  data._author = event.pubkey;
102
155
  }
103
156
  return data;
104
157
  } catch (error) {
105
- console.error('Failed to parse event content:', error);
158
+ // Silent - return null for unparseable events
106
159
  return null;
107
160
  }
108
161
  }
@@ -110,6 +163,7 @@ export async function nostrGet(client, path, kind = 30000, options = {}) {
110
163
  /**
111
164
  * Query all events under a path prefix
112
165
  * LOCAL-FIRST: Checks persistent storage first, never blocks on network
166
+ * Uses query deduplication to prevent duplicate relay queries within a time window
113
167
  * @param {Object} client - NostrClient instance
114
168
  * @param {string} pathPrefix - Path prefix to match
115
169
  * @param {number} kind - Event kind (default: 30000)
@@ -143,8 +197,11 @@ export async function nostrGetAll(client, pathPrefix, kind = 30000, options = {}
143
197
  try {
144
198
  const data = JSON.parse(event.content);
145
199
 
146
- // Skip deleted items
147
- if (data._deleted) continue;
200
+ // Handle deleted items - remove from map if this is newer
201
+ if (data._deleted) {
202
+ byPath.delete(path);
203
+ continue;
204
+ }
148
205
 
149
206
  // Optionally include author information
150
207
  if (options.includeAuthor) {
@@ -167,7 +224,41 @@ export async function nostrGetAll(client, pathPrefix, kind = 30000, options = {}
167
224
  }
168
225
  }
169
226
 
170
- // Fallback to relay query (existing logic)
227
+ // Query deduplication: Check if same query is already pending
228
+ const queryKey = `getAll:${client.publicKey}:${kind}:${pathPrefix}:${authors.join(',')}`;
229
+ const pending = pendingQueries.get(queryKey);
230
+
231
+ if (pending && Date.now() - pending.timestamp < QUERY_DEDUP_WINDOW) {
232
+ // Reuse pending query result
233
+ return pending.promise;
234
+ }
235
+
236
+ // Create new query with deduplication
237
+ const queryPromise = _executeNostrGetAll(client, pathPrefix, kind, authors, timeout, limit, options);
238
+
239
+ pendingQueries.set(queryKey, {
240
+ promise: queryPromise,
241
+ timestamp: Date.now(),
242
+ });
243
+
244
+ // Clean up after promise resolves
245
+ queryPromise.finally(() => {
246
+ setTimeout(() => {
247
+ const current = pendingQueries.get(queryKey);
248
+ if (current && current.promise === queryPromise) {
249
+ pendingQueries.delete(queryKey);
250
+ }
251
+ }, QUERY_DEDUP_WINDOW);
252
+ });
253
+
254
+ return queryPromise;
255
+ }
256
+
257
+ /**
258
+ * Internal function to execute nostrGetAll query
259
+ * @private
260
+ */
261
+ async function _executeNostrGetAll(client, pathPrefix, kind, authors, timeout, limit, options) {
171
262
  const filter = {
172
263
  kinds: [kind],
173
264
  authors: authors,
@@ -192,8 +283,11 @@ export async function nostrGetAll(client, pathPrefix, kind = 30000, options = {}
192
283
  try {
193
284
  const data = JSON.parse(event.content);
194
285
 
195
- // Skip deleted items
196
- if (data._deleted) continue;
286
+ // Handle deleted items - remove from map if this is newer
287
+ if (data._deleted) {
288
+ byPath.delete(dTag);
289
+ continue;
290
+ }
197
291
 
198
292
  // Optionally include author information
199
293
  if (options.includeAuthor) {
@@ -201,7 +295,7 @@ export async function nostrGetAll(client, pathPrefix, kind = 30000, options = {}
201
295
  }
202
296
  byPath.set(dTag, { data, created_at: event.created_at });
203
297
  } catch (error) {
204
- console.error('Failed to parse event content:', error);
298
+ // Silent - skip unparseable events
205
299
  }
206
300
  }
207
301
  }
@@ -252,13 +346,22 @@ export async function nostrGetAllHybrid(client, pathPrefix, kind = 30000, option
252
346
  if (!existing || event.created_at > existing.created_at) {
253
347
  try {
254
348
  const data = JSON.parse(event.content);
349
+ // Skip null or non-object content
350
+ if (data === null || typeof data !== 'object') {
351
+ continue;
352
+ }
353
+ // Handle deleted items - remove from map if this is newer
354
+ if (data._deleted) {
355
+ byPath.delete(dTag);
356
+ continue;
357
+ }
255
358
  // Optionally include author information
256
359
  if (options.includeAuthor) {
257
360
  data._author = event.pubkey;
258
361
  }
259
362
  byPath.set(dTag, { data, created_at: event.created_at });
260
363
  } catch (error) {
261
- console.error('Failed to parse event content:', error);
364
+ // Silent - skip unparseable events
262
365
  }
263
366
  }
264
367
  }
@@ -319,14 +422,10 @@ export async function nostrDelete(client, path, kind = 30000) {
319
422
 
320
423
  // Publish deletion event (best effort, don't block on failure)
321
424
  try {
322
- const publishResult = await client.publish(deletionEvent);
323
- console.log(`✅ NIP-09 deletion event published for ${path}:`, {
324
- coordinate,
325
- relays: publishResult?.relays?.length || 0,
326
- successful: publishResult?.successful || []
327
- });
425
+ await client.publish(deletionEvent);
426
+ // Silent success - deletion queued
328
427
  } catch (error) {
329
- console.error(`❌ Failed to publish NIP-09 deletion event for ${path}:`, error.message);
428
+ // Silent - deletion will be retried
330
429
  }
331
430
 
332
431
  return result;
@@ -438,15 +537,10 @@ export async function nostrDeleteAll(client, pathPrefix, kind = 30000) {
438
537
  content: '', // Optional deletion reason/note
439
538
  };
440
539
 
441
- const publishResult = await client.publish(deletionEvent);
442
- console.log(`✅ NIP-09 bulk deletion event published for ${pathPrefix}:`, {
443
- itemsDeleted: matching.length,
444
- coordinates: coordinates.length,
445
- relays: publishResult?.relays?.length || 0,
446
- successful: publishResult?.successful || []
447
- });
540
+ await client.publish(deletionEvent);
541
+ // Silent success - bulk deletion queued
448
542
  } catch (error) {
449
- console.error(`❌ Failed to publish NIP-09 bulk deletion event for ${pathPrefix}:`, error.message);
543
+ // Silent - bulk deletion will be retried
450
544
  }
451
545
 
452
546
  return {
@@ -458,6 +552,7 @@ export async function nostrDeleteAll(client, pathPrefix, kind = 30000) {
458
552
 
459
553
  /**
460
554
  * Subscribe to path changes
555
+ * Uses subscription deduplication - multiple subscribers to same path share one relay subscription
461
556
  * @param {Object} client - NostrClient instance
462
557
  * @param {string} path - Path to subscribe to
463
558
  * @param {Function} callback - Callback function (data, event) => void
@@ -466,7 +561,42 @@ export async function nostrDeleteAll(client, pathPrefix, kind = 30000) {
466
561
  */
467
562
  export function nostrSubscribe(client, path, callback, options = {}) {
468
563
  const kind = options.kind || 30000;
469
- const includeInitial = options.includeInitial !== false;
564
+
565
+ // Create unique key for this subscription
566
+ const subscriptionKey = `single:${client.publicKey}:${kind}:${path}`;
567
+
568
+ // Check if we already have an active subscription for this path
569
+ const existing = singlePathSubscriptions.get(subscriptionKey);
570
+ if (existing) {
571
+ // Add callback to existing subscription
572
+ existing.callbacks.push(callback);
573
+
574
+ // Return wrapper that removes only this callback on unsubscribe
575
+ return {
576
+ unsubscribe: () => {
577
+ const idx = existing.callbacks.indexOf(callback);
578
+ if (idx > -1) {
579
+ existing.callbacks.splice(idx, 1);
580
+ }
581
+
582
+ // If no more callbacks, unsubscribe the whole thing
583
+ if (existing.callbacks.length === 0) {
584
+ existing.subscription.unsubscribe();
585
+ singlePathSubscriptions.delete(subscriptionKey);
586
+ }
587
+ }
588
+ };
589
+ }
590
+
591
+ // Create new subscription
592
+ const callbacks = [callback];
593
+ const subscriptionInfo = {
594
+ callbacks,
595
+ subscription: null,
596
+ };
597
+
598
+ // Store before creating subscription to handle race conditions
599
+ singlePathSubscriptions.set(subscriptionKey, subscriptionInfo);
470
600
 
471
601
  const filter = {
472
602
  kinds: [kind],
@@ -475,36 +605,57 @@ export function nostrSubscribe(client, path, callback, options = {}) {
475
605
  limit: 10, // Limit results for single item subscription
476
606
  };
477
607
 
478
- let initialLoadComplete = false;
479
-
480
608
  const subscription = client.subscribe(
481
609
  filter,
482
610
  (event) => {
483
611
  // Verify event is from our public key (relay may not respect author filter)
484
612
  if (event.pubkey !== client.publicKey) {
485
- console.warn('[nostrSubscribe] Rejecting event from different author:', {
486
- expected: client.publicKey,
487
- received: event.pubkey,
488
- eventId: event.id
489
- });
490
613
  return;
491
614
  }
492
615
 
493
616
  try {
494
617
  const data = JSON.parse(event.content);
495
- callback(data, event);
618
+
619
+ // Skip deleted items - don't send tombstones to subscribers
620
+ if (data._deleted) {
621
+ return;
622
+ }
623
+
624
+ // Dispatch to all registered callbacks
625
+ for (const cb of callbacks) {
626
+ try {
627
+ cb(data, event);
628
+ } catch (err) {
629
+ console.error('Subscription callback error:', err);
630
+ }
631
+ }
496
632
  } catch (error) {
497
633
  console.error('Failed to parse event in subscription:', error);
498
634
  }
499
635
  },
500
636
  {
501
637
  onEOSE: () => {
502
- initialLoadComplete = true;
638
+ // EOSE received
503
639
  },
504
640
  }
505
641
  );
506
642
 
507
- return subscription;
643
+ subscriptionInfo.subscription = subscription;
644
+
645
+ return {
646
+ unsubscribe: () => {
647
+ const idx = callbacks.indexOf(callback);
648
+ if (idx > -1) {
649
+ callbacks.splice(idx, 1);
650
+ }
651
+
652
+ // If no more callbacks, unsubscribe the whole thing
653
+ if (callbacks.length === 0) {
654
+ subscription.unsubscribe();
655
+ singlePathSubscriptions.delete(subscriptionKey);
656
+ }
657
+ }
658
+ };
508
659
  }
509
660
 
510
661
  /**
@@ -589,9 +740,15 @@ export async function nostrSubscribeMany(client, pathPrefix, callback, options =
589
740
 
590
741
  // Client-side prefix filter
591
742
  if (dTag && dTag.startsWith(pathPrefix)) {
592
- acceptedCount++;
593
743
  try {
594
744
  const data = JSON.parse(event.content);
745
+
746
+ // Skip deleted items - don't send tombstones to subscribers
747
+ if (data._deleted) {
748
+ return;
749
+ }
750
+
751
+ acceptedCount++;
595
752
  // Notify all registered callbacks
596
753
  for (const cb of callbacks) {
597
754
  cb(data, dTag, event);