@qntx/nostr 0.5.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 (178) 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-BtIAtxrL.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-QxvrpMfF.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-Dmc8FJgO.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-BcKzxHIP.mjs +220 -0
  38. package/dist/instance-BcKzxHIP.mjs.map +1 -0
  39. package/dist/instance-DjsL8h84.d.mts +15 -0
  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/nostr_crypto_wasm_bg.wasm +0 -0
  117. package/dist/{pool-DBIYMtwy.d.mts → pool-SzJKPIa3.d.mts} +96 -91
  118. package/dist/pool-YHRZgcQ3.mjs +596 -0
  119. package/dist/pool-YHRZgcQ3.mjs.map +1 -0
  120. package/dist/put-DS0yzVJx.mjs +187 -0
  121. package/dist/put-DS0yzVJx.mjs.map +1 -0
  122. package/dist/{reactive-DFpqtIf_.d.mts → reactive-BJ1ggn4d.d.mts} +9 -22
  123. package/dist/{reactive-DDlThDSZ.mjs → reactive-BJRoVHxU.mjs} +113 -64
  124. package/dist/reactive-BJRoVHxU.mjs.map +1 -0
  125. package/dist/{pool-As20OGxB.mjs → relay-CvxPiQ_m.mjs} +178 -721
  126. package/dist/relay-CvxPiQ_m.mjs.map +1 -0
  127. package/dist/relay.d.mts +4 -4
  128. package/dist/relay.mjs +3 -2
  129. package/dist/serial-Bzpw-xz3.mjs +21 -0
  130. package/dist/serial-Bzpw-xz3.mjs.map +1 -0
  131. package/dist/{signer-CYQutDzD.mjs → signer-yW3slDC2.mjs} +12 -14
  132. package/dist/signer-yW3slDC2.mjs.map +1 -0
  133. package/dist/signer.d.mts +2 -2
  134. package/dist/signer.mjs +2 -2
  135. package/dist/{storage-Dky3wuoH.mjs → storage-CZ52ifcQ.mjs} +17 -42
  136. package/dist/storage-CZ52ifcQ.mjs.map +1 -0
  137. package/dist/storage.d.mts +3 -3
  138. package/dist/storage.mjs +4 -4
  139. package/dist/store.d.mts +1 -1
  140. package/dist/store.mjs +1 -1
  141. package/dist/testing.d.mts +18 -18
  142. package/dist/testing.mjs +9 -9
  143. package/dist/testing.mjs.map +1 -1
  144. package/dist/{types-2pCUprRU.d.mts → types-Cm-XMgtH.d.mts} +6 -6
  145. package/dist/{types-CRQEk0Oi.d.mts → types-DNmiscfd.d.mts} +2 -2
  146. package/dist/{util-LfQirgDk.mjs → util-yFYzlbrm.mjs} +29 -7
  147. package/dist/util-yFYzlbrm.mjs.map +1 -0
  148. package/dist/{verifier-t5WoWbtY.mjs → verifier-BTLOpo1n.mjs} +3 -3
  149. package/dist/verifier-BTLOpo1n.mjs.map +1 -0
  150. package/dist/verifier-CgTFpgsC.d.mts +22 -0
  151. package/dist/wasm.browser.d.mts +8 -0
  152. package/dist/wasm.browser.mjs +19 -0
  153. package/dist/wasm.browser.mjs.map +1 -0
  154. package/dist/wasm.d.mts +3 -13
  155. package/dist/wasm.mjs +7 -208
  156. package/dist/wasm.mjs.map +1 -1
  157. package/package.json +13 -10
  158. package/dist/abort-Ba8tDrOI.mjs.map +0 -1
  159. package/dist/builder-9tjI6WQ3.mjs.map +0 -1
  160. package/dist/client-BtIAtxrL.mjs.map +0 -1
  161. package/dist/event-D8Nfoh4K.mjs.map +0 -1
  162. package/dist/gossip-oloDMWEj.mjs.map +0 -1
  163. package/dist/key-Bps-LqX7.mjs.map +0 -1
  164. package/dist/loaders-o6YXB_Ri.mjs.map +0 -1
  165. package/dist/memory-C2EVx0_O.mjs.map +0 -1
  166. package/dist/memory-index-Do1mz0WF.mjs.map +0 -1
  167. package/dist/message-DcY6Fi9o.mjs +0 -193
  168. package/dist/message-DcY6Fi9o.mjs.map +0 -1
  169. package/dist/nip65-CjgmdpPB.d.mts +0 -26
  170. package/dist/nips/nip96.d.mts +0 -40
  171. package/dist/nips/nip96.mjs +0 -118
  172. package/dist/nips/nip96.mjs.map +0 -1
  173. package/dist/pool-As20OGxB.mjs.map +0 -1
  174. package/dist/reactive-DDlThDSZ.mjs.map +0 -1
  175. package/dist/signer-CYQutDzD.mjs.map +0 -1
  176. package/dist/storage-Dky3wuoH.mjs.map +0 -1
  177. package/dist/util-LfQirgDk.mjs.map +0 -1
  178. package/dist/verifier-t5WoWbtY.mjs.map +0 -1
@@ -1,188 +1,7 @@
1
- import { o as isHex32 } from "./util-LfQirgDk.mjs";
2
- import { i as isEphemeralKind, t as Kind } from "./kind-Cxwhhkx8.mjs";
3
- import { _ as formatEventAddress, a as isReplaceableWinner, b as parseEventAddress, d as sortEvents, g as eventAddress, m as validateSignedEvent, o as itemCompare, t as compareEventsDesc } from "./event-D8Nfoh4K.mjs";
1
+ import { d as sortEvents, g as eventAddress, o as itemCompare, t as compareEventsDesc, v as formatEventAddress, x as parseEventAddress } from "./event-DinQMmJ1.mjs";
4
2
  import { a as matchFilter } from "./filter-BQl3GlHt.mjs";
