@qntx/nostr 0.1.0 → 0.2.1

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 (186) hide show
  1. package/README.md +16 -0
  2. package/dist/abort-Ba8tDrOI.mjs +38 -0
  3. package/dist/abort-Ba8tDrOI.mjs.map +1 -0
  4. package/dist/{builder-BphJa-Y-.d.mts → builder-BWn55Paf.d.mts} +20 -14
  5. package/dist/{builder-q0VsvnYE.mjs → builder-CyrGKRVP.mjs} +32 -16
  6. package/dist/builder-CyrGKRVP.mjs.map +1 -0
  7. package/dist/{client-Bd8B1mYm.mjs → client-5DhgYV7P.mjs} +372 -195
  8. package/dist/client-5DhgYV7P.mjs.map +1 -0
  9. package/dist/client.d.mts +1 -1
  10. package/dist/client.mjs +1 -1
  11. package/dist/core.d.mts +8 -8
  12. package/dist/core.mjs +8 -9
  13. package/dist/error-BW7re1UN.d.mts +9 -0
  14. package/dist/error-B__OMBRY.mjs +14 -0
  15. package/dist/error-B__OMBRY.mjs.map +1 -0
  16. package/dist/{error-WEFS-R-4.d.mts → error-CR8Urc84.d.mts} +20 -8
  17. package/dist/{error-CwBxle6N.mjs → error-DrEIM7FO.mjs} +5 -3
  18. package/dist/error-DrEIM7FO.mjs.map +1 -0
  19. package/dist/{event-CzoQEFMF.mjs → event-D8Nfoh4K.mjs} +40 -32
  20. package/dist/event-D8Nfoh4K.mjs.map +1 -0
  21. package/dist/{event-B5D2jxt_.d.mts → event-fU3Y01PR.d.mts} +23 -20
  22. package/dist/{filter-BxwjEkZp.mjs → filter-BQl3GlHt.mjs} +42 -35
  23. package/dist/filter-BQl3GlHt.mjs.map +1 -0
  24. package/dist/filter-C0IMByE3.d.mts +37 -0
  25. package/dist/{gossip-C5RVB1UB.d.mts → gossip-BRQwIM_q.d.mts} +25 -23
  26. package/dist/{gossip-3xkRRCm_.mjs → gossip-oloDMWEj.mjs} +52 -34
  27. package/dist/gossip-oloDMWEj.mjs.map +1 -0
  28. package/dist/gossip.d.mts +1 -1
  29. package/dist/gossip.mjs +1 -1
  30. package/dist/{http-6vevbBlu.d.mts → http-C4HwIu4B.d.mts} +7 -7
  31. package/dist/{http-D2rhokZo.mjs → http-CuazNRIP.mjs} +10 -6
  32. package/dist/http-CuazNRIP.mjs.map +1 -0
  33. package/dist/index-B-CGNDPS.d.mts +207 -0
  34. package/dist/index-BIhSADwK.d.mts +339 -0
  35. package/dist/{index-BYMnhRIC.d.mts → index-Ba7bJ7DD.d.mts} +68 -50
  36. package/dist/{index-DaRyjo7O.d.mts → index-Boe8FshM.d.mts} +13 -7
  37. package/dist/index-D-TK_KJG.d.mts +84 -0
  38. package/dist/index-D1bqFHKB.d.mts +188 -0
  39. package/dist/index.d.mts +22 -20
  40. package/dist/index.mjs +19 -18
  41. package/dist/{key-Dn-aX4x4.mjs → key-Bbtys6o7.mjs} +22 -14
  42. package/dist/key-Bbtys6o7.mjs.map +1 -0
  43. package/dist/{key-BiGxGLhg.d.mts → key-BlsYhN1V.d.mts} +9 -6
  44. package/dist/{kind-BLl4cLOc.mjs → kind-Cxwhhkx8.mjs} +2 -1
  45. package/dist/kind-Cxwhhkx8.mjs.map +1 -0
  46. package/dist/{loaders-BNQszDAn.mjs → loaders-o6YXB_Ri.mjs} +329 -305
  47. package/dist/loaders-o6YXB_Ri.mjs.map +1 -0
  48. package/dist/loaders.d.mts +3 -3
  49. package/dist/loaders.mjs +2 -2
  50. package/dist/memory-C2EVx0_O.mjs +46 -0
  51. package/dist/memory-C2EVx0_O.mjs.map +1 -0
  52. package/dist/{memory-BCmhZPek.mjs → memory-index-Do1mz0WF.mjs} +212 -82
  53. package/dist/memory-index-Do1mz0WF.mjs.map +1 -0
  54. package/dist/{message-C_Uj-Y-c.mjs → message-DcY6Fi9o.mjs} +38 -24
  55. package/dist/message-DcY6Fi9o.mjs.map +1 -0
  56. package/dist/{message-DJC-Jz6T.d.mts → message-iGPIqnn0.d.mts} +15 -8
  57. package/dist/{nip17-BWq-Kfq5.d.mts → nip17-DNEkz8rM.d.mts} +18 -17
  58. package/dist/{nip19-D7905oIv.d.mts → nip19-BevCJZsp.d.mts} +23 -3
  59. package/dist/{nip46-C_DoxiS_.d.mts → nip46-DDV4dC56.d.mts} +13 -12
  60. package/dist/{nip59-DljW_sYh.d.mts → nip59-zQgdLcEr.d.mts} +28 -25
  61. package/dist/{nip65-Di5hZRuV.d.mts → nip65-DBfh2smt.d.mts} +8 -3
  62. package/dist/{nip77-ByxlgoQO.d.mts → nip77-DH4RhS5r.d.mts} +9 -8
  63. package/dist/nips/blossom.d.mts +37 -37
  64. package/dist/nips/blossom.mjs +67 -52
  65. package/dist/nips/blossom.mjs.map +1 -1
  66. package/dist/nips/nip04.d.mts +2 -3
  67. package/dist/nips/nip04.mjs +6 -10
  68. package/dist/nips/nip04.mjs.map +1 -1
  69. package/dist/nips/nip05.d.mts +37 -43
  70. package/dist/nips/nip05.mjs +57 -57
  71. package/dist/nips/nip05.mjs.map +1 -1
  72. package/dist/nips/nip10.d.mts +18 -19
  73. package/dist/nips/nip10.mjs +76 -61
  74. package/dist/nips/nip10.mjs.map +1 -1
  75. package/dist/nips/nip11.d.mts +9 -8
  76. package/dist/nips/nip11.mjs +14 -11
  77. package/dist/nips/nip11.mjs.map +1 -1
  78. package/dist/nips/nip13.d.mts +9 -9
  79. package/dist/nips/nip13.mjs +15 -17
  80. package/dist/nips/nip13.mjs.map +1 -1
  81. package/dist/nips/nip17.d.mts +1 -1
  82. package/dist/nips/nip17.mjs +17 -12
  83. package/dist/nips/nip17.mjs.map +1 -1
  84. package/dist/nips/nip19.d.mts +1 -1
  85. package/dist/nips/nip19.mjs +52 -29
  86. package/dist/nips/nip19.mjs.map +1 -1
  87. package/dist/nips/nip21.d.mts +5 -6
  88. package/dist/nips/nip21.mjs +8 -7
  89. package/dist/nips/nip21.mjs.map +1 -1
  90. package/dist/nips/nip27.d.mts +7 -8
  91. package/dist/nips/nip27.mjs +26 -25
  92. package/dist/nips/nip27.mjs.map +1 -1
  93. package/dist/nips/nip42.d.mts +5 -6
  94. package/dist/nips/nip42.mjs +3 -3
  95. package/dist/nips/nip42.mjs.map +1 -1
  96. package/dist/nips/nip44.d.mts +24 -7
  97. package/dist/nips/nip44.mjs +25 -15
  98. package/dist/nips/nip44.mjs.map +1 -1
  99. package/dist/nips/nip46.d.mts +1 -1
  100. package/dist/nips/nip46.mjs +52 -49
  101. package/dist/nips/nip46.mjs.map +1 -1
  102. package/dist/nips/nip49.d.mts +8 -7
  103. package/dist/nips/nip49.mjs +16 -12
  104. package/dist/nips/nip49.mjs.map +1 -1
  105. package/dist/nips/nip51.d.mts +25 -27
  106. package/dist/nips/nip51.mjs +47 -36
  107. package/dist/nips/nip51.mjs.map +1 -1
  108. package/dist/nips/nip57.d.mts +12 -13
  109. package/dist/nips/nip57.mjs +35 -38
  110. package/dist/nips/nip57.mjs.map +1 -1
  111. package/dist/nips/nip59.d.mts +1 -1
  112. package/dist/nips/nip59.mjs +14 -18
  113. package/dist/nips/nip59.mjs.map +1 -1
  114. package/dist/nips/nip65.d.mts +1 -1
  115. package/dist/nips/nip65.mjs +11 -8
  116. package/dist/nips/nip65.mjs.map +1 -1
  117. package/dist/nips/nip77.d.mts +1 -1
  118. package/dist/nips/nip77.mjs +56 -34
  119. package/dist/nips/nip77.mjs.map +1 -1
  120. package/dist/nips/nip96.d.mts +21 -12
  121. package/dist/nips/nip96.mjs +59 -36
  122. package/dist/nips/nip96.mjs.map +1 -1
  123. package/dist/nips/nip98.d.mts +12 -12
  124. package/dist/nips/nip98.mjs +18 -19
  125. package/dist/nips/nip98.mjs.map +1 -1
  126. package/dist/pool-B7p_reAm.d.mts +333 -0
  127. package/dist/{pool-C_j1OmBB.mjs → pool-C2xOcsun.mjs} +792 -568
  128. package/dist/pool-C2xOcsun.mjs.map +1 -0
  129. package/dist/reactive-DDlThDSZ.mjs +324 -0
  130. package/dist/reactive-DDlThDSZ.mjs.map +1 -0
  131. package/dist/reactive-DFpqtIf_.d.mts +66 -0
  132. package/dist/relay.d.mts +5 -4
  133. package/dist/relay.mjs +2 -2
  134. package/dist/report-CHld4uWj.mjs +32 -0
  135. package/dist/report-CHld4uWj.mjs.map +1 -0
  136. package/dist/{signer-CZ6K6FUc.mjs → signer-CKR9EHce.mjs} +107 -72
  137. package/dist/signer-CKR9EHce.mjs.map +1 -0
  138. package/dist/signer.d.mts +3 -3
  139. package/dist/signer.mjs +3 -2
  140. package/dist/storage-xXxLGCVb.mjs +1376 -0
  141. package/dist/storage-xXxLGCVb.mjs.map +1 -0
  142. package/dist/storage.d.mts +4 -4
  143. package/dist/storage.mjs +5 -4
  144. package/dist/store.d.mts +2 -0
  145. package/dist/store.mjs +2 -0
  146. package/dist/testing.d.mts +135 -0
  147. package/dist/testing.mjs +963 -0
  148. package/dist/testing.mjs.map +1 -0
  149. package/dist/types-2pCUprRU.d.mts +47 -0
  150. package/dist/types-CRQEk0Oi.d.mts +19 -0
  151. package/dist/util-LfQirgDk.mjs +113 -0
  152. package/dist/util-LfQirgDk.mjs.map +1 -0
  153. package/dist/wasm.d.mts +4 -4
  154. package/dist/wasm.mjs +54 -55
  155. package/dist/wasm.mjs.map +1 -1
  156. package/dist/websocket-CmeqvAkp.d.mts +27 -0
  157. package/package.json +93 -39
  158. package/dist/builder-q0VsvnYE.mjs.map +0 -1
  159. package/dist/client-Bd8B1mYm.mjs.map +0 -1
  160. package/dist/error-B49rbDzT.d.mts +0 -7
  161. package/dist/error-CwBxle6N.mjs.map +0 -1
  162. package/dist/error-DZEs5OuZ.mjs +0 -24
  163. package/dist/error-DZEs5OuZ.mjs.map +0 -1
  164. package/dist/event-CzoQEFMF.mjs.map +0 -1
  165. package/dist/filter-BxwjEkZp.mjs.map +0 -1
  166. package/dist/filter-DUEKP3q1.d.mts +0 -40
  167. package/dist/gossip-3xkRRCm_.mjs.map +0 -1
  168. package/dist/http-D2rhokZo.mjs.map +0 -1
  169. package/dist/index-C0C4e-Gn.d.mts +0 -55
  170. package/dist/index-DgOugrpF.d.mts +0 -275
  171. package/dist/index-EwL6fHLo.d.mts +0 -267
  172. package/dist/index-k2YhCgsh.d.mts +0 -57
  173. package/dist/key-Dn-aX4x4.mjs.map +0 -1
  174. package/dist/kind-BLl4cLOc.mjs.map +0 -1
  175. package/dist/loaders-BNQszDAn.mjs.map +0 -1
  176. package/dist/memory-BCmhZPek.mjs.map +0 -1
  177. package/dist/message-C_Uj-Y-c.mjs.map +0 -1
  178. package/dist/pool-C_j1OmBB.mjs.map +0 -1
  179. package/dist/pool-DhRi7l-b.d.mts +0 -297
  180. package/dist/signer-CZ6K6FUc.mjs.map +0 -1
  181. package/dist/storage-DOy0iLRE.mjs +0 -870
  182. package/dist/storage-DOy0iLRE.mjs.map +0 -1
  183. package/dist/types-BzyADqnw.d.mts +0 -19
  184. package/dist/types-Dn24TPFs.d.mts +0 -43
  185. package/dist/util-6tfLQ9JF.mjs +0 -73
  186. package/dist/util-6tfLQ9JF.mjs.map +0 -1
