@qntx/nostr 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (186) hide show
  1. package/README.md +16 -0
  2. package/dist/abort-Ba8tDrOI.mjs +38 -0
  3. package/dist/abort-Ba8tDrOI.mjs.map +1 -0
  4. package/dist/{builder-BphJa-Y-.d.mts → builder-BWn55Paf.d.mts} +20 -14
  5. package/dist/{builder-q0VsvnYE.mjs → builder-CyrGKRVP.mjs} +32 -16
  6. package/dist/builder-CyrGKRVP.mjs.map +1 -0
  7. package/dist/{client-Bd8B1mYm.mjs → client-5DhgYV7P.mjs} +372 -195
  8. package/dist/client-5DhgYV7P.mjs.map +1 -0
  9. package/dist/client.d.mts +1 -1
  10. package/dist/client.mjs +1 -1
  11. package/dist/core.d.mts +8 -8
  12. package/dist/core.mjs +8 -9
  13. package/dist/error-BW7re1UN.d.mts +9 -0
  14. package/dist/error-B__OMBRY.mjs +14 -0
  15. package/dist/error-B__OMBRY.mjs.map +1 -0
  16. package/dist/{error-WEFS-R-4.d.mts → error-CR8Urc84.d.mts} +20 -8
  17. package/dist/{error-CwBxle6N.mjs → error-DrEIM7FO.mjs} +5 -3
  18. package/dist/error-DrEIM7FO.mjs.map +1 -0
  19. package/dist/{event-CzoQEFMF.mjs → event-D8Nfoh4K.mjs} +40 -32
  20. package/dist/event-D8Nfoh4K.mjs.map +1 -0
  21. package/dist/{event-B5D2jxt_.d.mts → event-fU3Y01PR.d.mts} +23 -20
  22. package/dist/{filter-BxwjEkZp.mjs → filter-BQl3GlHt.mjs} +42 -35
  23. package/dist/filter-BQl3GlHt.mjs.map +1 -0
  24. package/dist/filter-C0IMByE3.d.mts +37 -0
  25. package/dist/{gossip-C5RVB1UB.d.mts → gossip-BRQwIM_q.d.mts} +25 -23
  26. package/dist/{gossip-3xkRRCm_.mjs → gossip-oloDMWEj.mjs} +52 -34
  27. package/dist/gossip-oloDMWEj.mjs.map +1 -0
  28. package/dist/gossip.d.mts +1 -1
  29. package/dist/gossip.mjs +1 -1
  30. package/dist/{http-6vevbBlu.d.mts → http-C4HwIu4B.d.mts} +7 -7
  31. package/dist/{http-D2rhokZo.mjs → http-CuazNRIP.mjs} +10 -6
  32. package/dist/http-CuazNRIP.mjs.map +1 -0
  33. package/dist/index-B-CGNDPS.d.mts +207 -0
  34. package/dist/index-BIhSADwK.d.mts +339 -0
  35. package/dist/{index-BYMnhRIC.d.mts → index-Ba7bJ7DD.d.mts} +68 -50
  36. package/dist/{index-DaRyjo7O.d.mts → index-Boe8FshM.d.mts} +13 -7
  37. package/dist/index-D-TK_KJG.d.mts +84 -0
  38. package/dist/index-D1bqFHKB.d.mts +188 -0
  39. package/dist/index.d.mts +22 -20
  40. package/dist/index.mjs +19 -18
  41. package/dist/{key-Dn-aX4x4.mjs → key-Bbtys6o7.mjs} +22 -14
  42. package/dist/key-Bbtys6o7.mjs.map +1 -0
  43. package/dist/{key-BiGxGLhg.d.mts → key-BlsYhN1V.d.mts} +9 -6
  44. package/dist/{kind-BLl4cLOc.mjs → kind-Cxwhhkx8.mjs} +2 -1
  45. package/dist/kind-Cxwhhkx8.mjs.map +1 -0
  46. package/dist/{loaders-BNQszDAn.mjs → loaders-o6YXB_Ri.mjs} +329 -305
  47. package/dist/loaders-o6YXB_Ri.mjs.map +1 -0
  48. package/dist/loaders.d.mts +3 -3
  49. package/dist/loaders.mjs +2 -2
  50. package/dist/memory-C2EVx0_O.mjs +46 -0
  51. package/dist/memory-C2EVx0_O.mjs.map +1 -0
  52. package/dist/{memory-BCmhZPek.mjs → memory-index-Do1mz0WF.mjs} +212 -82
  53. package/dist/memory-index-Do1mz0WF.mjs.map +1 -0
  54. package/dist/{message-C_Uj-Y-c.mjs → message-DcY6Fi9o.mjs} +38 -24
  55. package/dist/message-DcY6Fi9o.mjs.map +1 -0
  56. package/dist/{message-DJC-Jz6T.d.mts → message-iGPIqnn0.d.mts} +15 -8
  57. package/dist/{nip17-BWq-Kfq5.d.mts → nip17-DNEkz8rM.d.mts} +18 -17
  58. package/dist/{nip19-D7905oIv.d.mts → nip19-BevCJZsp.d.mts} +23 -3
  59. package/dist/{nip46-C_DoxiS_.d.mts → nip46-DDV4dC56.d.mts} +13 -12
  60. package/dist/{nip59-DljW_sYh.d.mts → nip59-zQgdLcEr.d.mts} +28 -25
  61. package/dist/{nip65-Di5hZRuV.d.mts → nip65-DBfh2smt.d.mts} +8 -3
  62. package/dist/{nip77-ByxlgoQO.d.mts → nip77-DH4RhS5r.d.mts} +9 -8
  63. package/dist/nips/blossom.d.mts +37 -37
  64. package/dist/nips/blossom.mjs +67 -52
  65. package/dist/nips/blossom.mjs.map +1 -1
  66. package/dist/nips/nip04.d.mts +2 -3
  67. package/dist/nips/nip04.mjs +6 -10
  68. package/dist/nips/nip04.mjs.map +1 -1
  69. package/dist/nips/nip05.d.mts +37 -43
  70. package/dist/nips/nip05.mjs +57 -57
  71. package/dist/nips/nip05.mjs.map +1 -1
  72. package/dist/nips/nip10.d.mts +18 -19
  73. package/dist/nips/nip10.mjs +76 -61
  74. package/dist/nips/nip10.mjs.map +1 -1
  75. package/dist/nips/nip11.d.mts +9 -8
  76. package/dist/nips/nip11.mjs +14 -11
  77. package/dist/nips/nip11.mjs.map +1 -1
  78. package/dist/nips/nip13.d.mts +9 -9
  79. package/dist/nips/nip13.mjs +15 -17
  80. package/dist/nips/nip13.mjs.map +1 -1
  81. package/dist/nips/nip17.d.mts +1 -1
  82. package/dist/nips/nip17.mjs +17 -12
  83. package/dist/nips/nip17.mjs.map +1 -1
  84. package/dist/nips/nip19.d.mts +1 -1
  85. package/dist/nips/nip19.mjs +52 -29
  86. package/dist/nips/nip19.mjs.map +1 -1
  87. package/dist/nips/nip21.d.mts +5 -6
  88. package/dist/nips/nip21.mjs +8 -7
  89. package/dist/nips/nip21.mjs.map +1 -1
  90. package/dist/nips/nip27.d.mts +7 -8
  91. package/dist/nips/nip27.mjs +26 -25
  92. package/dist/nips/nip27.mjs.map +1 -1
  93. package/dist/nips/nip42.d.mts +5 -6
  94. package/dist/nips/nip42.mjs +3 -3
  95. package/dist/nips/nip42.mjs.map +1 -1
  96. package/dist/nips/nip44.d.mts +24 -7
  97. package/dist/nips/nip44.mjs +25 -15
  98. package/dist/nips/nip44.mjs.map +1 -1
  99. package/dist/nips/nip46.d.mts +1 -1
  100. package/dist/nips/nip46.mjs +52 -49
  101. package/dist/nips/nip46.mjs.map +1 -1
  102. package/dist/nips/nip49.d.mts +8 -7
  103. package/dist/nips/nip49.mjs +16 -12
  104. package/dist/nips/nip49.mjs.map +1 -1
  105. package/dist/nips/nip51.d.mts +25 -27
  106. package/dist/nips/nip51.mjs +47 -36
  107. package/dist/nips/nip51.mjs.map +1 -1
  108. package/dist/nips/nip57.d.mts +12 -13
  109. package/dist/nips/nip57.mjs +35 -38
  110. package/dist/nips/nip57.mjs.map +1 -1
  111. package/dist/nips/nip59.d.mts +1 -1
  112. package/dist/nips/nip59.mjs +14 -18
  113. package/dist/nips/nip59.mjs.map +1 -1
  114. package/dist/nips/nip65.d.mts +1 -1
  115. package/dist/nips/nip65.mjs +11 -8
  116. package/dist/nips/nip65.mjs.map +1 -1
  117. package/dist/nips/nip77.d.mts +1 -1
  118. package/dist/nips/nip77.mjs +56 -34
  119. package/dist/nips/nip77.mjs.map +1 -1
  120. package/dist/nips/nip96.d.mts +21 -12
  121. package/dist/nips/nip96.mjs +59 -36
  122. package/dist/nips/nip96.mjs.map +1 -1
  123. package/dist/nips/nip98.d.mts +12 -12
  124. package/dist/nips/nip98.mjs +18 -19
  125. package/dist/nips/nip98.mjs.map +1 -1
  126. package/dist/pool-B7p_reAm.d.mts +333 -0
  127. package/dist/{pool-C_j1OmBB.mjs → pool-C2xOcsun.mjs} +792 -568
  128. package/dist/pool-C2xOcsun.mjs.map +1 -0
  129. package/dist/reactive-DDlThDSZ.mjs +324 -0
  130. package/dist/reactive-DDlThDSZ.mjs.map +1 -0
  131. package/dist/reactive-DFpqtIf_.d.mts +66 -0
  132. package/dist/relay.d.mts +5 -4
  133. package/dist/relay.mjs +2 -2
  134. package/dist/report-CHld4uWj.mjs +32 -0
  135. package/dist/report-CHld4uWj.mjs.map +1 -0
  136. package/dist/{signer-CZ6K6FUc.mjs → signer-CKR9EHce.mjs} +107 -72
  137. package/dist/signer-CKR9EHce.mjs.map +1 -0
  138. package/dist/signer.d.mts +3 -3
  139. package/dist/signer.mjs +3 -2
  140. package/dist/storage-xXxLGCVb.mjs +1376 -0
  141. package/dist/storage-xXxLGCVb.mjs.map +1 -0
  142. package/dist/storage.d.mts +4 -4
  143. package/dist/storage.mjs +5 -4
  144. package/dist/store.d.mts +2 -0
  145. package/dist/store.mjs +2 -0
  146. package/dist/testing.d.mts +135 -0
  147. package/dist/testing.mjs +963 -0
  148. package/dist/testing.mjs.map +1 -0
  149. package/dist/types-2pCUprRU.d.mts +47 -0
  150. package/dist/types-CRQEk0Oi.d.mts +19 -0
  151. package/dist/util-LfQirgDk.mjs +113 -0
  152. package/dist/util-LfQirgDk.mjs.map +1 -0
  153. package/dist/wasm.d.mts +4 -4
  154. package/dist/wasm.mjs +54 -55
  155. package/dist/wasm.mjs.map +1 -1
  156. package/dist/websocket-CmeqvAkp.d.mts +27 -0
  157. package/package.json +93 -39
  158. package/dist/builder-q0VsvnYE.mjs.map +0 -1
  159. package/dist/client-Bd8B1mYm.mjs.map +0 -1
  160. package/dist/error-B49rbDzT.d.mts +0 -7
  161. package/dist/error-CwBxle6N.mjs.map +0 -1
  162. package/dist/error-DZEs5OuZ.mjs +0 -24
  163. package/dist/error-DZEs5OuZ.mjs.map +0 -1
  164. package/dist/event-CzoQEFMF.mjs.map +0 -1
  165. package/dist/filter-BxwjEkZp.mjs.map +0 -1
  166. package/dist/filter-DUEKP3q1.d.mts +0 -40
  167. package/dist/gossip-3xkRRCm_.mjs.map +0 -1
  168. package/dist/http-D2rhokZo.mjs.map +0 -1
  169. package/dist/index-C0C4e-Gn.d.mts +0 -55
  170. package/dist/index-DgOugrpF.d.mts +0 -275
  171. package/dist/index-EwL6fHLo.d.mts +0 -267
  172. package/dist/index-k2YhCgsh.d.mts +0 -57
  173. package/dist/key-Dn-aX4x4.mjs.map +0 -1
  174. package/dist/kind-BLl4cLOc.mjs.map +0 -1
  175. package/dist/loaders-BNQszDAn.mjs.map +0 -1
  176. package/dist/memory-BCmhZPek.mjs.map +0 -1
  177. package/dist/message-C_Uj-Y-c.mjs.map +0 -1
  178. package/dist/pool-C_j1OmBB.mjs.map +0 -1
  179. package/dist/pool-DhRi7l-b.d.mts +0 -297
  180. package/dist/signer-CZ6K6FUc.mjs.map +0 -1
  181. package/dist/storage-DOy0iLRE.mjs +0 -870
  182. package/dist/storage-DOy0iLRE.mjs.map +0 -1
  183. package/dist/types-BzyADqnw.d.mts +0 -19
  184. package/dist/types-Dn24TPFs.d.mts +0 -43
  185. package/dist/util-6tfLQ9JF.mjs +0 -73
  186. package/dist/util-6tfLQ9JF.mjs.map +0 -1