5
- //#region src/storage/deletion.ts
6
- /**
7
- * NIP-09 application plan for a kind 5 event.
8
- *
9
- * - `e` tags: delete only when the target exists, is not itself kind 5, and shares pubkey.
10
- * - Unknown `e` ids are pending until the event arrives (client MUST validate pubkey).
11
- * - `a` tags: tombstone that coordinate up to this deletion's `created_at` when pubkey matches.
12
- * - Deleting a deletion request is a no-op.
13
- */
14
- function planDeletion(deletion, getById) {
15
- const { pubkey } = deletion;
16
- const removeIds = [];
17
- const pendingIds = [];
18
- const coordinates = [];
19
- const seenIds = /* @__PURE__ */ new Set();
20
- const seenCoords = /* @__PURE__ */ new Set();
21
- for (const tag of deletion.tags) {
22
- if (tag[0] === "e" && tag[1] !== void 0 && isHex32(tag[1].toLowerCase())) {
23
- const id = tag[1].toLowerCase();
24
- if (seenIds.has(id)) continue;
25
- seenIds.add(id);
26
- const existing = getById(id);
27
- if (!existing) {
28
- pendingIds.push({
29
- id,
30
- pubkey
31
- });
32
- continue;
33
- }
34
- if (existing.kind === Kind.EventDeletion) continue;
35
- if (existing.pubkey !== pubkey) continue;
36
- removeIds.push(id);
37
- continue;
38
- }
39
- if (tag[0] === "a" && tag[1] !== void 0) {
40
- const coord = parseEventAddress(tag[1]);
41
- if (!coord || coord.pubkey !== pubkey) continue;
42
- const key = `${coord.kind}:${coord.pubkey}:${coord.identifier}`;
43
- if (seenCoords.has(key)) continue;
44
- seenCoords.add(key);
45
- coordinates.push({
46
- key,
47
- until: deletion.created_at
48
- });
49
- }
50
- }
51
- return {
52
- removeIds,
53
- pendingIds,
54
- coordinates
55
- };
56
- }
57
- /** In-memory NIP-09 tombstones shared by MemoryEventStore and IndexedDbEventStore. */
58
- var DeletionState = class {
59
- ids = /* @__PURE__ */ new Set();
60
- pending = /* @__PURE__ */ new Map();
61
- coordinates = /* @__PURE__ */ new Map();
62
- clear() {
63
- this.ids.clear();
64
- this.pending.clear();
65
- this.coordinates.clear();
66
- }
67
- covers(event) {
68
- if (this.ids.has(event.id)) return true;
69
- if (event.kind === Kind.EventDeletion) return false;
70
- const pendingPk = this.pending.get(event.id);
71
- if (pendingPk !== void 0 && event.pubkey === pendingPk) return true;
72
- const addr = eventAddress(event);
73
- if (addr === void 0) return false;
74
- const until = this.coordinates.get(addr);
75
- return until !== void 0 && event.created_at <= until;
76
- }
77
- absorb(plan) {
78
- for (const id of plan.removeIds) {
79
- this.ids.add(id);
80
- this.pending.delete(id);
81
- }
82
- for (const p of plan.pendingIds) if (!this.ids.has(p.id)) this.pending.set(p.id, p.pubkey);
83
- for (const c of plan.coordinates) {
84
- const prev = this.coordinates.get(c.key) ?? Number.NEGATIVE_INFINITY;
85
- this.coordinates.delete(c.key);
86
- this.coordinates.set(c.key, Math.max(prev, c.until));
87
- }
88
- }
89
- };
90
- function coordinateRemovals(coordinates, getCurrent) {
91
- const ids = [];
92
- for (const c of coordinates) {
93
- const current = getCurrent(c.key);
94
- if (current && current.created_at <= c.until) ids.push(current.id);
95
- }
96
- return ids;
97
- }
98
- //#endregion
99
- //#region src/storage/put.ts
100
- function outboxBoundKey(pubkey, kind) {
101
- return `${pubkey.toLowerCase()}:${kind}`;
102
- }
103
- function decidePut(raw, lookup) {
104
- if (!validateSignedEvent(raw)) return {
105
- action: "skip",
106
- result: "invalid",
107
- event: raw
108
- };
109
- const event = raw;
110
- if (lookup.deletion.ids.has(event.id) || lookup.getById(event.id)) return {
111
- action: "skip",
112
- result: "duplicate",
113
- event
114
- };
115
- if (event.kind === Kind.EventDeletion) {
116
- const plan = planDeletion(event, lookup.getById);
117
- return {
118
- action: "delete",
119
- result: "deleted",
120
- event,
121
- plan,
122
- coordIds: coordinateRemovals(plan.coordinates, lookup.getReplaceable)
123
- };
124
- }
125
- if (lookup.deletion.covers(event)) return {
126
- action: "tombstone",
127
- result: "duplicate",
128
- event
129
- };
130
- if (isEphemeralKind(event.kind)) return {
131
- action: "skip",
132
- result: "ephemeral",
133
- event
134
- };
135
- const address = eventAddress(event);
136
- if (address !== void 0) {
137
- const prev = lookup.getReplaceable(address);
138
- if (prev && prev.id !== event.id && !isReplaceableWinner(event, prev)) return {
139
- action: "skip",
140
- result: "rejected",
141
- event
142
- };
143
- if (prev !== void 0 && prev.evicted !== true) return {
144
- action: "insert",
145
- result: "replaced",
146
- event,
147
- address,
148
- replaceId: prev.id
149
- };
150
- return {
151
- action: "insert",
152
- result: "accepted",
153
- event,
154
- address
155
- };
156
- }
157
- return {
158
- action: "insert",
159
- result: "accepted",
160
- event
161
- };
162
- }
163
- function applyPutMemory(s, d) {
164
- if (d.action === "skip") return d.result;
165
- if (d.action === "tombstone") {
166
- s.deletion.ids.add(d.event.id);
167
- s.deletion.pending.delete(d.event.id);
168
- return "duplicate";
169
- }
170
- if (d.action === "delete") {
171
- s.deletion.pending.delete(d.event.id);
172
- s.deletion.absorb(d.plan);
173
- for (const id of d.plan.removeIds) s.indexRemove(id);
174
- for (const id of d.coordIds) {
175
- s.deletion.ids.add(id);
176
- s.indexRemove(id);
177
- }
178
- s.indexInsert(d.event);
179
- return "deleted";
180
- }
181
- if (d.replaceId !== void 0) s.indexRemove(d.replaceId);
182
- s.indexInsert(d.event);
183
- return d.result;
184
- }
185
- //#endregion
3
+ import { i as trimOldest, n as removeFromSetMap, t as addToSetMap } from "./collections-DtwMc1NU.mjs";
4
+ import { i as DeletionState, n as decidePut, r as outboxBoundKey, t as applyPutMemory } from "./put-DS0yzVJx.mjs";
186
5
  //#region src/storage/memory-index.ts
187
6
  /**
188
7
  * Synchronous in-memory event index with NIP-01 replaceable / addressable / ephemeral handling and
@@ -207,11 +26,11 @@ var MemoryIndex = class {
207
26
  constructor(opts) {
208
27
  this.#maxTombstones = opts?.maxTombstones;
209
28
  this.#maxWatermarks = opts?.maxWatermarks ?? 0;
210
- this.#onInsert = opts?.onInsert === void 0 ? void 0 : (event) => opts.onInsert?.(event);
211
- this.#onRemove = opts?.onRemove === void 0 ? void 0 : (event) => opts.onRemove?.(event);
29
+ this.#onInsert = opts?.onInsert;
30
+ this.#onRemove = opts?.onRemove;
212
31
  }
213
32
  put(raw) {
214
- const decision = decidePut(raw, {
33
+ const lookup = {
215
34
  deletion: this.#deletion,
216
35
  getById: (id) => this.#byId.get(id),
217
36
  getReplaceable: (addr) => {
@@ -227,7 +46,8 @@ var MemoryIndex = class {
227
46
  evicted: true
228
47
  };
229
48
  }
230
- });
49
+ };
50
+ const decision = decidePut(raw, lookup);
231
51
  if (decision.action === "delete") {
232
52
  for (const p of decision.plan.pendingIds) this.#dropWatermarkId(p.id);
233
53
  for (const c of decision.plan.coordinates) this.#dropWatermark(c.key);
@@ -257,9 +77,8 @@ var MemoryIndex = class {
257
77
  if (!coord) return;
258
78
  const id = this.#replaceable.get(formatEventAddress(coord.kind, coord.pubkey, coord.identifier));
259
79
  if (id === void 0) return;
260
- const key = id.toLowerCase();
261
- if (this.#deletion.ids.has(key)) return;
262
- return this.#byId.get(key);
80
+ if (this.#deletion.ids.has(id)) return;
81
+ return this.#byId.get(id);
263
82
  }
264
83
  query(filters) {
265
84
  const seen = /* @__PURE__ */ new Set();
@@ -289,7 +108,7 @@ var MemoryIndex = class {
289
108
  });
290
109
  });
291
110
  if (filter.limit !== void 0) {
292
- items.sort(queryItemOrder);
111
+ items.sort(compareEventsDesc);
293
112
  if (items.length > filter.limit) items.length = filter.limit;
294
113
  }
295
114
  items.sort(itemCompare);
