@qntx/nostr 0.6.0 → 0.7.0

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 (177) hide show
  1. package/dist/{abort-Ba8tDrOI.mjs → abort-DBq2STPj.mjs} +13 -6
  2. package/dist/abort-DBq2STPj.mjs.map +1 -0
  3. package/dist/{builder-9tjI6WQ3.mjs → builder-CdLE4J6O.mjs} +11 -17
  4. package/dist/builder-CdLE4J6O.mjs.map +1 -0
  5. package/dist/{builder-sSflu6nB.d.mts → builder-DgkqDylK.d.mts} +5 -6
  6. package/dist/{client-CadO5fAX.mjs → client-CILhdfsF.mjs} +104 -314
  7. package/dist/client-CILhdfsF.mjs.map +1 -0
  8. package/dist/client.d.mts +2 -2
  9. package/dist/client.mjs +2 -2
  10. package/dist/collections-DtwMc1NU.mjs +33 -0
  11. package/dist/collections-DtwMc1NU.mjs.map +1 -0
  12. package/dist/core.d.mts +9 -8
  13. package/dist/core.mjs +7 -7
  14. package/dist/{error-BW7re1UN.d.mts → error-BD_0QFKT.d.mts} +2 -2
  15. package/dist/{error-CR8Urc84.d.mts → error-BquXDmIN.d.mts} +4 -4
  16. package/dist/{error-DrEIM7FO.mjs → error-L_QdmSnV.mjs} +2 -2
  17. package/dist/{error-DrEIM7FO.mjs.map → error-L_QdmSnV.mjs.map} +1 -1
  18. package/dist/{error-B__OMBRY.mjs → error-gtJvV6YS.mjs} +2 -2
  19. package/dist/{error-B__OMBRY.mjs.map → error-gtJvV6YS.mjs.map} +1 -1
  20. package/dist/{event-fU3Y01PR.d.mts → event-DJeb_DIL.d.mts} +4 -2
  21. package/dist/{event-D8Nfoh4K.mjs → event-DinQMmJ1.mjs} +16 -10
  22. package/dist/event-DinQMmJ1.mjs.map +1 -0
  23. package/dist/{filter-C0IMByE3.d.mts → filter-jI_9htF7.d.mts} +2 -2
  24. package/dist/{gossip-DpIvkBuj.d.mts → gossip-C7HLW5kI.d.mts} +35 -26
  25. package/dist/{gossip-oloDMWEj.mjs → gossip-CcTp9VpU.mjs} +23 -33
  26. package/dist/gossip-CcTp9VpU.mjs.map +1 -0
  27. package/dist/gossip.d.mts +1 -1
  28. package/dist/gossip.mjs +1 -1
  29. package/dist/{index-CvkvqB16.d.mts → index-DU9tRFTZ.d.mts} +7 -20
  30. package/dist/{index-q4MDce_r.d.mts → index-DiXwNn2M.d.mts} +6 -8
  31. package/dist/{index-CNl2q67H.d.mts → index-DocgD9K0.d.mts} +92 -167
  32. package/dist/{index-D1bqFHKB.d.mts → index-InQZpHyQ.d.mts} +18 -18
  33. package/dist/{index-MxC28S4P.d.mts → index-Tx15HGAD.d.mts} +26 -28
  34. package/dist/{index-lvXeER5t.d.mts → index-iLT8UzY3.d.mts} +19 -37
  35. package/dist/index.d.mts +23 -21
  36. package/dist/index.mjs +21 -19
  37. package/dist/{instance-DQE8BoWt.mjs → instance-BcKzxHIP.mjs} +26 -27
  38. package/dist/instance-BcKzxHIP.mjs.map +1 -0
  39. package/dist/{instance-CIca-ynn.d.mts → instance-DjsL8h84.d.mts} +5 -5
  40. package/dist/{key-Cv4_pN3x.d.mts → key-D_5OgGT7.d.mts} +11 -7
  41. package/dist/{key-Bps-LqX7.mjs → key-bshqfmZL.mjs} +13 -9
  42. package/dist/key-bshqfmZL.mjs.map +1 -0
  43. package/dist/{loaders-o6YXB_Ri.mjs → loaders-DD0XAGzw.mjs} +73 -82
  44. package/dist/loaders-DD0XAGzw.mjs.map +1 -0
  45. package/dist/loaders.d.mts +3 -3
  46. package/dist/loaders.mjs +2 -2
  47. package/dist/{memory-C2EVx0_O.mjs → memory-D_-5-T31.mjs} +2 -2
  48. package/dist/memory-D_-5-T31.mjs.map +1 -0
  49. package/dist/{memory-index-Do1mz0WF.mjs → memory-index-C6rujtoc.mjs} +30 -247
  50. package/dist/memory-index-C6rujtoc.mjs.map +1 -0
  51. package/dist/message-CWLcFTQ1.mjs +194 -0
  52. package/dist/message-CWLcFTQ1.mjs.map +1 -0
  53. package/dist/{message-iGPIqnn0.d.mts → message-CbBZOhq3.d.mts} +3 -3
  54. package/dist/{nip17-CKIufV98.d.mts → nip17-cTDFfFVt.d.mts} +5 -6
  55. package/dist/{nip19-BevCJZsp.d.mts → nip19-D1OnJ192.d.mts} +3 -9
  56. package/dist/{nip46-DDV4dC56.d.mts → nip46-DrsPI8EX.d.mts} +3 -5
  57. package/dist/nip51-DoPUvQ--.d.mts +87 -0
  58. package/dist/{nip59-zQgdLcEr.d.mts → nip59-BFIihLYY.d.mts} +3 -4
  59. package/dist/nip65-52HiwiyD.d.mts +23 -0
  60. package/dist/{nip77-DH4RhS5r.d.mts → nip77-C9QcZn7b.d.mts} +3 -4
  61. package/dist/nips/blossom.d.mts +12 -8
  62. package/dist/nips/blossom.mjs +30 -9
  63. package/dist/nips/blossom.mjs.map +1 -1
  64. package/dist/nips/nip04.mjs +17 -7
  65. package/dist/nips/nip04.mjs.map +1 -1
  66. package/dist/nips/nip05.d.mts +2 -3
  67. package/dist/nips/nip05.mjs +1 -4
  68. package/dist/nips/nip05.mjs.map +1 -1
  69. package/dist/nips/nip10.d.mts +3 -3
  70. package/dist/nips/nip10.mjs +3 -3
  71. package/dist/nips/nip11.d.mts +1 -1
  72. package/dist/nips/nip11.mjs +4 -4
  73. package/dist/nips/nip11.mjs.map +1 -1
  74. package/dist/nips/nip13.d.mts +7 -7
  75. package/dist/nips/nip13.mjs +32 -10
  76. package/dist/nips/nip13.mjs.map +1 -1
  77. package/dist/nips/nip17.d.mts +1 -1
  78. package/dist/nips/nip17.mjs +13 -33
  79. package/dist/nips/nip17.mjs.map +1 -1
  80. package/dist/nips/nip19.d.mts +2 -2
  81. package/dist/nips/nip19.mjs +16 -28
  82. package/dist/nips/nip19.mjs.map +1 -1
  83. package/dist/nips/nip21.d.mts +6 -1
  84. package/dist/nips/nip21.mjs +15 -5
  85. package/dist/nips/nip21.mjs.map +1 -1
  86. package/dist/nips/nip27.d.mts +4 -4
  87. package/dist/nips/nip27.mjs.map +1 -1
  88. package/dist/nips/nip42.d.mts +1 -1
  89. package/dist/nips/nip42.mjs +2 -1
  90. package/dist/nips/nip42.mjs.map +1 -1
  91. package/dist/nips/nip44.mjs +1 -1
  92. package/dist/nips/nip46.d.mts +2 -2
  93. package/dist/nips/nip46.mjs +15 -19
  94. package/dist/nips/nip46.mjs.map +1 -1
  95. package/dist/nips/nip49.d.mts +5 -5
  96. package/dist/nips/nip49.mjs +4 -4
  97. package/dist/nips/nip49.mjs.map +1 -1
  98. package/dist/nips/nip51.d.mts +2 -86
  99. package/dist/nips/nip51.mjs +7 -23
  100. package/dist/nips/nip51.mjs.map +1 -1
  101. package/dist/nips/nip57.d.mts +7 -5
  102. package/dist/nips/nip57.mjs +12 -17
  103. package/dist/nips/nip57.mjs.map +1 -1
  104. package/dist/nips/nip59.d.mts +1 -1
  105. package/dist/nips/nip59.mjs +16 -16
  106. package/dist/nips/nip59.mjs.map +1 -1
  107. package/dist/nips/nip65.d.mts +2 -2
  108. package/dist/nips/nip65.mjs +14 -37
  109. package/dist/nips/nip65.mjs.map +1 -1
  110. package/dist/nips/nip77.d.mts +1 -1
  111. package/dist/nips/nip77.mjs +33 -32
  112. package/dist/nips/nip77.mjs.map +1 -1
  113. package/dist/nips/nip98.d.mts +5 -5
  114. package/dist/nips/nip98.mjs +8 -8
  115. package/dist/nips/nip98.mjs.map +1 -1
  116. package/dist/{pool-DBIYMtwy.d.mts → pool-SzJKPIa3.d.mts} +96 -91
  117. package/dist/pool-YHRZgcQ3.mjs +596 -0
  118. package/dist/pool-YHRZgcQ3.mjs.map +1 -0
  119. package/dist/put-DS0yzVJx.mjs +187 -0
  120. package/dist/put-DS0yzVJx.mjs.map +1 -0
  121. package/dist/{reactive-7aqeip4A.d.mts → reactive-BJ1ggn4d.d.mts} +7 -22
  122. package/dist/{reactive-BfhPp9a3.mjs → reactive-BJRoVHxU.mjs} +80 -65
  123. package/dist/reactive-BJRoVHxU.mjs.map +1 -0
  124. package/dist/{pool-As20OGxB.mjs → relay-CvxPiQ_m.mjs} +178 -721
  125. package/dist/relay-CvxPiQ_m.mjs.map +1 -0
  126. package/dist/relay.d.mts +4 -4
  127. package/dist/relay.mjs +3 -2
  128. package/dist/serial-Bzpw-xz3.mjs +21 -0
  129. package/dist/serial-Bzpw-xz3.mjs.map +1 -0
  130. package/dist/{signer-CYQutDzD.mjs → signer-yW3slDC2.mjs} +12 -14
  131. package/dist/signer-yW3slDC2.mjs.map +1 -0
  132. package/dist/signer.d.mts +2 -2
  133. package/dist/signer.mjs +2 -2
  134. package/dist/{storage-Dky3wuoH.mjs → storage-CZ52ifcQ.mjs} +17 -42
  135. package/dist/storage-CZ52ifcQ.mjs.map +1 -0
  136. package/dist/storage.d.mts +3 -3
  137. package/dist/storage.mjs +4 -4
  138. package/dist/store.d.mts +1 -1
  139. package/dist/store.mjs +1 -1
  140. package/dist/testing.d.mts +18 -18
  141. package/dist/testing.mjs +9 -9
  142. package/dist/testing.mjs.map +1 -1
  143. package/dist/{types-2pCUprRU.d.mts → types-Cm-XMgtH.d.mts} +6 -6
  144. package/dist/{types-CRQEk0Oi.d.mts → types-DNmiscfd.d.mts} +2 -2
  145. package/dist/{util-LfQirgDk.mjs → util-yFYzlbrm.mjs} +29 -7
  146. package/dist/util-yFYzlbrm.mjs.map +1 -0
  147. package/dist/{verifier-t5WoWbtY.mjs → verifier-BTLOpo1n.mjs} +3 -3
  148. package/dist/verifier-BTLOpo1n.mjs.map +1 -0
  149. package/dist/verifier-CgTFpgsC.d.mts +22 -0
  150. package/dist/wasm.browser.d.mts +3 -3
  151. package/dist/wasm.browser.mjs +3 -3
  152. package/dist/wasm.d.mts +3 -3
  153. package/dist/wasm.mjs +3 -3
  154. package/dist/wasm.mjs.map +1 -1
  155. package/package.json +5 -9
  156. package/dist/abort-Ba8tDrOI.mjs.map +0 -1
  157. package/dist/builder-9tjI6WQ3.mjs.map +0 -1
  158. package/dist/client-CadO5fAX.mjs.map +0 -1
  159. package/dist/event-D8Nfoh4K.mjs.map +0 -1
  160. package/dist/gossip-oloDMWEj.mjs.map +0 -1
  161. package/dist/instance-DQE8BoWt.mjs.map +0 -1
  162. package/dist/key-Bps-LqX7.mjs.map +0 -1
  163. package/dist/loaders-o6YXB_Ri.mjs.map +0 -1
  164. package/dist/memory-C2EVx0_O.mjs.map +0 -1
  165. package/dist/memory-index-Do1mz0WF.mjs.map +0 -1
  166. package/dist/message-DcY6Fi9o.mjs +0 -193
  167. package/dist/message-DcY6Fi9o.mjs.map +0 -1
  168. package/dist/nip65-CjgmdpPB.d.mts +0 -26
  169. package/dist/nips/nip96.d.mts +0 -40
  170. package/dist/nips/nip96.mjs +0 -118
  171. package/dist/nips/nip96.mjs.map +0 -1
  172. package/dist/pool-As20OGxB.mjs.map +0 -1
  173. package/dist/reactive-BfhPp9a3.mjs.map +0 -1
  174. package/dist/signer-CYQutDzD.mjs.map +0 -1
  175. package/dist/storage-Dky3wuoH.mjs.map +0 -1
  176. package/dist/util-LfQirgDk.mjs.map +0 -1
  177. package/dist/verifier-t5WoWbtY.mjs.map +0 -1