@@ -0,0 +1,324 @@
1
+ import { l as normalizeURL } from "./util-LfQirgDk.mjs";
2
+ import { _ as formatEventAddress, g as eventAddress, o as itemCompare } from "./event-D8Nfoh4K.mjs";
3
+ import { o as matchFilters, r as filterFingerprint } from "./filter-BQl3GlHt.mjs";
4
+ import { t as invokeSafely } from "./report-CHld4uWj.mjs";
5
+ import { t as MemoryIndex } from "./memory-index-Do1mz0WF.mjs";
6
+ //#region src/store/reactive.ts
7
+ const SEEN_ON_PER_ID = 16;
8
+ var WatchImpl = class {
9
+ store;
10
+ kind;
11
+ key;
12
+ filters;
13
+ compute;
14
+ equal;
15
+ snapshotIds;
16
+ #subscribers = /* @__PURE__ */ new Set();
17
+ #snapshot;
18
+ #version = -1;
19
+ #dirty = false;
20
+ #pendingRemove = false;
21
+ constructor(store, kind, key, compute, equal, snapshotIds, filters) {
22
+ this.store = store;
23
+ this.kind = kind;
24
+ this.key = key;
25
+ this.filters = filters;
26
+ this.compute = compute;
27
+ this.equal = equal;
28
+ this.snapshotIds = snapshotIds;
29
+ this.#snapshot = compute();
30
+ this.#version = store._version;
31
+ }
32
+ get subscribed() {
33
+ return this.#subscribers.size > 0;
34
+ }
35
+ get pinnedIds() {
36
+ return this.snapshotIds(this.#snapshot);
37
+ }
38
+ subscribe(onChange) {
39
+ const wasUnsubscribed = this.#subscribers.size === 0;
40
+ this.#subscribers.add(onChange);
41
+ this.#pendingRemove = false;
42
+ if (wasUnsubscribed && this.#version !== this.store._version) this.#dirty = true;
43
+ this.store._register(this);
44
+ return () => {
45
+ this.#subscribers.delete(onChange);
46
+ if (this.#subscribers.size > 0 || this.#pendingRemove) return;
47
+ this.#pendingRemove = true;
48
+ queueMicrotask(() => {
49
+ if (this.#pendingRemove) this.store._unregister(this);
50
+ });
51
+ };
52
+ }
53
+ getSnapshot() {
54
+ if (this.subscribed ? this.#dirty : this.#version !== this.store._version) {
55
+ const next = this.compute();
56
+ if (!this.equal(next, this.#snapshot)) this.#snapshot = next;
57
+ this.#version = this.store._version;
58
+ this.#dirty = false;
59
+ }
60
+ return this.#snapshot;
61
+ }
62
+ _markDirty() {
63
+ this.#dirty = true;
64
+ }
65
+ _notify() {
66
+ for (const onChange of this.#subscribers) invokeSafely(onChange);
67
+ }
68
+ };
69
+ const EVENT_IDS = (e) => e ? [e.id] : [];
70
+ const LIST_IDS = (events) => events.map((e) => e.id);
71
+ function sameRef(a, b) {
72
+ return a === b;
73
+ }
74
+ function sameList(a, b) {
75
+ if (a.length !== b.length) return false;
76
+ for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
77
+ return true;
78
+ }
79
+ /**
80
+ * Synchronous reactive in-memory event store backed by {@link MemoryIndex}. The single read source
81
+ * for Nostr events on the UI side: `Watch` snapshots keep referential stability for
82
+ * `useSyncExternalStore` and are invalidated on matching writes, deletions and tombstones. Writes
83
+ * batch notifications into one microtask flush.
84
+ */
85
+ var ReactiveEventStore = class {
86
+ #index;
87
+ #maxEvents;
88
+ #maxSeenOnEntries;
89
+ _version = 0;
90
+ #recency = /* @__PURE__ */ new Set();
91
+ #seenOn = /* @__PURE__ */ new Map();
92
+ #idWatches = /* @__PURE__ */ new Map();
93
+ #addressWatches = /* @__PURE__ */ new Map();
94
+ #queryCache = /* @__PURE__ */ new Map();
95
+ #queryRegistry = /* @__PURE__ */ new Set();
96
+ #dirty = /* @__PURE__ */ new Set();
97
+ #flushScheduled = false;
98
+ #insertListeners = /* @__PURE__ */ new Set();
99
+ constructor(opts) {
100
+ this.#maxEvents = opts?.maxEvents ?? 5e4;
101
+ this.#maxSeenOnEntries = opts?.maxSeenOnEntries ?? 2e4;
102
+ this.#index = new MemoryIndex({
103
+ maxTombstones: opts?.maxTombstones ?? 1e5,
104
+ maxWatermarks: this.#maxEvents,
105
+ onInsert: (event) => this.#onInsert(event),
106
+ onRemove: (event) => this.#onRemove(event)
107
+ });
108
+ }
109
+ add(event, relayUrl) {
110
+ const result = this.#index.put(event);
111
+ const { id } = event;
112
+ if (relayUrl !== void 0 && result !== "ephemeral" && result !== "rejected" && result !== "invalid") this.#recordSeen(id, relayUrl);
113
+ this.#evictIfNeeded(id);
114
+ return result;
115
+ }
116
+ addMany(events, relayUrl) {
117
+ const results = [];
118
+ for (const event of events) results.push(this.add(event, relayUrl));
119
+ return results;
120
+ }
121
+ /** Record a relay sighting for an id already in the index. No notification. */
122
+ markSeen(id, relayUrl) {
123
+ const key = id.toLowerCase();
124
+ if (this.#index.get(key) === void 0) return false;
125
+ this.#recordSeen(key, relayUrl);
126
+ return true;
127
+ }
128
+ /** Bulk load (initial hydration): no seenOn, one batched notification. */
129
+ hydrate(events) {
130
+ const sorted = [...events].sort(itemCompare);
131
+ for (const event of sorted) this.#index.put(event);
132
+ this.#evictIfNeeded();
133
+ }
134
+ remove(ids) {
135
+ return this.#index.remove(ids);
136
+ }
137
+ clear() {
138
+ this.#index.clear();
139
+ this.#seenOn.clear();
140
+ this.#recency.clear();
141
+ this._version += 1;
142
+ this.#invalidateAll();
143
+ }
144
+ get(id) {
145
+ const event = this.#index.get(id);
146
+ if (event) this.#touch(event.id);
147
+ return event;
148
+ }
149
+ getReplaceable(kind, pubkey, d) {
150
+ return this.getByAddress(formatEventAddress(kind, pubkey, d ?? ""));
151
+ }
152
+ getByAddress(address) {
153
+ const event = this.#index.getByAddress(address);
154
+ if (event) this.#touch(event.id);
155
+ return event;
156
+ }
157
+ query(filters) {
158
+ const events = this.#index.query(filters);
159
+ for (const event of [...events].reverse()) this.#touch(event.id);
160
+ return events;
161
+ }
162
+ isDeleted(idOrAddress) {
163
+ return this.#index.isDeleted(idOrAddress);
164
+ }
165
+ seenOn(id) {
166
+ return this.#seenOn.get(id.toLowerCase()) ?? [];
167
+ }
168
+ get size() {
169
+ return this.#index.size;
170
+ }
171
+ watchEvent(id) {
172
+ const key = id.toLowerCase();
173
+ return new WatchImpl(this, "event", key, () => this.get(key), sameRef, EVENT_IDS);
174
+ }
175
+ watchReplaceable(kind, pubkey, d) {
176
+ const address = formatEventAddress(kind, pubkey, d ?? "");
177
+ return new WatchImpl(this, "replaceable", address, () => this.getByAddress(address), sameRef, EVENT_IDS);
178
+ }
179
+ watchQuery(filters) {
180
+ const key = filterFingerprint(filters);
181
+ let watch = this.#queryCache.get(key);
182
+ if (!watch) {
183
+ const list = [...filters];
184
+ watch = new WatchImpl(this, "query", key, () => this.query(list), sameList, LIST_IDS, list);
185
+ this.#queryCache.set(key, watch);
186
+ }
187
+ return watch;
188
+ }
189
+ /** Synchronous listener for every physical index insert (after update). */
190
+ onInsert(listener) {
191
+ this.#insertListeners.add(listener);
192
+ return () => {
193
+ this.#insertListeners.delete(listener);
194
+ };
195
+ }
196
+ _register(watch) {
197
+ switch (watch.kind) {
198
+ case "event":
199
+ addWatch(this.#idWatches, watch.key, watch);
200
+ return;
201
+ case "replaceable":
202
+ addWatch(this.#addressWatches, watch.key, watch);
203
+ return;
204
+ case "query": this.#queryRegistry.add(watch);
205
+ }
206
+ }
207
+ _unregister(watch) {
208
+ if (watch.subscribed) return;
209
+ switch (watch.kind) {
210
+ case "event":
211
+ removeWatch(this.#idWatches, watch.key, watch);
212
+ return;
213
+ case "replaceable":
214
+ removeWatch(this.#addressWatches, watch.key, watch);
215
+ return;
216
+ case "query":
217
+ this.#queryRegistry.delete(watch);
218
+ if (this.#queryCache.get(watch.key) === watch) this.#queryCache.delete(watch.key);
219
+ }
220
+ }
221
+ #touch(id) {
222
+ if (this.#recency.delete(id)) this.#recency.add(id);
223
+ else this.#recency.add(id);
224
+ }
225
+ #recordSeen(id, relayUrl) {
226
+ const url = normalizeURL(relayUrl);
227
+ let urls = this.#seenOn.get(id);
228
+ if (!urls) {
229
+ urls = [];
230
+ this.#seenOn.set(id, urls);
231
+ }
232
+ if (!urls.includes(url) && urls.length < SEEN_ON_PER_ID) urls.push(url);
233
+ while (this.#seenOn.size > this.#maxSeenOnEntries) {
234
+ const oldest = this.#seenOn.keys().next().value;
235
+ if (oldest === void 0) break;
236
+ this.#seenOn.delete(oldest);
237
+ }
238
+ }
239
+ #onInsert(event) {
240
+ this._version += 1;
241
+ this.#touch(event.id);
242
+ this.#invalidateByEvent(event);
243
+ for (const listener of this.#insertListeners) invokeSafely(() => listener(event));
244
+ }
245
+ #onRemove(event) {
246
+ this._version += 1;
247
+ this.#recency.delete(event.id);
248
+ this.#invalidateByEvent(event);
249
+ }
250
+ #invalidateByEvent(event) {
251
+ const byId = this.#idWatches.get(event.id);
252
+ if (byId) for (const watch of byId) this.#markDirty(watch);
253
+ const address = eventAddress(event);
254
+ if (address !== void 0) {
255
+ const byAddress = this.#addressWatches.get(address);
256
+ if (byAddress) for (const watch of byAddress) this.#markDirty(watch);
257
+ }
258
+ for (const watch of this.#queryRegistry) if (watch.filters !== void 0 && matchFilters(watch.filters, event)) this.#markDirty(watch);
259
+ }
260
+ #invalidateAll() {
261
+ for (const watches of this.#idWatches.values()) for (const watch of watches) this.#markDirty(watch);
262
+ for (const watches of this.#addressWatches.values()) for (const watch of watches) this.#markDirty(watch);
263
+ for (const watch of this.#queryRegistry) this.#markDirty(watch);
264
+ this.#scheduleFlush();
265
+ }
266
+ #markDirty(watch) {
267
+ watch._markDirty();
268
+ this.#dirty.add(watch);
269
+ this.#scheduleFlush();
270
+ }
271
+ #scheduleFlush() {
272
+ if (this.#flushScheduled) return;
273
+ this.#flushScheduled = true;
274
+ queueMicrotask(() => {
275
+ while (this.#dirty.size > 0) {
276
+ const batch = [...this.#dirty];
277
+ this.#dirty.clear();
278
+ for (const watch of batch) watch._notify();
279
+ }
280
+ this.#flushScheduled = false;
281
+ });
282
+ }
283
+ #pinnedIds() {
284
+ const pinned = /* @__PURE__ */ new Set();
285
+ const collect = (watch) => {
286
+ for (const id of watch.pinnedIds) pinned.add(id);
287
+ };
288
+ for (const watches of this.#idWatches.values()) for (const watch of watches) collect(watch);
289
+ for (const watches of this.#addressWatches.values()) for (const watch of watches) collect(watch);
290
+ for (const watch of this.#queryRegistry) collect(watch);
291
+ return pinned;
292
+ }
293
+ #evictIfNeeded(protectedId) {
294
+ if (this.#index.size <= this.#maxEvents) return;
295
+ const pinned = this.#pinnedIds();
296
+ const evicting = [];
297
+ for (const id of this.#recency) {
298
+ if (this.#index.size - evicting.length <= this.#maxEvents) break;
299
+ if (pinned.has(id) || id === protectedId) continue;
300
+ if (this.#index.get(id) === void 0) continue;
301
+ evicting.push(id);
302
+ }
303
+ this.#index.evict(evicting);
304
+ for (const id of evicting) this.#recency.delete(id);
305
+ }
306
+ };
307
+ function addWatch(map, key, watch) {
308
+ let set = map.get(key);
309
+ if (!set) {
310
+ set = /* @__PURE__ */ new Set();
311
+ map.set(key, set);
312
+ }
313
+ set.add(watch);
314
+ }
315
+ function removeWatch(map, key, watch) {
316
+ const set = map.get(key);
317
+ if (!set) return;
318
+ set.delete(watch);
319
+ if (set.size === 0) map.delete(key);
320
+ }
321
+ //#endregion
322
+ export { ReactiveEventStore as t };
323
+
324
+ //# sourceMappingURL=reactive-DDlThDSZ.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reactive-DDlThDSZ.mjs","names":[],"sources":["../src/store/reactive.ts"],"sourcesContent":["import { itemCompare } from \"../core/event.ts\";\nimport type { Event } from \"../core/event.ts\";\nimport type { Filter } from \"../core/filter.ts\";\nimport { filterFingerprint, matchFilters } from \"../core/filter.ts\";\nimport { invokeSafely } from \"../core/report.ts\";\nimport { formatEventAddress, eventAddress } from \"../core/tag.ts\";\nimport { normalizeURL } from \"../core/util.ts\";\nimport { MemoryIndex } from \"../storage/memory-index.ts\";\nimport type { PutResult } from \"../storage/types.ts\";\n\n/** A reactive snapshot source compatible with `useSyncExternalStore`. */\nexport type Watch<T> = {\n subscribe: (onChange: () => void) => () => void;\n getSnapshot: () => T;\n};\n\n/** Capacity options for {@link ReactiveEventStore}. */\nexport type ReactiveEventStoreOptions = {\n /** Max stored events before LRU eviction. Default 50_000. */\n maxEvents?: number;\n /** Max ids tracked by `seenOn`. Default 20_000. */\n maxSeenOnEntries?: number;\n /** FIFO cap on tombstoned deletion ids. Default 100_000. */\n maxTombstones?: number;\n};\n\nconst SEEN_ON_PER_ID = 16;\n\n/** Category of a {@link Watch} subscription. */\ntype WatchKind = \"event\" | \"replaceable\" | \"query\";\n\n/** Structural handle the store needs for invalidation; implemented by WatchImpl. */\ntype WatchHandle = {\n readonly kind: WatchKind;\n readonly key: string;\n readonly filters: ReadonlyArray<Filter> | undefined;\n readonly subscribed: boolean;\n readonly pinnedIds: ReadonlyArray<string>;\n _markDirty: () => void;\n _notify: () => void;\n};\n\nclass WatchImpl<T> implements Watch<T>, WatchHandle {\n readonly store: ReactiveEventStore;\n readonly kind: WatchKind;\n readonly key: string;\n readonly filters: ReadonlyArray<Filter> | undefined;\n readonly compute: () => T;\n readonly equal: (a: T, b: T) => boolean;\n readonly snapshotIds: (snapshot: T) => ReadonlyArray<string>;\n readonly #subscribers = new Set<() => void>();\n #snapshot: T;\n #version = -1;\n #dirty = false;\n #pendingRemove = false;\n\n constructor(\n store: ReactiveEventStore,\n kind: WatchKind,\n key: string,\n compute: () => T,\n equal: (a: T, b: T) => boolean,\n snapshotIds: (snapshot: T) => ReadonlyArray<string>,\n filters?: ReadonlyArray<Filter>,\n ) {\n this.store = store;\n this.kind = kind;\n this.key = key;\n this.filters = filters;\n this.compute = compute;\n this.equal = equal;\n this.snapshotIds = snapshotIds;\n this.#snapshot = compute();\n this.#version = store._version;\n }\n\n get subscribed(): boolean {\n return this.#subscribers.size > 0;\n }\n\n get pinnedIds(): ReadonlyArray<string> {\n return this.snapshotIds(this.#snapshot);\n }\n\n subscribe(onChange: () => void): () => void {\n const wasUnsubscribed = this.#subscribers.size === 0;\n this.#subscribers.add(onChange);\n this.#pendingRemove = false;\n // Writes that landed while unregistered must not be lost.\n if (wasUnsubscribed && this.#version !== this.store._version) {\n this.#dirty = true;\n }\n this.store._register(this);\n return () => {\n this.#subscribers.delete(onChange);\n if (this.#subscribers.size > 0 || this.#pendingRemove) {\n return;\n }\n this.#pendingRemove = true;\n queueMicrotask(() => {\n if (this.#pendingRemove) {\n this.store._unregister(this);\n }\n });\n };\n }\n\n getSnapshot(): T {\n if (this.subscribed ? this.#dirty : this.#version !== this.store._version) {\n const next = this.compute();\n if (!this.equal(next, this.#snapshot)) {\n this.#snapshot = next;\n }\n this.#version = this.store._version;\n this.#dirty = false;\n }\n return this.#snapshot;\n }\n\n _markDirty(): void {\n this.#dirty = true;\n }\n\n _notify(): void {\n // Do not sync #version here: subscribers will call getSnapshot, which\n // recomputes against the bumped store version and keeps the reference\n // when the result is element-wise identical. A throwing subscriber is\n // reported and never aborts the remaining notifications.\n for (const onChange of this.#subscribers) {\n invokeSafely(onChange);\n }\n }\n}\n\nconst EVENT_IDS = (e: Event | undefined): ReadonlyArray<string> => (e ? [e.id] : []);\nconst LIST_IDS = (events: ReadonlyArray<Event>): ReadonlyArray<string> => events.map((e) => e.id);\n\nfunction sameRef(a: Event | undefined, b: Event | undefined): boolean {\n return a === b;\n}\n\nfunction sameList(a: ReadonlyArray<Event>, b: ReadonlyArray<Event>): boolean {\n if (a.length !== b.length) {\n return false;\n }\n for (let i = 0; i < a.length; i++) {\n if (a[i] !== b[i]) {\n return false;\n }\n }\n return true;\n}\n\n/**\n * Synchronous reactive in-memory event store backed by {@link MemoryIndex}. The single read source\n * for Nostr events on the UI side: `Watch` snapshots keep referential stability for\n * `useSyncExternalStore` and are invalidated on matching writes, deletions and tombstones. Writes\n * batch notifications into one microtask flush.\n */\nexport class ReactiveEventStore {\n readonly #index: MemoryIndex;\n readonly #maxEvents: number;\n readonly #maxSeenOnEntries: number;\n\n _version = 0;\n\n // id -> recency-ordered access order (delete+add moves to the newest end)\n readonly #recency = new Set<string>();\n // id -> relay urls (≤ SEEN_ON_PER_ID); Map insertion order for eviction\n readonly #seenOn = new Map<string, string[]>();\n\n readonly #idWatches = new Map<string, Set<WatchHandle>>();\n readonly #addressWatches = new Map<string, Set<WatchHandle>>();\n readonly #queryCache = new Map<string, WatchImpl<ReadonlyArray<Event>>>();\n readonly #queryRegistry = new Set<WatchHandle>();\n\n readonly #dirty = new Set<WatchHandle>();\n #flushScheduled = false;\n readonly #insertListeners = new Set<(event: Event) => void>();\n\n constructor(opts?: ReactiveEventStoreOptions) {\n this.#maxEvents = opts?.maxEvents ?? 50_000;\n this.#maxSeenOnEntries = opts?.maxSeenOnEntries ?? 20_000;\n this.#index = new MemoryIndex({\n maxTombstones: opts?.maxTombstones ?? 100_000,\n // Keep evicted replaceable winners rejectable while their body is gone.\n maxWatermarks: this.#maxEvents,\n onInsert: (event) => this.#onInsert(event),\n onRemove: (event) => this.#onRemove(event),\n });\n }\n\n add(event: Event, relayUrl?: string): PutResult {\n const result = this.#index.put(event);\n // Stored events are canonical; seenOn keys are their lowercase ids.\n const { id } = event;\n if (\n relayUrl !== undefined &&\n result !== \"ephemeral\" &&\n result !== \"rejected\" &&\n result !== \"invalid\"\n ) {\n this.#recordSeen(id, relayUrl);\n }\n // The just-inserted event must survive its own eviction pass even when\n // every older entry is pinned by a subscribed watch snapshot.\n this.#evictIfNeeded(id);\n return result;\n }\n\n addMany(events: ReadonlyArray<Event>, relayUrl?: string): PutResult[] {\n const results: PutResult[] = [];\n for (const event of events) {\n results.push(this.add(event, relayUrl));\n }\n return results;\n }\n\n /** Record a relay sighting for an id already in the index. No notification. */\n markSeen(id: string, relayUrl: string): boolean {\n const key = id.toLowerCase();\n if (this.#index.get(key) === undefined) {\n return false;\n }\n this.#recordSeen(key, relayUrl);\n return true;\n }\n\n /** Bulk load (initial hydration): no seenOn, one batched notification. */\n hydrate(events: ReadonlyArray<Event>): void {\n // Insert oldest-first so recency ends with the newest entries hottest.\n const sorted = [...events].sort(itemCompare);\n for (const event of sorted) {\n this.#index.put(event);\n }\n this.#evictIfNeeded();\n }\n\n remove(ids: ReadonlyArray<string>): number {\n return this.#index.remove(ids);\n }\n\n clear(): void {\n this.#index.clear();\n this.#seenOn.clear();\n this.#recency.clear();\n this._version += 1;\n this.#invalidateAll();\n }\n\n get(id: string): Event | undefined {\n const event = this.#index.get(id);\n if (event) {\n this.#touch(event.id);\n }\n return event;\n }\n\n getReplaceable(kind: number, pubkey: string, d?: string): Event | undefined {\n return this.getByAddress(formatEventAddress(kind, pubkey, d ?? \"\"));\n }\n\n getByAddress(address: string): Event | undefined {\n const event = this.#index.getByAddress(address);\n if (event) {\n this.#touch(event.id);\n }\n return event;\n }\n\n query(filters: ReadonlyArray<Filter>): ReadonlyArray<Event> {\n const events = this.#index.query(filters);\n // Results come back newest-first; touch oldest→newest so the newest\n // entries end up hottest in the recency order.\n for (const event of [...events].reverse()) {\n this.#touch(event.id);\n }\n return events;\n }\n\n isDeleted(idOrAddress: string): boolean {\n return this.#index.isDeleted(idOrAddress);\n }\n\n seenOn(id: string): ReadonlyArray<string> {\n return this.#seenOn.get(id.toLowerCase()) ?? [];\n }\n\n get size(): number {\n return this.#index.size;\n }\n\n watchEvent(id: string): Watch<Event | undefined> {\n const key = id.toLowerCase();\n return new WatchImpl(this, \"event\", key, () => this.get(key), sameRef, EVENT_IDS);\n }\n\n watchReplaceable(kind: number, pubkey: string, d?: string): Watch<Event | undefined> {\n const address = formatEventAddress(kind, pubkey, d ?? \"\");\n return new WatchImpl(\n this,\n \"replaceable\",\n address,\n () => this.getByAddress(address),\n sameRef,\n EVENT_IDS,\n );\n }\n\n watchQuery(filters: ReadonlyArray<Filter>): Watch<ReadonlyArray<Event>> {\n const key = filterFingerprint(filters);\n let watch = this.#queryCache.get(key);\n if (!watch) {\n const list = [...filters];\n watch = new WatchImpl(this, \"query\", key, () => this.query(list), sameList, LIST_IDS, list);\n this.#queryCache.set(key, watch);\n }\n return watch;\n }\n\n /** Synchronous listener for every physical index insert (after update). */\n onInsert(listener: (event: Event) => void): () => void {\n this.#insertListeners.add(listener);\n return () => {\n this.#insertListeners.delete(listener);\n };\n }\n\n _register(watch: WatchHandle): void {\n switch (watch.kind) {\n case \"event\":\n addWatch(this.#idWatches, watch.key, watch);\n return;\n case \"replaceable\":\n addWatch(this.#addressWatches, watch.key, watch);\n return;\n case \"query\":\n this.#queryRegistry.add(watch);\n }\n }\n\n _unregister(watch: WatchHandle): void {\n if (watch.subscribed) {\n return;\n }\n switch (watch.kind) {\n case \"event\":\n removeWatch(this.#idWatches, watch.key, watch);\n return;\n case \"replaceable\":\n removeWatch(this.#addressWatches, watch.key, watch);\n return;\n case \"query\":\n this.#queryRegistry.delete(watch);\n if (this.#queryCache.get(watch.key) === watch) {\n this.#queryCache.delete(watch.key);\n }\n }\n }\n\n #touch(id: string): void {\n if (this.#recency.delete(id)) {\n this.#recency.add(id);\n } else {\n this.#recency.add(id);\n }\n }\n\n #recordSeen(id: string, relayUrl: string): void {\n const url = normalizeURL(relayUrl);\n let urls = this.#seenOn.get(id);\n if (!urls) {\n urls = [];\n this.#seenOn.set(id, urls);\n }\n if (!urls.includes(url) && urls.length < SEEN_ON_PER_ID) {\n urls.push(url);\n }\n while (this.#seenOn.size > this.#maxSeenOnEntries) {\n const oldest = this.#seenOn.keys().next().value;\n if (oldest === undefined) {\n break;\n }\n this.#seenOn.delete(oldest);\n }\n }\n\n #onInsert(event: Event): void {\n this._version += 1;\n this.#touch(event.id);\n this.#invalidateByEvent(event);\n for (const listener of this.#insertListeners) {\n invokeSafely(() => listener(event));\n }\n }\n\n #onRemove(event: Event): void {\n this._version += 1;\n this.#recency.delete(event.id);\n this.#invalidateByEvent(event);\n }\n\n #invalidateByEvent(event: Event): void {\n const byId = this.#idWatches.get(event.id);\n if (byId) {\n for (const watch of byId) {\n this.#markDirty(watch);\n }\n }\n const address = eventAddress(event);\n if (address !== undefined) {\n const byAddress = this.#addressWatches.get(address);\n if (byAddress) {\n for (const watch of byAddress) {\n this.#markDirty(watch);\n }\n }\n }\n for (const watch of this.#queryRegistry) {\n if (watch.filters !== undefined && matchFilters(watch.filters, event)) {\n this.#markDirty(watch);\n }\n }\n }\n\n #invalidateAll(): void {\n for (const watches of this.#idWatches.values()) {\n for (const watch of watches) {\n this.#markDirty(watch);\n }\n }\n for (const watches of this.#addressWatches.values()) {\n for (const watch of watches) {\n this.#markDirty(watch);\n }\n }\n for (const watch of this.#queryRegistry) {\n this.#markDirty(watch);\n }\n this.#scheduleFlush();\n }\n\n #markDirty(watch: WatchHandle): void {\n watch._markDirty();\n this.#dirty.add(watch);\n this.#scheduleFlush();\n }\n\n #scheduleFlush(): void {\n if (this.#flushScheduled) {\n return;\n }\n this.#flushScheduled = true;\n queueMicrotask(() => {\n // Keep the flag set while flushing: writes re-entered from a\n // subscriber land in #dirty and are notified in the same pass.\n while (this.#dirty.size > 0) {\n const batch = [...this.#dirty];\n this.#dirty.clear();\n for (const watch of batch) {\n watch._notify();\n }\n }\n this.#flushScheduled = false;\n });\n }\n\n #pinnedIds(): Set<string> {\n const pinned = new Set<string>();\n const collect = (watch: WatchHandle): void => {\n for (const id of watch.pinnedIds) {\n pinned.add(id);\n }\n };\n for (const watches of this.#idWatches.values()) {\n for (const watch of watches) {\n collect(watch);\n }\n }\n for (const watches of this.#addressWatches.values()) {\n for (const watch of watches) {\n collect(watch);\n }\n }\n for (const watch of this.#queryRegistry) {\n collect(watch);\n }\n return pinned;\n }\n\n #evictIfNeeded(protectedId?: string): void {\n if (this.#index.size <= this.#maxEvents) {\n return;\n }\n const pinned = this.#pinnedIds();\n const evicting: string[] = [];\n for (const id of this.#recency) {\n if (this.#index.size - evicting.length <= this.#maxEvents) {\n break;\n }\n if (pinned.has(id) || id === protectedId) {\n continue;\n }\n const event = this.#index.get(id);\n if (event === undefined) {\n continue;\n }\n // Replaceable/addressable winners evict like any other event; the index\n // records a watermark so stale versions stay rejected afterwards.\n evicting.push(id);\n }\n this.#index.evict(evicting);\n for (const id of evicting) {\n this.#recency.delete(id);\n }\n }\n}\n\nfunction addWatch(map: Map<string, Set<WatchHandle>>, key: string, watch: WatchHandle): void {\n let set = map.get(key);\n if (!set) {\n set = new Set();\n map.set(key, set);\n }\n set.add(watch);\n}\n\nfunction removeWatch(map: Map<string, Set<WatchHandle>>, key: string, watch: WatchHandle): void {\n const set = map.get(key);\n if (!set) {\n return;\n }\n set.delete(watch);\n if (set.size === 0) {\n map.delete(key);\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ import { t as Event } from "./event-fU3Y01PR.mjs";
2
+ import { t as Filter } from "./filter-C0IMByE3.mjs";
3
+ import { i as PutResult } from "./types-2pCUprRU.mjs";
4
+ //#region src/store/reactive.d.ts
5
+ /** A reactive snapshot source compatible with `useSyncExternalStore`. */
6
+ type Watch<T> = {
7
+ subscribe: (onChange: () => void) => () => void;
8
+ getSnapshot: () => T;
9
+ };
10
+ /** Capacity options for {@link ReactiveEventStore}. */
11
+ type ReactiveEventStoreOptions = {
12
+ /** Max stored events before LRU eviction. Default 50_000. */
13
+ maxEvents?: number;
14
+ /** Max ids tracked by `seenOn`. Default 20_000. */
15
+ maxSeenOnEntries?: number;
16
+ /** FIFO cap on tombstoned deletion ids. Default 100_000. */
17
+ maxTombstones?: number;
18
+ };
19
+ /** Category of a {@link Watch} subscription. */
20
+ type WatchKind = "event" | "replaceable" | "query";
21
+ /** Structural handle the store needs for invalidation; implemented by WatchImpl. */
22
+ type WatchHandle = {
23
+ readonly kind: WatchKind;
24
+ readonly key: string;
25
+ readonly filters: ReadonlyArray<Filter> | undefined;
26
+ readonly subscribed: boolean;
27
+ readonly pinnedIds: ReadonlyArray<string>;
28
+ _markDirty: () => void;
29
+ _notify: () => void;
30
+ };
31
+ /**
32
+ * Synchronous reactive in-memory event store backed by {@link MemoryIndex}. The single read source
33
+ * for Nostr events on the UI side: `Watch` snapshots keep referential stability for
34
+ * `useSyncExternalStore` and are invalidated on matching writes, deletions and tombstones. Writes
35
+ * batch notifications into one microtask flush.
36
+ */
37
+ declare class ReactiveEventStore {
38
+ #private;
39
+ _version: number;
40
+ constructor(opts?: ReactiveEventStoreOptions);
41
+ add(event: Event, relayUrl?: string): PutResult;
42
+ addMany(events: ReadonlyArray<Event>, relayUrl?: string): PutResult[];
43
+ /** Record a relay sighting for an id already in the index. No notification. */
44
+ markSeen(id: string, relayUrl: string): boolean;
45
+ /** Bulk load (initial hydration): no seenOn, one batched notification. */
46
+ hydrate(events: ReadonlyArray<Event>): void;
47
+ remove(ids: ReadonlyArray<string>): number;
48
+ clear(): void;
49
+ get(id: string): Event | undefined;
50
+ getReplaceable(kind: number, pubkey: string, d?: string): Event | undefined;
51
+ getByAddress(address: string): Event | undefined;
52
+ query(filters: ReadonlyArray<Filter>): ReadonlyArray<Event>;
53
+ isDeleted(idOrAddress: string): boolean;
54
+ seenOn(id: string): ReadonlyArray<string>;
55
+ get size(): number;
56
+ watchEvent(id: string): Watch<Event | undefined>;
57
+ watchReplaceable(kind: number, pubkey: string, d?: string): Watch<Event | undefined>;
58
+ watchQuery(filters: ReadonlyArray<Filter>): Watch<ReadonlyArray<Event>>;
59
+ /** Synchronous listener for every physical index insert (after update). */
60
+ onInsert(listener: (event: Event) => void): () => void;
61
+ _register(watch: WatchHandle): void;
62
+ _unregister(watch: WatchHandle): void;
63
+ }
64
+ //#endregion
65
+ export { ReactiveEventStoreOptions as n, Watch as r, ReactiveEventStore as t };
66
+ //# sourceMappingURL=reactive-DFpqtIf_.d.mts.map
package/dist/relay.d.mts CHANGED
@@ -1,4 +1,5 @@
1
- import { n as CountResult } from "./message-DJC-Jz6T.mjs";
2
- import { _ as subscriptionToAsyncIterable, a as PublishResult, c as RelayStatus, d as WebSocketLike, f as getWebSocketImplementation, g as SubscriptionHandlers, h as Subscription, i as PoolPublishResult, l as RelayStatusName, m as SubscribeOptions, n as PoolCountResult, o as Relay, p as useWebSocketImplementation, r as PoolOptions, s as RelayOptions, t as Pool, u as WebSocketConstructor } from "./pool-DhRi7l-b.mjs";
3
- import { a as isInsecureRelayUrl, c as RelayError, i as fetchRouted, l as RelayPublishError, n as RoutedJob, o as RelayClosedError, r as fanIn, s as RelayConnectionError, t as FanInOptions } from "./index-C0C4e-Gn.mjs";
4
- export { type CountResult, type FanInOptions, Pool, type PoolCountResult, type PoolOptions, type PoolPublishResult, type PublishResult, Relay, RelayClosedError, RelayConnectionError, RelayError, type RelayOptions, RelayPublishError, RelayStatus, type RelayStatusName, type RoutedJob, type SubscribeOptions, Subscription, type SubscriptionHandlers, type WebSocketConstructor, type WebSocketLike, fanIn, fetchRouted, getWebSocketImplementation, isInsecureRelayUrl, subscriptionToAsyncIterable, useWebSocketImplementation };
1
+ import { n as CountResult } from "./message-iGPIqnn0.mjs";
2
+ import { a as PoolPublishResult, c as Relay, d as RelayStatusName, f as SubscribeOptions, h as subscriptionToAsyncIterable, i as PoolOptions, l as RelayOptions, m as SubscriptionHandlers, n as Pool, o as PoolSubscribeOptions, p as Subscription, r as PoolCountResult, s as PublishResult, t as InvalidEventPolicy, u as RelayStatus } from "./pool-B7p_reAm.mjs";
3
+ import { i as useWebSocketImplementation, n as WebSocketLike, r as getWebSocketImplementation, t as WebSocketConstructor } from "./websocket-CmeqvAkp.mjs";
4
+ import { a as isInsecureRelayUrl, c as RelayError, d as RelayTimeoutError, i as fetchRouted, l as RelayPublishError, n as RoutedJob, o as RelayClosedError, r as fanIn, s as RelayConnectionError, t as FanInOptions, u as RelaySuspendedError } from "./index-D-TK_KJG.mjs";
5
+ export { type CountResult, type FanInOptions, type InvalidEventPolicy, Pool, type PoolCountResult, type PoolOptions, type PoolPublishResult, type PoolSubscribeOptions, type PublishResult, Relay, RelayClosedError, RelayConnectionError, RelayError, type RelayOptions, RelayPublishError, RelayStatus, type RelayStatusName, RelaySuspendedError, RelayTimeoutError, type RoutedJob, type SubscribeOptions, Subscription, type SubscriptionHandlers, type WebSocketConstructor, type WebSocketLike, fanIn, fetchRouted, getWebSocketImplementation, isInsecureRelayUrl, subscriptionToAsyncIterable, useWebSocketImplementation };
package/dist/relay.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { a as RelayStatus, c as isInsecureRelayUrl, d as RelayClosedError, f as RelayConnectionError, i as Relay, l as getWebSocketImplementation, m as RelayPublishError, n as fanIn, o as Subscription, p as RelayError, r as fetchRouted, s as subscriptionToAsyncIterable, t as Pool, u as useWebSocketImplementation } from "./pool-C_j1OmBB.mjs";
2
- export { Pool, Relay, RelayClosedError, RelayConnectionError, RelayError, RelayPublishError, RelayStatus, Subscription, fanIn, fetchRouted, getWebSocketImplementation, isInsecureRelayUrl, subscriptionToAsyncIterable, useWebSocketImplementation };
1
+ import { a as RelayStatus, c as isInsecureRelayUrl, d as RelayClosedError, f as RelayConnectionError, g as RelayTimeoutError, h as RelaySuspendedError, i as Relay, l as getWebSocketImplementation, m as RelayPublishError, n as fanIn, o as Subscription, p as RelayError, r as fetchRouted, s as subscriptionToAsyncIterable, t as Pool, u as useWebSocketImplementation } from "./pool-C2xOcsun.mjs";
2
+ export { Pool, Relay, RelayClosedError, RelayConnectionError, RelayError, RelayPublishError, RelayStatus, RelaySuspendedError, RelayTimeoutError, Subscription, fanIn, fetchRouted, getWebSocketImplementation, isInsecureRelayUrl, subscriptionToAsyncIterable, useWebSocketImplementation };
@@ -0,0 +1,32 @@
1
+ //#region src/core/report.ts
2
+ /**
3
+ * Report a user-callback error without aborting sibling work: a throwing listener must not change
4
+ * library state or interrupt the dispatch loop. Prefers the runtime's `reportError`
5
+ * (unhandled-error semantics that still reach `error` event listeners); falls back to a microtask
6
+ * throw.
7
+ */
8
+ function reportError(error) {
9
+ const report = globalThis.reportError;
10
+ if (typeof report === "function") {
11
+ report(error);
12
+ return;
13
+ }
14
+ queueMicrotask(() => {
15
+ throw error;
16
+ });
17
+ }
18
+ /**
19
+ * Invoke a user callback isolated from the caller: a throw is reported via {@link reportError} and
20
+ * never aborts sibling work or alters library state.
21
+ */
22
+ function invokeSafely(fn) {
23
+ try {
24
+ fn();
25
+ } catch (error) {
26
+ reportError(error);
27
+ }
28
+ }
29
+ //#endregion
30
+ export { invokeSafely as t };
31
+
32
+ //# sourceMappingURL=report-CHld4uWj.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report-CHld4uWj.mjs","names":[],"sources":["../src/core/report.ts"],"sourcesContent":["/**\n * Report a user-callback error without aborting sibling work: a throwing listener must not change\n * library state or interrupt the dispatch loop. Prefers the runtime's `reportError`\n * (unhandled-error semantics that still reach `error` event listeners); falls back to a microtask\n * throw.\n */\nexport function reportError(error: unknown): void {\n // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- reads an optional host global\n const report = (globalThis as { reportError?: (error: unknown) => void }).reportError;\n if (typeof report === \"function\") {\n report(error);\n return;\n }\n queueMicrotask(() => {\n throw error;\n });\n}\n\n/**\n * Invoke a user callback isolated from the caller: a throw is reported via {@link reportError} and\n * never aborts sibling work or alters library state.\n */\nexport function invokeSafely(fn: () => void): void {\n try {\n fn();\n } catch (error) {\n reportError(error);\n }\n}\n"],"mappings":";;;;;;;AAMA,SAAgB,YAAY,OAAsB;CAEhD,MAAM,SAAU,WAA0D;CAC1E,IAAI,OAAO,WAAW,YAAY;EAChC,OAAO,KAAK;EACZ;CACF;CACA,qBAAqB;EACnB,MAAM;CACR,CAAC;AACH;;;;;AAMA,SAAgB,aAAa,IAAsB;CACjD,IAAI;EACF,GAAG;CACL,SAAS,OAAO;EACd,YAAY,KAAK;CACnB;AACF"}