@@ -379,12 +198,12 @@ var MemoryIndex = class {
379
198
  #indexInsert(event) {
380
199
  this.#byId.set(event.id, event);
381
200
  const { pubkey } = event;
382
- addToSet(this.#byPubkey, pubkey, event.id);
383
- addToSet(this.#byKind, event.kind, event.id);
384
- addToSet(this.#byKindPubkey, `${event.kind}:${pubkey}`, event.id);
201
+ addToSetMap(this.#byPubkey, pubkey, event.id);
202
+ addToSetMap(this.#byKind, event.kind, event.id);
203
+ addToSetMap(this.#byKindPubkey, `${event.kind}:${pubkey}`, event.id);
385
204
  for (const tag of event.tags) {
386
205
  if (tag[0] !== "e" && tag[0] !== "p" || tag[1] === void 0) continue;
387
- addToSet(this.#byEpTag, `${tag[0]}:${tag[1].toLowerCase()}`, event.id);
206
+ addToSetMap(this.#byEpTag, `${tag[0]}:${tag[1].toLowerCase()}`, event.id);
388
207
  }
389
208
  const addr = eventAddress(event);
390
209
  if (addr !== void 0) {
@@ -394,21 +213,20 @@ var MemoryIndex = class {
394
213
  this.#onInsert?.(event);
395
214
  }
396
215
  #indexRemove(id) {
397
- const key = id;
398
- this.#dropWatermarkId(key);
399
- const event = this.#byId.get(key);
216
+ this.#dropWatermarkId(id);
217
+ const event = this.#byId.get(id);
400
218
  if (!event) return false;
401
- this.#byId.delete(key);
219
+ this.#byId.delete(id);
402
220
  const { pubkey } = event;
403
- removeFromSet(this.#byPubkey, pubkey, key);
404
- removeFromSet(this.#byKind, event.kind, key);
405
- removeFromSet(this.#byKindPubkey, `${event.kind}:${pubkey}`, key);
221
+ removeFromSetMap(this.#byPubkey, pubkey, id);
222
+ removeFromSetMap(this.#byKind, event.kind, id);
223
+ removeFromSetMap(this.#byKindPubkey, `${event.kind}:${pubkey}`, id);
406
224
  for (const tag of event.tags) {
407
225
  if (tag[0] !== "e" && tag[0] !== "p" || tag[1] === void 0) continue;
408
- removeFromSet(this.#byEpTag, `${tag[0]}:${tag[1].toLowerCase()}`, key);
226
+ removeFromSetMap(this.#byEpTag, `${tag[0]}:${tag[1].toLowerCase()}`, id);
409
227
  }
410
228
  const addr = eventAddress(event);
411
- if (addr !== void 0 && this.#replaceable.get(addr) === key) this.#replaceable.delete(addr);
229
+ if (addr !== void 0 && this.#replaceable.get(addr) === id) this.#replaceable.delete(addr);
412
230
  this.#onRemove?.(event);
413
231
  return true;
414
232
  }
@@ -513,11 +331,7 @@ var MemoryIndex = class {
513
331
  this.#dropWatermark(addr);
514
332
  this.#watermarks.set(addr, watermark);
515
333
  this.#watermarkIds.set(watermark.id, addr);
516
- while (this.#watermarks.size > this.#maxWatermarks) {
517
- const oldest = this.#watermarks.keys().next().value;
518
- if (oldest === void 0) break;
519
- this.#dropWatermark(oldest);
520
- }
334
+ trimOldest(this.#watermarks, this.#maxWatermarks, (a) => this.#dropWatermark(a));
521
335
  }
522
336
  #dropWatermark(addr) {
523
337
  const watermark = this.#watermarks.get(addr);
@@ -534,25 +348,11 @@ var MemoryIndex = class {
534
348
  #trimTombstones() {
535
349
  const cap = this.#maxTombstones;
536
350
  if (cap === void 0) return;
537
- let excess = this.#deletion.ids.size - cap;
538
- for (const id of this.#deletion.ids) {
539
- if (excess <= 0) break;
540
- this.#deletion.ids.delete(id);
351
+ trimOldest(this.#deletion.ids, cap, (id) => {
541
352
  this.#deletion.pending.delete(id);
542
- excess--;
543
- }
544
- excess = this.#deletion.pending.size - cap;
545
- for (const id of this.#deletion.pending.keys()) {
546
- if (excess <= 0) break;
547
- this.#deletion.pending.delete(id);
548
- excess--;
549
- }
550
- excess = this.#deletion.coordinates.size - cap;
551
- for (const key of this.#deletion.coordinates.keys()) {
552
- if (excess <= 0) break;
553
- this.#deletion.coordinates.delete(key);
554
- excess--;
555
- }
353
+ });
354
+ trimOldest(this.#deletion.pending, cap);
355
+ trimOldest(this.#deletion.coordinates, cap);
556
356
  }
557
357
  };
558
358
  function visitEpTagIds(byEpTag, byId, name, values, seen, visit) {
@@ -568,24 +368,7 @@ function visitEpTagIds(byEpTag, byId, name, values, seen, visit) {
568
368
  }
569
369
  }
570
370
  }
571
- function addToSet(map, key, id) {
572
- let set = map.get(key);
573
- if (!set) {
574
- set = /* @__PURE__ */ new Set();
575
- map.set(key, set);
576
- }
577
- set.add(id);
578
- }
579
- function removeFromSet(map, key, id) {
580
- const set = map.get(key);
581
- if (!set) return;
582
- set.delete(id);
583
- if (set.size === 0) map.delete(key);
584
- }
585
- function queryItemOrder(a, b) {
586
- return compareEventsDesc(a, b);
587
- }
588
371
  //#endregion
589
- export { planDeletion as a, DeletionState as i, decidePut as n, outboxBoundKey as r, MemoryIndex as t };
372
+ export { MemoryIndex as t };
590
373
 
591
- //# sourceMappingURL=memory-index-Do1mz0WF.mjs.map
374
+ //# sourceMappingURL=memory-index-C6rujtoc.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-index-C6rujtoc.mjs","names":[],"sources":["../src/storage/memory-index.ts"],"sourcesContent":["import { addToSetMap, removeFromSetMap, trimOldest } from \"../core/collections.ts\";\nimport type { Event } from \"../core/event.ts\";\nimport { compareEventsDesc, itemCompare, sortEvents } from \"../core/event.ts\";\nimport type { Filter } from \"../core/filter.ts\";\nimport { matchFilter } from \"../core/filter.ts\";\nimport { eventAddress, formatEventAddress, parseEventAddress } from \"../core/tag.ts\";\nimport { DeletionState } from \"./deletion.ts\";\nimport { applyPutMemory, decidePut, outboxBoundKey } from \"./put.ts\";\nimport type { PutLookup } from \"./put.ts\";\nimport type { NegentropyItem, OutboxBound, PutResult } from \"./types.ts\";\n\nexport type MemoryIndexOptions = {\n /**\n * FIFO cap applied independently to each deletion-state collection: tombstoned event ids, pending\n * e-tag ids, and coordinate tombstones. Default unbounded. Trade-off: once an entry is trimmed, a\n * re-arriving old deleted event or replaceable version can be accepted again.\n */\n maxTombstones?: number | undefined;\n /**\n * FIFO cap on replaceable/addressable winner watermarks recorded by {@link evict}. A watermark\n * keeps only `{ id, created_at }` per address so stale versions stay rejected after the winner's\n * body is evicted, and a re-put of the watermarked id re-inserts it. Default 0 (no watermark\n * retention); callers that evict should pass a bound. Once a watermark is trimmed, an older\n * evicted version can be accepted again — the same trade-off as deletion tombstones.\n */\n maxWatermarks?: number | undefined;\n /** Fires after every physical index insert (accept, replace, deletion event). */\n onInsert?: ((event: Event) => void) | undefined;\n /** Fires after every physical index remove (replace, delete, evict, remove, clear). */\n onRemove?: ((event: Event) => void) | undefined;\n};\n\n/**\n * Synchronous in-memory event index with NIP-01 replaceable / addressable / ephemeral handling and\n * NIP-09 deletion (kind 5) application. Shared by {@link MemoryEventStore} (async facade) and\n * `ReactiveEventStore`.\n */\nexport class MemoryIndex {\n readonly #byId = new Map<string, Event>();\n readonly #byPubkey = new Map<string, Set<string>>();\n readonly #byKind = new Map<number, Set<string>>();\n readonly #byKindPubkey = new Map<string, Set<string>>(); // `${kind}:${pubkey}` → ids\n readonly #byEpTag = new Map<string, Set<string>>(); // `${\"e\"|\"p\"}:${value.toLowerCase()}` → ids\n readonly #replaceable = new Map<string, string>(); // address -> event id\n // address -> last evicted winner; insertion order is the FIFO trim order\n readonly #watermarks = new Map<string, { id: string; created_at: number }>();\n // winner id -> address reverse index for O(1) watermark drops on deletion\n readonly #watermarkIds = new Map<string, string>();\n readonly #deletion = new DeletionState();\n readonly #outboxBounds = new Map<string, OutboxBound>();\n readonly #maxTombstones: number | undefined;\n readonly #maxWatermarks: number;\n readonly #onInsert: ((event: Event) => void) | undefined;\n readonly #onRemove: ((event: Event) => void) | undefined;\n\n constructor(opts?: MemoryIndexOptions) {\n this.#maxTombstones = opts?.maxTombstones;\n this.#maxWatermarks = opts?.maxWatermarks ?? 0;\n this.#onInsert = opts?.onInsert;\n this.#onRemove = opts?.onRemove;\n }\n\n put(raw: Event): PutResult {\n const lookup: PutLookup = {\n deletion: this.#deletion,\n getById: (id) => this.#byId.get(id),\n getReplaceable: (addr) => {\n const id = this.#replaceable.get(addr);\n const ev = id === undefined ? undefined : this.#byId.get(id);\n if (ev) {\n return { id: ev.id, created_at: ev.created_at };\n }\n const watermark = this.#watermarks.get(addr);\n return watermark === undefined ? undefined : { ...watermark, evicted: true };\n },\n };\n const decision = decidePut(raw, lookup);\n if (decision.action === \"delete\") {\n // Pending e-tag targets and coordinate tombstones never reach\n // indexRemove — drop their watermarks here.\n for (const p of decision.plan.pendingIds) {\n this.#dropWatermarkId(p.id);\n }\n for (const c of decision.plan.coordinates) {\n this.#dropWatermark(c.key);\n }\n }\n const result = applyPutMemory(\n {\n deletion: this.#deletion,\n indexInsert: (e) => this.#indexInsert(e),\n indexRemove: (id) => this.#indexRemove(id),\n },\n decision,\n );\n this.#trimTombstones();\n return result;\n }\n\n /** Sequential `put` in input order. */\n putMany(events: ReadonlyArray<Event>): PutResult[] {\n const results: PutResult[] = [];\n for (const event of events) {\n results.push(this.put(event));\n }\n return results;\n }\n\n get(id: string): Event | undefined {\n const key = id.toLowerCase();\n if (this.#deletion.ids.has(key)) {\n return undefined;\n }\n return this.#byId.get(key);\n }\n\n /** Current event at a `kind:pubkey:d` coordinate (replaceable or addressable). */\n getByAddress(address: string): Event | undefined {\n const coord = parseEventAddress(address);\n if (!coord) {\n return undefined;\n }\n const id = this.#replaceable.get(\n formatEventAddress(coord.kind, coord.pubkey, coord.identifier),\n );\n if (id === undefined) {\n return undefined;\n }\n if (this.#deletion.ids.has(id)) {\n return undefined;\n }\n return this.#byId.get(id);\n }\n\n query(filters: ReadonlyArray<Filter>): Event[] {\n const seen = new Set<string>();\n const events: Event[] = [];\n for (const filter of filters) {\n for (const event of this.#matchedEvents(filter)) {\n if (seen.has(event.id)) {\n continue;\n }\n seen.add(event.id);\n events.push(event);\n }\n }\n sortEvents(events);\n return events;\n }\n\n count(filters: ReadonlyArray<Filter>): number {\n const seen = new Set<string>();\n for (const filter of filters) {\n for (const event of this.#matchedEvents(filter)) {\n seen.add(event.id);\n }\n }\n return seen.size;\n }\n\n negentropyItems(filter: Filter): NegentropyItem[] {\n if (filter.limit === 0) {\n return [];\n }\n const items: NegentropyItem[] = [];\n this.#eachCandidate(filter, (event) => {\n if (this.#deletion.covers(event)) {\n return;\n }\n if (!matchFilter(filter, event)) {\n return;\n }\n items.push({ id: event.id, created_at: event.created_at });\n });\n if (filter.limit !== undefined) {\n items.sort(compareEventsDesc);\n if (items.length > filter.limit) {\n items.length = filter.limit;\n }\n }\n items.sort(itemCompare);\n return items;\n }\n\n /** Remove by id and tombstone (like a local NIP-09). Returns removed count. */\n remove(ids: ReadonlyArray<string>): number {\n let n = 0;\n for (const raw of ids) {\n const id = raw.toLowerCase();\n if (this.#indexRemove(id)) {\n n += 1;\n }\n this.#deletion.ids.add(id);\n this.#deletion.pending.delete(id);\n }\n this.#trimTombstones();\n return n;\n }\n\n /**\n * Remove by id without writing a tombstone (LRU eviction). When the evicted event is the current\n * winner of a replaceable/addressable address, a `{ id, created_at }` watermark is recorded so\n * stale versions stay rejected while the body is gone; the watermarks are FIFO-bounded by\n * `maxWatermarks`.\n */\n evict(ids: ReadonlyArray<string>): number {\n let n = 0;\n for (const raw of ids) {\n const id = raw.toLowerCase();\n const event = this.#byId.get(id);\n if (event === undefined) {\n continue;\n }\n const addr = eventAddress(event);\n const winner = addr !== undefined && this.#replaceable.get(addr) === id;\n if (this.#indexRemove(id)) {\n n += 1;\n }\n if (winner) {\n this.#setWatermark(addr, { id, created_at: event.created_at });\n }\n }\n return n;\n }\n\n /**\n * True when the id is tombstoned, or the coordinate has a deletion marker that still covers the\n * current event at that address: a replacement published after the marker's `until` clears the\n * deletion. Coordinates are matched on the canonical lowercase pubkey.\n */\n isDeleted(idOrAddress: string): boolean {\n if (this.#deletion.ids.has(idOrAddress.toLowerCase())) {\n return true;\n }\n const coord = parseEventAddress(idOrAddress);\n if (!coord) {\n return false;\n }\n const address = formatEventAddress(coord.kind, coord.pubkey, coord.identifier);\n const until = this.#deletion.coordinates.get(address);\n if (until === undefined) {\n return false;\n }\n const id = this.#replaceable.get(address);\n if (id === undefined) {\n return true;\n }\n const event = this.#byId.get(id);\n return event === undefined || event.created_at <= until;\n }\n\n getOutboxBound(pubkey: string, kind: number): OutboxBound | undefined {\n const persisted = this.#outboxBounds.get(outboxBoundKey(pubkey, kind));\n if (persisted) {\n return { oldest: persisted.oldest, newest: persisted.newest };\n }\n return this.#deriveOutboxBound(pubkey, kind);\n }\n\n setOutboxBound(pubkey: string, kind: number, bound: OutboxBound): void {\n this.#outboxBounds.set(outboxBoundKey(pubkey, kind), {\n oldest: bound.oldest,\n newest: bound.newest,\n });\n }\n\n clear(): void {\n if (this.#onRemove) {\n for (const event of this.#byId.values()) {\n this.#onRemove(event);\n }\n }\n this.#byId.clear();\n this.#byPubkey.clear();\n this.#byKind.clear();\n this.#byKindPubkey.clear();\n this.#byEpTag.clear();\n this.#replaceable.clear();\n this.#watermarks.clear();\n this.#watermarkIds.clear();\n this.#deletion.clear();\n this.#outboxBounds.clear();\n }\n\n get size(): number {\n return this.#byId.size;\n }\n\n #indexInsert(event: Event): void {\n this.#byId.set(event.id, event);\n const { pubkey } = event;\n addToSetMap(this.#byPubkey, pubkey, event.id);\n addToSetMap(this.#byKind, event.kind, event.id);\n addToSetMap(this.#byKindPubkey, `${event.kind}:${pubkey}`, event.id);\n for (const tag of event.tags) {\n if ((tag[0] !== \"e\" && tag[0] !== \"p\") || tag[1] === undefined) {\n continue;\n }\n addToSetMap(this.#byEpTag, `${tag[0]}:${tag[1].toLowerCase()}`, event.id);\n }\n const addr = eventAddress(event);\n if (addr !== undefined) {\n this.#replaceable.set(addr, event.id);\n this.#dropWatermark(addr);\n }\n this.#onInsert?.(event);\n }\n\n #indexRemove(id: string): boolean {\n // A removed/deleted id must not keep a stale watermark alive; eviction\n // re-records its winner watermark right after this call.\n this.#dropWatermarkId(id);\n const event = this.#byId.get(id);\n if (!event) {\n return false;\n }\n this.#byId.delete(id);\n const { pubkey } = event;\n removeFromSetMap(this.#byPubkey, pubkey, id);\n removeFromSetMap(this.#byKind, event.kind, id);\n removeFromSetMap(this.#byKindPubkey, `${event.kind}:${pubkey}`, id);\n for (const tag of event.tags) {\n if ((tag[0] !== \"e\" && tag[0] !== \"p\") || tag[1] === undefined) {\n continue;\n }\n removeFromSetMap(this.#byEpTag, `${tag[0]}:${tag[1].toLowerCase()}`, id);\n }\n const addr = eventAddress(event);\n if (addr !== undefined && this.#replaceable.get(addr) === id) {\n this.#replaceable.delete(addr);\n }\n this.#onRemove?.(event);\n return true;\n }\n\n #matchedEvents(filter: Filter): Event[] {\n if (filter.limit === 0) {\n return [];\n }\n const matched: Event[] = [];\n this.#eachCandidate(filter, (event) => {\n if (this.#deletion.covers(event)) {\n return;\n }\n if (!matchFilter(filter, event)) {\n return;\n }\n matched.push(event);\n });\n sortEvents(matched);\n return filter.limit === undefined ? matched : matched.slice(0, filter.limit);\n }\n\n #deriveOutboxBound(pubkey: string, kind: number): OutboxBound | undefined {\n const byPk = this.#byPubkey.get(pubkey.toLowerCase());\n const byKind = this.#byKind.get(kind);\n if (!byPk || !byKind) {\n return undefined;\n }\n let oldest: number | undefined;\n let newest: number | undefined;\n for (const id of byPk) {\n if (!byKind.has(id) || this.#deletion.ids.has(id)) {\n continue;\n }\n const event = this.#byId.get(id);\n if (!event || this.#deletion.covers(event)) {\n continue;\n }\n if (oldest === undefined || event.created_at < oldest) {\n oldest = event.created_at;\n }\n if (newest === undefined || event.created_at > newest) {\n newest = event.created_at;\n }\n }\n if (oldest === undefined || newest === undefined) {\n return undefined;\n }\n return { oldest, newest };\n }\n\n #eachCandidate(filter: Filter, visit: (event: Event) => void): void {\n if (filter.ids) {\n const seen = new Set<string>();\n for (const raw of filter.ids) {\n const event = this.#byId.get(raw.toLowerCase());\n if (!event || seen.has(event.id)) {\n continue;\n }\n seen.add(event.id);\n visit(event);\n }\n return;\n }\n\n if (filter.authors && filter.kinds) {\n const seen = new Set<string>();\n for (const pk of filter.authors) {\n const pubkey = pk.toLowerCase();\n for (const kind of filter.kinds) {\n const ids = this.#byKindPubkey.get(`${kind}:${pubkey}`);\n if (!ids) {\n continue;\n }\n for (const id of ids) {\n if (seen.has(id)) {\n continue;\n }\n seen.add(id);\n const event = this.#byId.get(id);\n if (event) {\n visit(event);\n }\n }\n }\n }\n return;\n }\n\n if (filter.authors) {\n const seen = new Set<string>();\n for (const pk of filter.authors) {\n const byPk = this.#byPubkey.get(pk.toLowerCase());\n if (!byPk) {\n continue;\n }\n for (const id of byPk) {\n if (seen.has(id)) {\n continue;\n }\n seen.add(id);\n const event = this.#byId.get(id);\n if (event) {\n visit(event);\n }\n }\n }\n return;\n }\n\n if (filter.kinds) {\n const seen = new Set<string>();\n for (const kind of filter.kinds) {\n const byKind = this.#byKind.get(kind);\n if (!byKind) {\n continue;\n }\n for (const id of byKind) {\n if (seen.has(id)) {\n continue;\n }\n seen.add(id);\n const event = this.#byId.get(id);\n if (event) {\n visit(event);\n }\n }\n }\n return;\n }\n\n const eTags = filter[\"#e\"];\n const pTags = filter[\"#p\"];\n if (eTags !== undefined || pTags !== undefined) {\n const seen = new Set<string>();\n visitEpTagIds(this.#byEpTag, this.#byId, \"e\", eTags, seen, visit);\n visitEpTagIds(this.#byEpTag, this.#byId, \"p\", pTags, seen, visit);\n return;\n }\n\n // #t/#d and other non-e/p tags are not indexed (e/p only). A generic tag\n // store is extra put/remove amp; hashtag-only queries scan #byId.\n for (const event of this.#byId.values()) {\n visit(event);\n }\n }\n\n #setWatermark(addr: string, watermark: { id: string; created_at: number }): void {\n if (this.#maxWatermarks === 0) {\n return;\n }\n this.#dropWatermark(addr);\n this.#watermarks.set(addr, watermark);\n this.#watermarkIds.set(watermark.id, addr);\n trimOldest(this.#watermarks, this.#maxWatermarks, (a) => this.#dropWatermark(a));\n }\n\n #dropWatermark(addr: string): void {\n const watermark = this.#watermarks.get(addr);\n if (watermark === undefined) {\n return;\n }\n this.#watermarks.delete(addr);\n this.#watermarkIds.delete(watermark.id);\n }\n\n #dropWatermarkId(id: string): void {\n const addr = this.#watermarkIds.get(id);\n if (addr === undefined) {\n return;\n }\n this.#watermarkIds.delete(id);\n this.#watermarks.delete(addr);\n }\n\n #trimTombstones(): void {\n const cap = this.#maxTombstones;\n if (cap === undefined) {\n return;\n }\n trimOldest(this.#deletion.ids, cap, (id) => {\n this.#deletion.pending.delete(id);\n });\n trimOldest(this.#deletion.pending, cap);\n trimOldest(this.#deletion.coordinates, cap);\n }\n}\n\nfunction visitEpTagIds(\n byEpTag: Map<string, Set<string>>,\n byId: Map<string, Event>,\n name: \"e\" | \"p\",\n values: ReadonlyArray<string> | undefined,\n seen: Set<string>,\n visit: (event: Event) => void,\n): void {\n if (values === undefined) {\n return;\n }\n for (const value of values) {\n const ids = byEpTag.get(`${name}:${value.toLowerCase()}`);\n if (!ids) {\n continue;\n }\n for (const id of ids) {\n if (seen.has(id)) {\n continue;\n }\n seen.add(id);\n const event = byId.get(id);\n if (event) {\n visit(event);\n }\n }\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,194 @@
1
+ import { _ as MessageError, a as hexToBytes, c as isRecord, i as bytesToHex } from "./util-yFYzlbrm.mjs";
2
+ import { m as validateSignedEvent } from "./event-DinQMmJ1.mjs";
3
+ //#region src/core/message.ts
4
+ /** Validate a subscription id; throws {@link MessageError} unless 1..64 chars. */
5
+ function assertSubscriptionId(id) {
6
+ if (id.length === 0 || id.length > 64) throw new MessageError(`subscription id length must be 1..64`);
7
+ return id;
8
+ }
9
+ /** Validate the given id, or mint a random 16-hex-char subscription id. */
10
+ function createSubscriptionId(id) {
11
+ if (id !== void 0) return assertSubscriptionId(id);
12
+ const bytes = /* @__PURE__ */ new Uint8Array(8);
13
+ crypto.getRandomValues(bytes);
14
+ return bytesToHex(bytes);
15
+ }
16
+ const HLL_BYTES = 256;
17
+ const HLL_HEX_LEN = HLL_BYTES * 2;
18
+ const HEX_RE = /^[0-9a-fA-F]+$/;
19
+ /**
20
+ * Register-wise max of NIP-45 HyperLogLog sketches. Output is always lowercase 512 hex. Empty input
21
+ * is the zero sketch (identity).
22
+ */
23
+ function mergeCountHll(hexes) {
24
+ const merged = new Uint8Array(HLL_BYTES);
25
+ for (const hex of hexes) {
26
+ if (hex.length !== HLL_HEX_LEN || !HEX_RE.test(hex)) throw new MessageError("invalid NIP-45 HLL sketch: expected 512-char hex");
27
+ const bytes = hexToBytes(hex);
28
+ for (const [i, b] of bytes.entries()) {
29
+ const m = merged[i];
30
+ if (m !== void 0 && b > m) merged[i] = b;
31
+ }
32
+ }
33
+ return bytesToHex(merged);
34
+ }
35
+ /** Serialize a client->relay message to its NIP-01 JSON wire form. */
36
+ function encodeClientMessage(message) {
37
+ return JSON.stringify(message);
38
+ }
39
+ /** Serialize a relay->client message to its NIP-01 JSON wire form. */
40
+ function encodeRelayMessage(message) {
41
+ return JSON.stringify(message);
42
+ }
43
+ /** Parse a client->relay JSON message; throws {@link MessageError} on malformed input. */
44
+ function parseClientMessage(raw) {
45
+ let parsed;
46
+ try {
47
+ parsed = JSON.parse(raw);
48
+ } catch {
49
+ throw new MessageError("client message is not valid JSON");
50
+ }
51
+ if (!Array.isArray(parsed) || parsed.length === 0 || typeof parsed[0] !== "string") throw new MessageError("client message must be a non-empty JSON array");
52
+ const items = parsed;
53
+ const [type] = items;
54
+ switch (type) {
55
+ case "EVENT":
56
+ if (items.length !== 2 || !validateSignedEvent(items[1])) throw new MessageError("invalid EVENT client message");
57
+ return ["EVENT", items[1]];
58
+ case "REQ":
59
+ if (items.length < 3 || typeof items[1] !== "string") throw new MessageError("invalid REQ client message");
60
+ return [
61
+ "REQ",
62
+ items[1],
63
+ ...parseWireFilters(items.slice(2), "REQ")
64
+ ];
65
+ case "CLOSE":
66
+ if (items.length !== 2 || typeof items[1] !== "string") throw new MessageError("invalid CLOSE client message");
67
+ return ["CLOSE", items[1]];
68
+ case "AUTH":
69
+ if (items.length !== 2 || !validateSignedEvent(items[1])) throw new MessageError("invalid AUTH client message");
70
+ return ["AUTH", items[1]];
71
+ case "COUNT":
72
+ if (items.length < 3 || typeof items[1] !== "string") throw new MessageError("invalid COUNT client message");
73
+ return [
74
+ "COUNT",
75
+ items[1],
76
+ ...parseWireFilters(items.slice(2), "COUNT")
77
+ ];
78
+ case "NEG-OPEN":
79
+ if (items.length === 5) throw new MessageError("obsolete 5-element NEG-OPEN; expected [NEG-OPEN, id, filter, hex]");
80
+ if (items.length !== 4 || typeof items[1] !== "string" || !isWireFilter(items[2]) || !isNegHex(items[3])) throw new MessageError("invalid NEG-OPEN client message");
81
+ return [
82
+ "NEG-OPEN",
83
+ items[1],
84
+ items[2],
85
+ items[3].toLowerCase()
86
+ ];
87
+ case "NEG-MSG":
88
+ if (items.length !== 3 || typeof items[1] !== "string" || !isNegHex(items[2])) throw new MessageError("invalid NEG-MSG client message");
89
+ return [
90
+ "NEG-MSG",
91
+ items[1],
92
+ items[2].toLowerCase()
93
+ ];
94
+ case "NEG-CLOSE":
95
+ if (items.length !== 2 || typeof items[1] !== "string") throw new MessageError("invalid NEG-CLOSE client message");
96
+ return ["NEG-CLOSE", items[1]];
97
+ default: throw new MessageError(`unknown client message type: ${String(type)}`);
98
+ }
99
+ }
100
+ /** Parse a relay->client JSON message; throws {@link MessageError} on malformed input. */
101
+ function parseRelayMessage(raw) {
102
+ let parsed;
103
+ try {
104
+ parsed = JSON.parse(raw);
105
+ } catch {
106
+ throw new MessageError("relay message is not valid JSON");
107
+ }
108
+ if (!Array.isArray(parsed) || parsed.length === 0 || typeof parsed[0] !== "string") throw new MessageError("relay message must be a non-empty JSON array");
109
+ const items = parsed;
110
+ const [type] = items;
111
+ switch (type) {
112
+ case "EVENT":
113
+ if (items.length !== 3 || typeof items[1] !== "string" || !validateSignedEvent(items[2])) throw new MessageError("invalid EVENT relay message");
114
+ return [
115
+ "EVENT",
116
+ items[1],
117
+ items[2]
118
+ ];
119
+ case "OK":
120
+ if (items.length !== 4 || typeof items[1] !== "string" || typeof items[2] !== "boolean" || typeof items[3] !== "string") throw new MessageError("invalid OK relay message");
121
+ return [
122
+ "OK",
123
+ items[1],
124
+ items[2],
125
+ items[3]
126
+ ];
127
+ case "EOSE":
128
+ if (items.length !== 2 || typeof items[1] !== "string") throw new MessageError("invalid EOSE relay message");
129
+ return ["EOSE", items[1]];
130
+ case "CLOSED":
131
+ if (items.length !== 3 || typeof items[1] !== "string" || typeof items[2] !== "string") throw new MessageError("invalid CLOSED relay message");
132
+ return [
133
+ "CLOSED",
134
+ items[1],
135
+ items[2]
136
+ ];
137
+ case "NOTICE":
138
+ if (items.length !== 2 || typeof items[1] !== "string") throw new MessageError("invalid NOTICE relay message");
139
+ return ["NOTICE", items[1]];
140
+ case "AUTH":
141
+ if (items.length !== 2 || typeof items[1] !== "string") throw new MessageError("invalid AUTH relay message");
142
+ return ["AUTH", items[1]];
143
+ case "COUNT": {
144
+ const payload = items.at(2);
145
+ if (items.length !== 3 || typeof items[1] !== "string" || !isRecord(payload) || typeof payload["count"] !== "number") throw new MessageError("invalid COUNT relay message");
146
+ const result = { count: payload["count"] };
147
+ if (typeof payload["approximate"] === "boolean") result.approximate = payload["approximate"];
148
+ const hll = parseCountHll(payload["hll"]);
149
+ if (hll !== void 0) result.hll = hll;
150
+ return [
151
+ "COUNT",
152
+ items[1],
153
+ result
154
+ ];
155
+ }
156
+ case "NEG-MSG":
157
+ if (items.length !== 3 || typeof items[1] !== "string" || !isNegHex(items[2])) throw new MessageError("invalid NEG-MSG relay message");
158
+ return [
159
+ "NEG-MSG",
160
+ items[1],
161
+ items[2].toLowerCase()
162
+ ];
163
+ case "NEG-ERR":
164
+ if (items.length !== 3 && items.length !== 4 || typeof items[1] !== "string" || typeof items[2] !== "string") throw new MessageError("invalid NEG-ERR relay message");
165
+ return [
166
+ "NEG-ERR",
167
+ items[1],
168
+ items[2]
169
+ ];
170
+ default: throw new MessageError(`unknown relay message type: ${String(type)}`);
171
+ }
172
+ }
173
+ function parseCountHll(value) {
174
+ if (typeof value !== "string" || value.length !== HLL_HEX_LEN || !HEX_RE.test(value)) return;
175
+ return value.toLowerCase();
176
+ }
177
+ function isWireFilter(value) {
178
+ return isRecord(value);
179
+ }
180
+ function parseWireFilters(items, kind) {
181
+ const filters = [];
182
+ for (const item of items) {
183
+ if (!isWireFilter(item)) throw new MessageError(`invalid ${kind} filter`);
184
+ filters.push(item);
185
+ }
186
+ return filters;
187
+ }
188
+ function isNegHex(value) {
189
+ return typeof value === "string" && value.length > 0 && value.length % 2 === 0 && HEX_RE.test(value);
190
+ }
191
+ //#endregion
192
+ export { mergeCountHll as a, encodeRelayMessage as i, createSubscriptionId as n, parseClientMessage as o, encodeClientMessage as r, parseRelayMessage as s, assertSubscriptionId as t };
193
+
194
+ //# sourceMappingURL=message-CWLcFTQ1.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-CWLcFTQ1.mjs","names":[],"sources":["../src/core/message.ts"],"sourcesContent":["import { MessageError } from \"./error.ts\";\nimport type { Event } from \"./event.ts\";\nimport { validateSignedEvent } from \"./event.ts\";\nimport type { Filter } from \"./filter.ts\";\nimport { SUBSCRIPTION_ID_MAX_CHARS } from \"./limits.ts\";\nimport { bytesToHex, hexToBytes, isRecord } from \"./util.ts\";\n\n/** NIP-01 subscription id: 1..64 chars. */\nexport type SubscriptionId = string;\n\n/** Validate a subscription id; throws {@link MessageError} unless 1..64 chars. */\nexport function assertSubscriptionId(id: string): SubscriptionId {\n if (id.length === 0 || id.length > SUBSCRIPTION_ID_MAX_CHARS) {\n throw new MessageError(`subscription id length must be 1..${SUBSCRIPTION_ID_MAX_CHARS}`);\n }\n return id;\n}\n\n/** Validate the given id, or mint a random 16-hex-char subscription id. */\nexport function createSubscriptionId(id?: string): SubscriptionId {\n if (id !== undefined) {\n return assertSubscriptionId(id);\n }\n const bytes = new Uint8Array(8);\n crypto.getRandomValues(bytes);\n return bytesToHex(bytes);\n}\n\n/** Client → relay messages (NIP-01 + NIP-42 AUTH + NIP-45 COUNT + NIP-77). */\nexport type ClientMessage =\n | [\"EVENT\", Event]\n | [\"REQ\", SubscriptionId, ...Filter[]]\n | [\"CLOSE\", SubscriptionId]\n | [\"AUTH\", Event]\n | [\"COUNT\", SubscriptionId, ...Filter[]]\n | [\"NEG-OPEN\", SubscriptionId, Filter, string]\n | [\"NEG-MSG\", SubscriptionId, string]\n | [\"NEG-CLOSE\", SubscriptionId];\n\n/** Relay → client messages. */\nexport type RelayMessage =\n | [\"EVENT\", SubscriptionId, Event]\n | [\"OK\", string, boolean, string]\n | [\"EOSE\", SubscriptionId]\n | [\"CLOSED\", SubscriptionId, string]\n | [\"NOTICE\", string]\n | [\"AUTH\", string]\n | [\"COUNT\", SubscriptionId, CountResult]\n | [\"NEG-MSG\", SubscriptionId, string]\n | [\"NEG-ERR\", SubscriptionId, string];\n\n/** NIP-45 COUNT reply payload. */\nexport type CountResult = {\n count: number;\n approximate?: boolean;\n /**\n * Optional 512-char hex HyperLogLog sketch (256 registers). Merge sketches with `mergeCountHll`.\n * Estimation is unspecified by NIP-45 and not provided.\n */\n hll?: string;\n};\n\nconst HLL_BYTES = 256; // 512 hex chars (NIP-45; hex only, not base64)\nconst HLL_HEX_LEN = HLL_BYTES * 2;\nconst HEX_RE = /^[0-9a-fA-F]+$/;\n\n/**\n * Register-wise max of NIP-45 HyperLogLog sketches. Output is always lowercase 512 hex. Empty input\n * is the zero sketch (identity).\n */\nexport function mergeCountHll(hexes: ReadonlyArray<string>): string {\n const merged = new Uint8Array(HLL_BYTES);\n for (const hex of hexes) {\n if (hex.length !== HLL_HEX_LEN || !HEX_RE.test(hex)) {\n throw new MessageError(\"invalid NIP-45 HLL sketch: expected 512-char hex\");\n }\n const bytes = hexToBytes(hex);\n for (const [i, b] of bytes.entries()) {\n const m = merged[i];\n if (m !== undefined && b > m) {\n merged[i] = b;\n }\n }\n }\n return bytesToHex(merged);\n}\n\n/** Serialize a client->relay message to its NIP-01 JSON wire form. */\nexport function encodeClientMessage(message: ClientMessage): string {\n return JSON.stringify(message);\n}\n\n/** Serialize a relay->client message to its NIP-01 JSON wire form. */\nexport function encodeRelayMessage(message: RelayMessage): string {\n return JSON.stringify(message);\n}\n\n/** Parse a client->relay JSON message; throws {@link MessageError} on malformed input. */\nexport function parseClientMessage(raw: string): ClientMessage {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw new MessageError(\"client message is not valid JSON\");\n }\n if (!Array.isArray(parsed) || parsed.length === 0 || typeof parsed[0] !== \"string\") {\n throw new MessageError(\"client message must be a non-empty JSON array\");\n }\n const items: unknown[] = parsed;\n const [type] = items;\n switch (type) {\n case \"EVENT\":\n if (items.length !== 2 || !validateSignedEvent(items[1])) {\n throw new MessageError(\"invalid EVENT client message\");\n }\n return [\"EVENT\", items[1]];\n\n case \"REQ\":\n if (items.length < 3 || typeof items[1] !== \"string\") {\n throw new MessageError(\"invalid REQ client message\");\n }\n return [\"REQ\", items[1], ...parseWireFilters(items.slice(2), \"REQ\")];\n\n case \"CLOSE\":\n if (items.length !== 2 || typeof items[1] !== \"string\") {\n throw new MessageError(\"invalid CLOSE client message\");\n }\n return [\"CLOSE\", items[1]];\n\n case \"AUTH\":\n if (items.length !== 2 || !validateSignedEvent(items[1])) {\n throw new MessageError(\"invalid AUTH client message\");\n }\n return [\"AUTH\", items[1]];\n\n case \"COUNT\":\n if (items.length < 3 || typeof items[1] !== \"string\") {\n throw new MessageError(\"invalid COUNT client message\");\n }\n return [\"COUNT\", items[1], ...parseWireFilters(items.slice(2), \"COUNT\")];\n\n case \"NEG-OPEN\":\n if (items.length === 5) {\n throw new MessageError(\"obsolete 5-element NEG-OPEN; expected [NEG-OPEN, id, filter, hex]\");\n }\n if (\n items.length !== 4 ||\n typeof items[1] !== \"string\" ||\n !isWireFilter(items[2]) ||\n !isNegHex(items[3])\n ) {\n throw new MessageError(\"invalid NEG-OPEN client message\");\n }\n return [\"NEG-OPEN\", items[1], items[2], items[3].toLowerCase()];\n\n case \"NEG-MSG\":\n if (items.length !== 3 || typeof items[1] !== \"string\" || !isNegHex(items[2])) {\n throw new MessageError(\"invalid NEG-MSG client message\");\n }\n return [\"NEG-MSG\", items[1], items[2].toLowerCase()];\n\n case \"NEG-CLOSE\":\n if (items.length !== 2 || typeof items[1] !== \"string\") {\n throw new MessageError(\"invalid NEG-CLOSE client message\");\n }\n return [\"NEG-CLOSE\", items[1]];\n\n default:\n throw new MessageError(`unknown client message type: ${String(type)}`);\n }\n}\n\n/** Parse a relay->client JSON message; throws {@link MessageError} on malformed input. */\nexport function parseRelayMessage(raw: string): RelayMessage {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw new MessageError(\"relay message is not valid JSON\");\n }\n if (!Array.isArray(parsed) || parsed.length === 0 || typeof parsed[0] !== \"string\") {\n throw new MessageError(\"relay message must be a non-empty JSON array\");\n }\n const items: unknown[] = parsed;\n const [type] = items;\n switch (type) {\n case \"EVENT\":\n if (items.length !== 3 || typeof items[1] !== \"string\" || !validateSignedEvent(items[2])) {\n throw new MessageError(\"invalid EVENT relay message\");\n }\n return [\"EVENT\", items[1], items[2]];\n\n case \"OK\":\n if (\n items.length !== 4 ||\n typeof items[1] !== \"string\" ||\n typeof items[2] !== \"boolean\" ||\n typeof items[3] !== \"string\"\n ) {\n throw new MessageError(\"invalid OK relay message\");\n }\n return [\"OK\", items[1], items[2], items[3]];\n\n case \"EOSE\":\n if (items.length !== 2 || typeof items[1] !== \"string\") {\n throw new MessageError(\"invalid EOSE relay message\");\n }\n return [\"EOSE\", items[1]];\n\n case \"CLOSED\":\n if (items.length !== 3 || typeof items[1] !== \"string\" || typeof items[2] !== \"string\") {\n throw new MessageError(\"invalid CLOSED relay message\");\n }\n return [\"CLOSED\", items[1], items[2]];\n\n case \"NOTICE\":\n if (items.length !== 2 || typeof items[1] !== \"string\") {\n throw new MessageError(\"invalid NOTICE relay message\");\n }\n return [\"NOTICE\", items[1]];\n\n case \"AUTH\":\n if (items.length !== 2 || typeof items[1] !== \"string\") {\n throw new MessageError(\"invalid AUTH relay message\");\n }\n return [\"AUTH\", items[1]];\n\n case \"COUNT\": {\n const payload = items.at(2);\n if (\n items.length !== 3 ||\n typeof items[1] !== \"string\" ||\n !isRecord(payload) ||\n typeof payload[\"count\"] !== \"number\"\n ) {\n throw new MessageError(\"invalid COUNT relay message\");\n }\n const result: CountResult = { count: payload[\"count\"] };\n if (typeof payload[\"approximate\"] === \"boolean\") {\n result.approximate = payload[\"approximate\"];\n }\n const hll = parseCountHll(payload[\"hll\"]);\n if (hll !== undefined) {\n result.hll = hll;\n }\n return [\"COUNT\", items[1], result];\n }\n case \"NEG-MSG\":\n if (items.length !== 3 || typeof items[1] !== \"string\" || !isNegHex(items[2])) {\n throw new MessageError(\"invalid NEG-MSG relay message\");\n }\n return [\"NEG-MSG\", items[1], items[2].toLowerCase()];\n\n case \"NEG-ERR\":\n if (\n (items.length !== 3 && items.length !== 4) ||\n typeof items[1] !== \"string\" ||\n typeof items[2] !== \"string\"\n ) {\n throw new MessageError(\"invalid NEG-ERR relay message\");\n }\n return [\"NEG-ERR\", items[1], items[2]];\n\n default:\n throw new MessageError(`unknown relay message type: ${String(type)}`);\n }\n}\n\nfunction parseCountHll(value: unknown): string | undefined {\n if (typeof value !== \"string\" || value.length !== HLL_HEX_LEN || !HEX_RE.test(value)) {\n return undefined;\n }\n return value.toLowerCase();\n}\n\nfunction isWireFilter(value: unknown): value is Filter {\n return isRecord(value);\n}\n\nfunction parseWireFilters(items: unknown[], kind: string): Filter[] {\n const filters: Filter[] = [];\n for (const item of items) {\n if (!isWireFilter(item)) {\n throw new MessageError(`invalid ${kind} filter`);\n }\n filters.push(item);\n }\n return filters;\n}\n\nfunction isNegHex(value: unknown): value is string {\n return (\n typeof value === \"string\" && value.length > 0 && value.length % 2 === 0 && HEX_RE.test(value)\n );\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
- import { t as Event } from "./event-fU3Y01PR.mjs";
2
- import { t as Filter } from "./filter-C0IMByE3.mjs";
1
+ import { t as Event } from "./event-DJeb_DIL.mjs";
2
+ import { t as Filter } from "./filter-jI_9htF7.mjs";
3
3
  //#region src/core/message.d.ts
4
4
  /** NIP-01 subscription id: 1..64 chars. */
5
5
  type SubscriptionId = string;
@@ -36,4 +36,4 @@ declare function parseClientMessage(raw: string): ClientMessage;
36
36
  declare function parseRelayMessage(raw: string): RelayMessage;
37
37
  //#endregion
38
38
  export { assertSubscriptionId as a, encodeRelayMessage as c, parseRelayMessage as d, SubscriptionId as i, mergeCountHll as l, CountResult as n, createSubscriptionId as o, RelayMessage as r, encodeClientMessage as s, ClientMessage as t, parseClientMessage as u };
39
- //# sourceMappingURL=message-iGPIqnn0.d.mts.map
39
+ //# sourceMappingURL=message-CbBZOhq3.d.mts.map