@@ -1,267 +0,0 @@
1
- import { t as Event } from "./event-B5D2jxt_.mjs";
2
- import { n as ProfileMetadata } from "./builder-BphJa-Y-.mjs";
3
- import { t as RelayListItem } from "./nip65-Di5hZRuV.mjs";
4
- import { t as Gossip } from "./gossip-C5RVB1UB.mjs";
5
- import { a as NostrError } from "./error-WEFS-R-4.mjs";
6
- import { t as Pool } from "./pool-DhRi7l-b.mjs";
7
- import { i as EventPointer, t as AddressPointer } from "./nip19-D7905oIv.mjs";
8
- import { r as OutboxBound, t as EventStore } from "./types-Dn24TPFs.mjs";
9
- //#region src/loaders/cache.d.ts
10
- type ReplaceableKey = {
11
- kind: number;
12
- pubkey: string;
13
- dTag?: string;
14
- };
15
- /** In-memory replaceable-event cache used by loaders (no DOM / localStorage). */
16
- declare class ReplaceableCache {
17
- #private;
18
- get(spec: ReplaceableKey): {
19
- event: Event | null;
20
- fetchedAt: number;
21
- } | undefined;
22
- set(spec: ReplaceableKey, event: Event | null, fetchedAt?: number): void;
23
- /** Keep newer event by created_at. */
24
- putIfNewer(event: Event, dTag?: string): void;
25
- clear(spec?: ReplaceableKey): void;
26
- get size(): number;
27
- }
28
- //#endregion
29
- //#region src/loaders/context.d.ts
30
- type LoaderContextOptions = {
31
- pool: Pool;
32
- /** Fallback / discovery relays when no per-user routing is known. */
33
- relays: readonly string[];
34
- cache?: ReplaceableCache;
35
- /** Max age (seconds) before a cached replaceable is considered stale. Default 2 days. */
36
- staleAfterSec?: number;
37
- fetchTimeoutMs?: number;
38
- };
39
- /**
40
- * Explicit dependency bag for loaders — never a module-level singleton.
41
- */
42
- declare class LoaderContext {
43
- #private;
44
- readonly pool: Pool;
45
- readonly cache: ReplaceableCache;
46
- readonly staleAfterSec: number;
47
- readonly fetchTimeoutMs: number;
48
- constructor(opts: LoaderContextOptions);
49
- /** Snapshot of discovery/fallback relays. */
50
- get relays(): readonly string[];
51
- addRelay(url: string): void;
52
- removeRelay(url: string): void;
53
- setRelays(urls: readonly string[]): void;
54
- isFresh(fetchedAt: number, now?: number): boolean;
55
- }
56
- //#endregion
57
- //#region src/loaders/dataloader.d.ts
58
- declare class LoaderError extends NostrError {}
59
- /** Minimal request coalescer. Batches keys within a microtask; no global state. */
60
- type BatchLoadFn<K, V> = (keys: readonly K[]) => Promise<readonly (V | Error)[]>;
61
- type DataLoaderOptions<K, C = K> = {
62
- cacheKeyFn?: (key: K) => C;
63
- maxBatchSize?: number;
64
- /** When false, only coalesces in-flight requests (no durable memoization). Default true. */
65
- cache?: boolean;
66
- };
67
- declare class DataLoader<K, V, C = K> {
68
- #private;
69
- constructor(batchLoadFn: BatchLoadFn<K, V>, options?: DataLoaderOptions<K, C>);
70
- load(key: K): Promise<V>;
71
- clear(key: K): void;
72
- clearAll(): void;
73
- }
74
- //#endregion
75
- //#region src/loaders/replaceable.d.ts
76
- type ReplaceableLoadResult = {
77
- event: Event | null;
78
- fresh: boolean;
79
- };
80
- type LoadStyle = "default" | "force" | "cache-only";
81
- /**
82
- * Batch-fetch replaceable events (kind + authors) via the pool, with cache.
83
- * Durable cache is ReplaceableCache; DataLoader only coalesces in-flight requests.
84
- */
85
- declare function createReplaceableLoader(ctx: LoaderContext, kind: number): {
86
- load(pubkey: string, opts?: {
87
- hints?: string[];
88
- style?: LoadStyle;
89
- }): Promise<ReplaceableLoadResult>;
90
- clear(pubkey?: string): void;
91
- };
92
- //#endregion
93
- //#region src/loaders/lists.d.ts
94
- type ListResult<T> = {
95
- event: Event | null;
96
- items: T[];
97
- fresh: boolean;
98
- };
99
- type MutedEntity = {
100
- label: "pubkey";
101
- value: string;
102
- } | {
103
- label: "thread";
104
- value: string;
105
- } | {
106
- label: "hashtag";
107
- value: string;
108
- } | {
109
- label: "word";
110
- value: string;
111
- };
112
- declare function createListLoaders(ctx: LoaderContext): {
113
- follows(pubkey: string, opts?: {
114
- hints?: string[];
115
- style?: LoadStyle;
116
- }): Promise<ListResult<string>>;
117
- muteList(pubkey: string, opts?: {
118
- hints?: string[];
119
- style?: LoadStyle;
120
- }): Promise<ListResult<MutedEntity>>;
121
- relayList(pubkey: string, opts?: {
122
- hints?: string[];
123
- style?: LoadStyle;
124
- }): Promise<ListResult<RelayListItem>>;
125
- dmRelayList(pubkey: string, opts?: {
126
- hints?: string[];
127
- style?: LoadStyle;
128
- }): Promise<ListResult<string>>;
129
- };
130
- type ListLoaders = ReturnType<typeof createListLoaders>;
131
- //#endregion
132
- //#region src/loaders/profile.d.ts
133
- type NostrUser = {
134
- pubkey: string;
135
- npub: string;
136
- shortName: string;
137
- image?: string;
138
- metadata: ProfileMetadata;
139
- lastUpdated: number;
140
- event: Event | null;
141
- fresh: boolean;
142
- };
143
- declare function bareNostrUser(pubkey: string): NostrUser;
144
- declare function createProfileLoader(ctx: LoaderContext): {
145
- load(pubkey: string, opts?: {
146
- hints?: string[];
147
- style?: LoadStyle;
148
- }): Promise<NostrUser>;
149
- };
150
- type ProfileLoader = ReturnType<typeof createProfileLoader>;
151
- //#endregion
152
- //#region src/loaders/event.d.ts
153
- type EventRef = string | EventPointer | AddressPointer;
154
- declare function createEventLoader(ctx: LoaderContext): {
155
- load(ref: EventRef): Promise<Event | undefined>;
156
- clearAll(): void;
157
- };
158
- type EventLoader = ReturnType<typeof createEventLoader>;
159
- //#endregion
160
- //#region src/loaders/outbox.d.ts
161
- declare class OutboxError extends NostrError {}
162
- type OutboxFeedOptions = {
163
- pool: Pool;
164
- gossip: Gossip;
165
- storage: EventStore;
166
- /** Fallback relays when an author has no known outbox routes. */
167
- discoveryRelays: readonly string[];
168
- authors: readonly string[];
169
- /** Event kinds to fetch. Default `[Kind.TextNote]`. */
170
- kinds?: readonly number[];
171
- /** Max write-relays to use per author. Default 3. */
172
- maxRelaysPerAuthor?: number;
173
- fetchTimeoutMs?: number;
174
- /** Called for live events after observe, and for sync events returned by applySync. */
175
- onEvent?: (event: Event) => void;
176
- /** Live ingest (e.g. `client.observe`). StartLive never putManys. */
177
- observe?: (event: Event) => void;
178
- /**
179
- * Sync path only. Awaited. Return events that should advance bounds.
180
- * Throw → sync throws, bounds unchanged.
181
- */
182
- applySync?: (events: readonly Event[]) => Promise<readonly Event[]>;
183
- /**
184
- * Load kind:10002 for authors before sync when routes are missing.
185
- * Requires a relay-list loader.
186
- */
187
- hydrate?: (pubkeys: readonly string[]) => Promise<void>;
188
- };
189
- /**
190
- * Group authors by outbox relay (discovery fallback).
191
- * `prefer` reorders existing candidates only; it never appends a URL
192
- * that is not already in the author's outbox or discovery list.
193
- * Returns Map<relayUrl, authors[]>.
194
- */
195
- declare function groupAuthorsByOutboxRelay(authors: readonly string[], gossip: Gossip, discoveryRelays: readonly string[], maxRelaysPerAuthor?: number, prefer?: readonly string[]): Map<string, string[]>;
196
- /**
197
- * Outbox-model feed: history sync + live subscription for a set of authors,
198
- * routed via NIP-65 write relays (with discovery fallback).
199
- */
200
- declare class OutboxFeed {
201
- #private;
202
- constructor(opts: OutboxFeedOptions);
203
- get authors(): readonly string[];
204
- get kinds(): readonly number[];
205
- /** Replace the author set (does not auto-restart live). */
206
- setAuthors(authors: readonly string[]): void;
207
- getBound(pubkey: string, kind: number): OutboxBound | undefined;
208
- /**
209
- * Load NIP-65 relay lists for authors that have no outbox routes yet.
210
- */
211
- hydrate(): Promise<void>;
212
- /**
213
- * One-shot history pull. Persistence is `applySync`; bounds move only for events it returns.
214
- */
215
- sync(opts?: {
216
- limit?: number;
217
- since?: number;
218
- until?: number;
219
- signal?: AbortSignal;
220
- /** Skip hydrate() even if provided. */
221
- skipHydrate?: boolean;
222
- }): Promise<Event[]>;
223
- /**
224
- * Start a live subscription across outbox relays for the current authors.
225
- * Closes any previous live subscription.
226
- */
227
- startLive(opts?: {
228
- signal?: AbortSignal;
229
- since?: number;
230
- }): {
231
- close: (reason?: string) => void;
232
- };
233
- /** Convenience: hydrate + sync + startLive. */
234
- start(opts?: {
235
- limit?: number;
236
- signal?: AbortSignal;
237
- }): Promise<{
238
- close: (reason?: string) => void;
239
- events: Event[];
240
- }>;
241
- close(): void;
242
- }
243
- /** Factory matching other loaders helpers. */
244
- declare function createOutboxFeed(opts: OutboxFeedOptions): OutboxFeed;
245
- //#endregion
246
- //#region src/loaders/index.d.ts
247
- type Loaders = {
248
- context: LoaderContext;
249
- follows: ListLoaders["follows"];
250
- muteList: ListLoaders["muteList"];
251
- relayList: ListLoaders["relayList"];
252
- dmRelayList: ListLoaders["dmRelayList"];
253
- profile: ProfileLoader["load"];
254
- event: EventLoader["load"];
255
- };
256
- type CreateLoadersOptions = {
257
- pool: Pool;
258
- relays: readonly string[];
259
- cache?: ReplaceableCache;
260
- staleAfterSec?: number;
261
- fetchTimeoutMs?: number;
262
- };
263
- /** Build an instance-scoped loader suite (no module globals). */
264
- declare function createLoaders(opts: CreateLoadersOptions): Loaders;
265
- //#endregion
266
- export { DataLoader as C, ReplaceableCache as D, LoaderContextOptions as E, createReplaceableLoader as S, LoaderContext as T, ListResult as _, OutboxFeed as a, LoadStyle as b, groupAuthorsByOutboxRelay as c, createEventLoader as d, NostrUser as f, ListLoaders as g, createProfileLoader as h, OutboxError as i, EventLoader as l, bareNostrUser as m, Loaders as n, OutboxFeedOptions as o, ProfileLoader as p, createLoaders as r, createOutboxFeed as s, CreateLoadersOptions as t, EventRef as u, MutedEntity as v, LoaderError as w, ReplaceableLoadResult as x, createListLoaders as y };
267
- //# sourceMappingURL=index-EwL6fHLo.d.mts.map
@@ -1,57 +0,0 @@
1
- import { t as Event } from "./event-B5D2jxt_.mjs";
2
- import { t as Filter } from "./filter-DUEKP3q1.mjs";
3
- import { i as PutResult, n as NegentropyItem, r as OutboxBound, t as EventStore } from "./types-Dn24TPFs.mjs";
4
- //#region src/storage/memory.d.ts
5
- /**
6
- * In-memory event store with NIP-01 replaceable / addressable / ephemeral
7
- * handling and NIP-09 deletion (kind 5) application.
8
- */
9
- declare class MemoryEventStore implements EventStore {
10
- #private;
11
- put(raw: Event): Promise<PutResult>;
12
- /** Sequential `put` in input order. No transaction: a throw leaves earlier events applied. */
13
- putMany(events: readonly Event[]): Promise<PutResult[]>;
14
- get(id: string): Promise<Event | undefined>;
15
- query(filters: Filter[]): Promise<Event[]>;
16
- count(filters: Filter[]): Promise<number>;
17
- negentropyItems(filter: Filter): Promise<NegentropyItem[]>;
18
- remove(ids: string[]): Promise<number>;
19
- getOutboxBound(pubkey: string, kind: number): Promise<OutboxBound | undefined>;
20
- setOutboxBound(pubkey: string, kind: number, bound: OutboxBound): Promise<void>;
21
- clear(): Promise<void>;
22
- get size(): number;
23
- }
24
- //#endregion
25
- //#region src/storage/indexeddb.d.ts
26
- type IndexedDbEventStoreOptions = {
27
- /** IndexedDB database name. */
28
- dbName?: string;
29
- };
30
- /**
31
- * Browser IndexedDB event store with the same replaceable / deletion semantics
32
- * as {@link MemoryEventStore}. Requires a DOM IndexedDB implementation.
33
- *
34
- * `open` loads every tombstone and address row into memory. Fine at 10^4 events;
35
- * tens of MB at 10^5 addressables.
36
- */
37
- declare class IndexedDbEventStore implements EventStore {
38
- #private;
39
- constructor(opts?: IndexedDbEventStoreOptions);
40
- static isAvailable(): boolean;
41
- open(): Promise<void>;
42
- put(event: Event): Promise<PutResult>;
43
- /** One readwrite transaction; abort rejects the whole batch with no partial persist. */
44
- putMany(events: readonly Event[]): Promise<PutResult[]>;
45
- get(id: string): Promise<Event | undefined>;
46
- query(filters: Filter[]): Promise<Event[]>;
47
- count(filters: Filter[]): Promise<number>;
48
- negentropyItems(filter: Filter): Promise<NegentropyItem[]>;
49
- getOutboxBound(pubkey: string, kind: number): Promise<OutboxBound | undefined>;
50
- setOutboxBound(pubkey: string, kind: number, bound: OutboxBound): Promise<void>;
51
- remove(ids: string[]): Promise<number>;
52
- clear(): Promise<void>;
53
- close(): void;
54
- }
55
- //#endregion
56
- export { IndexedDbEventStoreOptions as n, MemoryEventStore as r, IndexedDbEventStore as t };
57
- //# sourceMappingURL=index-k2YhCgsh.d.mts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"key-Dn-aX4x4.mjs","names":["#bytes"],"sources":["../src/core/key.ts"],"sourcesContent":["import { schnorr } from \"@noble/curves/secp256k1.js\";\nimport { CryptoError } from \"./error.ts\";\nimport {\n type Event,\n type EventTemplate,\n type UnsignedEvent,\n getEventHash,\n isMarkedFailed,\n isMarkedVerified,\n markUnverified,\n markVerified,\n validateEvent,\n validateSignedEvent,\n} from \"./event.ts\";\nimport { assertHex32, assertSecretKeyBytes, bytesToHex, hexToBytes } from \"./util.ts\";\n\n/** 32-byte secret key held as bytes; prefer zeroize when done. */\nexport class SecretKey {\n #bytes: Uint8Array | null;\n\n private constructor(bytes: Uint8Array) {\n assertSecretKeyBytes(bytes);\n this.#bytes = new Uint8Array(bytes);\n }\n\n static generate(): SecretKey {\n return new SecretKey(schnorr.utils.randomSecretKey());\n }\n\n static fromBytes(bytes: Uint8Array): SecretKey {\n return new SecretKey(bytes);\n }\n\n static fromHex(hex: string): SecretKey {\n return new SecretKey(hexToBytes(assertHex32(hex, \"secret key\")));\n }\n\n get bytes(): Uint8Array {\n if (!this.#bytes) throw new CryptoError(\"secret key has been zeroized\");\n return new Uint8Array(this.#bytes);\n }\n\n toHex(): string {\n return bytesToHex(this.bytes);\n }\n\n /** Best-effort wipe of the internal buffer. */\n zeroize(): void {\n if (this.#bytes) {\n this.#bytes.fill(0);\n this.#bytes = null;\n }\n }\n}\n\nexport type PublicKey = string;\n\nexport function publicKeyFromHex(hex: string): PublicKey {\n return assertHex32(hex, \"public key\");\n}\n\nexport function getPublicKey(secretKey: SecretKey | Uint8Array | string): PublicKey {\n const bytes =\n secretKey instanceof SecretKey\n ? secretKey.bytes\n : typeof secretKey === \"string\"\n ? hexToBytes(assertHex32(secretKey, \"secret key\"))\n : (assertSecretKeyBytes(secretKey), secretKey);\n return bytesToHex(schnorr.getPublicKey(bytes));\n}\n\n/** Keypair convenience wrapper. */\nexport class Keys {\n readonly secretKey: SecretKey;\n readonly publicKey: PublicKey;\n\n private constructor(secretKey: SecretKey) {\n this.secretKey = secretKey;\n this.publicKey = getPublicKey(secretKey);\n }\n\n static generate(): Keys {\n return new Keys(SecretKey.generate());\n }\n\n static fromSecretKey(secretKey: SecretKey | Uint8Array | string): Keys {\n const sk =\n secretKey instanceof SecretKey\n ? secretKey\n : typeof secretKey === \"string\"\n ? SecretKey.fromHex(secretKey)\n : SecretKey.fromBytes(secretKey);\n return new Keys(sk);\n }\n}\n\nfunction resolveSecretKeyBytes(secretKey: SecretKey | Uint8Array | string): Uint8Array {\n if (secretKey instanceof SecretKey) return secretKey.bytes;\n if (typeof secretKey === \"string\") return hexToBytes(assertHex32(secretKey, \"secret key\"));\n assertSecretKeyBytes(secretKey);\n return secretKey;\n}\n\n/** Fill pubkey/id/sig on a template and return a signed event. */\nexport function finalizeEvent(\n template: EventTemplate,\n secretKey: SecretKey | Uint8Array | string,\n): Event {\n const sk = resolveSecretKeyBytes(secretKey);\n const pubkey = bytesToHex(schnorr.getPublicKey(sk));\n const unsigned: UnsignedEvent = {\n kind: template.kind,\n tags: template.tags,\n content: template.content,\n created_at: template.created_at,\n pubkey,\n };\n return signEvent(unsigned, sk);\n}\n\n/**\n * Sign an already-assembled unsigned event.\n * Rejects when `unsigned.pubkey` does not match the secret key.\n */\nexport function signEvent(\n unsigned: UnsignedEvent,\n secretKey: SecretKey | Uint8Array | string,\n): Event {\n const sk = resolveSecretKeyBytes(secretKey);\n if (!validateEvent(unsigned)) {\n throw new CryptoError(\"cannot sign invalid unsigned event\");\n }\n\n const expected = bytesToHex(schnorr.getPublicKey(sk));\n if (unsigned.pubkey.toLowerCase() !== expected) {\n throw new CryptoError(\"unsigned event pubkey does not match secret key\");\n }\n\n const normalized: UnsignedEvent = {\n kind: unsigned.kind,\n tags: unsigned.tags,\n content: unsigned.content,\n created_at: unsigned.created_at,\n pubkey: expected,\n };\n\n const id = getEventHash(normalized);\n const sig = bytesToHex(schnorr.sign(hexToBytes(id), sk));\n const event: Event = { ...normalized, id, sig };\n markVerified(event);\n return event;\n}\n\n/** Verify event id and BIP-340 signature. Uses WeakSet cache (does not mutate the event). */\nexport function verifyEvent(event: Event): boolean {\n if (isMarkedVerified(event)) return true;\n if (isMarkedFailed(event)) return false;\n\n if (!validateSignedEvent(event)) {\n markUnverified(event);\n return false;\n }\n\n try {\n const hash = getEventHash(event);\n if (hash !== event.id.toLowerCase()) {\n markUnverified(event);\n return false;\n }\n const ok = schnorr.verify(\n hexToBytes(event.sig.toLowerCase()),\n hexToBytes(hash),\n hexToBytes(event.pubkey.toLowerCase()),\n );\n if (ok) markVerified(event);\n else markUnverified(event);\n return ok;\n } catch {\n markUnverified(event);\n return false;\n }\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"kind-BLl4cLOc.mjs","names":[],"sources":["../src/core/kind.ts"],"sourcesContent":["/** NIP-01 regular: stored by relays. */\nexport function isRegularKind(kind: number): boolean {\n return kind === 1 || kind === 2 || (kind >= 4 && kind < 45) || (kind >= 1000 && kind < 10000);\n}\n\n/** Replaceable: latest per (pubkey, kind) wins. */\nexport function isReplaceableKind(kind: number): boolean {\n return kind === 0 || kind === 3 || (kind >= 10000 && kind < 20000);\n}\n\n/** Ephemeral: not expected to be stored. */\nexport function isEphemeralKind(kind: number): boolean {\n return kind >= 20000 && kind < 30000;\n}\n\n/** Addressable (parameterized replaceable): latest per (pubkey, kind, d-tag) wins. */\nexport function isAddressableKind(kind: number): boolean {\n return kind >= 30000 && kind < 40000;\n}\n\nexport type KindClassification =\n | \"regular\"\n | \"replaceable\"\n | \"ephemeral\"\n | \"addressable\"\n | \"unknown\";\n\nexport function classifyKind(kind: number): KindClassification {\n if (isRegularKind(kind)) return \"regular\";\n if (isReplaceableKind(kind)) return \"replaceable\";\n if (isEphemeralKind(kind)) return \"ephemeral\";\n if (isAddressableKind(kind)) return \"addressable\";\n return \"unknown\";\n}\n\n/** Kind numbers used by this package. */\nexport const Kind = {\n Metadata: 0,\n TextNote: 1,\n Contacts: 3,\n EventDeletion: 5,\n Repost: 6,\n Reaction: 7,\n Seal: 13,\n PrivateDirectMessage: 14,\n GenericRepost: 16,\n GiftWrap: 1059,\n ZapRequest: 9734,\n Zap: 9735,\n MuteList: 10000,\n PinList: 10001,\n RelayList: 10002,\n BookmarkList: 10003,\n FavoriteRelays: 10012,\n UserEmojiList: 10030,\n DirectMessageRelaysList: 10050,\n BlossomServerList: 10063,\n GiftWrapEphemeral: 21059,\n ClientAuth: 22242,\n NostrConnect: 24133,\n BlobsAuth: 24242,\n HttpAuth: 27235,\n RelaySets: 30002,\n EmojiSet: 30030,\n StarterPack: 39089,\n} as const;\n\nexport type KindName = keyof typeof Kind;\nexport type KnownKind = (typeof Kind)[KindName];\n"],"mappings":";;AACA,SAAgB,cAAc,MAAuB;CACnD,OAAO,SAAS,KAAK,SAAS,KAAM,QAAQ,KAAK,OAAO,MAAQ,QAAQ,OAAQ,OAAO;AACzF;;AAGA,SAAgB,kBAAkB,MAAuB;CACvD,OAAO,SAAS,KAAK,SAAS,KAAM,QAAQ,OAAS,OAAO;AAC9D;;AAGA,SAAgB,gBAAgB,MAAuB;CACrD,OAAO,QAAQ,OAAS,OAAO;AACjC;;AAGA,SAAgB,kBAAkB,MAAuB;CACvD,OAAO,QAAQ,OAAS,OAAO;AACjC;AASA,SAAgB,aAAa,MAAkC;CAC7D,IAAI,cAAc,IAAI,GAAG,OAAO;CAChC,IAAI,kBAAkB,IAAI,GAAG,OAAO;CACpC,IAAI,gBAAgB,IAAI,GAAG,OAAO;CAClC,IAAI,kBAAkB,IAAI,GAAG,OAAO;CACpC,OAAO;AACT;;AAGA,MAAa,OAAO;CAClB,UAAU;CACV,UAAU;CACV,UAAU;CACV,eAAe;CACf,QAAQ;CACR,UAAU;CACV,MAAM;CACN,sBAAsB;CACtB,eAAe;CACf,UAAU;CACV,YAAY;CACZ,KAAK;CACL,UAAU;CACV,SAAS;CACT,WAAW;CACX,cAAc;CACd,gBAAgB;CAChB,eAAe;CACf,yBAAyB;CACzB,mBAAmB;CACnB,mBAAmB;CACnB,YAAY;CACZ,cAAc;CACd,WAAW;CACX,UAAU;CACV,WAAW;CACX,UAAU;CACV,aAAa;AACf"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"loaders-BNQszDAn.mjs","names":["#events","#relays","#batchLoadFn","#cacheKeyFn","#maxBatchSize","#useCache","#cache","#inflight","#queue","#dispatch","#scheduled","#pool","#gossip","#storage","#discovery","#kinds","#maxRelays","#timeoutMs","#onEvent","#observe","#applySync","#hydrate","#bounds","#authors","#assertOpen","#hydrateBoundsFromStore","#syncFilters","#updateBounds","#persistBounds","#liveCloser","#noteEvent","#closed"],"sources":["../src/loaders/cache.ts","../src/loaders/context.ts","../src/loaders/event.ts","../src/loaders/dataloader.ts","../src/loaders/replaceable.ts","../src/loaders/lists.ts","../src/loaders/profile.ts","../src/loaders/outbox.ts","../src/loaders/index.ts"],"sourcesContent":["import type { Event } from \"../core/event.ts\";\nimport { isReplaceableWinner } from \"../core/event.ts\";\n\nexport type ReplaceableKey = {\n kind: number;\n pubkey: string;\n dTag?: string;\n};\n\nfunction keyOf(spec: ReplaceableKey): string {\n return `${spec.kind}:${spec.pubkey.toLowerCase()}:${spec.dTag ?? \"\"}`;\n}\n\n/** In-memory replaceable-event cache used by loaders (no DOM / localStorage). */\nexport class ReplaceableCache {\n #events = new Map<string, { event: Event | null; fetchedAt: number }>();\n\n get(spec: ReplaceableKey): { event: Event | null; fetchedAt: number } | undefined {\n return this.#events.get(keyOf(spec));\n }\n\n set(spec: ReplaceableKey, event: Event | null, fetchedAt = Math.floor(Date.now() / 1000)): void {\n this.#events.set(keyOf(spec), { event, fetchedAt });\n }\n\n /** Keep newer event by created_at. */\n putIfNewer(event: Event, dTag?: string): void {\n const spec: ReplaceableKey = { kind: event.kind, pubkey: event.pubkey, dTag };\n const prev = this.get(spec);\n if (!prev?.event || isReplaceableWinner(event, prev.event)) {\n this.set(spec, event);\n }\n }\n\n clear(spec?: ReplaceableKey): void {\n if (spec) this.#events.delete(keyOf(spec));\n else this.#events.clear();\n }\n\n get size(): number {\n return this.#events.size;\n }\n}\n","import type { Pool } from \"../relay/pool.ts\";\nimport { ReplaceableCache } from \"./cache.ts\";\n\nexport type LoaderContextOptions = {\n pool: Pool;\n /** Fallback / discovery relays when no per-user routing is known. */\n relays: readonly string[];\n cache?: ReplaceableCache;\n /** Max age (seconds) before a cached replaceable is considered stale. Default 2 days. */\n staleAfterSec?: number;\n fetchTimeoutMs?: number;\n};\n\n/**\n * Explicit dependency bag for loaders — never a module-level singleton.\n */\nexport class LoaderContext {\n readonly pool: Pool;\n readonly cache: ReplaceableCache;\n readonly staleAfterSec: number;\n readonly fetchTimeoutMs: number;\n #relays: string[];\n\n constructor(opts: LoaderContextOptions) {\n this.pool = opts.pool;\n this.#relays = [...opts.relays];\n this.cache = opts.cache ?? new ReplaceableCache();\n this.staleAfterSec = opts.staleAfterSec ?? 60 * 60 * 24 * 2;\n this.fetchTimeoutMs = opts.fetchTimeoutMs ?? 4400;\n }\n\n /** Snapshot of discovery/fallback relays. */\n get relays(): readonly string[] {\n return this.#relays;\n }\n\n addRelay(url: string): void {\n if (!this.#relays.includes(url)) this.#relays.push(url);\n }\n\n removeRelay(url: string): void {\n this.#relays = this.#relays.filter((r) => r !== url);\n }\n\n setRelays(urls: readonly string[]): void {\n this.#relays = [...urls];\n }\n\n isFresh(fetchedAt: number, now = Math.floor(Date.now() / 1000)): boolean {\n return now - fetchedAt < this.staleAfterSec;\n }\n}\n","import { isReplaceableWinner, type Event } from \"../core/event.ts\";\nimport { isAddressableKind } from \"../core/kind.ts\";\nimport { isHex32 } from \"../core/util.ts\";\nimport { decode, Nip19Error, type AddressPointer, type EventPointer } from \"../nips/nip19.ts\";\nimport type { LoaderContext } from \"./context.ts\";\n\nexport type EventRef = string | EventPointer | AddressPointer;\n\nfunction dFilter(kind: number, identifier: string): { \"#d\"?: string[] } {\n if (isAddressableKind(kind)) return { \"#d\": [identifier] };\n return {};\n}\n\nfunction parseRef(ref: EventRef): {\n filter: { ids?: string[]; authors?: string[]; kinds?: number[]; \"#d\"?: string[] };\n hints: string[];\n cacheKey: string;\n} {\n if (typeof ref === \"string\") {\n if (isHex32(ref)) {\n return {\n filter: { ids: [ref.toLowerCase()] },\n hints: [],\n cacheKey: `id:${ref.toLowerCase()}`,\n };\n }\n const decoded = decode(ref);\n switch (decoded.type) {\n case \"note\":\n return { filter: { ids: [decoded.data] }, hints: [], cacheKey: `id:${decoded.data}` };\n case \"nevent\":\n return {\n filter: { ids: [decoded.data.id] },\n hints: decoded.data.relays ?? [],\n cacheKey: `id:${decoded.data.id}`,\n };\n case \"naddr\":\n return {\n filter: {\n authors: [decoded.data.pubkey.toLowerCase()],\n kinds: [decoded.data.kind],\n ...dFilter(decoded.data.kind, decoded.data.identifier),\n },\n hints: decoded.data.relays ?? [],\n cacheKey: `addr:${decoded.data.kind}:${decoded.data.pubkey}:${decoded.data.identifier}`,\n };\n default:\n throw new Nip19Error(`cannot load event from ${decoded.type}`);\n }\n }\n if (\"id\" in ref) {\n return {\n filter: { ids: [ref.id.toLowerCase()] },\n hints: ref.relays ?? [],\n cacheKey: `id:${ref.id.toLowerCase()}`,\n };\n }\n return {\n filter: {\n authors: [ref.pubkey.toLowerCase()],\n kinds: [ref.kind],\n ...dFilter(ref.kind, ref.identifier),\n },\n hints: ref.relays ?? [],\n cacheKey: `addr:${ref.kind}:${ref.pubkey}:${ref.identifier}`,\n };\n}\n\nexport function createEventLoader(ctx: LoaderContext) {\n const inflight = new Map<string, Promise<Event | undefined>>();\n const cache = new Map<string, Event | undefined>();\n return {\n load(ref: EventRef): Promise<Event | undefined> {\n const parsed = parseRef(ref);\n if (cache.has(parsed.cacheKey)) return Promise.resolve(cache.get(parsed.cacheKey));\n const hit = inflight.get(parsed.cacheKey);\n if (hit) return hit;\n const p = (async () => {\n const relays = [...new Set([...parsed.hints, ...ctx.relays])];\n if (relays.length === 0) return undefined;\n const events = await ctx.pool.fetch(relays, [parsed.filter], {\n timeoutMs: ctx.fetchTimeoutMs,\n });\n let best: Event | undefined;\n for (const e of events) {\n if (!best || isReplaceableWinner(e, best)) best = e;\n }\n cache.set(parsed.cacheKey, best);\n return best;\n })().finally(() => inflight.delete(parsed.cacheKey));\n inflight.set(parsed.cacheKey, p);\n return p;\n },\n clearAll() {\n inflight.clear();\n cache.clear();\n },\n };\n}\n\nexport type EventLoader = ReturnType<typeof createEventLoader>;\n","import { NostrError } from \"../core/error.ts\";\n\nexport class LoaderError extends NostrError {}\n\n/** Minimal request coalescer. Batches keys within a microtask; no global state. */\nexport type BatchLoadFn<K, V> = (keys: readonly K[]) => Promise<readonly (V | Error)[]>;\n\nexport type DataLoaderOptions<K, C = K> = {\n cacheKeyFn?: (key: K) => C;\n maxBatchSize?: number;\n /** When false, only coalesces in-flight requests (no durable memoization). Default true. */\n cache?: boolean;\n};\n\nexport class DataLoader<K, V, C = K> {\n readonly #batchLoadFn: BatchLoadFn<K, V>;\n readonly #cacheKeyFn: (key: K) => C;\n readonly #maxBatchSize: number;\n readonly #useCache: boolean;\n readonly #cache = new Map<C, Promise<V>>();\n readonly #inflight = new Map<C, Promise<V>>();\n #queue: Array<{\n key: K;\n resolve: (v: V) => void;\n reject: (e: unknown) => void;\n }> = [];\n #scheduled = false;\n\n constructor(batchLoadFn: BatchLoadFn<K, V>, options: DataLoaderOptions<K, C> = {}) {\n this.#batchLoadFn = batchLoadFn;\n this.#cacheKeyFn = options.cacheKeyFn ?? ((k: K) => k as unknown as C);\n this.#maxBatchSize = options.maxBatchSize ?? Infinity;\n this.#useCache = options.cache !== false;\n }\n\n load(key: K): Promise<V> {\n const cacheKey = this.#cacheKeyFn(key);\n if (this.#useCache) {\n const cached = this.#cache.get(cacheKey);\n if (cached) return cached;\n }\n const inflight = this.#inflight.get(cacheKey);\n if (inflight) return inflight;\n\n const promise = new Promise<V>((resolve, reject) => {\n this.#queue.push({ key, resolve, reject });\n if (this.#queue.length >= this.#maxBatchSize) {\n this.#dispatch();\n } else if (!this.#scheduled) {\n this.#scheduled = true;\n queueMicrotask(() => this.#dispatch());\n }\n }).finally(() => {\n this.#inflight.delete(cacheKey);\n });\n\n this.#inflight.set(cacheKey, promise);\n if (this.#useCache) this.#cache.set(cacheKey, promise);\n return promise;\n }\n\n clear(key: K): void {\n this.#cache.delete(this.#cacheKeyFn(key));\n this.#inflight.delete(this.#cacheKeyFn(key));\n }\n\n clearAll(): void {\n this.#cache.clear();\n this.#inflight.clear();\n }\n\n #dispatch(): void {\n this.#scheduled = false;\n const batch = this.#queue.splice(0, this.#maxBatchSize);\n if (batch.length === 0) return;\n\n const keys = batch.map((b) => b.key);\n void this.#batchLoadFn(keys)\n .then((values) => {\n if (values.length !== keys.length) {\n const err = new LoaderError(\n `DataLoader batch function must return array of length ${keys.length}, got ${values.length}`,\n );\n for (const item of batch) item.reject(err);\n return;\n }\n for (let i = 0; i < batch.length; i++) {\n const value = values[i]!;\n if (value instanceof Error) batch[i]!.reject(value);\n else batch[i]!.resolve(value);\n }\n })\n .catch((err) => {\n for (const item of batch) item.reject(err);\n });\n\n if (this.#queue.length > 0) {\n this.#scheduled = true;\n queueMicrotask(() => this.#dispatch());\n }\n }\n}\n","import type { Event } from \"../core/event.ts\";\nimport { isReplaceableWinner } from \"../core/event.ts\";\nimport type { Filter } from \"../core/filter.ts\";\nimport { getDTag } from \"../core/tag.ts\";\nimport { DataLoader } from \"./dataloader.ts\";\nimport type { LoaderContext } from \"./context.ts\";\n\nexport type ReplaceableLoadResult = {\n event: Event | null;\n fresh: boolean;\n};\n\nexport type LoadStyle = \"default\" | \"force\" | \"cache-only\";\n\n/**\n * Batch-fetch replaceable events (kind + authors) via the pool, with cache.\n * Durable cache is ReplaceableCache; DataLoader only coalesces in-flight requests.\n */\nexport function createReplaceableLoader(ctx: LoaderContext, kind: number) {\n type Key = { pubkey: string; hints?: string[] };\n\n const loader = new DataLoader<Key, Event | null, string>(\n async (keys) => {\n const authors = [...new Set(keys.map((k) => k.pubkey))];\n const hintRelays = [...new Set(keys.flatMap((k) => k.hints ?? []).concat(ctx.relays))];\n const filter: Filter = { kinds: [kind], authors };\n const events =\n hintRelays.length > 0\n ? await ctx.pool.fetch(hintRelays, [filter], { timeoutMs: ctx.fetchTimeoutMs })\n : [];\n\n const best = new Map<string, Event>();\n for (const event of events) {\n if (event.kind !== kind) continue;\n const prev = best.get(event.pubkey);\n if (!prev || isReplaceableWinner(event, prev)) best.set(event.pubkey, event);\n }\n\n const now = Math.floor(Date.now() / 1000);\n return keys.map((k) => {\n const event = best.get(k.pubkey) ?? null;\n ctx.cache.set(\n { kind, pubkey: k.pubkey, dTag: event ? (getDTag(event.tags) ?? undefined) : undefined },\n event,\n now,\n );\n return event;\n });\n },\n {\n cache: false,\n cacheKeyFn: (k) => `${kind}:${k.pubkey}:${(k.hints ?? []).join(\",\")}`,\n maxBatchSize: 50,\n },\n );\n\n return {\n async load(\n pubkey: string,\n opts?: { hints?: string[]; style?: LoadStyle },\n ): Promise<ReplaceableLoadResult> {\n const pk = pubkey.toLowerCase();\n const style = opts?.style ?? \"default\";\n const cached = ctx.cache.get({ kind, pubkey: pk });\n\n if (style === \"cache-only\") {\n return { event: cached?.event ?? null, fresh: false };\n }\n if (style === \"default\" && cached && ctx.isFresh(cached.fetchedAt)) {\n return { event: cached.event, fresh: false };\n }\n\n const event = await loader.load({ pubkey: pk, hints: opts?.hints });\n return { event, fresh: true };\n },\n clear(pubkey?: string) {\n if (pubkey) {\n const pk = pubkey.toLowerCase();\n loader.clear({ pubkey: pk });\n ctx.cache.clear({ kind, pubkey: pk });\n } else {\n loader.clearAll();\n }\n },\n };\n}\n","import type { Event } from \"../core/event.ts\";\nimport { Kind } from \"../core/kind.ts\";\nimport { isHex32, normalizeURL } from \"../core/util.ts\";\nimport { parseDmRelayList } from \"../nips/nip17.ts\";\nimport type { RelayListItem } from \"../nips/nip65.ts\";\nimport { parseRelayList } from \"../nips/nip65.ts\";\nimport type { LoaderContext } from \"./context.ts\";\nimport { createReplaceableLoader, type LoadStyle } from \"./replaceable.ts\";\n\nexport type ListResult<T> = {\n event: Event | null;\n items: T[];\n fresh: boolean;\n};\n\nexport type MutedEntity =\n | { label: \"pubkey\"; value: string }\n | { label: \"thread\"; value: string }\n | { label: \"hashtag\"; value: string }\n | { label: \"word\"; value: string };\n\nfunction fromTags<T>(event: Event | null, map: (tag: readonly string[]) => T | undefined): T[] {\n if (!event) return [];\n const out: T[] = [];\n for (const tag of event.tags) {\n const item = map(tag);\n if (item !== undefined) out.push(item);\n }\n return out;\n}\n\nexport function createListLoaders(ctx: LoaderContext) {\n const followsLoader = createReplaceableLoader(ctx, Kind.Contacts);\n const muteLoader = createReplaceableLoader(ctx, Kind.MuteList);\n const relayListLoader = createReplaceableLoader(ctx, Kind.RelayList);\n const dmRelayListLoader = createReplaceableLoader(ctx, Kind.DirectMessageRelaysList);\n\n return {\n async follows(\n pubkey: string,\n opts?: { hints?: string[]; style?: LoadStyle },\n ): Promise<ListResult<string>> {\n const { event, fresh } = await followsLoader.load(pubkey, opts);\n return {\n event,\n fresh,\n items: fromTags(event, (tag) =>\n tag[0] === \"p\" && tag[1] && isHex32(tag[1]) ? tag[1].toLowerCase() : undefined,\n ),\n };\n },\n\n async muteList(\n pubkey: string,\n opts?: { hints?: string[]; style?: LoadStyle },\n ): Promise<ListResult<MutedEntity>> {\n const { event, fresh } = await muteLoader.load(pubkey, opts);\n return {\n event,\n fresh,\n items: fromTags(event, (tag) => {\n if (!tag[1]) return undefined;\n switch (tag[0]) {\n case \"p\":\n return isHex32(tag[1]) ? { label: \"pubkey\", value: tag[1].toLowerCase() } : undefined;\n case \"e\":\n return isHex32(tag[1]) ? { label: \"thread\", value: tag[1].toLowerCase() } : undefined;\n case \"t\":\n return { label: \"hashtag\", value: tag[1] };\n case \"word\":\n return { label: \"word\", value: tag[1] };\n default:\n return undefined;\n }\n }),\n };\n },\n\n async relayList(\n pubkey: string,\n opts?: { hints?: string[]; style?: LoadStyle },\n ): Promise<ListResult<RelayListItem>> {\n const { event, fresh } = await relayListLoader.load(pubkey, opts);\n let items: RelayListItem[] = [];\n if (event) {\n try {\n items = parseRelayList(event);\n } catch {\n items = [];\n }\n }\n // ensure normalized urls even if parse skipped\n items = items.map((i) => {\n try {\n return { ...i, url: normalizeURL(i.url) };\n } catch {\n return i;\n }\n });\n return { event, fresh, items };\n },\n\n async dmRelayList(\n pubkey: string,\n opts?: { hints?: string[]; style?: LoadStyle },\n ): Promise<ListResult<string>> {\n const { event, fresh } = await dmRelayListLoader.load(pubkey, opts);\n let items: string[] = [];\n if (event) {\n try {\n items = parseDmRelayList(event);\n } catch {\n items = [];\n }\n }\n return { event, fresh, items };\n },\n };\n}\n\nexport type ListLoaders = ReturnType<typeof createListLoaders>;\n","import type { Event } from \"../core/event.ts\";\nimport type { ProfileMetadata } from \"../core/builder.ts\";\nimport { Kind } from \"../core/kind.ts\";\nimport { npubEncode } from \"../nips/nip19.ts\";\nimport type { LoaderContext } from \"./context.ts\";\nimport { createReplaceableLoader, type LoadStyle } from \"./replaceable.ts\";\n\nexport type NostrUser = {\n pubkey: string;\n npub: string;\n shortName: string;\n image?: string;\n metadata: ProfileMetadata;\n lastUpdated: number;\n event: Event | null;\n fresh: boolean;\n};\n\nexport function bareNostrUser(pubkey: string): NostrUser {\n const pk = pubkey.toLowerCase();\n let npub: string;\n try {\n npub = npubEncode(pk);\n } catch {\n npub = pk;\n }\n return {\n pubkey: pk,\n npub,\n shortName: npub.startsWith(\"npub1\") ? `${npub.slice(0, 8)}…${npub.slice(-4)}` : pk.slice(0, 8),\n metadata: {},\n lastUpdated: 0,\n event: null,\n fresh: false,\n };\n}\n\nfunction parseMetadata(content: string): ProfileMetadata {\n try {\n const obj = JSON.parse(content) as ProfileMetadata;\n return typeof obj === \"object\" && obj !== null ? obj : {};\n } catch {\n return {};\n }\n}\n\nexport function createProfileLoader(ctx: LoaderContext) {\n const loader = createReplaceableLoader(ctx, Kind.Metadata);\n\n return {\n async load(pubkey: string, opts?: { hints?: string[]; style?: LoadStyle }): Promise<NostrUser> {\n const base = bareNostrUser(pubkey);\n const { event, fresh } = await loader.load(pubkey, opts);\n if (!event) return { ...base, fresh };\n\n const metadata = parseMetadata(event.content);\n const display = metadata.display_name || metadata.name;\n return {\n ...base,\n shortName: display || base.shortName,\n image: metadata.picture,\n metadata,\n lastUpdated: event.created_at,\n event,\n fresh,\n };\n },\n };\n}\n\nexport type ProfileLoader = ReturnType<typeof createProfileLoader>;\n","import type { Event } from \"../core/event.ts\";\nimport { NostrError } from \"../core/error.ts\";\nimport type { Filter } from \"../core/filter.ts\";\nimport { sortedEvents } from \"../core/event.ts\";\nimport { Kind } from \"../core/kind.ts\";\nimport { normalizeURL } from \"../core/util.ts\";\nimport type { Gossip } from \"../gossip/gossip.ts\";\nimport type { Pool } from \"../relay/pool.ts\";\nimport { toStorageError } from \"../storage/error.ts\";\nimport type { EventStore, OutboxBound } from \"../storage/types.ts\";\n\nexport type { OutboxBound } from \"../storage/types.ts\";\n\nexport class OutboxError extends NostrError {}\n\nexport type OutboxFeedOptions = {\n pool: Pool;\n gossip: Gossip;\n storage: EventStore;\n /** Fallback relays when an author has no known outbox routes. */\n discoveryRelays: readonly string[];\n authors: readonly string[];\n /** Event kinds to fetch. Default `[Kind.TextNote]`. */\n kinds?: readonly number[];\n /** Max write-relays to use per author. Default 3. */\n maxRelaysPerAuthor?: number;\n fetchTimeoutMs?: number;\n /** Called for live events after observe, and for sync events returned by applySync. */\n onEvent?: (event: Event) => void;\n /** Live ingest (e.g. `client.observe`). StartLive never putManys. */\n observe?: (event: Event) => void;\n /**\n * Sync path only. Awaited. Return events that should advance bounds.\n * Throw → sync throws, bounds unchanged.\n */\n applySync?: (events: readonly Event[]) => Promise<readonly Event[]>;\n /**\n * Load kind:10002 for authors before sync when routes are missing.\n * Requires a relay-list loader.\n */\n hydrate?: (pubkeys: readonly string[]) => Promise<void>;\n};\n\nfunction boundKey(pubkey: string, kind: number): string {\n return `${pubkey.toLowerCase()}:${kind}`;\n}\n\n/** Same skip/dedup as Gossip.setRoutes so prefer can match Client.relays. */\nfunction canonicalRelayUrls(urls: readonly string[]): string[] {\n const out: string[] = [];\n for (const raw of urls) {\n try {\n const url = normalizeURL(raw);\n if (!out.includes(url)) out.push(url);\n } catch {\n // not a relay URL\n }\n }\n return out;\n}\n\n/**\n * Group authors by outbox relay (discovery fallback).\n * `prefer` reorders existing candidates only; it never appends a URL\n * that is not already in the author's outbox or discovery list.\n * Returns Map<relayUrl, authors[]>.\n */\nexport function groupAuthorsByOutboxRelay(\n authors: readonly string[],\n gossip: Gossip,\n discoveryRelays: readonly string[],\n maxRelaysPerAuthor = 3,\n prefer: readonly string[] = [],\n): Map<string, string[]> {\n const leftover = new Set(\n (gossip.route({ authors: authors.map((a) => a.toLowerCase()) }).remainder?.authors ?? []).map(\n (a) => a.toLowerCase(),\n ),\n );\n const preferSet = new Set(canonicalRelayUrls(prefer));\n const discovery = canonicalRelayUrls(discoveryRelays);\n const map = new Map<string, string[]>();\n\n for (const author of authors) {\n const pk = author.toLowerCase();\n const urls = leftover.has(pk) ? discovery : gossip.outboxRelays(pk);\n const preferred: string[] = [];\n const rest: string[] = [];\n for (const url of urls) {\n if (preferSet.has(url)) preferred.push(url);\n else rest.push(url);\n }\n for (const url of preferred.concat(rest).slice(0, maxRelaysPerAuthor)) {\n const list = map.get(url) ?? [];\n if (!list.includes(pk)) list.push(pk);\n map.set(url, list);\n }\n }\n return map;\n}\n\n/**\n * Outbox-model feed: history sync + live subscription for a set of authors,\n * routed via NIP-65 write relays (with discovery fallback).\n */\nexport class OutboxFeed {\n readonly #pool: Pool;\n readonly #gossip: Gossip;\n readonly #storage: EventStore;\n readonly #discovery: string[];\n readonly #kinds: number[];\n readonly #maxRelays: number;\n readonly #timeoutMs: number;\n readonly #onEvent: ((event: Event) => void) | undefined;\n readonly #observe: ((event: Event) => void) | undefined;\n readonly #applySync: ((events: readonly Event[]) => Promise<readonly Event[]>) | undefined;\n readonly #hydrate: ((pubkeys: readonly string[]) => Promise<void>) | undefined;\n readonly #bounds = new Map<string, OutboxBound>();\n #authors: string[];\n #liveCloser: { close: (reason?: string) => void } | undefined;\n #closed = false;\n\n constructor(opts: OutboxFeedOptions) {\n this.#pool = opts.pool;\n this.#gossip = opts.gossip;\n this.#storage = opts.storage;\n this.#discovery = [...opts.discoveryRelays];\n this.#authors = [...new Set(opts.authors.map((a) => a.toLowerCase()))];\n this.#kinds = [...(opts.kinds ?? [Kind.TextNote])];\n this.#maxRelays = opts.maxRelaysPerAuthor ?? 3;\n this.#timeoutMs = opts.fetchTimeoutMs ?? 4400;\n this.#onEvent = opts.onEvent;\n this.#observe = opts.observe;\n this.#applySync = opts.applySync;\n this.#hydrate = opts.hydrate;\n }\n\n get authors(): readonly string[] {\n return this.#authors;\n }\n\n get kinds(): readonly number[] {\n return this.#kinds;\n }\n\n /** Replace the author set (does not auto-restart live). */\n setAuthors(authors: readonly string[]): void {\n this.#authors = [...new Set(authors.map((a) => a.toLowerCase()))];\n }\n\n getBound(pubkey: string, kind: number): OutboxBound | undefined {\n return this.#bounds.get(boundKey(pubkey, kind));\n }\n\n /**\n * Load NIP-65 relay lists for authors that have no outbox routes yet.\n */\n async hydrate(): Promise<void> {\n this.#assertOpen();\n if (!this.#hydrate) return;\n const missing = this.#authors.filter((pk) => this.#gossip.outboxRelays(pk).length === 0);\n if (missing.length === 0) return;\n await this.#hydrate(missing);\n }\n\n /**\n * One-shot history pull. Persistence is `applySync`; bounds move only for events it returns.\n */\n async sync(opts?: {\n limit?: number;\n since?: number;\n until?: number;\n signal?: AbortSignal;\n /** Skip hydrate() even if provided. */\n skipHydrate?: boolean;\n }): Promise<Event[]> {\n this.#assertOpen();\n if (!opts?.skipHydrate) await this.hydrate();\n await this.#hydrateBoundsFromStore();\n\n const byRelay = groupAuthorsByOutboxRelay(\n this.#authors,\n this.#gossip,\n this.#discovery,\n this.#maxRelays,\n this.#pool.connectedUrls(),\n );\n\n if (byRelay.size === 0) return [];\n\n const byId = new Map<string, Event>();\n await Promise.all(\n [...byRelay.entries()].map(async ([url, authors]) => {\n if (opts?.signal?.aborted) return;\n\n const filters = this.#syncFilters(authors, opts);\n try {\n const batch = await this.#pool.fetch([url], filters, {\n timeoutMs: this.#timeoutMs,\n signal: opts?.signal,\n });\n for (const event of batch) byId.set(event.id, event);\n } catch {\n // skip failed relay\n }\n }),\n );\n\n const unique = [...byId.values()];\n if (unique.length === 0) return [];\n\n let applied: readonly Event[] = [];\n if (this.#applySync) {\n try {\n applied = await this.#applySync(unique);\n } catch (err) {\n throw toStorageError(err);\n }\n }\n const dirty = new Set<string>();\n for (const event of applied) {\n this.#updateBounds(event);\n dirty.add(boundKey(event.pubkey, event.kind));\n this.#onEvent?.(event);\n }\n await this.#persistBounds(dirty);\n return sortedEvents(unique);\n }\n\n /**\n * Start a live subscription across outbox relays for the current authors.\n * Closes any previous live subscription.\n */\n startLive(opts?: { signal?: AbortSignal; since?: number }): { close: (reason?: string) => void } {\n this.#assertOpen();\n this.#liveCloser?.close(\"restart\");\n\n const byRelay = groupAuthorsByOutboxRelay(\n this.#authors,\n this.#gossip,\n this.#discovery,\n this.#maxRelays,\n this.#pool.connectedUrls(),\n );\n\n const seen = new Set<string>();\n const closers: Array<{ close: (reason?: string) => void }> = [];\n const since = opts?.since ?? Math.floor(Date.now() / 1000) - 60;\n\n for (const [url, authors] of byRelay) {\n const filter: Filter = {\n authors,\n kinds: this.#kinds,\n since,\n };\n closers.push(\n this.#pool.subscribe([url], [filter], {\n signal: opts?.signal,\n onevent: (event) => {\n if (seen.has(event.id)) return;\n seen.add(event.id);\n this.#noteEvent(event);\n },\n }),\n );\n }\n\n const closer = {\n close: (reason?: string) => {\n for (const c of closers) c.close(reason);\n if (this.#liveCloser === closer) this.#liveCloser = undefined;\n },\n };\n this.#liveCloser = closer;\n return closer;\n }\n\n /** Convenience: hydrate + sync + startLive. */\n async start(opts?: {\n limit?: number;\n signal?: AbortSignal;\n }): Promise<{ close: (reason?: string) => void; events: Event[] }> {\n const events = await this.sync({ limit: opts?.limit, signal: opts?.signal });\n const live = this.startLive({ signal: opts?.signal });\n return { close: (r) => live.close(r), events };\n }\n\n close(): void {\n this.#closed = true;\n this.#liveCloser?.close(\"feed closed\");\n this.#liveCloser = undefined;\n }\n\n #assertOpen(): void {\n if (this.#closed) throw new OutboxError(\"OutboxFeed is closed\");\n }\n\n async #hydrateBoundsFromStore(): Promise<void> {\n await Promise.all(\n this.#authors.flatMap((pk) =>\n this.#kinds.map(async (kind) => {\n const key = boundKey(pk, kind);\n if (this.#bounds.has(key)) return;\n const bound = await this.#storage.getOutboxBound(pk, kind);\n if (!bound) return;\n await this.#storage.setOutboxBound(pk, kind, bound);\n this.#bounds.set(key, { oldest: bound.oldest, newest: bound.newest });\n }),\n ),\n );\n }\n\n #syncFilters(\n authors: string[],\n opts?: { limit?: number; since?: number; until?: number },\n ): Filter[] {\n const base: Filter = {\n kinds: this.#kinds,\n limit: opts?.limit ?? 50,\n ...(opts?.until !== undefined ? { until: opts.until } : {}),\n };\n\n if (opts?.since !== undefined) {\n return [{ ...base, authors, since: opts.since }];\n }\n\n const bounded: string[] = [];\n const unbounded: string[] = [];\n let minBoundedNewest: number | undefined;\n\n for (const pk of authors) {\n let newest: number | undefined;\n let complete = true;\n for (const kind of this.#kinds) {\n const b = this.#bounds.get(boundKey(pk, kind));\n if (!b) {\n complete = false;\n break;\n }\n if (newest === undefined || b.newest < newest) newest = b.newest;\n }\n if (complete && newest !== undefined) {\n bounded.push(pk);\n if (minBoundedNewest === undefined || newest < minBoundedNewest) {\n minBoundedNewest = newest;\n }\n } else {\n unbounded.push(pk);\n }\n }\n\n if (bounded.length === 0 || minBoundedNewest === undefined) {\n return [{ ...base, authors }];\n }\n if (unbounded.length === 0) {\n return [{ ...base, authors, since: Math.max(0, minBoundedNewest - 1) }];\n }\n return [\n { ...base, authors: bounded, since: Math.max(0, minBoundedNewest - 1) },\n { ...base, authors: unbounded },\n ];\n }\n\n #noteEvent(event: Event): void {\n this.#updateBounds(event);\n const bound = this.#bounds.get(boundKey(event.pubkey, event.kind));\n if (bound) {\n void this.#storage\n .setOutboxBound(event.pubkey, event.kind, {\n oldest: bound.oldest,\n newest: bound.newest,\n })\n .catch(() => {});\n }\n this.#observe?.(event);\n this.#onEvent?.(event);\n }\n\n async #persistBounds(keys: Iterable<string>): Promise<void> {\n const writes: Promise<void>[] = [];\n for (const key of keys) {\n const bound = this.#bounds.get(key);\n if (!bound) continue;\n const sep = key.lastIndexOf(\":\");\n writes.push(\n this.#storage.setOutboxBound(key.slice(0, sep), Number(key.slice(sep + 1)), {\n oldest: bound.oldest,\n newest: bound.newest,\n }),\n );\n }\n if (writes.length === 0) return;\n try {\n await Promise.all(writes);\n } catch (err) {\n throw toStorageError(err);\n }\n }\n\n #updateBounds(event: Event): void {\n const key = boundKey(event.pubkey, event.kind);\n const prev = this.#bounds.get(key);\n if (!prev) {\n this.#bounds.set(key, { oldest: event.created_at, newest: event.created_at });\n return;\n }\n if (event.created_at < prev.oldest) prev.oldest = event.created_at;\n if (event.created_at > prev.newest) prev.newest = event.created_at;\n }\n}\n\n/** Factory matching other loaders helpers. */\nexport function createOutboxFeed(opts: OutboxFeedOptions): OutboxFeed {\n return new OutboxFeed(opts);\n}\n","import type { Pool } from \"../relay/pool.ts\";\nimport { LoaderContext } from \"./context.ts\";\nimport { createEventLoader } from \"./event.ts\";\nimport { createListLoaders } from \"./lists.ts\";\nimport { createProfileLoader } from \"./profile.ts\";\nimport type { ReplaceableCache } from \"./cache.ts\";\n\nexport { DataLoader, LoaderError } from \"./dataloader.ts\";\nexport { ReplaceableCache } from \"./cache.ts\";\nexport { LoaderContext, type LoaderContextOptions } from \"./context.ts\";\nexport {\n createReplaceableLoader,\n type LoadStyle,\n type ReplaceableLoadResult,\n} from \"./replaceable.ts\";\nexport { createListLoaders, type ListResult, type ListLoaders, type MutedEntity } from \"./lists.ts\";\nexport {\n createProfileLoader,\n bareNostrUser,\n type NostrUser,\n type ProfileLoader,\n} from \"./profile.ts\";\nexport { createEventLoader, type EventLoader, type EventRef } from \"./event.ts\";\nexport {\n OutboxError,\n OutboxFeed,\n createOutboxFeed,\n groupAuthorsByOutboxRelay,\n type OutboxBound,\n type OutboxFeedOptions,\n} from \"./outbox.ts\";\n\nimport type { ListLoaders } from \"./lists.ts\";\nimport type { ProfileLoader } from \"./profile.ts\";\nimport type { EventLoader } from \"./event.ts\";\n\nexport type Loaders = {\n context: LoaderContext;\n follows: ListLoaders[\"follows\"];\n muteList: ListLoaders[\"muteList\"];\n relayList: ListLoaders[\"relayList\"];\n dmRelayList: ListLoaders[\"dmRelayList\"];\n profile: ProfileLoader[\"load\"];\n event: EventLoader[\"load\"];\n};\n\nexport type CreateLoadersOptions = {\n pool: Pool;\n relays: readonly string[];\n cache?: ReplaceableCache;\n staleAfterSec?: number;\n fetchTimeoutMs?: number;\n};\n\n/** Build an instance-scoped loader suite (no module globals). */\nexport function createLoaders(opts: CreateLoadersOptions): Loaders {\n const context = new LoaderContext(opts);\n const lists = createListLoaders(context);\n const profile = createProfileLoader(context);\n const event = createEventLoader(context);\n return {\n context,\n follows: (pubkey, o) => lists.follows(pubkey, o),\n muteList: (pubkey, o) => lists.muteList(pubkey, o),\n relayList: (pubkey, o) => lists.relayList(pubkey, o),\n dmRelayList: (pubkey, o) => lists.dmRelayList(pubkey, o),\n profile: (pubkey, o) => profile.load(pubkey, o),\n event: (ref) => event.load(ref),\n };\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"memory-BCmhZPek.mjs","names":["#deletion","#byId","#replaceable","#indexInsert","#indexRemove","#matchedEvents","#eachCandidate","#outboxBounds","#deriveOutboxBound","#byPubkey","#byKind","#byKindPubkey","#byEpTag"],"sources":["../src/storage/deletion.ts","../src/storage/put.ts","../src/storage/memory.ts"],"sourcesContent":["import type { Event } from \"../core/event.ts\";\nimport { Kind } from \"../core/kind.ts\";\nimport { eventAddress, parseEventAddress } from \"../core/tag.ts\";\nimport { isHex32 } from \"../core/util.ts\";\n\nexport type DeletionPlan = {\n /** Stored events validated as same-pubkey (not kind 5) and to be removed now. */\n removeIds: string[];\n /** Referenced ids not in the store; remember deletion pubkey until the event arrives. */\n pendingIds: Array<{ id: string; pubkey: string }>;\n /** Replaceable/addressable coordinates tombstoned up to `until` (deletion created_at). */\n coordinates: Array<{ key: string; until: number }>;\n};\n\n/**\n * NIP-09 application plan for a kind 5 event.\n *\n * - `e` tags: delete only when the target exists, is not itself kind 5, and shares pubkey.\n * - Unknown `e` ids are pending until the event arrives (client MUST validate pubkey).\n * - `a` tags: tombstone that coordinate up to this deletion's `created_at` when pubkey matches.\n * - Deleting a deletion request is a no-op.\n */\nexport function planDeletion(\n deletion: Pick<Event, \"pubkey\" | \"created_at\" | \"tags\">,\n getById: (id: string) => Pick<Event, \"id\" | \"pubkey\" | \"kind\"> | undefined,\n): DeletionPlan {\n const pubkey = deletion.pubkey.toLowerCase();\n const removeIds: string[] = [];\n const pendingIds: Array<{ id: string; pubkey: string }> = [];\n const coordinates: Array<{ key: string; until: number }> = [];\n const seenIds = new Set<string>();\n const seenCoords = new Set<string>();\n\n for (const tag of deletion.tags) {\n if (tag[0] === \"e\" && tag[1] && isHex32(tag[1])) {\n const id = tag[1].toLowerCase();\n if (seenIds.has(id)) continue;\n seenIds.add(id);\n const existing = getById(id);\n if (!existing) {\n pendingIds.push({ id, pubkey });\n continue;\n }\n if (existing.kind === Kind.EventDeletion) continue;\n if (existing.pubkey.toLowerCase() !== pubkey) continue;\n removeIds.push(id);\n continue;\n }\n\n if (tag[0] === \"a\" && tag[1]) {\n const coord = parseEventAddress(tag[1]);\n if (!coord || coord.pubkey !== pubkey) continue;\n const key = `${coord.kind}:${coord.pubkey}:${coord.identifier}`;\n if (seenCoords.has(key)) continue;\n seenCoords.add(key);\n coordinates.push({ key, until: deletion.created_at });\n }\n }\n\n return { removeIds, pendingIds, coordinates };\n}\n\n/** In-memory NIP-09 tombstones shared by MemoryEventStore and IndexedDbEventStore. */\nexport class DeletionState {\n readonly ids = new Set<string>();\n readonly pending = new Map<string, string>();\n readonly coordinates = new Map<string, number>();\n\n clear(): void {\n this.ids.clear();\n this.pending.clear();\n this.coordinates.clear();\n }\n\n covers(event: Pick<Event, \"id\" | \"pubkey\" | \"kind\" | \"created_at\" | \"tags\">): boolean {\n if (this.ids.has(event.id)) return true;\n if (event.kind === Kind.EventDeletion) return false;\n const pendingPk = this.pending.get(event.id);\n if (pendingPk && event.pubkey.toLowerCase() === pendingPk) return true;\n const addr = eventAddress(event);\n if (!addr) return false;\n const until = this.coordinates.get(addr);\n return until !== undefined && event.created_at <= until;\n }\n\n absorb(plan: DeletionPlan): void {\n for (const id of plan.removeIds) {\n this.ids.add(id);\n this.pending.delete(id);\n }\n for (const p of plan.pendingIds) {\n if (!this.ids.has(p.id)) this.pending.set(p.id, p.pubkey);\n }\n for (const c of plan.coordinates) {\n const prev = this.coordinates.get(c.key) ?? Number.NEGATIVE_INFINITY;\n this.coordinates.set(c.key, Math.max(prev, c.until));\n }\n }\n}\n\nexport function coordinateRemovals(\n coordinates: readonly { key: string; until: number }[],\n getCurrent: (key: string) => Pick<Event, \"id\" | \"created_at\"> | undefined,\n): string[] {\n const ids: string[] = [];\n for (const c of coordinates) {\n const current = getCurrent(c.key);\n if (current && current.created_at <= c.until) ids.push(current.id);\n }\n return ids;\n}\n","import type { Event } from \"../core/event.ts\";\nimport { isReplaceableWinner } from \"../core/event.ts\";\nimport { isEphemeralKind, Kind } from \"../core/kind.ts\";\nimport { eventAddress } from \"../core/tag.ts\";\nimport {\n coordinateRemovals,\n planDeletion,\n type DeletionPlan,\n type DeletionState,\n} from \"./deletion.ts\";\nimport type { PutResult } from \"./types.ts\";\n\nexport function normalizeEvent(event: Event): Event {\n const id = event.id.toLowerCase();\n const pubkey = event.pubkey.toLowerCase();\n if (id === event.id && pubkey === event.pubkey) return event;\n return { ...event, id, pubkey };\n}\n\nexport function outboxBoundKey(pubkey: string, kind: number): string {\n return `${pubkey.toLowerCase()}:${kind}`;\n}\n\nexport type PutDecision =\n | { action: \"skip\"; result: \"duplicate\" | \"ephemeral\" | \"rejected\"; event: Event }\n | { action: \"tombstone\"; result: \"duplicate\"; event: Event }\n | { action: \"delete\"; result: \"deleted\"; event: Event; plan: DeletionPlan; coordIds: string[] }\n | {\n action: \"insert\";\n result: \"accepted\" | \"replaced\";\n event: Event;\n address?: string;\n replaceId?: string;\n };\n\nexport type PutLookup = {\n deletion: DeletionState;\n getById: (\n id: string,\n ) => Pick<Event, \"id\" | \"pubkey\" | \"kind\" | \"created_at\" | \"tags\"> | undefined;\n getReplaceable: (address: string) => Pick<Event, \"id\" | \"created_at\"> | undefined;\n};\n\nexport function decidePut(raw: Event, lookup: PutLookup): PutDecision {\n const event = normalizeEvent(raw);\n if (lookup.deletion.ids.has(event.id) || lookup.getById(event.id)) {\n return { action: \"skip\", result: \"duplicate\", event };\n }\n if (event.kind === Kind.EventDeletion) {\n const plan = planDeletion(event, lookup.getById);\n const coordIds = coordinateRemovals(plan.coordinates, lookup.getReplaceable);\n return { action: \"delete\", result: \"deleted\", event, plan, coordIds };\n }\n if (lookup.deletion.covers(event)) return { action: \"tombstone\", result: \"duplicate\", event };\n if (isEphemeralKind(event.kind)) return { action: \"skip\", result: \"ephemeral\", event };\n const address = eventAddress(event);\n if (address) {\n const prev = lookup.getReplaceable(address);\n if (prev && !isReplaceableWinner(event, prev)) {\n return { action: \"skip\", result: \"rejected\", event };\n }\n return {\n action: \"insert\",\n result: prev ? \"replaced\" : \"accepted\",\n event,\n address,\n replaceId: prev?.id,\n };\n }\n return { action: \"insert\", result: \"accepted\", event };\n}\n\nexport function applyPutMemory(\n s: {\n deletion: DeletionState;\n indexInsert(event: Event): void;\n indexRemove(id: string): boolean;\n },\n d: PutDecision,\n): PutResult {\n switch (d.action) {\n case \"skip\":\n return d.result;\n case \"tombstone\":\n s.deletion.ids.add(d.event.id);\n s.deletion.pending.delete(d.event.id);\n return \"duplicate\";\n case \"delete\":\n s.deletion.pending.delete(d.event.id);\n s.deletion.absorb(d.plan);\n for (const id of d.plan.removeIds) s.indexRemove(id);\n for (const id of d.coordIds) {\n s.deletion.ids.add(id);\n s.indexRemove(id);\n }\n s.indexInsert(d.event);\n return \"deleted\";\n case \"insert\":\n if (d.replaceId) s.indexRemove(d.replaceId);\n s.indexInsert(d.event);\n return d.result;\n }\n}\n","import type { Event } from \"../core/event.ts\";\nimport { itemCompare, sortEvents } from \"../core/event.ts\";\nimport type { Filter } from \"../core/filter.ts\";\nimport { matchFilter } from \"../core/filter.ts\";\nimport { eventAddress } from \"../core/tag.ts\";\nimport { DeletionState } from \"./deletion.ts\";\nimport { applyPutMemory, decidePut, outboxBoundKey, type PutLookup } from \"./put.ts\";\nimport type { EventStore, NegentropyItem, OutboxBound, PutResult } from \"./types.ts\";\n\n/**\n * In-memory event store with NIP-01 replaceable / addressable / ephemeral\n * handling and NIP-09 deletion (kind 5) application.\n */\nexport class MemoryEventStore implements EventStore {\n #byId = new Map<string, Event>();\n #byPubkey = new Map<string, Set<string>>();\n #byKind = new Map<number, Set<string>>();\n #byKindPubkey = new Map<string, Set<string>>(); // `${kind}:${pubkey}` → ids\n #byEpTag = new Map<string, Set<string>>(); // `${\"e\"|\"p\"}:${value.toLowerCase()}` → ids\n #replaceable = new Map<string, string>(); // address -> event id\n #deletion = new DeletionState();\n #outboxBounds = new Map<string, OutboxBound>();\n\n async put(raw: Event): Promise<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 ? this.#byId.get(id) : undefined;\n return ev ? { id: ev.id, created_at: ev.created_at } : undefined;\n },\n };\n return applyPutMemory(\n {\n deletion: this.#deletion,\n indexInsert: (e) => this.#indexInsert(e),\n indexRemove: (id) => this.#indexRemove(id),\n },\n decidePut(raw, lookup),\n );\n }\n\n /** Sequential `put` in input order. No transaction: a throw leaves earlier events applied. */\n async putMany(events: readonly Event[]): Promise<PutResult[]> {\n const results: PutResult[] = [];\n for (const event of events) results.push(await this.put(event));\n return results;\n }\n\n async get(id: string): Promise<Event | undefined> {\n const key = id.toLowerCase();\n if (this.#deletion.ids.has(key)) return undefined;\n return this.#byId.get(key);\n }\n\n async query(filters: Filter[]): Promise<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)) continue;\n seen.add(event.id);\n events.push(event);\n }\n }\n sortEvents(events);\n return events;\n }\n\n async count(filters: Filter[]): Promise<number> {\n const seen = new Set<string>();\n for (const filter of filters) {\n for (const event of this.#matchedEvents(filter)) seen.add(event.id);\n }\n return seen.size;\n }\n\n async negentropyItems(filter: Filter): Promise<NegentropyItem[]> {\n if (filter.limit === 0) return [];\n const items: NegentropyItem[] = [];\n this.#eachCandidate(filter, (event) => {\n if (this.#deletion.covers(event)) return;\n if (!matchFilter(filter, event)) return;\n items.push({ id: event.id, created_at: event.created_at });\n });\n if (filter.limit !== undefined) {\n items.sort(queryItemOrder);\n if (items.length > filter.limit) items.length = filter.limit;\n }\n items.sort(itemCompare);\n return items;\n }\n\n async remove(ids: string[]): Promise<number> {\n let n = 0;\n for (const raw of ids) {\n const id = raw.toLowerCase();\n if (this.#indexRemove(id)) n += 1;\n this.#deletion.ids.add(id);\n this.#deletion.pending.delete(id);\n }\n return n;\n }\n\n async getOutboxBound(pubkey: string, kind: number): Promise<OutboxBound | undefined> {\n const persisted = this.#outboxBounds.get(outboxBoundKey(pubkey, kind));\n if (persisted) return { oldest: persisted.oldest, newest: persisted.newest };\n return this.#deriveOutboxBound(pubkey, kind);\n }\n\n async setOutboxBound(pubkey: string, kind: number, bound: OutboxBound): Promise<void> {\n this.#outboxBounds.set(outboxBoundKey(pubkey, kind), {\n oldest: bound.oldest,\n newest: bound.newest,\n });\n }\n\n async clear(): Promise<void> {\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.#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.pubkey.toLowerCase();\n addToSet(this.#byPubkey, pubkey, event.id);\n addToSet(this.#byKind, event.kind, event.id);\n addToSet(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) continue;\n addToSet(this.#byEpTag, `${tag[0]}:${tag[1].toLowerCase()}`, event.id);\n }\n const addr = eventAddress(event);\n if (addr) this.#replaceable.set(addr, event.id);\n }\n\n #indexRemove(id: string): boolean {\n const key = id.toLowerCase();\n const event = this.#byId.get(key);\n if (!event) return false;\n this.#byId.delete(key);\n const pubkey = event.pubkey.toLowerCase();\n removeFromSet(this.#byPubkey, pubkey, key);\n removeFromSet(this.#byKind, event.kind, key);\n removeFromSet(this.#byKindPubkey, `${event.kind}:${pubkey}`, key);\n for (const tag of event.tags) {\n if ((tag[0] !== \"e\" && tag[0] !== \"p\") || tag[1] === undefined) continue;\n removeFromSet(this.#byEpTag, `${tag[0]}:${tag[1].toLowerCase()}`, key);\n }\n const addr = eventAddress(event);\n if (addr && this.#replaceable.get(addr) === key) this.#replaceable.delete(addr);\n return true;\n }\n\n #matchedEvents(filter: Filter): Event[] {\n if (filter.limit === 0) return [];\n const matched: Event[] = [];\n this.#eachCandidate(filter, (event) => {\n if (this.#deletion.covers(event)) return;\n if (!matchFilter(filter, event)) return;\n matched.push(event);\n });\n sortEvents(matched);\n return filter.limit !== undefined ? matched.slice(0, filter.limit) : matched;\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) return undefined;\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)) continue;\n const event = this.#byId.get(id);\n if (!event || this.#deletion.covers(event)) continue;\n if (oldest === undefined || event.created_at < oldest) oldest = event.created_at;\n if (newest === undefined || event.created_at > newest) newest = event.created_at;\n }\n if (oldest === undefined || newest === undefined) return undefined;\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)) continue;\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) continue;\n for (const id of ids) {\n if (seen.has(id)) continue;\n seen.add(id);\n const event = this.#byId.get(id);\n if (event) visit(event);\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) continue;\n for (const id of byPk) {\n if (seen.has(id)) continue;\n seen.add(id);\n const event = this.#byId.get(id);\n if (event) visit(event);\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) continue;\n for (const id of byKind) {\n if (seen.has(id)) continue;\n seen.add(id);\n const event = this.#byId.get(id);\n if (event) visit(event);\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()) visit(event);\n }\n}\n\nfunction visitEpTagIds(\n byEpTag: Map<string, Set<string>>,\n byId: Map<string, Event>,\n name: \"e\" | \"p\",\n values: readonly string[] | undefined,\n seen: Set<string>,\n visit: (event: Event) => void,\n): void {\n if (values === undefined) return;\n for (const value of values) {\n const ids = byEpTag.get(`${name}:${value.toLowerCase()}`);\n if (!ids) continue;\n for (const id of ids) {\n if (seen.has(id)) continue;\n seen.add(id);\n const event = byId.get(id);\n if (event) visit(event);\n }\n }\n}\n\nfunction addToSet<K>(map: Map<K, Set<string>>, key: K, id: string): void {\n let set = map.get(key);\n if (!set) {\n set = new Set();\n map.set(key, set);\n }\n set.add(id);\n}\n\nfunction removeFromSet<K>(map: Map<K, Set<string>>, key: K, id: string): void {\n const set = map.get(key);\n if (!set) return;\n set.delete(id);\n if (set.size === 0) map.delete(key);\n}\n\nfunction queryItemOrder(a: NegentropyItem, b: NegentropyItem): number {\n if (a.created_at !== b.created_at) return b.created_at - a.created_at;\n return a.id.localeCompare(b.id);\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"message-C_Uj-Y-c.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 } from \"./util.ts\";\n\nexport type SubscriptionId = string;\n\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\nexport function createSubscriptionId(id?: string): SubscriptionId {\n if (id !== undefined) return assertSubscriptionId(id);\n const bytes = new Uint8Array(8);\n crypto.getRandomValues(bytes);\n return Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\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).\n * Merge sketches with `mergeCountHll`. 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.\n * Output is always lowercase 512 hex. Empty input is the zero sketch (identity).\n */\nexport function mergeCountHll(hexes: readonly 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 (let i = 0; i < HLL_BYTES; i++) {\n const b = bytes[i]!;\n if (b > merged[i]!) merged[i] = b;\n }\n }\n return bytesToHex(merged);\n}\n\nexport function encodeClientMessage(message: ClientMessage): string {\n return JSON.stringify(message);\n}\n\nexport function encodeRelayMessage(message: RelayMessage): string {\n return JSON.stringify(message);\n}\n\nexport function parseClientMessage(raw: string): ClientMessage {\n let data: unknown;\n try {\n data = JSON.parse(raw);\n } catch {\n throw new MessageError(\"client message is not valid JSON\");\n }\n if (!Array.isArray(data) || data.length < 1 || typeof data[0] !== \"string\") {\n throw new MessageError(\"client message must be a non-empty JSON array\");\n }\n const type = data[0];\n switch (type) {\n case \"EVENT\": {\n if (data.length !== 2 || !validateSignedEvent(data[1])) {\n throw new MessageError(\"invalid EVENT client message\");\n }\n return [\"EVENT\", data[1]];\n }\n case \"REQ\": {\n if (data.length < 3 || typeof data[1] !== \"string\") {\n throw new MessageError(\"invalid REQ client message\");\n }\n return [\"REQ\", data[1], ...(data.slice(2) as Filter[])];\n }\n case \"CLOSE\": {\n if (data.length !== 2 || typeof data[1] !== \"string\") {\n throw new MessageError(\"invalid CLOSE client message\");\n }\n return [\"CLOSE\", data[1]];\n }\n case \"AUTH\": {\n if (data.length !== 2 || !validateSignedEvent(data[1])) {\n throw new MessageError(\"invalid AUTH client message\");\n }\n return [\"AUTH\", data[1]];\n }\n case \"COUNT\": {\n if (data.length < 3 || typeof data[1] !== \"string\") {\n throw new MessageError(\"invalid COUNT client message\");\n }\n return [\"COUNT\", data[1], ...(data.slice(2) as Filter[])];\n }\n case \"NEG-OPEN\": {\n if (data.length === 5) {\n throw new MessageError(\"obsolete 5-element NEG-OPEN; expected [NEG-OPEN, id, filter, hex]\");\n }\n if (\n data.length !== 4 ||\n typeof data[1] !== \"string\" ||\n !isFilterObject(data[2]) ||\n !isNegHex(data[3])\n ) {\n throw new MessageError(\"invalid NEG-OPEN client message\");\n }\n return [\"NEG-OPEN\", data[1], data[2], data[3].toLowerCase()];\n }\n case \"NEG-MSG\": {\n if (data.length !== 3 || typeof data[1] !== \"string\" || !isNegHex(data[2])) {\n throw new MessageError(\"invalid NEG-MSG client message\");\n }\n return [\"NEG-MSG\", data[1], data[2].toLowerCase()];\n }\n case \"NEG-CLOSE\": {\n if (data.length !== 2 || typeof data[1] !== \"string\") {\n throw new MessageError(\"invalid NEG-CLOSE client message\");\n }\n return [\"NEG-CLOSE\", data[1]];\n }\n default:\n throw new MessageError(`unknown client message type: ${type}`);\n }\n}\n\nexport function parseRelayMessage(raw: string): RelayMessage {\n let data: unknown;\n try {\n data = JSON.parse(raw);\n } catch {\n throw new MessageError(\"relay message is not valid JSON\");\n }\n if (!Array.isArray(data) || data.length < 1 || typeof data[0] !== \"string\") {\n throw new MessageError(\"relay message must be a non-empty JSON array\");\n }\n const type = data[0];\n switch (type) {\n case \"EVENT\": {\n if (data.length !== 3 || typeof data[1] !== \"string\" || !validateSignedEvent(data[2])) {\n throw new MessageError(\"invalid EVENT relay message\");\n }\n return [\"EVENT\", data[1], data[2]];\n }\n case \"OK\": {\n if (\n data.length !== 4 ||\n typeof data[1] !== \"string\" ||\n typeof data[2] !== \"boolean\" ||\n typeof data[3] !== \"string\"\n ) {\n throw new MessageError(\"invalid OK relay message\");\n }\n return [\"OK\", data[1], data[2], data[3]];\n }\n case \"EOSE\": {\n if (data.length !== 2 || typeof data[1] !== \"string\") {\n throw new MessageError(\"invalid EOSE relay message\");\n }\n return [\"EOSE\", data[1]];\n }\n case \"CLOSED\": {\n if (data.length !== 3 || typeof data[1] !== \"string\" || typeof data[2] !== \"string\") {\n throw new MessageError(\"invalid CLOSED relay message\");\n }\n return [\"CLOSED\", data[1], data[2]];\n }\n case \"NOTICE\": {\n if (data.length !== 2 || typeof data[1] !== \"string\") {\n throw new MessageError(\"invalid NOTICE relay message\");\n }\n return [\"NOTICE\", data[1]];\n }\n case \"AUTH\": {\n if (data.length !== 2 || typeof data[1] !== \"string\") {\n throw new MessageError(\"invalid AUTH relay message\");\n }\n return [\"AUTH\", data[1]];\n }\n case \"COUNT\": {\n if (\n data.length !== 3 ||\n typeof data[1] !== \"string\" ||\n typeof data[2] !== \"object\" ||\n data[2] === null ||\n typeof (data[2] as { count?: unknown }).count !== \"number\"\n ) {\n throw new MessageError(\"invalid COUNT relay message\");\n }\n const payload = data[2] as { count: number; approximate?: unknown; hll?: unknown };\n const result: CountResult = { count: payload.count };\n if (typeof payload.approximate === \"boolean\") result.approximate = payload.approximate;\n const hll = parseCountHll(payload.hll);\n if (hll !== undefined) result.hll = hll;\n return [\"COUNT\", data[1], result];\n }\n case \"NEG-MSG\": {\n if (data.length !== 3 || typeof data[1] !== \"string\" || !isNegHex(data[2])) {\n throw new MessageError(\"invalid NEG-MSG relay message\");\n }\n return [\"NEG-MSG\", data[1], data[2].toLowerCase()];\n }\n case \"NEG-ERR\": {\n if (\n (data.length !== 3 && data.length !== 4) ||\n typeof data[1] !== \"string\" ||\n typeof data[2] !== \"string\"\n ) {\n throw new MessageError(\"invalid NEG-ERR relay message\");\n }\n return [\"NEG-ERR\", data[1], data[2]];\n }\n default:\n throw new MessageError(`unknown relay message type: ${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 isFilterObject(value: unknown): value is Filter {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\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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"}