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.
- package/dist/2019-D2OG2idw.js +6680 -0
- package/dist/2019-D2OG2idw.js.map +1 -0
- package/dist/2019-EION3wKo.cjs +8 -0
- package/dist/2019-EION3wKo.cjs.map +1 -0
- package/dist/_commonjsHelpers-C37NGDzP.cjs +2 -0
- package/dist/_commonjsHelpers-C37NGDzP.cjs.map +1 -0
- package/dist/_commonjsHelpers-CUmg6egw.js +7 -0
- package/dist/_commonjsHelpers-CUmg6egw.js.map +1 -0
- package/dist/browser-BSniCNqO.js +3058 -0
- package/dist/browser-BSniCNqO.js.map +1 -0
- package/dist/browser-Cq59Ij19.cjs +2 -0
- package/dist/browser-Cq59Ij19.cjs.map +1 -0
- package/dist/cjs/holosphere.cjs +1 -1
- package/dist/esm/holosphere.js +50 -53
- package/dist/index-BG8FStkt.cjs +12 -0
- package/dist/index-BG8FStkt.cjs.map +1 -0
- package/dist/index-Bbey4GkP.js +37869 -0
- package/dist/index-Bbey4GkP.js.map +1 -0
- package/dist/index-Cp3xctq8.js +15104 -0
- package/dist/index-Cp3xctq8.js.map +1 -0
- package/dist/index-hfVGRwSr.cjs +5 -0
- package/dist/index-hfVGRwSr.cjs.map +1 -0
- package/dist/indexeddb-storage-BD70pN7q.cjs +2 -0
- package/dist/indexeddb-storage-BD70pN7q.cjs.map +1 -0
- package/dist/{indexeddb-storage-CMW4qRQS.js → indexeddb-storage-Bjg84U5R.js} +49 -13
- package/dist/indexeddb-storage-Bjg84U5R.js.map +1 -0
- package/dist/{memory-storage-DQzcAZlf.js → memory-storage-CD0XFayE.js} +6 -2
- package/dist/memory-storage-CD0XFayE.js.map +1 -0
- package/dist/{memory-storage-DmePEP2q.cjs → memory-storage-DmMyJtOo.cjs} +2 -2
- package/dist/memory-storage-DmMyJtOo.cjs.map +1 -0
- package/dist/{secp256k1-vOXp40Fx.js → secp256k1-69sS9O-P.js} +2 -393
- package/dist/secp256k1-69sS9O-P.js.map +1 -0
- package/dist/secp256k1-TcN6vWGh.cjs +12 -0
- package/dist/secp256k1-TcN6vWGh.cjs.map +1 -0
- package/docs/CONTRACTS.md +797 -0
- package/examples/demo.html +47 -0
- package/package.json +10 -5
- package/src/contracts/abis/Appreciative.json +1280 -0
- package/src/contracts/abis/AppreciativeFactory.json +101 -0
- package/src/contracts/abis/Bundle.json +1435 -0
- package/src/contracts/abis/BundleFactory.json +106 -0
- package/src/contracts/abis/Holon.json +881 -0
- package/src/contracts/abis/Holons.json +330 -0
- package/src/contracts/abis/Managed.json +1262 -0
- package/src/contracts/abis/ManagedFactory.json +149 -0
- package/src/contracts/abis/Membrane.json +261 -0
- package/src/contracts/abis/Splitter.json +1624 -0
- package/src/contracts/abis/SplitterFactory.json +220 -0
- package/src/contracts/abis/TestToken.json +321 -0
- package/src/contracts/abis/Zoned.json +1461 -0
- package/src/contracts/abis/ZonedFactory.json +154 -0
- package/src/contracts/chain-manager.js +375 -0
- package/src/contracts/deployer.js +443 -0
- package/src/contracts/event-listener.js +507 -0
- package/src/contracts/holon-contracts.js +344 -0
- package/src/contracts/index.js +83 -0
- package/src/contracts/networks.js +224 -0
- package/src/contracts/operations.js +670 -0
- package/src/contracts/queries.js +589 -0
- package/src/core/holosphere.js +453 -1
- package/src/crypto/nostr-utils.js +263 -0
- package/src/federation/handshake.js +455 -0
- package/src/federation/hologram.js +1 -1
- package/src/hierarchical/upcast.js +6 -5
- package/src/index.js +463 -1939
- package/src/lib/ai-methods.js +308 -0
- package/src/lib/contract-methods.js +293 -0
- package/src/lib/errors.js +23 -0
- package/src/lib/federation-methods.js +238 -0
- package/src/lib/index.js +26 -0
- package/src/spatial/h3-operations.js +2 -2
- package/src/storage/backends/gundb-backend.js +377 -46
- package/src/storage/global-tables.js +28 -1
- package/src/storage/gun-auth.js +303 -0
- package/src/storage/gun-federation.js +776 -0
- package/src/storage/gun-references.js +198 -0
- package/src/storage/gun-schema.js +291 -0
- package/src/storage/gun-wrapper.js +347 -31
- package/src/storage/indexeddb-storage.js +49 -11
- package/src/storage/memory-storage.js +5 -0
- package/src/storage/nostr-async.js +194 -37
- package/src/storage/nostr-client.js +580 -51
- package/src/storage/persistent-storage.js +6 -1
- package/src/storage/unified-storage.js +119 -0
- package/src/subscriptions/manager.js +1 -1
- package/types/index.d.ts +133 -0
- package/dist/index-CDfIuXew.js +0 -15974
- package/dist/index-CDfIuXew.js.map +0 -1
- package/dist/index-ifOgtDvd.cjs +0 -3
- package/dist/index-ifOgtDvd.cjs.map +0 -1
- package/dist/indexeddb-storage-CMW4qRQS.js.map +0 -1
- package/dist/indexeddb-storage-DLZOgetM.cjs +0 -2
- package/dist/indexeddb-storage-DLZOgetM.cjs.map +0 -1
- package/dist/memory-storage-DQzcAZlf.js.map +0 -1
- package/dist/memory-storage-DmePEP2q.cjs.map +0 -1
- package/dist/secp256k1-CP0ZkpAx.cjs +0 -13
- package/dist/secp256k1-CP0ZkpAx.cjs.map +0 -1
- 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:
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
//
|
|
147
|
-
if (data._deleted)
|
|
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
|
-
//
|
|
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
|
-
//
|
|
196
|
-
if (data._deleted)
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
323
|
-
|
|
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
|
-
|
|
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
|
-
|
|
442
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
638
|
+
// EOSE received
|
|
503
639
|
},
|
|
504
640
|
}
|
|
505
641
|
);
|
|
506
642
|
|
|
507
|
-
|
|
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);
|