@@ -1,7 +1,8 @@
1
- import { l as normalizeURL } from "./util-LfQirgDk.mjs";
1
+ import { d as nowSeconds, l as normalizeRelayUrls, u as normalizeURL } from "./util-yFYzlbrm.mjs";
2
2
  import { t as Kind } from "./kind-Cxwhhkx8.mjs";
3
- import { a as isReplaceableWinner } from "./event-D8Nfoh4K.mjs";
3
+ import { a as isReplaceableWinner } from "./event-DinQMmJ1.mjs";
4
4
  import { parseRelayList } from "./nips/nip65.mjs";
5
+ import { i as trimOldest, r as touchKey } from "./collections-DtwMc1NU.mjs";
5
6
  import { parseDmRelayList } from "./nips/nip17.mjs";
6
7
  //#region src/gossip/gossip.ts
7
8
  function emptyRoutes() {
@@ -26,7 +27,11 @@ var Gossip = class {
26
27
  this.#maxPubkeys = opts.maxPubkeys ?? 1e4;
27
28
  }
28
29
  /**
29
- * Ingest a routing list event. - kind:10002 → write/read (NIP-65) - kind:10050 → dm (NIP-17)
30
+ * Ingest a routing list event.
31
+ *
32
+ * - Kind:10002 → write/read (NIP-65)
33
+ * - Kind:10050 → dm (NIP-17)
34
+ *
30
35
  * Returns true if routes for that list type were updated.
31
36
  */
32
37
  ingest(event) {
@@ -50,7 +55,7 @@ var Gossip = class {
50
55
  }
51
56
  return false;
52
57
  }
53
- setRoutes(pubkey, items, updatedAt = Math.floor(Date.now() / 1e3), eventId) {
58
+ setRoutes(pubkey, items, updatedAt = nowSeconds(), eventId) {
54
59
  const pk = pubkey.toLowerCase();
55
60
  const prev = this.#lookup(pk) ?? emptyRoutes();
56
61
  if (prev.updatedAt > updatedAt) return false;
@@ -70,8 +75,8 @@ var Gossip = class {
70
75
  } catch {
71
76
  continue;
72
77
  }
73
- if (item.write && !write.includes(url)) write.push(url);
74
- if (item.read && !read.includes(url)) read.push(url);
78
+ if (item.marker !== "read" && !write.includes(url)) write.push(url);
79
+ if (item.marker !== "write" && !read.includes(url)) read.push(url);
75
80
  }
76
81
  this.#put(pk, {
77
82
  write: write.slice(0, this.#maxRelays),
@@ -84,7 +89,7 @@ var Gossip = class {
84
89
  });
85
90
  return true;
86
91
  }
87
- setDmRoutes(pubkey, relays, updatedAt = Math.floor(Date.now() / 1e3), eventId) {
92
+ setDmRoutes(pubkey, relays, updatedAt = nowSeconds(), eventId) {
88
93
  const pk = pubkey.toLowerCase();
89
94
  const prev = this.#lookup(pk) ?? emptyRoutes();
90
95
  if (prev.dmUpdatedAt > updatedAt) return false;
@@ -95,16 +100,7 @@ var Gossip = class {
95
100
  created_at: prev.dmUpdatedAt,
96
101
  id: prev.dmListId
97
102
  })) return false;
98
- const dm = [];
99
- for (const raw of relays) {
100
- let url;
101
- try {
102
- url = normalizeURL(raw);
103
- } catch {
104
- continue;
105
- }
106
- if (!dm.includes(url)) dm.push(url);
107
- }
103
+ const dm = normalizeRelayUrls(relays);
108
104
  this.#put(pk, {
109
105
  write: prev.write,
110
106
  read: prev.read,
@@ -137,10 +133,12 @@ var Gossip = class {
137
133
  return this.#routes.size;
138
134
  }
139
135
  /**
140
- * Route a user-facing filter into per-relay sub-filters plus leftover. - authors → outbox relays,
141
- * filter narrowed per relay's authors - #p only → inbox relays of those pubkeys - neither / all
142
- * unrouted → remainder is the original filter - mixed → remainder is the narrowed leftover; all
143
- * routed → no remainder
136
+ * Route a user-facing filter into per-relay sub-filters plus leftover.
137
+ *
138
+ * - Authors → outbox relays, filter narrowed per relay's authors
139
+ * - #p only → inbox relays of those pubkeys
140
+ * - Neither / all unrouted → remainder is the original filter
141
+ * - Mixed → remainder is the narrowed leftover; all routed → no remainder
144
142
  */
145
143
  route(filter) {
146
144
  const authors = filter.authors?.map((a) => a.toLowerCase());
@@ -215,24 +213,16 @@ var Gossip = class {
215
213
  /** Read that refreshes LRU recency. */
216
214
  #lookup(pk) {
217
215
  const routes = this.#routes.get(pk);
218
- if (routes !== void 0) {
219
- this.#routes.delete(pk);
220
- this.#routes.set(pk, routes);
221
- }
216
+ if (routes !== void 0) touchKey(this.#routes, pk, routes);
222
217
  return routes;
223
218
  }
224
219
  /** Write at the newest LRU end, then trim the oldest beyond the cap. */
225
220
  #put(pk, routes) {
226
- this.#routes.delete(pk);
227
- this.#routes.set(pk, routes);
228
- while (this.#routes.size > this.#maxPubkeys) {
229
- const oldest = this.#routes.keys().next().value;
230
- if (oldest === void 0) break;
231
- this.#routes.delete(oldest);
232
- }
221
+ touchKey(this.#routes, pk, routes);
222
+ trimOldest(this.#routes, this.#maxPubkeys);
233
223
  }
234
224
  };
235
225
  //#endregion
236
226
  export { Gossip as t };
237
227
 
238
- //# sourceMappingURL=gossip-oloDMWEj.mjs.map
228
+ //# sourceMappingURL=gossip-CcTp9VpU.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gossip-CcTp9VpU.mjs","names":[],"sources":["../src/gossip/gossip.ts"],"sourcesContent":["import { touchKey, trimOldest } from \"../core/collections.ts\";\nimport type { Event } from \"../core/event.ts\";\nimport { isReplaceableWinner } from \"../core/event.ts\";\nimport type { Filter } from \"../core/filter.ts\";\nimport { Kind } from \"../core/kind.ts\";\nimport { normalizeRelayUrls, normalizeURL, nowSeconds } from \"../core/util.ts\";\nimport { parseDmRelayList } from \"../nips/nip17.ts\";\nimport { parseRelayList } from \"../nips/nip65.ts\";\nimport type { RelayListItem } from \"../nips/nip65.ts\";\n\n/** Relay routing state for one pubkey: NIP-65 outbox/inbox plus NIP-17 DM relays. */\nexport type PubkeyRoutes = {\n /** Relays the user writes to (outbox). NIP-65. */\n readonly write: ReadonlyArray<string>;\n /** Relays the user reads from (inbox). NIP-65. */\n readonly read: ReadonlyArray<string>;\n /** Relays for NIP-17 gift-wrap delivery. Kind 10050. */\n readonly dm: ReadonlyArray<string>;\n /** `created_at` of the last accepted kind:10002 list. */\n readonly updatedAt: number;\n /** `created_at` of the last accepted kind:10050 list. */\n readonly dmUpdatedAt: number;\n /** Event id of the last accepted kind:10002 (NIP-01 equal-timestamp tie-break). */\n readonly relayListId?: string | undefined;\n /** Event id of the last accepted kind:10050. */\n readonly dmListId?: string | undefined;\n};\n\n/** A filter split by gossip routes: per-relay narrowed filters plus the unrouted remainder. */\nexport type RoutedFilter = {\n /** Url → already-narrowed filter. Empty when nothing routed. */\n perRelay: Map<string, Filter>;\n /**\n * Unrouted work for the caller:\n *\n * - No authors and no #p: original filter (\"generic\")\n * - Authors/#p all unrouted: original filter (\"orphan\")\n * - Mixed: narrowed leftover (\"fallback\")\n * - All routed: undefined\n */\n remainder?: Filter;\n};\n\nexport type GossipOptions = {\n /** Max relays to keep per direction when ranking. Default 4. */\n maxRelaysPerPubkey?: number | undefined;\n /**\n * Max pubkeys with tracked routes; the map is LRU — writes and lookups refresh recency, and\n * inserting beyond the cap drops the oldest entry. Default 10_000.\n */\n maxPubkeys?: number | undefined;\n};\n\nfunction emptyRoutes(): PubkeyRoutes {\n return {\n write: [],\n read: [],\n dm: [],\n updatedAt: 0,\n dmUpdatedAt: 0,\n };\n}\n\n/**\n * Routing table for NIP-65 (10002) and NIP-17 DM relays (10050). Ingest replaceable list events,\n * then route filters into per-relay REQs.\n */\nexport class Gossip {\n readonly #routes = new Map<string, PubkeyRoutes>();\n readonly #maxRelays: number;\n readonly #maxPubkeys: number;\n\n constructor(opts: GossipOptions = {}) {\n this.#maxRelays = opts.maxRelaysPerPubkey ?? 4;\n this.#maxPubkeys = opts.maxPubkeys ?? 10_000;\n }\n\n /**\n * Ingest a routing list event.\n *\n * - Kind:10002 → write/read (NIP-65)\n * - Kind:10050 → dm (NIP-17)\n *\n * Returns true if routes for that list type were updated.\n */\n ingest(event: Event): boolean {\n if (event.kind === Kind.RelayList) {\n let items: RelayListItem[];\n try {\n items = parseRelayList(event);\n } catch {\n return false;\n }\n return this.setRoutes(event.pubkey, items, event.created_at, event.id);\n }\n if (event.kind === Kind.DirectMessageRelaysList) {\n let relays: string[];\n try {\n relays = parseDmRelayList(event);\n } catch {\n return false;\n }\n return this.setDmRoutes(event.pubkey, relays, event.created_at, event.id);\n }\n return false;\n }\n\n setRoutes(\n pubkey: string,\n items: ReadonlyArray<RelayListItem>,\n updatedAt: number = nowSeconds(),\n eventId?: string,\n ): boolean {\n const pk = pubkey.toLowerCase();\n const prev = this.#lookup(pk) ?? emptyRoutes();\n if (prev.updatedAt > updatedAt) {\n return false;\n }\n if (\n eventId !== undefined &&\n prev.relayListId !== undefined &&\n prev.updatedAt === updatedAt &&\n !isReplaceableWinner(\n { created_at: updatedAt, id: eventId },\n { created_at: prev.updatedAt, id: prev.relayListId },\n )\n ) {\n return false;\n }\n\n const write: string[] = [];\n const read: string[] = [];\n for (const item of items) {\n let url: string;\n try {\n url = normalizeURL(item.url);\n } catch {\n continue;\n }\n if (item.marker !== \"read\" && !write.includes(url)) {\n write.push(url);\n }\n if (item.marker !== \"write\" && !read.includes(url)) {\n read.push(url);\n }\n }\n\n this.#put(pk, {\n write: write.slice(0, this.#maxRelays),\n read: read.slice(0, this.#maxRelays),\n dm: prev.dm,\n updatedAt,\n dmUpdatedAt: prev.dmUpdatedAt,\n relayListId: eventId ?? prev.relayListId,\n dmListId: prev.dmListId,\n });\n return true;\n }\n\n setDmRoutes(\n pubkey: string,\n relays: ReadonlyArray<string>,\n updatedAt: number = nowSeconds(),\n eventId?: string,\n ): boolean {\n const pk = pubkey.toLowerCase();\n const prev = this.#lookup(pk) ?? emptyRoutes();\n if (prev.dmUpdatedAt > updatedAt) {\n return false;\n }\n if (\n eventId !== undefined &&\n prev.dmListId !== undefined &&\n prev.dmUpdatedAt === updatedAt &&\n !isReplaceableWinner(\n { created_at: updatedAt, id: eventId },\n { created_at: prev.dmUpdatedAt, id: prev.dmListId },\n )\n ) {\n return false;\n }\n\n const dm = normalizeRelayUrls(relays);\n\n this.#put(pk, {\n write: prev.write,\n read: prev.read,\n dm: dm.slice(0, this.#maxRelays),\n updatedAt: prev.updatedAt,\n dmUpdatedAt: updatedAt,\n relayListId: prev.relayListId,\n dmListId: eventId ?? prev.dmListId,\n });\n return true;\n }\n\n getRoutes(pubkey: string): Readonly<PubkeyRoutes> | undefined {\n return this.#lookup(pubkey.toLowerCase());\n }\n\n outboxRelays(pubkey: string): ReadonlyArray<string> {\n return this.#lookup(pubkey.toLowerCase())?.write ?? [];\n }\n\n inboxRelays(pubkey: string): ReadonlyArray<string> {\n return this.#lookup(pubkey.toLowerCase())?.read ?? [];\n }\n\n /** NIP-17 kind:10050 delivery relays for gift-wraps. */\n dmRelays(pubkey: string): ReadonlyArray<string> {\n return this.#lookup(pubkey.toLowerCase())?.dm ?? [];\n }\n\n clear(pubkey?: string): void {\n if (pubkey !== undefined && pubkey !== \"\") {\n this.#routes.delete(pubkey.toLowerCase());\n } else {\n this.#routes.clear();\n }\n }\n\n get size(): number {\n return this.#routes.size;\n }\n\n /**\n * Route a user-facing filter into per-relay sub-filters plus leftover.\n *\n * - Authors → outbox relays, filter narrowed per relay's authors\n * - #p only → inbox relays of those pubkeys\n * - Neither / all unrouted → remainder is the original filter\n * - Mixed → remainder is the narrowed leftover; all routed → no remainder\n */\n route(filter: Filter): RoutedFilter {\n const authors = filter.authors?.map((a) => a.toLowerCase());\n const pTags = filter[\"#p\"]?.map((p) => p.toLowerCase());\n\n if ((!authors || authors.length === 0) && (!pTags || pTags.length === 0)) {\n return { perRelay: new Map(), remainder: filter };\n }\n\n if (authors && authors.length > 0 && (!pTags || pTags.length === 0)) {\n return this.#breakAuthors(filter, authors, \"write\");\n }\n\n if (pTags && pTags.length > 0 && (!authors || authors.length === 0)) {\n const { authors: _authors, ...base } = filter;\n return this.#breakAuthors(base, pTags, \"read\", (b, pubkeys) => ({ ...b, \"#p\": pubkeys }));\n }\n\n // both authors and #p: send full filter to union of routes\n const unionKeys = new Set([...(authors ?? []), ...(pTags ?? [])]);\n const relays = new Set<string>();\n let anyUnrouted = false;\n for (const pk of unionKeys) {\n const r = this.#lookup(pk);\n const urls = r ? [...r.write, ...r.read] : [];\n if (urls.length === 0) {\n anyUnrouted = true;\n continue;\n }\n for (const u of urls) {\n relays.add(u);\n }\n }\n if (relays.size === 0) {\n return { perRelay: new Map(), remainder: filter };\n }\n const map = new Map<string, Filter>();\n for (const url of relays) {\n map.set(url, { ...filter });\n }\n return anyUnrouted ? { perRelay: map, remainder: filter } : { perRelay: map };\n }\n\n #breakAuthors(\n filter: Filter,\n pubkeys: string[],\n direction: \"read\" | \"write\",\n narrow: (base: Filter, pubkeys: string[]) => Filter = (base, pks) => ({\n ...base,\n authors: pks,\n }),\n ): RoutedFilter {\n const perRelay = new Map<string, string[]>();\n const unrouted: string[] = [];\n let anyRoute = false;\n\n for (const pk of pubkeys) {\n const routes = this.#lookup(pk);\n const urls = direction === \"write\" ? routes?.write : routes?.read;\n if (!urls || urls.length === 0) {\n unrouted.push(pk);\n continue;\n }\n anyRoute = true;\n for (const url of urls) {\n const list = perRelay.get(url) ?? [];\n list.push(pk);\n perRelay.set(url, list);\n }\n }\n\n if (!anyRoute) {\n return { perRelay: new Map(), remainder: filter };\n }\n\n const map = new Map<string, Filter>();\n for (const [url, pks] of perRelay) {\n map.set(url, narrow(filter, pks));\n }\n return unrouted.length > 0\n ? { perRelay: map, remainder: narrow(filter, unrouted) }\n : { perRelay: map };\n }\n\n /** Read that refreshes LRU recency. */\n #lookup(pk: string): PubkeyRoutes | undefined {\n const routes = this.#routes.get(pk);\n if (routes !== undefined) {\n touchKey(this.#routes, pk, routes);\n }\n return routes;\n }\n\n /** Write at the newest LRU end, then trim the oldest beyond the cap. */\n #put(pk: string, routes: PubkeyRoutes): void {\n touchKey(this.#routes, pk, routes);\n trimOldest(this.#routes, this.#maxPubkeys);\n }\n}\n"],"mappings":"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"}
package/dist/gossip.d.mts CHANGED
@@ -1,3 +1,3 @@
1
- import { i as RoutedFilter, n as GossipOptions, r as PubkeyRoutes, t as Gossip } from "./gossip-DpIvkBuj.mjs";
1
+ import { i as RoutedFilter, n as GossipOptions, r as PubkeyRoutes, t as Gossip } from "./gossip-C7HLW5kI.mjs";
2
2
  import "./index-CaxrMD1A.mjs";
3
3
  export { Gossip, type GossipOptions, type PubkeyRoutes, type RoutedFilter };
package/dist/gossip.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { t as Gossip } from "./gossip-oloDMWEj.mjs";
1
+ import { t as Gossip } from "./gossip-CcTp9VpU.mjs";
2
2
  export { Gossip };
@@ -1,4 +1,3 @@
1
- import { t as Event } from "./event-fU3Y01PR.mjs";
2
1
  //#region src/core/limits.d.ts
3
2
  /** NIP-01 event id length in bytes. */
4
3
  declare const EVENT_ID_BYTES = 32;
@@ -30,6 +29,11 @@ declare function assertSecretKeyBytes(bytes: Uint8Array): void;
30
29
  * serialization keeps a trailing `/` on the root path (`wss://a.example/`).
31
30
  */
32
31
  declare function normalizeURL(url: string): string;
32
+ /**
33
+ * Normalize each entry like {@link normalizeURL}; empty or invalid entries are skipped and the
34
+ * results are deduplicated in first-seen order.
35
+ */
36
+ declare function normalizeRelayUrls(urls: Iterable<string>): string[];
33
37
  //#endregion
34
38
  //#region src/core/kind.d.ts
35
39
  /** NIP-01 regular: stored by relays. */
@@ -80,22 +84,5 @@ type KindName = keyof typeof Kind;
80
84
  /** Union of the numeric kind values on {@link Kind}. */
81
85
  type KnownKind = (typeof Kind)[KindName];
82
86
  //#endregion
83
- //#region src/core/verifier.d.ts
84
- /**
85
- * Verification backend: return true iff `sha256(serializedUtf8)` equals `id` and `sig` is a valid
86
- * BIP-340 signature of `id` by the x-only `pubkey`. Inputs are raw bytes (`id`/`pubkey` 32, `sig`
87
- * 64). Exceptions propagate through the built verifier unmarked — a throwing backend is broken and
88
- * must surface.
89
- */
90
- type SerializedEventVerifier = (serializedUtf8: Uint8Array, id: Uint8Array, pubkey: Uint8Array, sig: Uint8Array) => boolean;
91
- /**
92
- * Build an event verifier with exactly the semantics of {@link verifyEvent}, delegating the hash and
93
- * signature check to `backend`. Verified/failed results share `verifyEvent`'s WeakSet caches, so
94
- * verifiers built this way and `verifyEvent` agree on (and reuse) each other's results.
95
- */
96
- declare function createEventVerifier(backend: SerializedEventVerifier): (event: Event) => boolean;
97
- /** Verify event id and BIP-340 signature. Uses WeakSet cache (does not mutate the event). */
98
- declare const verifyEvent: (event: Event) => boolean;
99
- //#endregion
100
- export { SIGNATURE_BYTES as C, SECRET_KEY_BYTES as S, isHex32 as _, KindClassification as a, EVENT_ID_BYTES as b, classifyKind as c, isRegularKind as d, isReplaceableKind as f, hexToBytes as g, bytesToHex as h, Kind as i, isAddressableKind as l, assertSecretKeyBytes as m, createEventVerifier as n, KindName as o, assertHex32 as p, verifyEvent as r, KnownKind as s, SerializedEventVerifier as t, isEphemeralKind as u, isHex64 as v, SUBSCRIPTION_ID_MAX_CHARS as w, PUBLIC_KEY_BYTES as x, normalizeURL as y };
101
- //# sourceMappingURL=index-CvkvqB16.d.mts.map
87
+ export { SUBSCRIPTION_ID_MAX_CHARS as S, normalizeURL as _, classifyKind as a, SECRET_KEY_BYTES as b, isRegularKind as c, assertSecretKeyBytes as d, bytesToHex as f, normalizeRelayUrls as g, isHex64 as h, KnownKind as i, isReplaceableKind as l, isHex32 as m, KindClassification as n, isAddressableKind as o, hexToBytes as p, KindName as r, isEphemeralKind as s, Kind as t, assertHex32 as u, EVENT_ID_BYTES as v, SIGNATURE_BYTES as x, PUBLIC_KEY_BYTES as y };
88
+ //# sourceMappingURL=index-DU9tRFTZ.d.mts.map
@@ -1,7 +1,7 @@
1
- import { t as Event } from "./event-fU3Y01PR.mjs";
2
- import { t as Filter } from "./filter-C0IMByE3.mjs";
3
- import { a as NostrError } from "./error-CR8Urc84.mjs";
4
- import { n as Pool } from "./pool-DBIYMtwy.mjs";
1
+ import { t as Event } from "./event-DJeb_DIL.mjs";
2
+ import { t as Filter } from "./filter-jI_9htF7.mjs";
3
+ import { a as NostrError } from "./error-BquXDmIN.mjs";
4
+ import { g as Closer, n as Pool } from "./pool-SzJKPIa3.mjs";
5
5
  //#region src/relay/error.d.ts
6
6
  /** Base class for relay errors; carries the relay URL when known. */
7
7
  declare class RelayError extends NostrError {
@@ -69,9 +69,7 @@ type FanInOptions = {
69
69
  * Aggregate EOSE/close across routed jobs. One `seen` set. `pending` counts URL list entries
70
70
  * (duplicates included). `pendingEose` counts unique `jobIndex:url` keys.
71
71
  */
72
- declare function fanIn(pool: Pool, jobs: ReadonlyArray<RoutedJob>, opts?: FanInOptions): {
73
- close: (reason?: string) => void;
74
- };
72
+ declare function fanIn(pool: Pool, jobs: ReadonlyArray<RoutedJob>, opts?: FanInOptions): Closer;
75
73
  declare function fetchRouted(pool: Pool, jobs: ReadonlyArray<RoutedJob>, opts?: {
76
74
  timeoutMs?: number | undefined;
77
75
  signal?: AbortSignal | undefined;
@@ -81,4 +79,4 @@ declare function fetchRouted(pool: Pool, jobs: ReadonlyArray<RoutedJob>, opts?:
81
79
  }): Promise<Event[]>;
82
80
  //#endregion
83
81
  export { isInsecureRelayUrl as a, RelayError as c, RelayTimeoutError as d, fetchRouted as i, RelayPublishError as l, RoutedJob as n, RelayClosedError as o, fanIn as r, RelayConnectionError as s, FanInOptions as t, RelaySuspendedError as u };
84
- //# sourceMappingURL=index-q4MDce_r.d.mts.map
82
+ //# sourceMappingURL=index-DiXwNn2M.d.mts.map
@@ -1,17 +1,16 @@
1
- import { n as EventTemplate, r as UnsignedEvent, t as Event } from "./event-fU3Y01PR.mjs";
2
- import { t as EventBuilder } from "./builder-sSflu6nB.mjs";
3
- import { t as Filter } from "./filter-C0IMByE3.mjs";
4
- import { t as Gossip } from "./gossip-DpIvkBuj.mjs";
5
- import { a as NostrError } from "./error-CR8Urc84.mjs";
6
- import { i as PoolFetchResult, n as Pool, o as PoolPublishResult, t as InvalidEventPolicy } from "./pool-DBIYMtwy.mjs";
7
- import { t as WebSocketConstructor } from "./websocket-CmeqvAkp.mjs";
8
- import { t as EventStore } from "./types-2pCUprRU.mjs";
9
- import { t as ReactiveEventStore } from "./reactive-7aqeip4A.mjs";
10
- import { a as OutboxFeed, n as Loaders } from "./index-lvXeER5t.mjs";
11
- import { a as Rumor } from "./nip59-zQgdLcEr.mjs";
12
- import { i as ReplyTo, r as Recipient } from "./nip17-CKIufV98.mjs";
13
- import { t as NostrSigner } from "./types-CRQEk0Oi.mjs";
14
- import { t as StorageError } from "./error-BW7re1UN.mjs";
1
+ import { n as EventTemplate, r as UnsignedEvent, t as Event } from "./event-DJeb_DIL.mjs";
2
+ import { t as EventBuilder } from "./builder-DgkqDylK.mjs";
3
+ import { t as Filter } from "./filter-jI_9htF7.mjs";
4
+ import { t as Gossip } from "./gossip-C7HLW5kI.mjs";
5
+ import { a as NostrError } from "./error-BquXDmIN.mjs";
6
+ import { a as PoolOptions, g as Closer, i as PoolFetchResult, n as Pool, o as PoolPublishResult } from "./pool-SzJKPIa3.mjs";
7
+ import { t as EventStore } from "./types-Cm-XMgtH.mjs";
8
+ import { t as ReactiveEventStore } from "./reactive-BJ1ggn4d.mjs";
9
+ import { a as OutboxFeed, n as Loaders } from "./index-iLT8UzY3.mjs";
10
+ import { a as Rumor } from "./nip59-BFIihLYY.mjs";
11
+ import { i as ReplyTo, r as Recipient } from "./nip17-cTDFfFVt.mjs";
12
+ import { t as NostrSigner } from "./types-DNmiscfd.mjs";
13
+ import { t as StorageError } from "./error-BD_0QFKT.mjs";
15
14
  //#region src/client/types.d.ts
16
15
  /** Direction of a {@link Client.sync} run: upload, download, or both. */
17
16
  declare const SyncDirection: {
@@ -23,18 +22,18 @@ declare const SyncDirection: {
23
22
  type SyncDirectionName = (typeof SyncDirection)[keyof typeof SyncDirection];
24
23
  /** Options for {@link Client.sync} (NIP-77 set reconciliation). */
25
24
  type SyncOptions = {
26
- relays?: ReadonlyArray<string>;
27
- direction?: SyncDirectionName;
25
+ relays?: ReadonlyArray<string> | undefined;
26
+ direction?: SyncDirectionName | undefined;
28
27
  /**
29
28
  * Wall-clock deadline for the Negentropy reconciliation session (`NEG-OPEN` through `NEG-CLOSE`),
30
29
  * in milliseconds. One clock for the whole session — not reset per `NEG-MSG`. Default: the relay
31
30
  * `publishTimeoutMs`. Upload/download phases reuse this value as their per-call timeout.
32
31
  */
33
- timeoutMs?: number;
34
- signal?: AbortSignal;
35
- dryRun?: boolean;
32
+ timeoutMs?: number | undefined;
33
+ signal?: AbortSignal | undefined;
34
+ dryRun?: boolean | undefined;
36
35
  /** When false, skip observe/storage on downloaded events. Default true. */
37
- observe?: boolean;
36
+ observe?: boolean | undefined;
38
37
  };
39
38
  /** Outcome of a {@link Client.sync} run: ids compared, sent, received, and failures. */
40
39
  type SyncSummary = {
@@ -45,126 +44,104 @@ type SyncSummary = {
45
44
  sendFailures: Record<string, string>;
46
45
  persistFailures: Record<string, string>;
47
46
  };
48
- /** Options for {@link Client.builder} / `ClientOptions` accepted by the builder. */
49
- type ClientOptions = {
50
- signer?: NostrSigner;
51
- relays?: ReadonlyArray<string>;
52
- websocketImplementation?: WebSocketConstructor;
53
- /** Injected EVENT verifier. Default is core BIP-340. */
54
- verifyEvent?: (event: Event) => boolean;
55
- connectTimeoutMs?: number;
56
- publishTimeoutMs?: number;
47
+ /**
48
+ * Options for the {@link Client} constructor. Pool fields are forwarded to {@link Pool}; the
49
+ * client-only fields are documented here.
50
+ */
51
+ type ClientOptions = Omit<PoolOptions, "automaticallyAuth"> & {
52
+ signer?: NostrSigner | undefined;
53
+ relays?: ReadonlyArray<string> | undefined;
57
54
  /**
58
55
  * When true (default), answer NIP-42 AUTH automatically. The signer is read at challenge time, so
59
56
  * `setSigner()` applies to live connections; challenges received while no signer is set are
60
57
  * ignored.
61
58
  */
62
- automaticAuth?: boolean;
63
- /** When true (default), relays reconnect with backoff after disconnect. */
64
- enableReconnect?: boolean;
65
- /** Keepalive ping. Default false. */
66
- enablePing?: boolean;
67
- pingIntervalMs?: number;
68
- pingTimeoutMs?: number;
69
- /** Forwarded to the pool: when false (default), `ws://` relays are rejected. */
70
- allowInsecure?: boolean;
71
- /** Forwarded to the pool: `ws://` URLs allowed despite `allowInsecure`. */
72
- trustedInsecureUrls?: ReadonlyArray<string>;
73
- /** Forwarded to the pool: close idle relays after this many ms. */
74
- idleTimeoutMs?: number;
75
- /** Forwarded to the pool: soft cap on connected non-pinned relays. */
76
- maxRelays?: number;
77
- /** Forwarded to the pool: relays never closed by idle cleanup or `maxRelays`. */
78
- pinnedUrls?: ReadonlyArray<string>;
79
- /**
80
- * Forwarded to the pool: a relay exceeding `limit` verification failures inside `windowMs` is
81
- * disconnected and suspended for `cooldownMs`.
82
- */
83
- invalidEventPolicy?: InvalidEventPolicy;
84
- /** Forwarded to the pool: fired once when a relay is suspended, with the lift time. */
85
- onRelaySuspended?: (url: string, until: number) => void;
86
- gossip?: Gossip;
59
+ automaticAuth?: boolean | undefined;
60
+ gossip?: Gossip | undefined;
87
61
  /**
88
62
  * Local event store. Defaults to {@link MemoryEventStore}. Browser apps that want persistence must
89
63
  * pass {@link IndexedDbEventStore} and `await open()`.
90
64
  */
91
- storage?: EventStore;
65
+ storage?: EventStore | undefined;
92
66
  /**
93
67
  * Synchronous reactive index that mirrors every ingested event before callbacks and persistence.
94
68
  * Defaults to a new {@link ReactiveEventStore}.
95
69
  */
96
- index?: ReactiveEventStore;
70
+ index?: ReactiveEventStore | undefined;
97
71
  /**
98
72
  * When true (default), every ingested event is written to storage. Set false to disable automatic
99
73
  * persistence while keeping the store for manual use.
100
74
  */
101
- persistEvents?: boolean;
75
+ persistEvents?: boolean | undefined;
76
+ };
77
+ /** Typed event payloads for {@link Client.on}. */
78
+ type ClientEventMap = {
102
79
  /**
103
80
  * Storage I/O failures: live `putMany` flush and `fetchEvents({ localFirst: true })` query. Those
104
81
  * paths do not throw.
105
82
  */
106
- onstorageerror?: (err: StorageError) => void;
83
+ storageerror: StorageError;
107
84
  };
108
85
  type FetchEventsOptions = {
109
- relays?: string[];
110
- timeoutMs?: number;
111
- signal?: AbortSignal;
112
- gossip?: boolean;
86
+ relays?: ReadonlyArray<string> | undefined;
87
+ timeoutMs?: number | undefined;
88
+ signal?: AbortSignal | undefined;
89
+ gossip?: boolean | undefined;
113
90
  /** When true, query local storage first and merge with network results. Defaults to false. */
114
- localFirst?: boolean;
91
+ localFirst?: boolean | undefined;
115
92
  /** When false, skip writing fetched events to storage/observe. Default true. */
116
- observe?: boolean;
93
+ observe?: boolean | undefined;
117
94
  /**
118
95
  * Every event of every relay batch, including duplicates across relays. `relayUrl` is the
119
96
  * normalized URL of the relay that delivered it.
120
97
  */
121
- onevent?: (event: Event, relayUrl: string) => void;
98
+ onevent?: ((event: Event, relayUrl: string) => void) | undefined;
122
99
  };
123
100
  /** Options for {@link Client.fetchEach}: one-shot per-relay fetch, no gossip routing. */
124
101
  type FetchEachOptions = {
125
- relays?: string[];
126
- timeoutMs?: number;
127
- signal?: AbortSignal;
102
+ relays?: ReadonlyArray<string> | undefined;
103
+ timeoutMs?: number | undefined;
104
+ signal?: AbortSignal | undefined;
128
105
  /** When false, skip writing fetched events to storage/observe. Default true. */
129
- observe?: boolean;
106
+ observe?: boolean | undefined;
130
107
  };
131
108
  type SubscribeOptions = {
132
- relays?: string[];
109
+ relays?: ReadonlyArray<string> | undefined;
133
110
  /**
134
111
  * First receipt of each event only (deduped across relays). `relayUrl` is the normalized URL of
135
112
  * the relay that delivered it first.
136
113
  */
137
- onevent?: (event: Event, relayUrl: string) => void;
114
+ onevent?: ((event: Event, relayUrl: string) => void) | undefined;
138
115
  /** Every receipt from every relay, including duplicates skipped by dedupe. */
139
- receivedEvent?: (id: string, relayUrl: string) => void;
140
- oneose?: () => void;
141
- onclose?: (reason: string) => void;
142
- signal?: AbortSignal;
143
- id?: string;
116
+ receivedEvent?: ((id: string, relayUrl: string) => void) | undefined;
117
+ oneose?: (() => void) | undefined;
118
+ onclose?: ((reason: string) => void) | undefined;
119
+ signal?: AbortSignal | undefined;
120
+ id?: string | undefined;
144
121
  /**
145
122
  * If set, fire `oneose` once after this many ms if not all relays have EOSEd. Does not close the
146
123
  * subscription.
147
124
  */
148
- eoseTimeoutMs?: number;
125
+ eoseTimeoutMs?: number | undefined;
149
126
  /** Fan out REQs via NIP-65 gossip routes when available. */
150
- gossip?: boolean;
127
+ gossip?: boolean | undefined;
151
128
  /** When false, skip writing received events to storage/observe. Default true. */
152
- observe?: boolean;
129
+ observe?: boolean | undefined;
153
130
  };
154
131
  type PublishOptions = {
155
- relays?: string[];
156
- timeoutMs?: number;
157
- gossip?: boolean;
132
+ relays?: ReadonlyArray<string> | undefined;
133
+ timeoutMs?: number | undefined;
134
+ gossip?: boolean | undefined;
158
135
  /** When false, skip writing the published event to storage/observe. Default true. */
159
- observe?: boolean;
136
+ observe?: boolean | undefined;
160
137
  };
161
138
  /** Options for sending a NIP-17 private message. */
162
139
  type SendPrivateMessageOptions = {
163
- readonly subject?: string;
164
- readonly replyTo?: ReplyTo;
165
- readonly created_at?: number;
166
- readonly timeoutMs?: number;
167
- readonly observe?: boolean;
140
+ readonly subject?: string | undefined;
141
+ readonly replyTo?: ReplyTo | undefined;
142
+ readonly created_at?: number | undefined;
143
+ readonly timeoutMs?: number | undefined;
144
+ readonly observe?: boolean | undefined;
168
145
  };
169
146
  /** Result of sending a NIP-17 DM: the rumor plus per-recipient wrap publish results. */
170
147
  type PrivateMessageSendResult = {
@@ -180,83 +157,31 @@ type ReceivedPrivateMessage = {
180
157
  wrap: Event;
181
158
  rumor: Rumor;
182
159
  /** Normalized URL of the relay that delivered the wrap, when known. */
183
- relayUrl?: string;
160
+ relayUrl?: string | undefined;
184
161
  };
185
162
  /** Options for fetching NIP-17 private-message history. */
186
163
  type FetchPrivateMessagesOptions = {
187
- readonly since?: number;
188
- readonly until?: number;
189
- readonly timeoutMs?: number;
190
- readonly signal?: AbortSignal;
191
- readonly observe?: boolean;
164
+ readonly since?: number | undefined;
165
+ readonly until?: number | undefined;
166
+ readonly timeoutMs?: number | undefined;
167
+ readonly signal?: AbortSignal | undefined;
168
+ readonly observe?: boolean | undefined;
192
169
  };
193
170
  /** Options for a live NIP-17 private-message subscription. */
194
171
  type SubscribePrivateMessagesOptions = {
195
- readonly since?: number;
196
- readonly onevent?: (msg: ReceivedPrivateMessage) => void;
197
- readonly oneose?: () => void;
198
- readonly onclose?: (reason: string) => void;
199
- readonly signal?: AbortSignal;
200
- readonly eoseTimeoutMs?: number;
201
- readonly observe?: boolean;
172
+ readonly since?: number | undefined;
173
+ readonly onevent?: ((msg: ReceivedPrivateMessage) => void) | undefined;
174
+ readonly oneose?: (() => void) | undefined;
175
+ readonly onclose?: ((reason: string) => void) | undefined;
176
+ readonly signal?: AbortSignal | undefined;
177
+ readonly eoseTimeoutMs?: number | undefined;
178
+ readonly observe?: boolean | undefined;
202
179
  };
203
180
  /** Client lifecycle, configuration, or abort failure (not cryptographic). */
204
181
  declare class ClientError extends NostrError {
205
182
  name: string;
206
183
  }
207
184
  //#endregion
208
- //#region src/client/builder.d.ts
209
- /**
210
- * Fluent constructor for {@link Client}. The constructor is supplied by {@link Client.builder} so
211
- * this module never imports the `Client` value, keeping the client/builder dependency
212
- * one-directional.
213
- */
214
- declare class ClientBuilder {
215
- #private;
216
- constructor(newClient: (opts: ClientOptions) => Client);
217
- signer(signer: NostrSigner): this;
218
- relays(urls: ReadonlyArray<string>): this;
219
- addRelay(url: string): this;
220
- websocketImplementation(impl: WebSocketConstructor): this;
221
- verifyEvent(fn: (event: Event) => boolean): this;
222
- connectTimeoutMs(ms: number): this;
223
- publishTimeoutMs(ms: number): this;
224
- automaticAuth(enabled: boolean): this;
225
- /** Allow `ws://` relays. Default false. */
226
- allowInsecure(allow: boolean): this;
227
- /** `ws://` URLs allowed despite `allowInsecure` being off. */
228
- trustedInsecureUrls(urls: ReadonlyArray<string>): this;
229
- /** Close idle relays after this many ms. Unset = disabled. */
230
- idleTimeoutMs(ms: number): this;
231
- /** Soft cap on connected non-pinned relays. */
232
- maxRelays(n: number): this;
233
- /** Relays never closed by idle cleanup or `maxRelays` eviction. */
234
- pinnedUrls(urls: ReadonlyArray<string>): this;
235
- /** Forwarded to the pool: suspend a relay delivering too many invalid events. */
236
- invalidEventPolicy(policy: InvalidEventPolicy): this;
237
- /** Forwarded to the pool: fired once when a relay is suspended, with the lift time. */
238
- onRelaySuspended(cb: (url: string, until: number) => void): this;
239
- enableReconnect(enabled: boolean): this;
240
- enablePing(enabled: boolean): this;
241
- pingIntervalMs(ms: number): this;
242
- pingTimeoutMs(ms: number): this;
243
- /**
244
- * Local event store. Defaults to {@link MemoryEventStore}. Browser apps that want persistence must
245
- * pass {@link IndexedDbEventStore} and `await open()`.
246
- */
247
- storage(store: EventStore): this;
248
- /**
249
- * Synchronous reactive index mirroring ingested events. Defaults to a new
250
- * {@link ReactiveEventStore}.
251
- */
252
- index(index: ReactiveEventStore): this;
253
- /** When false, Client will not auto-write events to storage. Default true. */
254
- persistEvents(enabled: boolean): this;
255
- gossip(gossip: Gossip): this;
256
- onstorageerror(fn: (err: StorageError) => void): this;
257
- build(): Client;
258
- }
259
- //#endregion
260
185
  //#region src/client/client.d.ts
261
186
  /** Layer-5 facade: signer + default relays + pool + loaders + gossip + event store. */
262
187
  declare class Client {
@@ -266,9 +191,7 @@ declare class Client {
266
191
  readonly gossip: Gossip;
267
192
  readonly storage: EventStore;
268
193
  readonly index: ReactiveEventStore;
269
- onstorageerror: ((err: StorageError) => void) | undefined;
270
194
  constructor(opts?: ClientOptions);
271
- static builder(): ClientBuilder;
272
195
  get signer(): NostrSigner | undefined;
273
196
  get relays(): ReadonlyArray<string>;
274
197
  get isShutdown(): boolean;
@@ -277,9 +200,15 @@ declare class Client {
277
200
  removeRelay(url: string): void;
278
201
  /** Connect all configured relays (best-effort; failures are ignored). */
279
202
  connect(opts?: {
280
- signal?: AbortSignal;
203
+ signal?: AbortSignal | undefined;
281
204
  }): Promise<void>;
282
205
  shutdown(): Promise<void>;
206
+ /**
207
+ * Listen for a client event. Multiple listeners per type are allowed and fire in registration
208
+ * order; a throwing listener is reported and does not break the dispatch. Returns an unsubscribe
209
+ * function.
210
+ */
211
+ on<K extends keyof ClientEventMap>(type: K, listener: (err: ClientEventMap[K]) => void): () => void;
283
212
  /** Ingest one inbound event: index (with relay URL) → meta → persistence. */
284
213
  observe(event: Event, relayUrl?: string): void;
285
214
  /** Ingest many events (deduped by id, order preserved) as one persist batch. */
@@ -316,26 +245,22 @@ declare class Client {
316
245
  * Fetch events. With `gossip: true`, routes filters by NIP-65 when possible. With `localFirst:
317
246
  * true`, merges storage hits with network results.
318
247
  */
319
- fetchEvents(filter: Filter | Filter[], opts?: FetchEventsOptions): Promise<Event[]>;
248
+ fetchEvents(filter: Filter | ReadonlyArray<Filter>, opts?: FetchEventsOptions): Promise<Event[]>;
320
249
  /**
321
250
  * One-shot fetch with per-relay results: each entry reports its events and end reason (`eose`,
322
251
  * `closed`, `timeout`, or `failed` when the relay could not be reached). Events are ingested into
323
252
  * the index with each relay's URL in `seenOn` and persisted exactly like {@link fetchEvents}
324
253
  * (`observe` defaults to true). No gossip routing and no local merge.
325
254
  */
326
- fetchEach(filter: Filter | Filter[], opts?: FetchEachOptions): Promise<PoolFetchResult[]>;
255
+ fetchEach(filter: Filter | ReadonlyArray<Filter>, opts?: FetchEachOptions): Promise<PoolFetchResult[]>;
327
256
  /** Query local storage only (no network). */
328
- queryLocal(filter: Filter | Filter[]): Promise<Event[]>;
329
- subscribe(filter: Filter | Filter[], opts?: SubscribeOptions): {
330
- close: (reason?: string) => void;
331
- };
257
+ queryLocal(filter: Filter | ReadonlyArray<Filter>): Promise<Event[]>;
258
+ subscribe(filter: Filter | ReadonlyArray<Filter>, opts?: SubscribeOptions): Closer;
332
259
  /** Publish a kind:10050 NIP-17 DM relay list (`relay` tags). Not kind 10002. */
333
260
  setDmRelays(relays: ReadonlyArray<string>, opts?: PublishOptions): Promise<PoolPublishResult[]>;
334
261
  sendPrivateMessage(recipients: string | Recipient | ReadonlyArray<string | Recipient>, content: string, opts?: SendPrivateMessageOptions): Promise<PrivateMessageSendResult>;
335
262
  fetchPrivateMessages(opts?: FetchPrivateMessagesOptions): Promise<ReceivedPrivateMessage[]>;
336
- subscribePrivateMessages(opts?: SubscribePrivateMessagesOptions): Promise<{
337
- close: (reason?: string) => void;
338
- }>;
263
+ subscribePrivateMessages(opts?: SubscribePrivateMessagesOptions): Promise<Closer>;
339
264
  /**
340
265
  * NIP-77 sync against one relay: reconcile, then optionally upload local-only events and/or
341
266
  * download remote-only events. `observe: false` skips putMany and ingest; received ids are still
@@ -350,5 +275,5 @@ declare class Client {
350
275
  sync(filter: Filter, opts?: SyncOptions): Promise<SyncSummary>;
351
276
  }
352
277
  //#endregion
353
- export { SyncSummary as _, FetchEachOptions as a, PrivateMessageSendResult as c, SendPrivateMessageOptions as d, SubscribeOptions as f, SyncOptions as g, SyncDirectionName as h, ClientOptions as i, PublishOptions as l, SyncDirection as m, ClientBuilder as n, FetchEventsOptions as o, SubscribePrivateMessagesOptions as p, ClientError as r, FetchPrivateMessagesOptions as s, Client as t, ReceivedPrivateMessage as u };
354
- //# sourceMappingURL=index-CNl2q67H.d.mts.map
278
+ export { SyncSummary as _, FetchEachOptions as a, PrivateMessageSendResult as c, SendPrivateMessageOptions as d, SubscribeOptions as f, SyncOptions as g, SyncDirectionName as h, ClientOptions as i, PublishOptions as l, SyncDirection as m, ClientError as n, FetchEventsOptions as o, SubscribePrivateMessagesOptions as p, ClientEventMap as r, FetchPrivateMessagesOptions as s, Client as t, ReceivedPrivateMessage as u };
279
+ //# sourceMappingURL=index-DocgD9K0.d.mts.map