@qntx/nostr 0.14.0 → 0.15.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.
- package/dist/{builder-CGgM_QHT.mjs → builder-70CC2EBq.mjs} +4 -4
- package/dist/{builder-CGgM_QHT.mjs.map → builder-70CC2EBq.mjs.map} +1 -1
- package/dist/{builder-C72nJITG.d.mts → builder-Dk00TdNf.d.mts} +2 -2
- package/dist/{client-COkEihj_.mjs → client-BXmvZ7vU.mjs} +45 -30
- package/dist/client-BXmvZ7vU.mjs.map +1 -0
- package/dist/client.d.mts +1 -1
- package/dist/client.mjs +1 -2
- package/dist/core.d.mts +8 -7
- package/dist/core.mjs +8 -8
- package/dist/{error-Dyb89ahb.mjs → error-CoP2myne.mjs} +2 -2
- package/dist/{error-Dyb89ahb.mjs.map → error-CoP2myne.mjs.map} +1 -1
- package/dist/{error-BPqAU5RB.d.mts → error-DE1UCZ2J.d.mts} +6 -2
- package/dist/{error-BjSUsx6M.d.mts → error-IHMcLy8-.d.mts} +2 -2
- package/dist/{error-QyECUDIp.mjs → error-VDn7Nv-6.mjs} +2 -2
- package/dist/{error-QyECUDIp.mjs.map → error-VDn7Nv-6.mjs.map} +1 -1
- package/dist/{event-DvM1f03b.mjs → event-DHtcm0jM.mjs} +17 -6
- package/dist/event-DHtcm0jM.mjs.map +1 -0
- package/dist/{filter-3jOh4uLN.mjs → filter-BbShkmc4.mjs} +34 -28
- package/dist/filter-BbShkmc4.mjs.map +1 -0
- package/dist/{filter-oYG2zjJ3.d.mts → filter-w1R-mU63.d.mts} +8 -6
- package/dist/{gossip-BvCItMVZ.d.mts → gossip-C-7WhJ7v.d.mts} +3 -3
- package/dist/{gossip-CRyhK6E6.mjs → gossip-CejMm_QM.mjs} +13 -11
- package/dist/gossip-CejMm_QM.mjs.map +1 -0
- package/dist/gossip.d.mts +1 -1
- package/dist/gossip.mjs +1 -2
- package/dist/{index-B2JlP2Jf.d.mts → index-11znTLM0.d.mts} +4 -4
- package/dist/{index-DzHmhHqD.d.mts → index-B7nMjbyt.d.mts} +3 -3
- package/dist/{index-muydfuYD.d.mts → index-BuNI8JH_.d.mts} +13 -13
- package/dist/{index-Dpj_MUxb.d.mts → index-C5QomO2D.d.mts} +10 -3
- package/dist/{index-DCobP2Jj.d.mts → index-DDZjqV48.d.mts} +10 -10
- package/dist/{index-CcgmG3F6.d.mts → index-DYk24u9o.d.mts} +12 -7
- package/dist/index.d.mts +21 -20
- package/dist/index.mjs +19 -19
- package/dist/{key-D_oEutQ6.mjs → key-Bt0ow7mI.mjs} +33 -11
- package/dist/key-Bt0ow7mI.mjs.map +1 -0
- package/dist/{key-DojW2QZl.d.mts → key-E0AJKMRB.d.mts} +19 -11
- package/dist/{loaders-BRh2riV9.mjs → loaders-COGGrZY4.mjs} +7 -7
- package/dist/loaders-COGGrZY4.mjs.map +1 -0
- package/dist/loaders.d.mts +2 -2
- package/dist/loaders.mjs +1 -2
- package/dist/{memory-D_GdEQYG.mjs → memory-Dq4dg-U4.mjs} +2 -2
- package/dist/{memory-D_GdEQYG.mjs.map → memory-Dq4dg-U4.mjs.map} +1 -1
- package/dist/{memory-index-3GSrPIWj.mjs → memory-index-DhlmbqWx.mjs} +19 -18
- package/dist/memory-index-DhlmbqWx.mjs.map +1 -0
- package/dist/{message-BMfreCQc.mjs → message-D6k16gtg.mjs} +20 -20
- package/dist/message-D6k16gtg.mjs.map +1 -0
- package/dist/{verifier-DhAbqVJF.d.mts → message-u_7KOq6Q.d.mts} +3 -22
- package/dist/{nip17-BZvFQLcs.d.mts → nip17--YwFSuph.d.mts} +4 -4
- package/dist/{nip19-D4RKn6GC.d.mts → nip19-qtyc5sCl.d.mts} +2 -2
- package/dist/{nip46-i8L8_nIm.d.mts → nip46-C_ZFQprM.d.mts} +2 -2
- package/dist/{nip51-C_kTYcud.d.mts → nip51-yi9w8FsM.d.mts} +2 -2
- package/dist/{nip59-DfJ6Moeu.d.mts → nip59-B6Ey8L9z.d.mts} +12 -3
- package/dist/{nip65-CPNE0SV0.d.mts → nip65-XrBjV9WM.d.mts} +2 -2
- package/dist/{nip77-DhpoL7d8.d.mts → nip77-BAXijIt_.d.mts} +2 -2
- package/dist/nips/blossom.d.mts +3 -2
- package/dist/nips/blossom.mjs +11 -11
- package/dist/nips/blossom.mjs.map +1 -1
- package/dist/nips/nip04.d.mts +3 -2
- package/dist/nips/nip04.mjs +10 -12
- package/dist/nips/nip04.mjs.map +1 -1
- package/dist/nips/nip05.d.mts +2 -2
- package/dist/nips/nip05.mjs +9 -8
- package/dist/nips/nip05.mjs.map +1 -1
- package/dist/nips/nip10.d.mts +3 -2
- package/dist/nips/nip10.mjs +19 -19
- package/dist/nips/nip10.mjs.map +1 -1
- package/dist/nips/nip11.d.mts +2 -1
- package/dist/nips/nip11.mjs +6 -4
- package/dist/nips/nip11.mjs.map +1 -1
- package/dist/nips/nip13.d.mts +1 -1
- package/dist/nips/nip13.mjs +2 -2
- package/dist/nips/nip13.mjs.map +1 -1
- package/dist/nips/nip17.d.mts +1 -1
- package/dist/nips/nip17.mjs +6 -6
- package/dist/nips/nip17.mjs.map +1 -1
- package/dist/nips/nip19.d.mts +1 -1
- package/dist/nips/nip19.mjs +1 -1
- package/dist/nips/nip21.d.mts +2 -2
- package/dist/nips/nip21.mjs +3 -3
- package/dist/nips/nip21.mjs.map +1 -1
- package/dist/nips/nip27.d.mts +3 -3
- package/dist/nips/nip27.mjs +308 -85
- package/dist/nips/nip27.mjs.map +1 -1
- package/dist/nips/nip42.mjs +1 -1
- package/dist/nips/nip44.d.mts +2 -1
- package/dist/nips/nip44.mjs +4 -4
- package/dist/nips/nip44.mjs.map +1 -1
- package/dist/nips/nip46.d.mts +1 -1
- package/dist/nips/nip46.mjs +9 -8
- package/dist/nips/nip46.mjs.map +1 -1
- package/dist/nips/nip49.d.mts +1 -1
- package/dist/nips/nip49.mjs +1 -1
- package/dist/nips/nip51.d.mts +1 -1
- package/dist/nips/nip51.mjs +14 -14
- package/dist/nips/nip51.mjs.map +1 -1
- package/dist/nips/nip57.mjs +10 -10
- package/dist/nips/nip57.mjs.map +1 -1
- package/dist/nips/nip59.d.mts +1 -1
- package/dist/nips/nip59.mjs +14 -11
- package/dist/nips/nip59.mjs.map +1 -1
- package/dist/nips/nip65.d.mts +1 -1
- package/dist/nips/nip65.mjs +2 -2
- package/dist/nips/nip77.d.mts +1 -1
- package/dist/nips/nip77.mjs +2 -2
- package/dist/nips/nip77.mjs.map +1 -1
- package/dist/nips/nip98.d.mts +1 -1
- package/dist/nips/nip98.mjs +5 -5
- package/dist/nips/nip98.mjs.map +1 -1
- package/dist/{pool-MehgIIzm.d.mts → pool-BMfTQZjM.d.mts} +5 -4
- package/dist/{pool-lMT_mvzn.mjs → pool-D13eVCqc.mjs} +5 -5
- package/dist/{pool-lMT_mvzn.mjs.map → pool-D13eVCqc.mjs.map} +1 -1
- package/dist/{put-BEM5j8xK.mjs → put-DuymazkT.mjs} +5 -5
- package/dist/put-DuymazkT.mjs.map +1 -0
- package/dist/{reactive-B7lPFgTS.d.mts → reactive-BkzOsSwU.d.mts} +3 -3
- package/dist/{reactive-DFHY-f00.mjs → reactive-C0-rTqN9.mjs} +10 -9
- package/dist/reactive-C0-rTqN9.mjs.map +1 -0
- package/dist/{relay-DIR77xQ2.mjs → relay-CbgToreY.mjs} +6 -6
- package/dist/{relay-DIR77xQ2.mjs.map → relay-CbgToreY.mjs.map} +1 -1
- package/dist/relay.d.mts +3 -3
- package/dist/relay.mjs +2 -2
- package/dist/{signer-Bha9gQy8.mjs → signer-DQzjm3pt.mjs} +28 -14
- package/dist/signer-DQzjm3pt.mjs.map +1 -0
- package/dist/signer.d.mts +2 -2
- package/dist/signer.mjs +2 -2
- package/dist/{storage-n2BE1bdY.mjs → storage-DvDAuxzC.mjs} +26 -41
- package/dist/storage-DvDAuxzC.mjs.map +1 -0
- package/dist/storage.d.mts +3 -3
- package/dist/storage.mjs +4 -4
- package/dist/store.d.mts +1 -1
- package/dist/store.mjs +1 -2
- package/dist/testing.d.mts +2 -2
- package/dist/testing.mjs +7 -7
- package/dist/{types-DiVVyf1e.d.mts → types-COupAqtN.d.mts} +2 -2
- package/dist/{types-BJ4wZ7JJ.d.mts → types-DF8yT6EG.d.mts} +7 -1
- package/dist/{util-DfsyJ9nz.mjs → util-BcRBO6o-.mjs} +18 -3
- package/dist/util-BcRBO6o-.mjs.map +1 -0
- package/dist/{verifier-DEHUy4AD.mjs → verifier-DBSPHzGg.mjs} +6 -15
- package/dist/verifier-DBSPHzGg.mjs.map +1 -0
- package/dist/verifier-Or2nDvHb.d.mts +16 -0
- package/package.json +3 -3
- package/dist/client-COkEihj_.mjs.map +0 -1
- package/dist/event-DvM1f03b.mjs.map +0 -1
- package/dist/filter-3jOh4uLN.mjs.map +0 -1
- package/dist/gossip-CRyhK6E6.mjs.map +0 -1
- package/dist/key-D_oEutQ6.mjs.map +0 -1
- package/dist/loaders-BRh2riV9.mjs.map +0 -1
- package/dist/memory-index-3GSrPIWj.mjs.map +0 -1
- package/dist/message-BMfreCQc.mjs.map +0 -1
- package/dist/put-BEM5j8xK.mjs.map +0 -1
- package/dist/reactive-DFHY-f00.mjs.map +0 -1
- package/dist/signer-Bha9gQy8.mjs.map +0 -1
- package/dist/storage-n2BE1bdY.mjs.map +0 -1
- package/dist/util-DfsyJ9nz.mjs.map +0 -1
- package/dist/verifier-DEHUy4AD.mjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pool-lMT_mvzn.mjs","names":[],"sources":["../src/relay/url.ts","../src/relay/fan-in.ts","../src/relay/pool.ts"],"sourcesContent":["import { normalizeURL } from \"../core/util.ts\";\n\n/**\n * {@link normalizeURL}, or the raw string when it is not a valid relay URL (ensureRelay then fails\n * it like a dead relay).\n */\nexport function relayUrlKey(url: string): string {\n try {\n return normalizeURL(url);\n } catch {\n return url;\n }\n}\n\n/** Unique relay keys in first-seen order. */\nexport function uniqueRelayUrls(urls: Iterable<string>): string[] {\n const seen = new Set<string>();\n const unique: string[] = [];\n for (const url of urls) {\n const key = relayUrlKey(url);\n if (seen.has(key)) {\n continue;\n }\n seen.add(key);\n unique.push(key);\n }\n return unique;\n}\n\n/**\n * True for `ws:` / `http:` URLs that are not `.onion`. Local-network `ws://` is still insecure;\n * allow those via a trusted set.\n */\nexport function isInsecureRelayUrl(url: string): boolean {\n try {\n const parsed = new URL(url);\n if (parsed.hostname.endsWith(\".onion\")) {\n return false;\n }\n return parsed.protocol === \"ws:\" || parsed.protocol === \"http:\";\n } catch {\n return false;\n }\n}\n","import { abortReason, onAbort, throwIfAborted } from \"../core/abort.ts\";\nimport type { Event } from \"../core/event.ts\";\nimport type { Filter } from \"../core/filter.ts\";\nimport { invokeSafely } from \"../core/report.ts\";\nimport type { Pool } from \"./pool.ts\";\nimport type { Relay } from \"./relay.ts\";\nimport type { Closer } from \"./subscription.ts\";\nimport { uniqueRelayUrls } from \"./url.ts\";\n\nexport type RoutedJob = {\n urls: ReadonlyArray<string>;\n filters: ReadonlyArray<Filter>;\n id?: string | undefined;\n};\n\nexport type FanInOptions = {\n /** First receipt only (deduped across jobs). `relayUrl` is the normalized relay URL. */\n onEvent?: ((event: Event, relayUrl: string) => void) | undefined;\n onEose?: (() => void) | undefined;\n onClose?: ((reason: string) => void) | undefined;\n signal?: AbortSignal | undefined;\n /** Armed at fan-in only. Never forwarded to relay.subscribe. */\n eoseTimeoutMs?: number | undefined;\n alreadyHaveEvent?: ((id: string) => boolean) | undefined;\n /** Every receipt from every relay, including skipped duplicates. */\n onEventSeen?: ((id: string, relayUrl: string) => void) | undefined;\n closeOnEose?: boolean | undefined;\n /** Optional. ensureRelay uses Pool.#opts.connectTimeoutMs when omitted. */\n connectTimeoutMs?: number | undefined;\n};\n\n/**\n * Aggregate EOSE/close across routed jobs. One `seen` set. `pending` counts URL list entries\n * (duplicates included). `pendingEose` counts unique `jobIndex:url` keys.\n */\nexport function fanIn(pool: Pool, jobs: ReadonlyArray<RoutedJob>, opts: FanInOptions = {}): Closer {\n const seen = new Set<string>();\n const closers: Closer[] = [];\n let closed = false;\n let eoseFired = false;\n let sawEose = false;\n let eoseTimer: ReturnType<typeof setTimeout> | undefined;\n const eoseDone = new Set<string>();\n const eoseAttempted = new Set<string>();\n let pendingEose = 0;\n let pending = 0;\n /** Close reason of the last attached subscription that ended; drives the aggregate reason. */\n let lastReason: string | undefined;\n\n const fireEose = () => {\n if (closed || eoseFired) {\n return;\n }\n eoseFired = true;\n if (eoseTimer !== undefined) {\n clearTimeout(eoseTimer);\n eoseTimer = undefined;\n }\n invokeSafely(() => opts.onEose?.());\n };\n\n /**\n * Remove one relay from the pending-EOSE set. `eosed` credits a real EOSE; a CLOSED or a connect\n * failure only drops the wait — the aggregate `onEose` fires once every remaining relay ended AND\n * at least one actually EOSE'd.\n */\n const settleEose = (jobIndex: number, url: string, eosed: boolean) => {\n const key = `${jobIndex}:${url}`;\n if (eoseDone.has(key)) {\n return;\n }\n eoseDone.add(key);\n if (eosed) {\n sawEose = true;\n }\n pendingEose -= 1;\n if (pendingEose === 0 && sawEose) {\n fireEose();\n }\n };\n\n const settleClose = () => {\n closed = true;\n disposeAbort();\n if (eoseTimer !== undefined) {\n clearTimeout(eoseTimer);\n eoseTimer = undefined;\n }\n };\n\n const closeAll = (reason?: string) => {\n if (closed) {\n return;\n }\n settleClose();\n for (const c of closers) {\n c.close(reason);\n }\n invokeSafely(() => opts.onClose?.(reason ?? \"closed by client\"));\n };\n\n if (opts.signal?.aborted === true) {\n closed = true;\n invokeSafely(() => opts.onClose?.(\"aborted\"));\n return { close: closeAll };\n }\n\n const disposeAbort = onAbort(opts.signal, () => closeAll(\"aborted\"));\n\n /**\n * Single completion rule for both endings: `pending` reaches zero when every URL entry either\n * failed to attach or its subscription ended — at that point no attached closer can still be\n * live. The reason is the last subscription's close reason, or \"all relays failed\" when no relay\n * ever attached.\n */\n const finish = (): void => {\n if (pending <= 0 && !closed) {\n settleClose();\n invokeSafely(() => opts.onClose?.(lastReason ?? \"all relays failed\"));\n }\n };\n\n const failUrl = (jobIndex: number, key: string): void => {\n settleEose(jobIndex, key, false);\n pending -= 1;\n finish();\n };\n\n const attach = (relay: Relay, job: RoutedJob, jobIndex: number): void => {\n if (closed) {\n return;\n }\n const received = opts.onEventSeen;\n const sub = relay.subscribe([...job.filters], {\n id: jobs.length === 1 ? job.id : undefined,\n closeOnEose: opts.closeOnEose,\n alreadyHaveEvent: (id) => {\n let have = seen.has(id);\n invokeSafely(() => {\n have = have || Boolean(opts.alreadyHaveEvent?.(id));\n });\n return have;\n },\n onEventSeen:\n received === undefined ? undefined : (id) => invokeSafely(() => received(id, relay.url)),\n onEvent: (event) => {\n seen.add(event.id);\n invokeSafely(() => opts.onEvent?.(event, relay.url));\n },\n onEose: () => settleEose(jobIndex, relay.url, true),\n onClose: (reason) => {\n settleEose(jobIndex, relay.url, false);\n lastReason = reason;\n pending -= 1;\n finish();\n },\n });\n closers.push(sub);\n };\n\n for (const [jobIndex, job] of jobs.entries()) {\n // Equivalent spellings of one relay URL attach exactly once per job.\n for (const key of uniqueRelayUrls(job.urls)) {\n pending += 1;\n const eoseKey = `${jobIndex}:${key}`;\n if (!eoseAttempted.has(eoseKey)) {\n eoseAttempted.add(eoseKey);\n pendingEose += 1;\n }\n\n const tryAttach = (relay: Relay): void => {\n try {\n attach(relay, job, jobIndex);\n } catch {\n // Any attach failure retires this URL: RelayClosedError is the expected\n // dropped-between-connect-and-subscribe race; anything else must not escape the\n // `void` wrapper as an unhandled rejection, so it fails the URL as well.\n failUrl(jobIndex, key);\n }\n };\n\n void (async (): Promise<void> => {\n let relay: Relay;\n try {\n relay = await pool.ensureRelay(key, {\n signal: opts.signal,\n timeoutMs: opts.connectTimeoutMs,\n });\n } catch {\n // ensureRelay rejected only — not tryAttach throws (those must not look like connect failure).\n const existing = pool.getRelay(key);\n if (existing !== undefined) {\n tryAttach(existing);\n return;\n }\n failUrl(jobIndex, key);\n return;\n }\n tryAttach(relay);\n })();\n }\n }\n\n if (opts.eoseTimeoutMs !== undefined && pending > 0) {\n eoseTimer = setTimeout(() => {\n eoseTimer = undefined;\n fireEose();\n }, opts.eoseTimeoutMs);\n }\n\n if (pending === 0) {\n queueMicrotask(() => {\n if (closed) {\n return;\n }\n settleClose();\n invokeSafely(() => opts.onClose?.(\"no relays\"));\n });\n }\n\n return { close: closeAll };\n}\n\nexport async function fetchRouted(\n pool: Pool,\n jobs: ReadonlyArray<RoutedJob>,\n opts: {\n timeoutMs?: number | undefined;\n signal?: AbortSignal | undefined;\n connectTimeoutMs?: number | undefined;\n /** Every event of every relay batch, including cross-relay duplicates. */\n onEvent?: ((event: Event, relayUrl: string) => void) | undefined;\n } = {},\n): Promise<Event[]> {\n throwIfAborted(opts.signal);\n const byId = new Map<string, Event>();\n await Promise.all(\n jobs.flatMap((job) => {\n return uniqueRelayUrls(job.urls).map(async (url) => {\n let batch: ReadonlyArray<Event>;\n let relayUrl: string;\n try {\n const relay = await pool.ensureRelay(url, {\n signal: opts.signal,\n timeoutMs: opts.connectTimeoutMs,\n });\n relayUrl = relay.url;\n const result = await relay.fetch([...job.filters], {\n timeoutMs: opts.timeoutMs,\n signal: opts.signal,\n });\n batch = result.events;\n } catch {\n // An abort rejects the whole call; per-relay failures are skipped.\n if (opts.signal?.aborted === true) {\n throw abortReason(opts.signal);\n }\n return;\n }\n // The whole batch lands before callbacks so a throwing onEvent cannot\n // drop events; listener errors are reported, never propagated.\n for (const event of batch) {\n if (!byId.has(event.id)) {\n byId.set(event.id, event);\n }\n }\n for (const event of batch) {\n invokeSafely(() => opts.onEvent?.(event, relayUrl));\n }\n });\n }),\n );\n return [...byId.values()];\n}\n","import { abortReason, throwIfAborted } from \"../core/abort.ts\";\nimport { Emitter } from \"../core/emitter.ts\";\nimport { MessageError, toError } from \"../core/error.ts\";\nimport type { Event, EventTemplate } from \"../core/event.ts\";\nimport { canonicalizeFilters } from \"../core/filter.ts\";\nimport type { Filter } from \"../core/filter.ts\";\nimport { assertSubscriptionId } from \"../core/message.ts\";\nimport type { CountResult } from \"../core/message.ts\";\nimport { normalizeURL } from \"../core/util.ts\";\nimport { RelayConnectionError, RelayPublishError, RelaySuspendedError } from \"./error.ts\";\nimport { fanIn, fetchRouted } from \"./fan-in.ts\";\nimport { Relay, RelayStatus } from \"./relay.ts\";\nimport type { RelayFetchEnd, RelayOptions, SubscribeOptions } from \"./relay.ts\";\nimport type { Closer } from \"./subscription.ts\";\nimport { isInsecureRelayUrl, uniqueRelayUrls } from \"./url.ts\";\n\n/** Per-relay tolerance for events that fail id/signature verification. */\nexport type InvalidEventPolicy = {\n /** Invalid events tolerated within `windowMs`; exceeding it suspends the relay. */\n limit: number;\n windowMs: number;\n /** How long a suspended relay stays closed before it may reconnect. */\n cooldownMs: number;\n};\n\n/** Pool-wide options applied to every managed relay. */\nexport type PoolOptions = Omit<RelayOptions, \"authSigner\"> & {\n /**\n * When set, automatically answer NIP-42 AUTH challenges for relays that send them. Return\n * undefined to skip a given relay URL.\n */\n automaticallyAuth?:\n | ((relayURL: string) => ((event: EventTemplate) => Promise<Event>) | undefined)\n | undefined;\n /** When false (default), ensureRelay rejects isInsecureRelayUrl unless trusted. */\n allowInsecure?: boolean | undefined;\n trustedInsecureUrls?: ReadonlyArray<string> | undefined;\n /** Close unused relays. Unset/0 = disabled. */\n idleTimeoutMs?: number | undefined;\n idleCleanupIntervalMs?: number | undefined;\n /**\n * Soft cap on connected non-pinned relays. When `ensureRelay` would create a new non-pinned relay\n * at the cap, the least-recently-used idle one is closed first (idle = no subscriptions and no\n * in-flight requests). If none is idle the connect proceeds anyway — the cap never fails a\n * request.\n */\n maxRelays?: number | undefined;\n /** Normalized URLs never closed by idle cleanup or `maxRelays` eviction. */\n pinnedUrls?: ReadonlyArray<string> | undefined;\n /**\n * When set, a relay whose `limit`-th-plus-one EVENT fails id/signature verification inside a\n * `windowMs` sliding window is disconnected and suspended for `cooldownMs`. During suspension\n * `ensureRelay` rejects with {@link RelaySuspendedError}; once it lifts, live subscriptions resume\n * through the normal reconnect path. Unset = events are dropped without counting, as before.\n */\n invalidEventPolicy?: InvalidEventPolicy | undefined;\n};\n\n/** Typed event payloads for {@link Pool.on}. */\nexport type PoolEventMap = {\n /** Normalized URLs closed by an idle-cleanup pass. */\n idle: ReadonlyArray<string>;\n /**\n * A relay suspended for exceeding `invalidEventPolicy`: `url` is the normalized relay URL and\n * `until` the epoch-ms time the suspension lifts.\n */\n suspend: { readonly url: string; readonly until: number };\n};\n\n/** Per-relay publish outcome: the relay's OK verdict or a transport-level failure. */\nexport type PoolPublishResult =\n | { readonly url: string; readonly status: \"ok\"; readonly message: string }\n | { readonly url: string; readonly status: \"rejected\"; readonly message: string }\n | { readonly url: string; readonly status: \"failed\"; readonly error: Error };\n\n/** Multi-relay subscribe options: callbacks also receive the normalized relay URL. */\nexport type PoolSubscribeOptions = Omit<SubscribeOptions, \"onEvent\" | \"onEventSeen\"> & {\n onEvent?: ((event: Event, relayUrl: string) => void) | undefined;\n onEventSeen?: ((id: string, relayUrl: string) => void) | undefined;\n};\n\n/** Per-relay one-shot fetch outcome: collected events plus how its REQ ended. */\nexport type PoolFetchResult = {\n readonly url: string;\n readonly events: ReadonlyArray<Event>;\n readonly end: RelayFetchEnd | { readonly type: \"failed\"; readonly error: Error };\n};\n\n/** Per-relay NIP-45 COUNT result (or a transport-level failure). */\nexport type PoolCountResult =\n | ({ readonly url: string; readonly status: \"ok\" } & CountResult)\n | { readonly url: string; readonly status: \"failed\"; readonly error: Error };\n\nconst countOk = (url: string, payload: CountResult): PoolCountResult => ({\n url,\n status: \"ok\",\n ...payload,\n});\n\nfunction isIdle(relay: Relay): boolean {\n return (\n relay.status !== RelayStatus.Connecting &&\n relay.subscriptionCount === 0 &&\n relay.inFlightCount === 0\n );\n}\n\n/** Multi-relay coordinator: connection reuse, cross-relay event dedup, fan-out publish. */\nexport class Pool {\n readonly #relays = new Map<string, Relay>();\n readonly #relayOptions: Omit<RelayOptions, \"authSigner\">;\n readonly #automaticallyAuth: PoolOptions[\"automaticallyAuth\"];\n readonly #maxRelays: number | undefined;\n readonly #invalidEventPolicy: InvalidEventPolicy | undefined;\n readonly #events = new Emitter<PoolEventMap>();\n readonly #lastActivity = new Map<string, number>();\n readonly #idleTimeoutMs: number;\n #idleTimer: ReturnType<typeof setInterval> | undefined;\n #allowInsecure: boolean;\n #trustedInsecure: Set<string>;\n #pinned: Set<string>;\n /** Per-relay timestamps of verification failures inside the sliding window. */\n readonly #invalidEvents = new Map<string, number[]>();\n /** Normalized URL → epoch ms when its suspension lifts. */\n readonly #suspendedUntil = new Map<string, number>();\n readonly #resumeTimers = new Map<string, ReturnType<typeof setTimeout>>();\n\n constructor(opts: PoolOptions = {}) {\n const {\n automaticallyAuth,\n allowInsecure,\n trustedInsecureUrls,\n idleTimeoutMs,\n idleCleanupIntervalMs,\n maxRelays,\n pinnedUrls,\n invalidEventPolicy,\n ...relayOptions\n } = opts;\n this.#relayOptions = relayOptions;\n this.#automaticallyAuth = automaticallyAuth;\n this.#maxRelays = maxRelays;\n this.#invalidEventPolicy = invalidEventPolicy;\n this.#allowInsecure = allowInsecure ?? false;\n this.#trustedInsecure = new Set((trustedInsecureUrls ?? []).map(normalizeURL));\n this.#pinned = new Set((pinnedUrls ?? []).map(normalizeURL));\n this.#idleTimeoutMs = idleTimeoutMs ?? 0;\n if (this.#idleTimeoutMs > 0) {\n this.#idleTimer = setInterval(() => this.cleanIdleRelays(), idleCleanupIntervalMs ?? 30_000);\n }\n }\n\n /**\n * Listen for a pool event. Multiple listeners per type are allowed and fire in registration\n * order; a throwing listener is reported and does not break the dispatch. Returns an unsubscribe\n * function.\n */\n on<K extends keyof PoolEventMap>(\n type: K,\n listener: (payload: PoolEventMap[K]) => void,\n ): () => void {\n return this.#events.on(type, listener);\n }\n\n setAllowInsecure(allow: boolean): void {\n this.#allowInsecure = allow;\n }\n\n setTrustedInsecureUrls(urls: ReadonlyArray<string>): void {\n this.#trustedInsecure = new Set(urls.map(normalizeURL));\n }\n\n setPinnedUrls(urls: ReadonlyArray<string>): void {\n this.#pinned = new Set(urls.map(normalizeURL));\n }\n\n /**\n * Drop cached AUTH rejections and re-fire pending challenges on every pooled relay, so a later\n * `setSigner` takes effect without reconnect.\n */\n resetAuth(): void {\n for (const relay of this.#relays.values()) {\n relay.resetAuth();\n }\n }\n\n cleanIdleRelays(): void {\n if (this.#idleTimeoutMs <= 0) {\n return;\n }\n const now = Date.now();\n const idle: string[] = [];\n for (const [url, relay] of this.#relays) {\n if (this.#pinned.has(url) || !isIdle(relay)) {\n continue;\n }\n const last = this.#lastActivity.get(url) ?? 0;\n if (!relay.connected || now - last >= this.#idleTimeoutMs) {\n idle.push(url);\n }\n }\n for (const url of this.#lastActivity.keys()) {\n if (!this.#relays.has(url)) {\n this.#lastActivity.delete(url);\n }\n }\n if (idle.length === 0) {\n return;\n }\n this.closeRelays(idle);\n this.#events.emit(\"idle\", idle);\n }\n\n #touch(url: string): void {\n this.#lastActivity.set(url, Date.now());\n }\n\n /**\n * Count one verification failure for `norm`; on the `limit + 1`-th inside `windowMs` suspend the\n * relay: drop its connection (subscriptions kept), record `until`, and emit `suspend`.\n */\n #noteInvalidEvent(norm: string): void {\n const policy = this.#invalidEventPolicy;\n if (policy === undefined) {\n return;\n }\n const now = Date.now();\n const window = (this.#invalidEvents.get(norm) ?? []).filter((at) => now - at < policy.windowMs);\n window.push(now);\n if (window.length <= policy.limit) {\n this.#invalidEvents.set(norm, window);\n return;\n }\n this.#invalidEvents.delete(norm);\n if (this.#suspendedUntil.has(norm)) {\n return;\n }\n const until = now + policy.cooldownMs;\n this.#suspendedUntil.set(norm, until);\n this.#relays.get(norm)?.disconnect();\n const resume = setTimeout(() => {\n this.#resumeTimers.delete(norm);\n this.#suspendedUntil.delete(norm);\n const relay = this.#relays.get(norm);\n if (relay === undefined || relay.connected || relay.subscriptionCount === 0) {\n return;\n }\n void (async (): Promise<void> => {\n try {\n await relay.connect();\n } catch {\n // With enableReconnect the relay's own backoff retries; otherwise the\n // next ensureRelay attempt reconnects on demand.\n }\n })();\n }, policy.cooldownMs);\n this.#resumeTimers.set(norm, resume);\n this.#events.emit(\"suspend\", { url: norm, until });\n }\n\n #rejectInsecure(url: string, norm: string): void {\n if (this.#allowInsecure) {\n return;\n }\n if (!isInsecureRelayUrl(url)) {\n return;\n }\n if (this.#trustedInsecure.has(norm)) {\n return;\n }\n throw new RelayConnectionError(\"insecure relay connection blocked\", norm);\n }\n\n #stopIdleCleanup(): void {\n if (this.#idleTimer === undefined) {\n return;\n }\n clearInterval(this.#idleTimer);\n this.#idleTimer = undefined;\n }\n\n /**\n * Soft cap: closing the least-recently-used idle non-pinned relay when a new non-pinned relay\n * would push the count past `maxRelays`. Busy relays are never evicted; with none idle the cap is\n * exceeded rather than failing.\n */\n #enforceMaxRelays(incoming: string): void {\n const cap = this.#maxRelays;\n if (cap === undefined || this.#pinned.has(incoming)) {\n return;\n }\n let count = 0;\n let oldestUrl: string | undefined;\n let oldestAt = Number.POSITIVE_INFINITY;\n for (const [url, relay] of this.#relays) {\n if (this.#pinned.has(url)) {\n continue;\n }\n count += 1;\n if (!isIdle(relay)) {\n continue;\n }\n const at = this.#lastActivity.get(url) ?? 0;\n if (at < oldestAt) {\n oldestAt = at;\n oldestUrl = url;\n }\n }\n if (count >= cap && oldestUrl !== undefined) {\n this.closeRelays([oldestUrl]);\n }\n }\n\n async ensureRelay(\n url: string,\n opts?: { signal?: AbortSignal | undefined; timeoutMs?: number | undefined },\n ): Promise<Relay> {\n const norm = normalizeURL(url);\n this.#rejectInsecure(url, norm);\n const suspendedUntil = this.#suspendedUntil.get(norm);\n if (suspendedUntil !== undefined) {\n if (Date.now() < suspendedUntil) {\n throw new RelaySuspendedError(norm, suspendedUntil);\n }\n this.#suspendedUntil.delete(norm);\n }\n let relay = this.#relays.get(norm);\n if (!relay) {\n this.#enforceMaxRelays(norm);\n const signFn = this.#automaticallyAuth?.(norm) ?? undefined;\n const created = new Relay(norm, { ...this.#relayOptions, authSigner: signFn });\n // Only drop from the pool on terminal close (reconnect keeps the entry). Guard on\n // identity: a stale relay's delayed close must not delete a newer relay's entry.\n created.on(\"close\", () => {\n if (this.#relays.get(norm) === created) {\n this.#relays.delete(norm);\n this.#lastActivity.delete(norm);\n }\n });\n created.on(\"invalidevent\", () => {\n this.#noteInvalidEvent(norm);\n });\n if (signFn) {\n created.on(\"auth\", () => {\n void (async (): Promise<void> => {\n try {\n await created.auth(signFn);\n } catch {\n // auth failure surfaces on subsequent CLOSED/OK; avoid unhandled rejection\n }\n })();\n });\n }\n this.#relays.set(norm, created);\n relay = created;\n }\n this.#touch(norm);\n if (!relay.connected) {\n try {\n await relay.connect({\n signal: opts?.signal,\n timeoutMs: opts?.timeoutMs ?? this.#relayOptions.connectTimeoutMs,\n });\n } catch (error) {\n // Keep the entry when reconnect is enabled so open subscriptions can recover.\n if (this.#relayOptions.enableReconnect !== true) {\n this.#relays.delete(norm);\n this.#lastActivity.delete(norm);\n }\n throw error;\n }\n }\n return relay;\n }\n\n /** Close every pooled relay and clear all pool state. */\n close(): void {\n this.#stopIdleCleanup();\n for (const timer of this.#resumeTimers.values()) {\n clearTimeout(timer);\n }\n this.#resumeTimers.clear();\n this.#suspendedUntil.clear();\n this.#invalidEvents.clear();\n for (const relay of this.#relays.values()) {\n relay.close();\n }\n this.#relays.clear();\n this.#lastActivity.clear();\n }\n\n /** Close and forget the listed relays; other pooled relays are untouched. */\n closeRelays(urls: ReadonlyArray<string>): void {\n for (const url of urls) {\n const norm = normalizeURL(url);\n const timer = this.#resumeTimers.get(norm);\n if (timer !== undefined) {\n clearTimeout(timer);\n this.#resumeTimers.delete(norm);\n }\n this.#relays.get(norm)?.close();\n this.#relays.delete(norm);\n this.#lastActivity.delete(norm);\n }\n }\n\n /**\n * Subscribe across relays. Deduplicates by event id. Returns a closer; callbacks receive every\n * new event once.\n */\n subscribe(\n relays: ReadonlyArray<string>,\n filters: ReadonlyArray<Filter>,\n opts: PoolSubscribeOptions = {},\n ): Closer {\n if (filters.length === 0) {\n throw new MessageError(\"REQ requires at least one filter\");\n }\n if (opts.id !== undefined) {\n assertSubscriptionId(opts.id);\n }\n const canonical = canonicalizeFilters(filters);\n return fanIn(this, [{ urls: relays, filters: canonical, id: opts.id }], {\n onEvent: opts.onEvent,\n onEose: opts.onEose,\n onClose: opts.onClose,\n signal: opts.signal,\n eoseTimeoutMs: opts.eoseTimeoutMs,\n alreadyHaveEvent: opts.alreadyHaveEvent,\n onEventSeen: opts.onEventSeen,\n closeOnEose: opts.closeOnEose,\n connectTimeoutMs: this.#relayOptions.connectTimeoutMs,\n });\n }\n\n /**\n * One-shot fetch per relay: each entry reports its own events and end reason (`eose`, `closed`,\n * `timeout`), or `failed` when the connect or REQ itself errored. An abort rejects the whole\n * call. Events are not deduped across relays — use {@link fetch} for that.\n */\n async fetchEach(\n relays: ReadonlyArray<string>,\n filters: ReadonlyArray<Filter>,\n opts?: { timeoutMs?: number | undefined; signal?: AbortSignal | undefined },\n ): Promise<PoolFetchResult[]> {\n if (filters.length === 0) {\n throw new MessageError(\"REQ requires at least one filter\");\n }\n throwIfAborted(opts?.signal);\n const canonical = canonicalizeFilters(filters);\n return Promise.all(\n uniqueRelayUrls(relays).map(async (url): Promise<PoolFetchResult> => {\n try {\n const relay = await this.ensureRelay(url, {\n signal: opts?.signal,\n timeoutMs: this.#relayOptions.connectTimeoutMs,\n });\n this.#touch(relay.url);\n const result = await relay.fetch([...canonical], {\n timeoutMs: opts?.timeoutMs,\n signal: opts?.signal,\n });\n return { url, events: result.events, end: result.end };\n } catch (error) {\n // An abort rejects the whole call; per-relay failures are reported.\n if (opts?.signal?.aborted === true) {\n throw abortReason(opts.signal);\n }\n return {\n url,\n events: [],\n end: { type: \"failed\", error: toError(error) },\n };\n }\n }),\n );\n }\n\n /** Fetch events until each connected relay EOSE or timeout; dedupe by id. */\n async fetch(\n relays: ReadonlyArray<string>,\n filters: ReadonlyArray<Filter>,\n opts?: {\n timeoutMs?: number | undefined;\n signal?: AbortSignal | undefined;\n /** Every event of every relay batch, including cross-relay duplicates. */\n onEvent?: ((event: Event, relayUrl: string) => void) | undefined;\n },\n ): Promise<Event[]> {\n if (filters.length === 0) {\n throw new MessageError(\"REQ requires at least one filter\");\n }\n return fetchRouted(this, [{ urls: relays, filters: canonicalizeFilters(filters) }], {\n timeoutMs: opts?.timeoutMs,\n signal: opts?.signal,\n connectTimeoutMs: this.#relayOptions.connectTimeoutMs,\n onEvent: opts?.onEvent,\n });\n }\n\n /** Publish to all listed relays; returns per-relay outcomes. */\n async publish(\n relays: ReadonlyArray<string>,\n event: Event,\n opts?: { timeoutMs?: number | undefined },\n ): Promise<PoolPublishResult[]> {\n return Promise.all(\n uniqueRelayUrls(relays).map(async (url): Promise<PoolPublishResult> => {\n try {\n const relay = await this.ensureRelay(url, {\n timeoutMs: this.#relayOptions.connectTimeoutMs,\n });\n this.#touch(relay.url);\n const result = await relay.publish(event, { timeoutMs: opts?.timeoutMs });\n return result.ok\n ? { url, status: \"ok\", message: result.message }\n : { url, status: \"rejected\", message: result.message };\n } catch (error) {\n return { url, status: \"failed\", error: toError(error) };\n }\n }),\n );\n }\n\n /** First successful publish (Promise.any semantics). */\n async publishAny(\n relays: ReadonlyArray<string>,\n event: Event,\n opts?: { timeoutMs?: number | undefined },\n ): Promise<Extract<PoolPublishResult, { status: \"ok\" }>> {\n return Promise.any(\n uniqueRelayUrls(relays).map(async (url) => {\n const relay = await this.ensureRelay(url, {\n timeoutMs: this.#relayOptions.connectTimeoutMs,\n });\n this.#touch(relay.url);\n const result = await relay.publish(event, { timeoutMs: opts?.timeoutMs });\n if (!result.ok) {\n throw new RelayPublishError(result.message || \"rejected\", relay.url);\n }\n return { url, status: \"ok\" as const, message: result.message };\n }),\n );\n }\n\n /**\n * NIP-45 COUNT across relays. Per-relay outcomes; failures do not throw. Counts are not summed —\n * each relay reports independently (may overlap).\n */\n async count(\n relays: ReadonlyArray<string>,\n filters: ReadonlyArray<Filter>,\n opts?: { timeoutMs?: number | undefined; signal?: AbortSignal | undefined },\n ): Promise<PoolCountResult[]> {\n throwIfAborted(opts?.signal);\n const canonical = canonicalizeFilters(filters);\n return Promise.all(\n uniqueRelayUrls(relays).map(async (url): Promise<PoolCountResult> => {\n try {\n const relay = await this.ensureRelay(url, {\n signal: opts?.signal,\n timeoutMs: this.#relayOptions.connectTimeoutMs,\n });\n this.#touch(relay.url);\n const payload: CountResult = await relay.count(canonical, {\n timeoutMs: opts?.timeoutMs,\n signal: opts?.signal,\n });\n return countOk(url, payload);\n } catch (error) {\n // An abort rejects the whole call; per-relay failures are reported.\n if (opts?.signal?.aborted === true) {\n throw abortReason(opts.signal);\n }\n return { url, status: \"failed\", error: toError(error) };\n }\n }),\n );\n }\n\n listRelays(): string[] {\n return [...this.#relays.keys()];\n }\n\n /** Lookup a pooled relay, including disconnected reconnecting entries. */\n getRelay(url: string): Relay | undefined {\n try {\n return this.#relays.get(normalizeURL(url));\n } catch {\n return this.#relays.get(url);\n }\n }\n\n /** Currently connected URLs. Unlike listRelays(), excludes reconnecting/disconnected entries. */\n connectedUrls(): string[] {\n const urls: string[] = [];\n for (const [url, relay] of this.#relays) {\n if (relay.connected) {\n urls.push(url);\n }\n }\n return urls;\n }\n}\n"],"mappings":";;;;;;;;;;;;AAMA,SAAgB,YAAY,KAAqB;CAC/C,IAAI;EACF,OAAO,aAAa,GAAG;CACzB,QAAQ;EACN,OAAO;CACT;AACF;;AAGA,SAAgB,gBAAgB,MAAkC;CAChE,MAAM,uBAAO,IAAI,IAAY;CAC7B,MAAM,SAAmB,CAAC;CAC1B,KAAK,MAAM,OAAO,MAAM;EACtB,MAAM,MAAM,YAAY,GAAG;EAC3B,IAAI,KAAK,IAAI,GAAG,GACd;EAEF,KAAK,IAAI,GAAG;EACZ,OAAO,KAAK,GAAG;CACjB;CACA,OAAO;AACT;;;;;AAMA,SAAgB,mBAAmB,KAAsB;CACvD,IAAI;EACF,MAAM,SAAS,IAAI,IAAI,GAAG;EAC1B,IAAI,OAAO,SAAS,SAAS,QAAQ,GACnC,OAAO;EAET,OAAO,OAAO,aAAa,SAAS,OAAO,aAAa;CAC1D,QAAQ;EACN,OAAO;CACT;AACF;;;;;;;ACRA,SAAgB,MAAM,MAAY,MAAgC,OAAqB,CAAC,GAAW;CACjG,MAAM,uBAAO,IAAI,IAAY;CAC7B,MAAM,UAAoB,CAAC;CAC3B,IAAI,SAAS;CACb,IAAI,YAAY;CAChB,IAAI,UAAU;CACd,IAAI;CACJ,MAAM,2BAAW,IAAI,IAAY;CACjC,MAAM,gCAAgB,IAAI,IAAY;CACtC,IAAI,cAAc;CAClB,IAAI,UAAU;;CAEd,IAAI;CAEJ,MAAM,iBAAiB;EACrB,IAAI,UAAU,WACZ;EAEF,YAAY;EACZ,IAAI,cAAc,KAAA,GAAW;GAC3B,aAAa,SAAS;GACtB,YAAY,KAAA;EACd;EACA,mBAAmB,KAAK,SAAS,CAAC;CACpC;;;;;;CAOA,MAAM,cAAc,UAAkB,KAAa,UAAmB;EACpE,MAAM,MAAM,GAAG,SAAS,GAAG;EAC3B,IAAI,SAAS,IAAI,GAAG,GAClB;EAEF,SAAS,IAAI,GAAG;EAChB,IAAI,OACF,UAAU;EAEZ,eAAe;EACf,IAAI,gBAAgB,KAAK,SACvB,SAAS;CAEb;CAEA,MAAM,oBAAoB;EACxB,SAAS;EACT,aAAa;EACb,IAAI,cAAc,KAAA,GAAW;GAC3B,aAAa,SAAS;GACtB,YAAY,KAAA;EACd;CACF;CAEA,MAAM,YAAY,WAAoB;EACpC,IAAI,QACF;EAEF,YAAY;EACZ,KAAK,MAAM,KAAK,SACd,EAAE,MAAM,MAAM;EAEhB,mBAAmB,KAAK,UAAU,UAAU,kBAAkB,CAAC;CACjE;CAEA,IAAI,KAAK,QAAQ,YAAY,MAAM;EACjC,SAAS;EACT,mBAAmB,KAAK,UAAU,SAAS,CAAC;EAC5C,OAAO,EAAE,OAAO,SAAS;CAC3B;CAEA,MAAM,eAAe,QAAQ,KAAK,cAAc,SAAS,SAAS,CAAC;;;;;;;CAQnE,MAAM,eAAqB;EACzB,IAAI,WAAW,KAAK,CAAC,QAAQ;GAC3B,YAAY;GACZ,mBAAmB,KAAK,UAAU,cAAc,mBAAmB,CAAC;EACtE;CACF;CAEA,MAAM,WAAW,UAAkB,QAAsB;EACvD,WAAW,UAAU,KAAK,KAAK;EAC/B,WAAW;EACX,OAAO;CACT;CAEA,MAAM,UAAU,OAAc,KAAgB,aAA2B;EACvE,IAAI,QACF;EAEF,MAAM,WAAW,KAAK;EACtB,MAAM,MAAM,MAAM,UAAU,CAAC,GAAG,IAAI,OAAO,GAAG;GAC5C,IAAI,KAAK,WAAW,IAAI,IAAI,KAAK,KAAA;GACjC,aAAa,KAAK;GAClB,mBAAmB,OAAO;IACxB,IAAI,OAAO,KAAK,IAAI,EAAE;IACtB,mBAAmB;KACjB,OAAO,QAAQ,QAAQ,KAAK,mBAAmB,EAAE,CAAC;IACpD,CAAC;IACD,OAAO;GACT;GACA,aACE,aAAa,KAAA,IAAY,KAAA,KAAa,OAAO,mBAAmB,SAAS,IAAI,MAAM,GAAG,CAAC;GACzF,UAAU,UAAU;IAClB,KAAK,IAAI,MAAM,EAAE;IACjB,mBAAmB,KAAK,UAAU,OAAO,MAAM,GAAG,CAAC;GACrD;GACA,cAAc,WAAW,UAAU,MAAM,KAAK,IAAI;GAClD,UAAU,WAAW;IACnB,WAAW,UAAU,MAAM,KAAK,KAAK;IACrC,aAAa;IACb,WAAW;IACX,OAAO;GACT;EACF,CAAC;EACD,QAAQ,KAAK,GAAG;CAClB;CAEA,KAAK,MAAM,CAAC,UAAU,QAAQ,KAAK,QAAQ,GAEzC,KAAK,MAAM,OAAO,gBAAgB,IAAI,IAAI,GAAG;EAC3C,WAAW;EACX,MAAM,UAAU,GAAG,SAAS,GAAG;EAC/B,IAAI,CAAC,cAAc,IAAI,OAAO,GAAG;GAC/B,cAAc,IAAI,OAAO;GACzB,eAAe;EACjB;EAEA,MAAM,aAAa,UAAuB;GACxC,IAAI;IACF,OAAO,OAAO,KAAK,QAAQ;GAC7B,QAAQ;IAIN,QAAQ,UAAU,GAAG;GACvB;EACF;EAEA,CAAM,YAA2B;GAC/B,IAAI;GACJ,IAAI;IACF,QAAQ,MAAM,KAAK,YAAY,KAAK;KAClC,QAAQ,KAAK;KACb,WAAW,KAAK;IAClB,CAAC;GACH,QAAQ;IAEN,MAAM,WAAW,KAAK,SAAS,GAAG;IAClC,IAAI,aAAa,KAAA,GAAW;KAC1B,UAAU,QAAQ;KAClB;IACF;IACA,QAAQ,UAAU,GAAG;IACrB;GACF;GACA,UAAU,KAAK;EACjB,EAAA,CAAG;CACL;CAGF,IAAI,KAAK,kBAAkB,KAAA,KAAa,UAAU,GAChD,YAAY,iBAAiB;EAC3B,YAAY,KAAA;EACZ,SAAS;CACX,GAAG,KAAK,aAAa;CAGvB,IAAI,YAAY,GACd,qBAAqB;EACnB,IAAI,QACF;EAEF,YAAY;EACZ,mBAAmB,KAAK,UAAU,WAAW,CAAC;CAChD,CAAC;CAGH,OAAO,EAAE,OAAO,SAAS;AAC3B;AAEA,eAAsB,YACpB,MACA,MACA,OAMI,CAAC,GACa;CAClB,eAAe,KAAK,MAAM;CAC1B,MAAM,uBAAO,IAAI,IAAmB;CACpC,MAAM,QAAQ,IACZ,KAAK,SAAS,QAAQ;EACpB,OAAO,gBAAgB,IAAI,IAAI,CAAC,CAAC,IAAI,OAAO,QAAQ;GAClD,IAAI;GACJ,IAAI;GACJ,IAAI;IACF,MAAM,QAAQ,MAAM,KAAK,YAAY,KAAK;KACxC,QAAQ,KAAK;KACb,WAAW,KAAK;IAClB,CAAC;IACD,WAAW,MAAM;IAKjB,SAAQ,MAJa,MAAM,MAAM,CAAC,GAAG,IAAI,OAAO,GAAG;KACjD,WAAW,KAAK;KAChB,QAAQ,KAAK;IACf,CAAC,EAAA,CACc;GACjB,QAAQ;IAEN,IAAI,KAAK,QAAQ,YAAY,MAC3B,MAAM,YAAY,KAAK,MAAM;IAE/B;GACF;GAGA,KAAK,MAAM,SAAS,OAClB,IAAI,CAAC,KAAK,IAAI,MAAM,EAAE,GACpB,KAAK,IAAI,MAAM,IAAI,KAAK;GAG5B,KAAK,MAAM,SAAS,OAClB,mBAAmB,KAAK,UAAU,OAAO,QAAQ,CAAC;EAEtD,CAAC;CACH,CAAC,CACH;CACA,OAAO,CAAC,GAAG,KAAK,OAAO,CAAC;AAC1B;;;ACpLA,MAAM,WAAW,KAAa,aAA2C;CACvE;CACA,QAAQ;CACR,GAAG;AACL;AAEA,SAAS,OAAO,OAAuB;CACrC,OACE,MAAM,WAAW,YAAY,cAC7B,MAAM,sBAAsB,KAC5B,MAAM,kBAAkB;AAE5B;;AAGA,IAAa,OAAb,MAAkB;CAChB,0BAAmB,IAAI,IAAmB;CAC1C;CACA;CACA;CACA;CACA,UAAmB,IAAI,QAAsB;CAC7C,gCAAyB,IAAI,IAAoB;CACjD;CACA;CACA;CACA;CACA;;CAEA,iCAA0B,IAAI,IAAsB;;CAEpD,kCAA2B,IAAI,IAAoB;CACnD,gCAAyB,IAAI,IAA2C;CAExE,YAAY,OAAoB,CAAC,GAAG;EAClC,MAAM,EACJ,mBACA,eACA,qBACA,eACA,uBACA,WACA,YACA,oBACA,GAAG,iBACD;EACJ,KAAK,gBAAgB;EACrB,KAAK,qBAAqB;EAC1B,KAAK,aAAa;EAClB,KAAK,sBAAsB;EAC3B,KAAK,iBAAiB,iBAAiB;EACvC,KAAK,mBAAmB,IAAI,KAAK,uBAAuB,CAAC,EAAA,CAAG,IAAI,YAAY,CAAC;EAC7E,KAAK,UAAU,IAAI,KAAK,cAAc,CAAC,EAAA,CAAG,IAAI,YAAY,CAAC;EAC3D,KAAK,iBAAiB,iBAAiB;EACvC,IAAI,KAAK,iBAAiB,GACxB,KAAK,aAAa,kBAAkB,KAAK,gBAAgB,GAAG,yBAAyB,GAAM;CAE/F;;;;;;CAOA,GACE,MACA,UACY;EACZ,OAAO,KAAK,QAAQ,GAAG,MAAM,QAAQ;CACvC;CAEA,iBAAiB,OAAsB;EACrC,KAAK,iBAAiB;CACxB;CAEA,uBAAuB,MAAmC;EACxD,KAAK,mBAAmB,IAAI,IAAI,KAAK,IAAI,YAAY,CAAC;CACxD;CAEA,cAAc,MAAmC;EAC/C,KAAK,UAAU,IAAI,IAAI,KAAK,IAAI,YAAY,CAAC;CAC/C;;;;;CAMA,YAAkB;EAChB,KAAK,MAAM,SAAS,KAAK,QAAQ,OAAO,GACtC,MAAM,UAAU;CAEpB;CAEA,kBAAwB;EACtB,IAAI,KAAK,kBAAkB,GACzB;EAEF,MAAM,MAAM,KAAK,IAAI;EACrB,MAAM,OAAiB,CAAC;EACxB,KAAK,MAAM,CAAC,KAAK,UAAU,KAAK,SAAS;GACvC,IAAI,KAAK,QAAQ,IAAI,GAAG,KAAK,CAAC,OAAO,KAAK,GACxC;GAEF,MAAM,OAAO,KAAK,cAAc,IAAI,GAAG,KAAK;GAC5C,IAAI,CAAC,MAAM,aAAa,MAAM,QAAQ,KAAK,gBACzC,KAAK,KAAK,GAAG;EAEjB;EACA,KAAK,MAAM,OAAO,KAAK,cAAc,KAAK,GACxC,IAAI,CAAC,KAAK,QAAQ,IAAI,GAAG,GACvB,KAAK,cAAc,OAAO,GAAG;EAGjC,IAAI,KAAK,WAAW,GAClB;EAEF,KAAK,YAAY,IAAI;EACrB,KAAK,QAAQ,KAAK,QAAQ,IAAI;CAChC;CAEA,OAAO,KAAmB;EACxB,KAAK,cAAc,IAAI,KAAK,KAAK,IAAI,CAAC;CACxC;;;;;CAMA,kBAAkB,MAAoB;EACpC,MAAM,SAAS,KAAK;EACpB,IAAI,WAAW,KAAA,GACb;EAEF,MAAM,MAAM,KAAK,IAAI;EACrB,MAAM,UAAU,KAAK,eAAe,IAAI,IAAI,KAAK,CAAC,EAAA,CAAG,QAAQ,OAAO,MAAM,KAAK,OAAO,QAAQ;EAC9F,OAAO,KAAK,GAAG;EACf,IAAI,OAAO,UAAU,OAAO,OAAO;GACjC,KAAK,eAAe,IAAI,MAAM,MAAM;GACpC;EACF;EACA,KAAK,eAAe,OAAO,IAAI;EAC/B,IAAI,KAAK,gBAAgB,IAAI,IAAI,GAC/B;EAEF,MAAM,QAAQ,MAAM,OAAO;EAC3B,KAAK,gBAAgB,IAAI,MAAM,KAAK;EACpC,KAAK,QAAQ,IAAI,IAAI,CAAC,EAAE,WAAW;EACnC,MAAM,SAAS,iBAAiB;GAC9B,KAAK,cAAc,OAAO,IAAI;GAC9B,KAAK,gBAAgB,OAAO,IAAI;GAChC,MAAM,QAAQ,KAAK,QAAQ,IAAI,IAAI;GACnC,IAAI,UAAU,KAAA,KAAa,MAAM,aAAa,MAAM,sBAAsB,GACxE;GAEF,CAAM,YAA2B;IAC/B,IAAI;KACF,MAAM,MAAM,QAAQ;IACtB,QAAQ,CAGR;GACF,EAAA,CAAG;EACL,GAAG,OAAO,UAAU;EACpB,KAAK,cAAc,IAAI,MAAM,MAAM;EACnC,KAAK,QAAQ,KAAK,WAAW;GAAE,KAAK;GAAM;EAAM,CAAC;CACnD;CAEA,gBAAgB,KAAa,MAAoB;EAC/C,IAAI,KAAK,gBACP;EAEF,IAAI,CAAC,mBAAmB,GAAG,GACzB;EAEF,IAAI,KAAK,iBAAiB,IAAI,IAAI,GAChC;EAEF,MAAM,IAAI,qBAAqB,qCAAqC,IAAI;CAC1E;CAEA,mBAAyB;EACvB,IAAI,KAAK,eAAe,KAAA,GACtB;EAEF,cAAc,KAAK,UAAU;EAC7B,KAAK,aAAa,KAAA;CACpB;;;;;;CAOA,kBAAkB,UAAwB;EACxC,MAAM,MAAM,KAAK;EACjB,IAAI,QAAQ,KAAA,KAAa,KAAK,QAAQ,IAAI,QAAQ,GAChD;EAEF,IAAI,QAAQ;EACZ,IAAI;EACJ,IAAI,WAAW,OAAO;EACtB,KAAK,MAAM,CAAC,KAAK,UAAU,KAAK,SAAS;GACvC,IAAI,KAAK,QAAQ,IAAI,GAAG,GACtB;GAEF,SAAS;GACT,IAAI,CAAC,OAAO,KAAK,GACf;GAEF,MAAM,KAAK,KAAK,cAAc,IAAI,GAAG,KAAK;GAC1C,IAAI,KAAK,UAAU;IACjB,WAAW;IACX,YAAY;GACd;EACF;EACA,IAAI,SAAS,OAAO,cAAc,KAAA,GAChC,KAAK,YAAY,CAAC,SAAS,CAAC;CAEhC;CAEA,MAAM,YACJ,KACA,MACgB;EAChB,MAAM,OAAO,aAAa,GAAG;EAC7B,KAAK,gBAAgB,KAAK,IAAI;EAC9B,MAAM,iBAAiB,KAAK,gBAAgB,IAAI,IAAI;EACpD,IAAI,mBAAmB,KAAA,GAAW;GAChC,IAAI,KAAK,IAAI,IAAI,gBACf,MAAM,IAAI,oBAAoB,MAAM,cAAc;GAEpD,KAAK,gBAAgB,OAAO,IAAI;EAClC;EACA,IAAI,QAAQ,KAAK,QAAQ,IAAI,IAAI;EACjC,IAAI,CAAC,OAAO;GACV,KAAK,kBAAkB,IAAI;GAC3B,MAAM,SAAS,KAAK,qBAAqB,IAAI,KAAK,KAAA;GAClD,MAAM,UAAU,IAAI,MAAM,MAAM;IAAE,GAAG,KAAK;IAAe,YAAY;GAAO,CAAC;GAG7E,QAAQ,GAAG,eAAe;IACxB,IAAI,KAAK,QAAQ,IAAI,IAAI,MAAM,SAAS;KACtC,KAAK,QAAQ,OAAO,IAAI;KACxB,KAAK,cAAc,OAAO,IAAI;IAChC;GACF,CAAC;GACD,QAAQ,GAAG,sBAAsB;IAC/B,KAAK,kBAAkB,IAAI;GAC7B,CAAC;GACD,IAAI,QACF,QAAQ,GAAG,cAAc;IACvB,CAAM,YAA2B;KAC/B,IAAI;MACF,MAAM,QAAQ,KAAK,MAAM;KAC3B,QAAQ,CAER;IACF,EAAA,CAAG;GACL,CAAC;GAEH,KAAK,QAAQ,IAAI,MAAM,OAAO;GAC9B,QAAQ;EACV;EACA,KAAK,OAAO,IAAI;EAChB,IAAI,CAAC,MAAM,WACT,IAAI;GACF,MAAM,MAAM,QAAQ;IAClB,QAAQ,MAAM;IACd,WAAW,MAAM,aAAa,KAAK,cAAc;GACnD,CAAC;EACH,SAAS,OAAO;GAEd,IAAI,KAAK,cAAc,oBAAoB,MAAM;IAC/C,KAAK,QAAQ,OAAO,IAAI;IACxB,KAAK,cAAc,OAAO,IAAI;GAChC;GACA,MAAM;EACR;EAEF,OAAO;CACT;;CAGA,QAAc;EACZ,KAAK,iBAAiB;EACtB,KAAK,MAAM,SAAS,KAAK,cAAc,OAAO,GAC5C,aAAa,KAAK;EAEpB,KAAK,cAAc,MAAM;EACzB,KAAK,gBAAgB,MAAM;EAC3B,KAAK,eAAe,MAAM;EAC1B,KAAK,MAAM,SAAS,KAAK,QAAQ,OAAO,GACtC,MAAM,MAAM;EAEd,KAAK,QAAQ,MAAM;EACnB,KAAK,cAAc,MAAM;CAC3B;;CAGA,YAAY,MAAmC;EAC7C,KAAK,MAAM,OAAO,MAAM;GACtB,MAAM,OAAO,aAAa,GAAG;GAC7B,MAAM,QAAQ,KAAK,cAAc,IAAI,IAAI;GACzC,IAAI,UAAU,KAAA,GAAW;IACvB,aAAa,KAAK;IAClB,KAAK,cAAc,OAAO,IAAI;GAChC;GACA,KAAK,QAAQ,IAAI,IAAI,CAAC,EAAE,MAAM;GAC9B,KAAK,QAAQ,OAAO,IAAI;GACxB,KAAK,cAAc,OAAO,IAAI;EAChC;CACF;;;;;CAMA,UACE,QACA,SACA,OAA6B,CAAC,GACtB;EACR,IAAI,QAAQ,WAAW,GACrB,MAAM,IAAI,aAAa,kCAAkC;EAE3D,IAAI,KAAK,OAAO,KAAA,GACd,qBAAqB,KAAK,EAAE;EAE9B,MAAM,YAAY,oBAAoB,OAAO;EAC7C,OAAO,MAAM,MAAM,CAAC;GAAE,MAAM;GAAQ,SAAS;GAAW,IAAI,KAAK;EAAG,CAAC,GAAG;GACtE,SAAS,KAAK;GACd,QAAQ,KAAK;GACb,SAAS,KAAK;GACd,QAAQ,KAAK;GACb,eAAe,KAAK;GACpB,kBAAkB,KAAK;GACvB,aAAa,KAAK;GAClB,aAAa,KAAK;GAClB,kBAAkB,KAAK,cAAc;EACvC,CAAC;CACH;;;;;;CAOA,MAAM,UACJ,QACA,SACA,MAC4B;EAC5B,IAAI,QAAQ,WAAW,GACrB,MAAM,IAAI,aAAa,kCAAkC;EAE3D,eAAe,MAAM,MAAM;EAC3B,MAAM,YAAY,oBAAoB,OAAO;EAC7C,OAAO,QAAQ,IACb,gBAAgB,MAAM,CAAC,CAAC,IAAI,OAAO,QAAkC;GACnE,IAAI;IACF,MAAM,QAAQ,MAAM,KAAK,YAAY,KAAK;KACxC,QAAQ,MAAM;KACd,WAAW,KAAK,cAAc;IAChC,CAAC;IACD,KAAK,OAAO,MAAM,GAAG;IACrB,MAAM,SAAS,MAAM,MAAM,MAAM,CAAC,GAAG,SAAS,GAAG;KAC/C,WAAW,MAAM;KACjB,QAAQ,MAAM;IAChB,CAAC;IACD,OAAO;KAAE;KAAK,QAAQ,OAAO;KAAQ,KAAK,OAAO;IAAI;GACvD,SAAS,OAAO;IAEd,IAAI,MAAM,QAAQ,YAAY,MAC5B,MAAM,YAAY,KAAK,MAAM;IAE/B,OAAO;KACL;KACA,QAAQ,CAAC;KACT,KAAK;MAAE,MAAM;MAAU,OAAO,QAAQ,KAAK;KAAE;IAC/C;GACF;EACF,CAAC,CACH;CACF;;CAGA,MAAM,MACJ,QACA,SACA,MAMkB;EAClB,IAAI,QAAQ,WAAW,GACrB,MAAM,IAAI,aAAa,kCAAkC;EAE3D,OAAO,YAAY,MAAM,CAAC;GAAE,MAAM;GAAQ,SAAS,oBAAoB,OAAO;EAAE,CAAC,GAAG;GAClF,WAAW,MAAM;GACjB,QAAQ,MAAM;GACd,kBAAkB,KAAK,cAAc;GACrC,SAAS,MAAM;EACjB,CAAC;CACH;;CAGA,MAAM,QACJ,QACA,OACA,MAC8B;EAC9B,OAAO,QAAQ,IACb,gBAAgB,MAAM,CAAC,CAAC,IAAI,OAAO,QAAoC;GACrE,IAAI;IACF,MAAM,QAAQ,MAAM,KAAK,YAAY,KAAK,EACxC,WAAW,KAAK,cAAc,iBAChC,CAAC;IACD,KAAK,OAAO,MAAM,GAAG;IACrB,MAAM,SAAS,MAAM,MAAM,QAAQ,OAAO,EAAE,WAAW,MAAM,UAAU,CAAC;IACxE,OAAO,OAAO,KACV;KAAE;KAAK,QAAQ;KAAM,SAAS,OAAO;IAAQ,IAC7C;KAAE;KAAK,QAAQ;KAAY,SAAS,OAAO;IAAQ;GACzD,SAAS,OAAO;IACd,OAAO;KAAE;KAAK,QAAQ;KAAU,OAAO,QAAQ,KAAK;IAAE;GACxD;EACF,CAAC,CACH;CACF;;CAGA,MAAM,WACJ,QACA,OACA,MACuD;EACvD,OAAO,QAAQ,IACb,gBAAgB,MAAM,CAAC,CAAC,IAAI,OAAO,QAAQ;GACzC,MAAM,QAAQ,MAAM,KAAK,YAAY,KAAK,EACxC,WAAW,KAAK,cAAc,iBAChC,CAAC;GACD,KAAK,OAAO,MAAM,GAAG;GACrB,MAAM,SAAS,MAAM,MAAM,QAAQ,OAAO,EAAE,WAAW,MAAM,UAAU,CAAC;GACxE,IAAI,CAAC,OAAO,IACV,MAAM,IAAI,kBAAkB,OAAO,WAAW,YAAY,MAAM,GAAG;GAErE,OAAO;IAAE;IAAK,QAAQ;IAAe,SAAS,OAAO;GAAQ;EAC/D,CAAC,CACH;CACF;;;;;CAMA,MAAM,MACJ,QACA,SACA,MAC4B;EAC5B,eAAe,MAAM,MAAM;EAC3B,MAAM,YAAY,oBAAoB,OAAO;EAC7C,OAAO,QAAQ,IACb,gBAAgB,MAAM,CAAC,CAAC,IAAI,OAAO,QAAkC;GACnE,IAAI;IACF,MAAM,QAAQ,MAAM,KAAK,YAAY,KAAK;KACxC,QAAQ,MAAM;KACd,WAAW,KAAK,cAAc;IAChC,CAAC;IACD,KAAK,OAAO,MAAM,GAAG;IACrB,MAAM,UAAuB,MAAM,MAAM,MAAM,WAAW;KACxD,WAAW,MAAM;KACjB,QAAQ,MAAM;IAChB,CAAC;IACD,OAAO,QAAQ,KAAK,OAAO;GAC7B,SAAS,OAAO;IAEd,IAAI,MAAM,QAAQ,YAAY,MAC5B,MAAM,YAAY,KAAK,MAAM;IAE/B,OAAO;KAAE;KAAK,QAAQ;KAAU,OAAO,QAAQ,KAAK;IAAE;GACxD;EACF,CAAC,CACH;CACF;CAEA,aAAuB;EACrB,OAAO,CAAC,GAAG,KAAK,QAAQ,KAAK,CAAC;CAChC;;CAGA,SAAS,KAAgC;EACvC,IAAI;GACF,OAAO,KAAK,QAAQ,IAAI,aAAa,GAAG,CAAC;EAC3C,QAAQ;GACN,OAAO,KAAK,QAAQ,IAAI,GAAG;EAC7B;CACF;;CAGA,gBAA0B;EACxB,MAAM,OAAiB,CAAC;EACxB,KAAK,MAAM,CAAC,KAAK,UAAU,KAAK,SAC9B,IAAI,MAAM,WACR,KAAK,KAAK,GAAG;EAGjB,OAAO;CACT;AACF"}
|
|
1
|
+
{"version":3,"file":"pool-D13eVCqc.mjs","names":[],"sources":["../src/relay/url.ts","../src/relay/fan-in.ts","../src/relay/pool.ts"],"sourcesContent":["import { normalizeURL } from \"../core/util.ts\";\n\n/**\n * {@link normalizeURL}, or the raw string when it is not a valid relay URL (ensureRelay then fails\n * it like a dead relay).\n */\nexport function relayUrlKey(url: string): string {\n try {\n return normalizeURL(url);\n } catch {\n return url;\n }\n}\n\n/** Unique relay keys in first-seen order. */\nexport function uniqueRelayUrls(urls: Iterable<string>): string[] {\n const seen = new Set<string>();\n const unique: string[] = [];\n for (const url of urls) {\n const key = relayUrlKey(url);\n if (seen.has(key)) {\n continue;\n }\n seen.add(key);\n unique.push(key);\n }\n return unique;\n}\n\n/**\n * True for `ws:` / `http:` URLs that are not `.onion`. Local-network `ws://` is still insecure;\n * allow those via a trusted set.\n */\nexport function isInsecureRelayUrl(url: string): boolean {\n try {\n const parsed = new URL(url);\n if (parsed.hostname.endsWith(\".onion\")) {\n return false;\n }\n return parsed.protocol === \"ws:\" || parsed.protocol === \"http:\";\n } catch {\n return false;\n }\n}\n","import { abortReason, onAbort, throwIfAborted } from \"../core/abort.ts\";\nimport type { Event } from \"../core/event.ts\";\nimport type { Filter } from \"../core/filter.ts\";\nimport { invokeSafely } from \"../core/report.ts\";\nimport type { Pool } from \"./pool.ts\";\nimport type { Relay } from \"./relay.ts\";\nimport type { Closer } from \"./subscription.ts\";\nimport { uniqueRelayUrls } from \"./url.ts\";\n\nexport type RoutedJob = {\n urls: ReadonlyArray<string>;\n filters: ReadonlyArray<Filter>;\n id?: string | undefined;\n};\n\nexport type FanInOptions = {\n /** First receipt only (deduped across jobs). `relayUrl` is the normalized relay URL. */\n onEvent?: ((event: Event, relayUrl: string) => void) | undefined;\n onEose?: (() => void) | undefined;\n onClose?: ((reason: string) => void) | undefined;\n signal?: AbortSignal | undefined;\n /** Armed at fan-in only. Never forwarded to relay.subscribe. */\n eoseTimeoutMs?: number | undefined;\n alreadyHaveEvent?: ((id: string) => boolean) | undefined;\n /** Every receipt from every relay, including skipped duplicates. */\n onEventSeen?: ((id: string, relayUrl: string) => void) | undefined;\n closeOnEose?: boolean | undefined;\n /** Optional. ensureRelay uses Pool.#opts.connectTimeoutMs when omitted. */\n connectTimeoutMs?: number | undefined;\n};\n\n/**\n * Aggregate EOSE/close across routed jobs. One `seen` set. `pending` counts URL list entries\n * (duplicates included). `pendingEose` counts unique `jobIndex:url` keys.\n */\nexport function fanIn(pool: Pool, jobs: ReadonlyArray<RoutedJob>, opts: FanInOptions = {}): Closer {\n const seen = new Set<string>();\n const closers: Closer[] = [];\n let closed = false;\n let eoseFired = false;\n let sawEose = false;\n let eoseTimer: ReturnType<typeof setTimeout> | undefined;\n const eoseDone = new Set<string>();\n const eoseAttempted = new Set<string>();\n let pendingEose = 0;\n let pending = 0;\n /** Close reason of the last attached subscription that ended; drives the aggregate reason. */\n let lastReason: string | undefined;\n\n const fireEose = () => {\n if (closed || eoseFired) {\n return;\n }\n eoseFired = true;\n if (eoseTimer !== undefined) {\n clearTimeout(eoseTimer);\n eoseTimer = undefined;\n }\n invokeSafely(() => opts.onEose?.());\n };\n\n /**\n * Remove one relay from the pending-EOSE set. `eosed` credits a real EOSE; a CLOSED or a connect\n * failure only drops the wait — the aggregate `onEose` fires once every remaining relay ended AND\n * at least one actually EOSE'd.\n */\n const settleEose = (jobIndex: number, url: string, eosed: boolean) => {\n const key = `${jobIndex}:${url}`;\n if (eoseDone.has(key)) {\n return;\n }\n eoseDone.add(key);\n if (eosed) {\n sawEose = true;\n }\n pendingEose -= 1;\n if (pendingEose === 0 && sawEose) {\n fireEose();\n }\n };\n\n const settleClose = () => {\n closed = true;\n disposeAbort();\n if (eoseTimer !== undefined) {\n clearTimeout(eoseTimer);\n eoseTimer = undefined;\n }\n };\n\n const closeAll = (reason?: string) => {\n if (closed) {\n return;\n }\n settleClose();\n for (const c of closers) {\n c.close(reason);\n }\n invokeSafely(() => opts.onClose?.(reason ?? \"closed by client\"));\n };\n\n if (opts.signal?.aborted === true) {\n closed = true;\n invokeSafely(() => opts.onClose?.(\"aborted\"));\n return { close: closeAll };\n }\n\n const disposeAbort = onAbort(opts.signal, () => closeAll(\"aborted\"));\n\n /**\n * Single completion rule for both endings: `pending` reaches zero when every URL entry either\n * failed to attach or its subscription ended — at that point no attached closer can still be\n * live. The reason is the last subscription's close reason, or \"all relays failed\" when no relay\n * ever attached.\n */\n const finish = (): void => {\n if (pending <= 0 && !closed) {\n settleClose();\n invokeSafely(() => opts.onClose?.(lastReason ?? \"all relays failed\"));\n }\n };\n\n const failUrl = (jobIndex: number, key: string): void => {\n settleEose(jobIndex, key, false);\n pending -= 1;\n finish();\n };\n\n const attach = (relay: Relay, job: RoutedJob, jobIndex: number): void => {\n if (closed) {\n return;\n }\n const received = opts.onEventSeen;\n const sub = relay.subscribe([...job.filters], {\n id: jobs.length === 1 ? job.id : undefined,\n closeOnEose: opts.closeOnEose,\n alreadyHaveEvent: (id) => {\n let have = seen.has(id);\n invokeSafely(() => {\n have = have || Boolean(opts.alreadyHaveEvent?.(id));\n });\n return have;\n },\n onEventSeen:\n received === undefined ? undefined : (id) => invokeSafely(() => received(id, relay.url)),\n onEvent: (event) => {\n seen.add(event.id);\n invokeSafely(() => opts.onEvent?.(event, relay.url));\n },\n onEose: () => settleEose(jobIndex, relay.url, true),\n onClose: (reason) => {\n settleEose(jobIndex, relay.url, false);\n lastReason = reason;\n pending -= 1;\n finish();\n },\n });\n closers.push(sub);\n };\n\n for (const [jobIndex, job] of jobs.entries()) {\n // Equivalent spellings of one relay URL attach exactly once per job.\n for (const key of uniqueRelayUrls(job.urls)) {\n pending += 1;\n const eoseKey = `${jobIndex}:${key}`;\n if (!eoseAttempted.has(eoseKey)) {\n eoseAttempted.add(eoseKey);\n pendingEose += 1;\n }\n\n const tryAttach = (relay: Relay): void => {\n try {\n attach(relay, job, jobIndex);\n } catch {\n // Any attach failure retires this URL: RelayClosedError is the expected\n // dropped-between-connect-and-subscribe race; anything else must not escape the\n // `void` wrapper as an unhandled rejection, so it fails the URL as well.\n failUrl(jobIndex, key);\n }\n };\n\n void (async (): Promise<void> => {\n let relay: Relay;\n try {\n relay = await pool.ensureRelay(key, {\n signal: opts.signal,\n timeoutMs: opts.connectTimeoutMs,\n });\n } catch {\n // ensureRelay rejected only — not tryAttach throws (those must not look like connect failure).\n const existing = pool.getRelay(key);\n if (existing !== undefined) {\n tryAttach(existing);\n return;\n }\n failUrl(jobIndex, key);\n return;\n }\n tryAttach(relay);\n })();\n }\n }\n\n if (opts.eoseTimeoutMs !== undefined && pending > 0) {\n eoseTimer = setTimeout(() => {\n eoseTimer = undefined;\n fireEose();\n }, opts.eoseTimeoutMs);\n }\n\n if (pending === 0) {\n queueMicrotask(() => {\n if (closed) {\n return;\n }\n settleClose();\n invokeSafely(() => opts.onClose?.(\"no relays\"));\n });\n }\n\n return { close: closeAll };\n}\n\nexport async function fetchRouted(\n pool: Pool,\n jobs: ReadonlyArray<RoutedJob>,\n opts: {\n timeoutMs?: number | undefined;\n signal?: AbortSignal | undefined;\n connectTimeoutMs?: number | undefined;\n /** Every event of every relay batch, including cross-relay duplicates. */\n onEvent?: ((event: Event, relayUrl: string) => void) | undefined;\n } = {},\n): Promise<Event[]> {\n throwIfAborted(opts.signal);\n const byId = new Map<string, Event>();\n await Promise.all(\n jobs.flatMap((job) => {\n return uniqueRelayUrls(job.urls).map(async (url) => {\n let batch: ReadonlyArray<Event>;\n let relayUrl: string;\n try {\n const relay = await pool.ensureRelay(url, {\n signal: opts.signal,\n timeoutMs: opts.connectTimeoutMs,\n });\n relayUrl = relay.url;\n const result = await relay.fetch([...job.filters], {\n timeoutMs: opts.timeoutMs,\n signal: opts.signal,\n });\n batch = result.events;\n } catch {\n // An abort rejects the whole call; per-relay failures are skipped.\n if (opts.signal?.aborted === true) {\n throw abortReason(opts.signal);\n }\n return;\n }\n // The whole batch lands before callbacks so a throwing onEvent cannot\n // drop events; listener errors are reported, never propagated.\n for (const event of batch) {\n if (!byId.has(event.id)) {\n byId.set(event.id, event);\n }\n }\n for (const event of batch) {\n invokeSafely(() => opts.onEvent?.(event, relayUrl));\n }\n });\n }),\n );\n return [...byId.values()];\n}\n","import { abortReason, throwIfAborted } from \"../core/abort.ts\";\nimport { Emitter } from \"../core/emitter.ts\";\nimport { MessageError, toError } from \"../core/error.ts\";\nimport type { Event, EventTemplate } from \"../core/event.ts\";\nimport { canonicalizeFilters } from \"../core/filter.ts\";\nimport type { Filter } from \"../core/filter.ts\";\nimport { assertSubscriptionId } from \"../core/message.ts\";\nimport type { CountResult } from \"../core/message.ts\";\nimport { normalizeURL } from \"../core/util.ts\";\nimport { RelayConnectionError, RelayPublishError, RelaySuspendedError } from \"./error.ts\";\nimport { fanIn, fetchRouted } from \"./fan-in.ts\";\nimport { Relay, RelayStatus } from \"./relay.ts\";\nimport type { RelayFetchEnd, RelayOptions, SubscribeOptions } from \"./relay.ts\";\nimport type { Closer } from \"./subscription.ts\";\nimport { isInsecureRelayUrl, uniqueRelayUrls } from \"./url.ts\";\n\n/** Per-relay tolerance for events that fail id/signature verification. */\nexport type InvalidEventPolicy = {\n /** Invalid events tolerated within `windowMs`; exceeding it suspends the relay. */\n limit: number;\n windowMs: number;\n /** How long a suspended relay stays closed before it may reconnect. */\n cooldownMs: number;\n};\n\n/** Pool-wide options applied to every managed relay. */\nexport type PoolOptions = Omit<RelayOptions, \"authSigner\"> & {\n /**\n * When set, automatically answer NIP-42 AUTH challenges for relays that send them. Return\n * undefined to skip a given relay URL.\n */\n automaticallyAuth?:\n | ((relayURL: string) => ((event: EventTemplate) => Promise<Event>) | undefined)\n | undefined;\n /** When false (default), ensureRelay rejects isInsecureRelayUrl unless trusted. */\n allowInsecure?: boolean | undefined;\n trustedInsecureUrls?: ReadonlyArray<string> | undefined;\n /** Close unused relays. Unset/0 = disabled. */\n idleTimeoutMs?: number | undefined;\n idleCleanupIntervalMs?: number | undefined;\n /**\n * Soft cap on connected non-pinned relays. When `ensureRelay` would create a new non-pinned relay\n * at the cap, the least-recently-used idle one is closed first (idle = no subscriptions and no\n * in-flight requests). If none is idle the connect proceeds anyway — the cap never fails a\n * request.\n */\n maxRelays?: number | undefined;\n /** Normalized URLs never closed by idle cleanup or `maxRelays` eviction. */\n pinnedUrls?: ReadonlyArray<string> | undefined;\n /**\n * When set, a relay whose `limit`-th-plus-one EVENT fails id/signature verification inside a\n * `windowMs` sliding window is disconnected and suspended for `cooldownMs`. During suspension\n * `ensureRelay` rejects with {@link RelaySuspendedError}; once it lifts, live subscriptions resume\n * through the normal reconnect path. Unset = events are dropped without counting, as before.\n */\n invalidEventPolicy?: InvalidEventPolicy | undefined;\n};\n\n/** Typed event payloads for {@link Pool.on}. */\nexport type PoolEventMap = {\n /** Normalized URLs closed by an idle-cleanup pass. */\n idle: ReadonlyArray<string>;\n /**\n * A relay suspended for exceeding `invalidEventPolicy`: `url` is the normalized relay URL and\n * `until` the epoch-ms time the suspension lifts.\n */\n suspend: { readonly url: string; readonly until: number };\n};\n\n/** Per-relay publish outcome: the relay's OK verdict or a transport-level failure. */\nexport type PoolPublishResult =\n | { readonly url: string; readonly status: \"ok\"; readonly message: string }\n | { readonly url: string; readonly status: \"rejected\"; readonly message: string }\n | { readonly url: string; readonly status: \"failed\"; readonly error: Error };\n\n/** Multi-relay subscribe options: callbacks also receive the normalized relay URL. */\nexport type PoolSubscribeOptions = Omit<SubscribeOptions, \"onEvent\" | \"onEventSeen\"> & {\n onEvent?: ((event: Event, relayUrl: string) => void) | undefined;\n onEventSeen?: ((id: string, relayUrl: string) => void) | undefined;\n};\n\n/** Per-relay one-shot fetch outcome: collected events plus how its REQ ended. */\nexport type PoolFetchResult = {\n readonly url: string;\n readonly events: ReadonlyArray<Event>;\n readonly end: RelayFetchEnd | { readonly type: \"failed\"; readonly error: Error };\n};\n\n/** Per-relay NIP-45 COUNT result (or a transport-level failure). */\nexport type PoolCountResult =\n | ({ readonly url: string; readonly status: \"ok\" } & CountResult)\n | { readonly url: string; readonly status: \"failed\"; readonly error: Error };\n\nconst countOk = (url: string, payload: CountResult): PoolCountResult => ({\n url,\n status: \"ok\",\n ...payload,\n});\n\nfunction isIdle(relay: Relay): boolean {\n return (\n relay.status !== RelayStatus.Connecting &&\n relay.subscriptionCount === 0 &&\n relay.inFlightCount === 0\n );\n}\n\n/** Multi-relay coordinator: connection reuse, cross-relay event dedup, fan-out publish. */\nexport class Pool {\n readonly #relays = new Map<string, Relay>();\n readonly #relayOptions: Omit<RelayOptions, \"authSigner\">;\n readonly #automaticallyAuth: PoolOptions[\"automaticallyAuth\"];\n readonly #maxRelays: number | undefined;\n readonly #invalidEventPolicy: InvalidEventPolicy | undefined;\n readonly #events = new Emitter<PoolEventMap>();\n readonly #lastActivity = new Map<string, number>();\n readonly #idleTimeoutMs: number;\n #idleTimer: ReturnType<typeof setInterval> | undefined;\n #allowInsecure: boolean;\n #trustedInsecure: Set<string>;\n #pinned: Set<string>;\n /** Per-relay timestamps of verification failures inside the sliding window. */\n readonly #invalidEvents = new Map<string, number[]>();\n /** Normalized URL → epoch ms when its suspension lifts. */\n readonly #suspendedUntil = new Map<string, number>();\n readonly #resumeTimers = new Map<string, ReturnType<typeof setTimeout>>();\n\n constructor(opts: PoolOptions = {}) {\n const {\n automaticallyAuth,\n allowInsecure,\n trustedInsecureUrls,\n idleTimeoutMs,\n idleCleanupIntervalMs,\n maxRelays,\n pinnedUrls,\n invalidEventPolicy,\n ...relayOptions\n } = opts;\n this.#relayOptions = relayOptions;\n this.#automaticallyAuth = automaticallyAuth;\n this.#maxRelays = maxRelays;\n this.#invalidEventPolicy = invalidEventPolicy;\n this.#allowInsecure = allowInsecure ?? false;\n this.#trustedInsecure = new Set((trustedInsecureUrls ?? []).map(normalizeURL));\n this.#pinned = new Set((pinnedUrls ?? []).map(normalizeURL));\n this.#idleTimeoutMs = idleTimeoutMs ?? 0;\n if (this.#idleTimeoutMs > 0) {\n this.#idleTimer = setInterval(() => this.cleanIdleRelays(), idleCleanupIntervalMs ?? 30_000);\n }\n }\n\n /**\n * Listen for a pool event. Multiple listeners per type are allowed and fire in registration\n * order; a throwing listener is reported and does not break the dispatch. Returns an unsubscribe\n * function.\n */\n on<K extends keyof PoolEventMap>(\n type: K,\n listener: (payload: PoolEventMap[K]) => void,\n ): () => void {\n return this.#events.on(type, listener);\n }\n\n setAllowInsecure(allow: boolean): void {\n this.#allowInsecure = allow;\n }\n\n setTrustedInsecureUrls(urls: ReadonlyArray<string>): void {\n this.#trustedInsecure = new Set(urls.map(normalizeURL));\n }\n\n setPinnedUrls(urls: ReadonlyArray<string>): void {\n this.#pinned = new Set(urls.map(normalizeURL));\n }\n\n /**\n * Drop cached AUTH rejections and re-fire pending challenges on every pooled relay, so a later\n * `setSigner` takes effect without reconnect.\n */\n resetAuth(): void {\n for (const relay of this.#relays.values()) {\n relay.resetAuth();\n }\n }\n\n cleanIdleRelays(): void {\n if (this.#idleTimeoutMs <= 0) {\n return;\n }\n const now = Date.now();\n const idle: string[] = [];\n for (const [url, relay] of this.#relays) {\n if (this.#pinned.has(url) || !isIdle(relay)) {\n continue;\n }\n const last = this.#lastActivity.get(url) ?? 0;\n if (!relay.connected || now - last >= this.#idleTimeoutMs) {\n idle.push(url);\n }\n }\n for (const url of this.#lastActivity.keys()) {\n if (!this.#relays.has(url)) {\n this.#lastActivity.delete(url);\n }\n }\n if (idle.length === 0) {\n return;\n }\n this.closeRelays(idle);\n this.#events.emit(\"idle\", idle);\n }\n\n #touch(url: string): void {\n this.#lastActivity.set(url, Date.now());\n }\n\n /**\n * Count one verification failure for `norm`; on the `limit + 1`-th inside `windowMs` suspend the\n * relay: drop its connection (subscriptions kept), record `until`, and emit `suspend`.\n */\n #noteInvalidEvent(norm: string): void {\n const policy = this.#invalidEventPolicy;\n if (policy === undefined) {\n return;\n }\n const now = Date.now();\n const window = (this.#invalidEvents.get(norm) ?? []).filter((at) => now - at < policy.windowMs);\n window.push(now);\n if (window.length <= policy.limit) {\n this.#invalidEvents.set(norm, window);\n return;\n }\n this.#invalidEvents.delete(norm);\n if (this.#suspendedUntil.has(norm)) {\n return;\n }\n const until = now + policy.cooldownMs;\n this.#suspendedUntil.set(norm, until);\n this.#relays.get(norm)?.disconnect();\n const resume = setTimeout(() => {\n this.#resumeTimers.delete(norm);\n this.#suspendedUntil.delete(norm);\n const relay = this.#relays.get(norm);\n if (relay === undefined || relay.connected || relay.subscriptionCount === 0) {\n return;\n }\n void (async (): Promise<void> => {\n try {\n await relay.connect();\n } catch {\n // With enableReconnect the relay's own backoff retries; otherwise the\n // next ensureRelay attempt reconnects on demand.\n }\n })();\n }, policy.cooldownMs);\n this.#resumeTimers.set(norm, resume);\n this.#events.emit(\"suspend\", { url: norm, until });\n }\n\n #rejectInsecure(url: string, norm: string): void {\n if (this.#allowInsecure) {\n return;\n }\n if (!isInsecureRelayUrl(url)) {\n return;\n }\n if (this.#trustedInsecure.has(norm)) {\n return;\n }\n throw new RelayConnectionError(\"insecure relay connection blocked\", norm);\n }\n\n #stopIdleCleanup(): void {\n if (this.#idleTimer === undefined) {\n return;\n }\n clearInterval(this.#idleTimer);\n this.#idleTimer = undefined;\n }\n\n /**\n * Soft cap: closing the least-recently-used idle non-pinned relay when a new non-pinned relay\n * would push the count past `maxRelays`. Busy relays are never evicted; with none idle the cap is\n * exceeded rather than failing.\n */\n #enforceMaxRelays(incoming: string): void {\n const cap = this.#maxRelays;\n if (cap === undefined || this.#pinned.has(incoming)) {\n return;\n }\n let count = 0;\n let oldestUrl: string | undefined;\n let oldestAt = Number.POSITIVE_INFINITY;\n for (const [url, relay] of this.#relays) {\n if (this.#pinned.has(url)) {\n continue;\n }\n count += 1;\n if (!isIdle(relay)) {\n continue;\n }\n const at = this.#lastActivity.get(url) ?? 0;\n if (at < oldestAt) {\n oldestAt = at;\n oldestUrl = url;\n }\n }\n if (count >= cap && oldestUrl !== undefined) {\n this.closeRelays([oldestUrl]);\n }\n }\n\n async ensureRelay(\n url: string,\n opts?: { signal?: AbortSignal | undefined; timeoutMs?: number | undefined },\n ): Promise<Relay> {\n const norm = normalizeURL(url);\n this.#rejectInsecure(url, norm);\n const suspendedUntil = this.#suspendedUntil.get(norm);\n if (suspendedUntil !== undefined) {\n if (Date.now() < suspendedUntil) {\n throw new RelaySuspendedError(norm, suspendedUntil);\n }\n this.#suspendedUntil.delete(norm);\n }\n let relay = this.#relays.get(norm);\n if (!relay) {\n this.#enforceMaxRelays(norm);\n const signFn = this.#automaticallyAuth?.(norm) ?? undefined;\n const created = new Relay(norm, { ...this.#relayOptions, authSigner: signFn });\n // Only drop from the pool on terminal close (reconnect keeps the entry). Guard on\n // identity: a stale relay's delayed close must not delete a newer relay's entry.\n created.on(\"close\", () => {\n if (this.#relays.get(norm) === created) {\n this.#relays.delete(norm);\n this.#lastActivity.delete(norm);\n }\n });\n created.on(\"invalidevent\", () => {\n this.#noteInvalidEvent(norm);\n });\n if (signFn) {\n created.on(\"auth\", () => {\n void (async (): Promise<void> => {\n try {\n await created.auth(signFn);\n } catch {\n // auth failure surfaces on subsequent CLOSED/OK; avoid unhandled rejection\n }\n })();\n });\n }\n this.#relays.set(norm, created);\n relay = created;\n }\n this.#touch(norm);\n if (!relay.connected) {\n try {\n await relay.connect({\n signal: opts?.signal,\n timeoutMs: opts?.timeoutMs ?? this.#relayOptions.connectTimeoutMs,\n });\n } catch (error) {\n // Keep the entry when reconnect is enabled so open subscriptions can recover.\n if (this.#relayOptions.enableReconnect !== true) {\n this.#relays.delete(norm);\n this.#lastActivity.delete(norm);\n }\n throw error;\n }\n }\n return relay;\n }\n\n /** Close every pooled relay and clear all pool state. */\n close(): void {\n this.#stopIdleCleanup();\n for (const timer of this.#resumeTimers.values()) {\n clearTimeout(timer);\n }\n this.#resumeTimers.clear();\n this.#suspendedUntil.clear();\n this.#invalidEvents.clear();\n for (const relay of this.#relays.values()) {\n relay.close();\n }\n this.#relays.clear();\n this.#lastActivity.clear();\n }\n\n /** Close and forget the listed relays; other pooled relays are untouched. */\n closeRelays(urls: ReadonlyArray<string>): void {\n for (const url of urls) {\n const norm = normalizeURL(url);\n const timer = this.#resumeTimers.get(norm);\n if (timer !== undefined) {\n clearTimeout(timer);\n this.#resumeTimers.delete(norm);\n }\n this.#relays.get(norm)?.close();\n this.#relays.delete(norm);\n this.#lastActivity.delete(norm);\n }\n }\n\n /**\n * Subscribe across relays. Deduplicates by event id. Returns a closer; callbacks receive every\n * new event once.\n */\n subscribe(\n relays: ReadonlyArray<string>,\n filters: ReadonlyArray<Filter>,\n opts: PoolSubscribeOptions = {},\n ): Closer {\n if (filters.length === 0) {\n throw new MessageError(\"REQ requires at least one filter\");\n }\n if (opts.id !== undefined) {\n assertSubscriptionId(opts.id);\n }\n const canonical = canonicalizeFilters(filters);\n return fanIn(this, [{ urls: relays, filters: canonical, id: opts.id }], {\n onEvent: opts.onEvent,\n onEose: opts.onEose,\n onClose: opts.onClose,\n signal: opts.signal,\n eoseTimeoutMs: opts.eoseTimeoutMs,\n alreadyHaveEvent: opts.alreadyHaveEvent,\n onEventSeen: opts.onEventSeen,\n closeOnEose: opts.closeOnEose,\n connectTimeoutMs: this.#relayOptions.connectTimeoutMs,\n });\n }\n\n /**\n * One-shot fetch per relay: each entry reports its own events and end reason (`eose`, `closed`,\n * `timeout`), or `failed` when the connect or REQ itself errored. An abort rejects the whole\n * call. Events are not deduped across relays — use {@link fetch} for that.\n */\n async fetchEach(\n relays: ReadonlyArray<string>,\n filters: ReadonlyArray<Filter>,\n opts?: { timeoutMs?: number | undefined; signal?: AbortSignal | undefined },\n ): Promise<PoolFetchResult[]> {\n if (filters.length === 0) {\n throw new MessageError(\"REQ requires at least one filter\");\n }\n throwIfAborted(opts?.signal);\n const canonical = canonicalizeFilters(filters);\n return Promise.all(\n uniqueRelayUrls(relays).map(async (url): Promise<PoolFetchResult> => {\n try {\n const relay = await this.ensureRelay(url, {\n signal: opts?.signal,\n timeoutMs: this.#relayOptions.connectTimeoutMs,\n });\n this.#touch(relay.url);\n const result = await relay.fetch([...canonical], {\n timeoutMs: opts?.timeoutMs,\n signal: opts?.signal,\n });\n return { url, events: result.events, end: result.end };\n } catch (error) {\n // An abort rejects the whole call; per-relay failures are reported.\n if (opts?.signal?.aborted === true) {\n throw abortReason(opts.signal);\n }\n return {\n url,\n events: [],\n end: { type: \"failed\", error: toError(error) },\n };\n }\n }),\n );\n }\n\n /** Fetch events until each connected relay EOSE or timeout; dedupe by id. */\n async fetch(\n relays: ReadonlyArray<string>,\n filters: ReadonlyArray<Filter>,\n opts?: {\n timeoutMs?: number | undefined;\n signal?: AbortSignal | undefined;\n /** Every event of every relay batch, including cross-relay duplicates. */\n onEvent?: ((event: Event, relayUrl: string) => void) | undefined;\n },\n ): Promise<Event[]> {\n if (filters.length === 0) {\n throw new MessageError(\"REQ requires at least one filter\");\n }\n return fetchRouted(this, [{ urls: relays, filters: canonicalizeFilters(filters) }], {\n timeoutMs: opts?.timeoutMs,\n signal: opts?.signal,\n connectTimeoutMs: this.#relayOptions.connectTimeoutMs,\n onEvent: opts?.onEvent,\n });\n }\n\n /** Publish to all listed relays; returns per-relay outcomes. */\n async publish(\n relays: ReadonlyArray<string>,\n event: Event,\n opts?: { timeoutMs?: number | undefined },\n ): Promise<PoolPublishResult[]> {\n return Promise.all(\n uniqueRelayUrls(relays).map(async (url): Promise<PoolPublishResult> => {\n try {\n const relay = await this.ensureRelay(url, {\n timeoutMs: this.#relayOptions.connectTimeoutMs,\n });\n this.#touch(relay.url);\n const result = await relay.publish(event, { timeoutMs: opts?.timeoutMs });\n return result.ok\n ? { url, status: \"ok\", message: result.message }\n : { url, status: \"rejected\", message: result.message };\n } catch (error) {\n return { url, status: \"failed\", error: toError(error) };\n }\n }),\n );\n }\n\n /** First successful publish (Promise.any semantics). */\n async publishAny(\n relays: ReadonlyArray<string>,\n event: Event,\n opts?: { timeoutMs?: number | undefined },\n ): Promise<Extract<PoolPublishResult, { status: \"ok\" }>> {\n return Promise.any(\n uniqueRelayUrls(relays).map(async (url) => {\n const relay = await this.ensureRelay(url, {\n timeoutMs: this.#relayOptions.connectTimeoutMs,\n });\n this.#touch(relay.url);\n const result = await relay.publish(event, { timeoutMs: opts?.timeoutMs });\n if (!result.ok) {\n throw new RelayPublishError(result.message || \"rejected\", relay.url);\n }\n return { url, status: \"ok\" as const, message: result.message };\n }),\n );\n }\n\n /**\n * NIP-45 COUNT across relays. Per-relay outcomes; failures do not throw. Counts are not summed —\n * each relay reports independently (may overlap).\n */\n async count(\n relays: ReadonlyArray<string>,\n filters: ReadonlyArray<Filter>,\n opts?: { timeoutMs?: number | undefined; signal?: AbortSignal | undefined },\n ): Promise<PoolCountResult[]> {\n throwIfAborted(opts?.signal);\n const canonical = canonicalizeFilters(filters);\n return Promise.all(\n uniqueRelayUrls(relays).map(async (url): Promise<PoolCountResult> => {\n try {\n const relay = await this.ensureRelay(url, {\n signal: opts?.signal,\n timeoutMs: this.#relayOptions.connectTimeoutMs,\n });\n this.#touch(relay.url);\n const payload: CountResult = await relay.count(canonical, {\n timeoutMs: opts?.timeoutMs,\n signal: opts?.signal,\n });\n return countOk(url, payload);\n } catch (error) {\n // An abort rejects the whole call; per-relay failures are reported.\n if (opts?.signal?.aborted === true) {\n throw abortReason(opts.signal);\n }\n return { url, status: \"failed\", error: toError(error) };\n }\n }),\n );\n }\n\n listRelays(): string[] {\n return [...this.#relays.keys()];\n }\n\n /** Lookup a pooled relay, including disconnected reconnecting entries. */\n getRelay(url: string): Relay | undefined {\n try {\n return this.#relays.get(normalizeURL(url));\n } catch {\n return this.#relays.get(url);\n }\n }\n\n /** Currently connected URLs. Unlike listRelays(), excludes reconnecting/disconnected entries. */\n connectedUrls(): string[] {\n const urls: string[] = [];\n for (const [url, relay] of this.#relays) {\n if (relay.connected) {\n urls.push(url);\n }\n }\n return urls;\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { c as isHex32 } from "./util-
|
|
1
|
+
import { c as isHex32 } from "./util-BcRBO6o-.mjs";
|
|
2
2
|
import { i as isEphemeralKind, t as Kind } from "./kind-I2Tvbe82.mjs";
|
|
3
|
-
import { S as parseEventAddress, _ as eventAddress, a as isReplaceableWinner, h as validateSignedEvent, i as isMarkedVerified } from "./event-
|
|
3
|
+
import { S as parseEventAddress, _ as eventAddress, a as isReplaceableWinner, h as validateSignedEvent, i as isMarkedVerified } from "./event-DHtcm0jM.mjs";
|
|
4
4
|
//#region src/storage/deletion.ts
|
|
5
5
|
/**
|
|
6
6
|
* NIP-09 application plan for a kind 5 event.
|
|
@@ -18,8 +18,8 @@ function planDeletion(deletion, getById) {
|
|
|
18
18
|
const seenIds = /* @__PURE__ */ new Set();
|
|
19
19
|
const seenCoords = /* @__PURE__ */ new Set();
|
|
20
20
|
for (const tag of deletion.tags) {
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
const [, id] = tag;
|
|
22
|
+
if (tag[0] === "e" && id !== void 0 && isHex32(id)) {
|
|
23
23
|
if (seenIds.has(id)) continue;
|
|
24
24
|
seenIds.add(id);
|
|
25
25
|
const existing = getById(id);
|
|
@@ -184,4 +184,4 @@ function applyPutMemory(s, d) {
|
|
|
184
184
|
//#endregion
|
|
185
185
|
export { DeletionState as i, decidePut as n, outboxBoundKey as r, applyPutMemory as t };
|
|
186
186
|
|
|
187
|
-
//# sourceMappingURL=put-
|
|
187
|
+
//# sourceMappingURL=put-DuymazkT.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"put-DuymazkT.mjs","names":[],"sources":["../src/storage/deletion.ts","../src/storage/put.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 readonly removeIds: ReadonlyArray<string>;\n /** Referenced ids not in the store; remember deletion pubkey until the event arrives. */\n readonly pendingIds: ReadonlyArray<{ readonly id: string; readonly pubkey: string }>;\n /** Replaceable/addressable coordinates tombstoned up to `until` (deletion created_at). */\n readonly coordinates: ReadonlyArray<{ readonly key: string; readonly 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;\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 const [, id] = tag;\n if (tag[0] === \"e\" && id !== undefined && isHex32(id)) {\n if (seenIds.has(id)) {\n continue;\n }\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) {\n continue;\n }\n if (existing.pubkey !== pubkey) {\n continue;\n }\n removeIds.push(id);\n continue;\n }\n\n if (tag[0] === \"a\" && tag[1] !== undefined) {\n const coord = parseEventAddress(tag[1]);\n if (!coord || coord.pubkey !== pubkey) {\n continue;\n }\n const key = `${coord.kind}:${coord.pubkey}:${coord.identifier}`;\n if (seenCoords.has(key)) {\n continue;\n }\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: Set<string> = new Set<string>();\n readonly pending: Map<string, string> = new Map<string, string>();\n readonly coordinates: Map<string, number> = 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)) {\n return true;\n }\n if (event.kind === Kind.EventDeletion) {\n return false;\n }\n const pendingPk = this.pending.get(event.id);\n if (pendingPk !== undefined && event.pubkey === pendingPk) {\n return true;\n }\n const addr = eventAddress(event);\n if (addr === undefined) {\n return false;\n }\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)) {\n this.pending.set(p.id, p.pubkey);\n }\n }\n for (const c of plan.coordinates) {\n const prev = this.coordinates.get(c.key) ?? Number.NEGATIVE_INFINITY;\n // Re-absorbing a coordinate moves it to the newest end so FIFO\n // tombstone caps evict the oldest marker, not the freshest.\n this.coordinates.delete(c.key);\n this.coordinates.set(c.key, Math.max(prev, c.until));\n }\n }\n}\n\nexport function coordinateRemovals(\n coordinates: ReadonlyArray<{ 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) {\n ids.push(current.id);\n }\n }\n return ids;\n}\n","import type { Event } from \"../core/event.ts\";\nimport { isMarkedVerified, isReplaceableWinner, validateSignedEvent } from \"../core/event.ts\";\nimport { isEphemeralKind, Kind } from \"../core/kind.ts\";\nimport { eventAddress } from \"../core/tag.ts\";\nimport { coordinateRemovals, planDeletion } from \"./deletion.ts\";\nimport type { DeletionPlan, DeletionState } from \"./deletion.ts\";\nimport type { PutResult } from \"./types.ts\";\n\nexport function outboxBoundKey(pubkey: string, kind: number): string {\n return `${pubkey.toLowerCase()}:${kind}`;\n}\n\nexport type PutDecision =\n | {\n readonly action: \"skip\";\n readonly result: \"duplicate\" | \"ephemeral\" | \"rejected\" | \"invalid\";\n readonly event: Event;\n }\n | { readonly action: \"tombstone\"; readonly result: \"duplicate\"; readonly event: Event }\n | {\n readonly action: \"delete\";\n readonly result: \"deleted\";\n readonly event: Event;\n readonly plan: DeletionPlan;\n readonly coordIds: ReadonlyArray<string>;\n }\n | {\n readonly action: \"insert\";\n readonly result: \"accepted\" | \"replaced\";\n readonly event: Event;\n readonly address?: string | undefined;\n readonly replaceId?: string | undefined;\n };\n\nexport type PutLookup = {\n deletion: DeletionState;\n getById: (\n id: string,\n ) => Pick<Event, \"id\" | \"pubkey\" | \"kind\" | \"created_at\" | \"tags\"> | undefined;\n /**\n * Current replaceable winner for an address. `evicted: true` marks an eviction watermark: the\n * winning event body was dropped, but its id/created_at still reject older versions, and a re-put\n * of the watermarked id re-inserts it.\n */\n getReplaceable: (\n address: string,\n ) => (Pick<Event, \"id\" | \"created_at\"> & { evicted?: boolean }) | undefined;\n};\n\nexport function decidePut(raw: Event, lookup: PutLookup): PutDecision {\n // Canonical-input precondition: non-canonical events are never indexed. An\n // already-verified event has necessarily passed validateSignedEvent.\n if (!isMarkedVerified(raw) && !validateSignedEvent(raw)) {\n return { action: \"skip\", result: \"invalid\", event: raw };\n }\n const event = 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)) {\n return { action: \"tombstone\", result: \"duplicate\", event };\n }\n if (isEphemeralKind(event.kind)) {\n return { action: \"skip\", result: \"ephemeral\", event };\n }\n const address = eventAddress(event);\n if (address !== undefined) {\n const prev = lookup.getReplaceable(address);\n // A stored incumbent rejects stale versions; an eviction watermark does\n // the same, except a re-put of the watermarked id re-inserts it.\n if (prev && prev.id !== event.id && !isReplaceableWinner(event, prev)) {\n return { action: \"skip\", result: \"rejected\", event };\n }\n if (prev !== undefined && prev.evicted !== true) {\n return { action: \"insert\", result: \"replaced\", event, address, replaceId: prev.id };\n }\n return { action: \"insert\", result: \"accepted\", event, address };\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 if (d.action === \"skip\") {\n return d.result;\n }\n if (d.action === \"tombstone\") {\n s.deletion.ids.add(d.event.id);\n s.deletion.pending.delete(d.event.id);\n return \"duplicate\";\n }\n if (d.action === \"delete\") {\n s.deletion.pending.delete(d.event.id);\n s.deletion.absorb(d.plan);\n for (const id of d.plan.removeIds) {\n s.indexRemove(id);\n }\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 }\n if (d.replaceId !== undefined) {\n s.indexRemove(d.replaceId);\n }\n s.indexInsert(d.event);\n return d.result;\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as Event } from "./event-j3BwI5dF.mjs";
|
|
2
|
-
import { t as Filter } from "./filter-
|
|
3
|
-
import { i as PutResult } from "./types-
|
|
2
|
+
import { t as Filter } from "./filter-w1R-mU63.mjs";
|
|
3
|
+
import { i as PutResult } from "./types-COupAqtN.mjs";
|
|
4
4
|
//#region src/store/reactive.d.ts
|
|
5
5
|
/** A reactive snapshot source compatible with `useSyncExternalStore`. */
|
|
6
6
|
type Watch<T> = {
|
|
@@ -59,4 +59,4 @@ declare class ReactiveEventStore {
|
|
|
59
59
|
}
|
|
60
60
|
//#endregion
|
|
61
61
|
export { Watch as i, ReactiveEventStoreEventMap as n, ReactiveEventStoreOptions as r, ReactiveEventStore as t };
|
|
62
|
-
//# sourceMappingURL=reactive-
|
|
62
|
+
//# sourceMappingURL=reactive-BkzOsSwU.d.mts.map
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { p as normalizeURL } from "./util-BcRBO6o-.mjs";
|
|
2
2
|
import { t as Kind } from "./kind-I2Tvbe82.mjs";
|
|
3
|
-
import { S as parseEventAddress, _ as eventAddress, o as itemCompare, y as formatEventAddress } from "./event-
|
|
4
|
-
import { r as filterFingerprint, s as matchFilters } from "./filter-
|
|
3
|
+
import { S as parseEventAddress, _ as eventAddress, o as itemCompare, y as formatEventAddress } from "./event-DHtcm0jM.mjs";
|
|
4
|
+
import { r as filterFingerprint, s as matchFilters, t as canonicalizeFilter } from "./filter-BbShkmc4.mjs";
|
|
5
5
|
import { n as reportError, t as invokeSafely } from "./report-D2BM5dgn.mjs";
|
|
6
6
|
import { t as Emitter } from "./emitter-u4kJqQDk.mjs";
|
|
7
7
|
import { i as trimOldest, n as removeFromSetMap, t as addToSetMap } from "./collections-DtwMc1NU.mjs";
|
|
8
|
-
import { t as MemoryIndex } from "./memory-index-
|
|
8
|
+
import { t as MemoryIndex } from "./memory-index-DhlmbqWx.mjs";
|
|
9
9
|
//#region src/store/reactive.ts
|
|
10
10
|
const SEEN_ON_PER_ID = 16;
|
|
11
11
|
/** Notify passes per microtask; overflow reschedules so a writing subscriber cannot livelock. */
|
|
@@ -282,13 +282,14 @@ var ReactiveEventStore = class {
|
|
|
282
282
|
}
|
|
283
283
|
}
|
|
284
284
|
/**
|
|
285
|
-
* Kind buckets a query watch belongs to (deduped across filters), or undefined when any filter
|
|
286
|
-
*
|
|
285
|
+
* Kind buckets a query watch belongs to (deduped across filters), or undefined when any filter is
|
|
286
|
+
* `kinds`-unconstrained and so can match every kind.
|
|
287
287
|
*/
|
|
288
288
|
#queryKinds(watch) {
|
|
289
289
|
const kinds = /* @__PURE__ */ new Set();
|
|
290
|
-
for (const
|
|
291
|
-
|
|
290
|
+
for (const raw of watch.filters ?? []) {
|
|
291
|
+
const filter = canonicalizeFilter(raw);
|
|
292
|
+
if (filter.kinds === void 0) return;
|
|
292
293
|
for (const kind of filter.kinds) kinds.add(kind);
|
|
293
294
|
}
|
|
294
295
|
return kinds;
|
|
@@ -407,4 +408,4 @@ var ReactiveEventStore = class {
|
|
|
407
408
|
//#endregion
|
|
408
409
|
export { ReactiveEventStore as t };
|
|
409
410
|
|
|
410
|
-
//# sourceMappingURL=reactive-
|
|
411
|
+
//# sourceMappingURL=reactive-C0-rTqN9.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reactive-C0-rTqN9.mjs","names":[],"sources":["../src/store/reactive.ts"],"sourcesContent":["import { addToSetMap, removeFromSetMap, trimOldest } from \"../core/collections.ts\";\nimport { Emitter } from \"../core/emitter.ts\";\nimport { itemCompare } from \"../core/event.ts\";\nimport type { Event } from \"../core/event.ts\";\nimport type { Filter } from \"../core/filter.ts\";\nimport { canonicalizeFilter, filterFingerprint, matchFilters } from \"../core/filter.ts\";\nimport { Kind } from \"../core/kind.ts\";\nimport { invokeSafely, reportError } from \"../core/report.ts\";\nimport { formatEventAddress, eventAddress, parseEventAddress } from \"../core/tag.ts\";\nimport { normalizeURL } from \"../core/util.ts\";\nimport { MemoryIndex } from \"../storage/memory-index.ts\";\nimport type { PutResult } from \"../storage/types.ts\";\n\n/** A reactive snapshot source compatible with `useSyncExternalStore`. */\nexport type Watch<T> = {\n subscribe: (onChange: () => void) => () => void;\n getSnapshot: () => T;\n};\n\n/** Capacity options for {@link ReactiveEventStore}. */\nexport type ReactiveEventStoreOptions = {\n /** Max stored events before LRU eviction. Default 50_000. */\n maxEvents?: number | undefined;\n /** Max ids tracked by `seenOn`. Default 20_000. */\n maxSeenOnEntries?: number | undefined;\n /** FIFO cap on tombstoned deletion ids. Default 100_000. */\n maxTombstones?: number | undefined;\n};\n\n/** Typed event payloads for {@link ReactiveEventStore.on}. */\nexport type ReactiveEventStoreEventMap = {\n /** Every physical index insert, fired synchronously after the update. */\n insert: Event;\n /** Every physical index remove, fired synchronously after watch invalidation. */\n remove: Event;\n};\n\nconst SEEN_ON_PER_ID = 16;\n\n/** Notify passes per microtask; overflow reschedules so a writing subscriber cannot livelock. */\nconst MAX_FLUSH_PASSES = 32;\n\n/** Category of a {@link Watch} subscription. */\ntype WatchKind = \"event\" | \"replaceable\" | \"query\";\n\n/** Structural handle the store needs for invalidation; implemented by WatchImpl. */\ntype WatchHandle = {\n readonly kind: WatchKind;\n readonly key: string;\n readonly filters: ReadonlyArray<Filter> | undefined;\n readonly subscribed: boolean;\n readonly pinnedIds: ReadonlyArray<string>;\n markDirty: () => void;\n notify: () => void;\n};\n\n/** Store internals exposed to a {@link WatchImpl}: version clock and registry/pin bookkeeping. */\ntype WatchHost = {\n version: () => number;\n register: (watch: WatchHandle) => void;\n unregister: (watch: WatchHandle) => void;\n /** Refresh pinned ids after a registered watch's snapshot changed. No-op when unregistered. */\n repin: (watch: WatchHandle) => void;\n};\n\nclass WatchImpl<T> implements Watch<T>, WatchHandle {\n readonly host: WatchHost;\n readonly kind: WatchKind;\n readonly key: string;\n readonly filters: ReadonlyArray<Filter> | undefined;\n readonly compute: () => T;\n readonly equal: (a: T, b: T) => boolean;\n readonly snapshotIds: (snapshot: T) => ReadonlyArray<string>;\n readonly #subscribers = new Set<() => void>();\n #snapshot: T;\n #version = -1;\n #dirty = false;\n #pendingRemove = false;\n\n constructor(\n host: WatchHost,\n kind: WatchKind,\n key: string,\n compute: () => T,\n equal: (a: T, b: T) => boolean,\n snapshotIds: (snapshot: T) => ReadonlyArray<string>,\n filters?: ReadonlyArray<Filter>,\n ) {\n this.host = host;\n this.kind = kind;\n this.key = key;\n this.filters = filters;\n this.compute = compute;\n this.equal = equal;\n this.snapshotIds = snapshotIds;\n this.#snapshot = compute();\n this.#version = host.version();\n }\n\n get subscribed(): boolean {\n return this.#subscribers.size > 0;\n }\n\n get pinnedIds(): ReadonlyArray<string> {\n return this.snapshotIds(this.#snapshot);\n }\n\n subscribe(onChange: () => void): () => void {\n const wasUnsubscribed = this.#subscribers.size === 0;\n this.#subscribers.add(onChange);\n this.#pendingRemove = false;\n // Writes that landed while unregistered must not be lost.\n if (wasUnsubscribed && this.#version !== this.host.version()) {\n this.#dirty = true;\n }\n this.host.register(this);\n return () => {\n this.#subscribers.delete(onChange);\n if (this.#subscribers.size > 0 || this.#pendingRemove) {\n return;\n }\n this.#pendingRemove = true;\n queueMicrotask(() => {\n if (this.#pendingRemove) {\n this.host.unregister(this);\n }\n });\n };\n }\n\n getSnapshot(): T {\n if (this.subscribed ? this.#dirty : this.#version !== this.host.version()) {\n const next = this.compute();\n if (!this.equal(next, this.#snapshot)) {\n this.#snapshot = next;\n this.host.repin(this);\n }\n this.#version = this.host.version();\n this.#dirty = false;\n }\n return this.#snapshot;\n }\n\n markDirty(): void {\n this.#dirty = true;\n }\n\n notify(): void {\n // Do not sync #version here: subscribers will call getSnapshot, which\n // recomputes against the bumped store version and keeps the reference\n // when the result is element-wise identical. A throwing subscriber is\n // reported and never aborts the remaining notifications.\n for (const onChange of this.#subscribers) {\n invokeSafely(onChange);\n }\n }\n}\n\nconst EVENT_IDS = (e: Event | undefined): ReadonlyArray<string> => (e ? [e.id] : []);\nconst LIST_IDS = (events: ReadonlyArray<Event>): ReadonlyArray<string> => events.map((e) => e.id);\n\nfunction sameRef(a: Event | undefined, b: Event | undefined): boolean {\n return a === b;\n}\n\nfunction sameList(a: ReadonlyArray<Event>, b: ReadonlyArray<Event>): boolean {\n if (a.length !== b.length) {\n return false;\n }\n for (let i = 0; i < a.length; i++) {\n if (a[i] !== b[i]) {\n return false;\n }\n }\n return true;\n}\n\n/**\n * Synchronous reactive in-memory event store backed by {@link MemoryIndex}. The single read source\n * for Nostr events on the UI side: `Watch` snapshots keep referential stability for\n * `useSyncExternalStore` and are invalidated on matching writes, deletions and tombstones. Writes\n * batch notifications into one microtask flush.\n */\nexport class ReactiveEventStore {\n readonly #index: MemoryIndex;\n readonly #maxEvents: number;\n readonly #maxSeenOnEntries: number;\n\n #version = 0;\n\n // id -> recency-ordered access order (delete+add moves to the newest end)\n readonly #recency = new Set<string>();\n // id -> relay urls (≤ SEEN_ON_PER_ID); Map insertion order for eviction\n readonly #seenOn = new Map<string, string[]>();\n\n readonly #idWatches = new Map<string, Set<WatchHandle>>();\n readonly #addressWatches = new Map<string, Set<WatchHandle>>();\n readonly #eventCache = new Map<string, WatchImpl<Event | undefined>>();\n readonly #addressCache = new Map<string, WatchImpl<Event | undefined>>();\n readonly #queryCache = new Map<string, WatchImpl<ReadonlyArray<Event>>>();\n // Query watches indexed by filter kinds; a filter without kinds matches any kind.\n readonly #queryByKind = new Map<number, Set<WatchHandle>>();\n readonly #queryAnyKind = new Set<WatchHandle>();\n\n // Event-id → pin refcount across registered watch snapshots; eviction skips pinned ids.\n readonly #pinned = new Map<string, number>();\n // Registered watch → ids it currently pins (repin/unpin need the old set).\n readonly #watchPins = new Map<WatchHandle, ReadonlyArray<string>>();\n\n readonly #dirty = new Set<WatchHandle>();\n #flushScheduled = false;\n readonly #events = new Emitter<ReactiveEventStoreEventMap>();\n\n readonly #watchHost: WatchHost = {\n version: () => this.#version,\n register: (watch) => {\n this.#registerWatch(watch);\n if (!this.#watchPins.has(watch)) {\n const ids = watch.pinnedIds;\n this.#watchPins.set(watch, ids);\n this.#pin(ids);\n }\n },\n unregister: (watch) => {\n if (watch.subscribed) {\n return;\n }\n this.#unregisterWatch(watch);\n const ids = this.#watchPins.get(watch);\n if (ids !== undefined) {\n this.#watchPins.delete(watch);\n this.#unpin(ids);\n }\n },\n repin: (watch) => {\n const old = this.#watchPins.get(watch);\n if (old === undefined) {\n return;\n }\n const next = watch.pinnedIds;\n this.#unpin(old);\n this.#pin(next);\n this.#watchPins.set(watch, next);\n },\n };\n\n constructor(opts?: ReactiveEventStoreOptions) {\n this.#maxEvents = opts?.maxEvents ?? 50_000;\n this.#maxSeenOnEntries = opts?.maxSeenOnEntries ?? 20_000;\n this.#index = new MemoryIndex({\n maxTombstones: opts?.maxTombstones ?? 100_000,\n // Keep evicted replaceable winners rejectable while their body is gone.\n maxWatermarks: this.#maxEvents,\n onInsert: (event) => this.#onInsert(event),\n onRemove: (event) => this.#onRemove(event),\n });\n }\n\n add(event: Event, relayUrl?: string): PutResult {\n const result = this.#index.put(event);\n // Stored events are canonical; seenOn keys are their lowercase ids.\n const { id } = event;\n if (\n relayUrl !== undefined &&\n result !== \"ephemeral\" &&\n result !== \"rejected\" &&\n result !== \"invalid\"\n ) {\n this.#recordSeen(id, relayUrl);\n }\n // The just-inserted event must survive its own eviction pass even when\n // every older entry is pinned by a subscribed watch snapshot.\n this.#evictIfNeeded(id);\n return result;\n }\n\n addMany(events: ReadonlyArray<Event>, relayUrl?: string): PutResult[] {\n const results: PutResult[] = [];\n for (const event of events) {\n results.push(this.add(event, relayUrl));\n }\n return results;\n }\n\n /** Record a relay sighting for an id already in the index. No notification. */\n markSeen(id: string, relayUrl: string): boolean {\n const key = id.toLowerCase();\n if (this.#index.get(key) === undefined) {\n return false;\n }\n this.#recordSeen(key, relayUrl);\n return true;\n }\n\n /** Bulk load (initial hydration): no seenOn, one batched notification. */\n hydrate(events: ReadonlyArray<Event>): void {\n // Insert oldest-first so recency ends with the newest entries hottest.\n const sorted = [...events].sort(itemCompare);\n for (const event of sorted) {\n this.#index.put(event);\n }\n this.#evictIfNeeded();\n }\n\n remove(ids: ReadonlyArray<string>): number {\n return this.#index.remove(ids);\n }\n\n clear(): void {\n this.#index.clear();\n this.#seenOn.clear();\n this.#recency.clear();\n this.#version += 1;\n this.#invalidateAll();\n }\n\n get(id: string): Event | undefined {\n const event = this.#index.get(id);\n if (event) {\n this.#touch(event.id);\n }\n return event;\n }\n\n getReplaceable(kind: number, pubkey: string, d?: string): Event | undefined {\n return this.getByAddress(formatEventAddress(kind, pubkey, d ?? \"\"));\n }\n\n getByAddress(address: string): Event | undefined {\n const event = this.#index.getByAddress(address);\n if (event) {\n this.#touch(event.id);\n }\n return event;\n }\n\n query(filters: ReadonlyArray<Filter>): ReadonlyArray<Event> {\n const events = this.#index.query(filters);\n // Results come back newest-first; touch oldest→newest so the newest\n // entries end up hottest in the recency order.\n for (const event of [...events].reverse()) {\n this.#touch(event.id);\n }\n return events;\n }\n\n isDeleted(idOrAddress: string): boolean {\n return this.#index.isDeleted(idOrAddress);\n }\n\n seenOn(id: string): ReadonlyArray<string> {\n return this.#seenOn.get(id.toLowerCase()) ?? [];\n }\n\n get size(): number {\n return this.#index.size;\n }\n\n watchEvent(id: string): Watch<Event | undefined> {\n const key = id.toLowerCase();\n let watch = this.#eventCache.get(key);\n if (!watch) {\n watch = new WatchImpl(this.#watchHost, \"event\", key, () => this.get(key), sameRef, EVENT_IDS);\n this.#eventCache.set(key, watch);\n }\n return watch;\n }\n\n watchReplaceable(kind: number, pubkey: string, d?: string): Watch<Event | undefined> {\n const address = formatEventAddress(kind, pubkey, d ?? \"\");\n let watch = this.#addressCache.get(address);\n if (!watch) {\n watch = new WatchImpl(\n this.#watchHost,\n \"replaceable\",\n address,\n () => this.getByAddress(address),\n sameRef,\n EVENT_IDS,\n );\n this.#addressCache.set(address, watch);\n }\n return watch;\n }\n\n watchQuery(filters: ReadonlyArray<Filter>): Watch<ReadonlyArray<Event>> {\n const key = filterFingerprint(filters);\n let watch = this.#queryCache.get(key);\n if (!watch) {\n const list = [...filters];\n watch = new WatchImpl(\n this.#watchHost,\n \"query\",\n key,\n () => this.query(list),\n sameList,\n LIST_IDS,\n list,\n );\n this.#queryCache.set(key, watch);\n }\n return watch;\n }\n\n /**\n * Listen for a store event. Multiple listeners per type are allowed and fire in registration\n * order; a throwing listener is reported and does not break the dispatch. Returns an unsubscribe\n * function.\n */\n on<K extends keyof ReactiveEventStoreEventMap>(\n type: K,\n listener: (payload: ReactiveEventStoreEventMap[K]) => void,\n ): () => void {\n return this.#events.on(type, listener);\n }\n\n #registerWatch(watch: WatchHandle): void {\n switch (watch.kind) {\n case \"event\":\n addToSetMap(this.#idWatches, watch.key, watch);\n return;\n case \"replaceable\":\n addToSetMap(this.#addressWatches, watch.key, watch);\n return;\n case \"query\": {\n const kinds = this.#queryKinds(watch);\n if (kinds === undefined) {\n this.#queryAnyKind.add(watch);\n return;\n }\n for (const kind of kinds) {\n addToSetMap(this.#queryByKind, kind, watch);\n }\n }\n }\n }\n\n #unregisterWatch(watch: WatchHandle): void {\n switch (watch.kind) {\n case \"event\":\n removeFromSetMap(this.#idWatches, watch.key, watch);\n if (this.#eventCache.get(watch.key) === watch) {\n this.#eventCache.delete(watch.key);\n }\n return;\n case \"replaceable\":\n removeFromSetMap(this.#addressWatches, watch.key, watch);\n if (this.#addressCache.get(watch.key) === watch) {\n this.#addressCache.delete(watch.key);\n }\n return;\n case \"query\": {\n const kinds = this.#queryKinds(watch);\n if (kinds === undefined) {\n this.#queryAnyKind.delete(watch);\n } else {\n for (const kind of kinds) {\n removeFromSetMap(this.#queryByKind, kind, watch);\n }\n }\n if (this.#queryCache.get(watch.key) === watch) {\n this.#queryCache.delete(watch.key);\n }\n }\n }\n }\n\n /**\n * Kind buckets a query watch belongs to (deduped across filters), or undefined when any filter is\n * `kinds`-unconstrained and so can match every kind.\n */\n #queryKinds(watch: WatchHandle): Set<number> | undefined {\n const kinds = new Set<number>();\n for (const raw of watch.filters ?? []) {\n const filter = canonicalizeFilter(raw);\n if (filter.kinds === undefined) {\n return undefined;\n }\n for (const kind of filter.kinds) {\n kinds.add(kind);\n }\n }\n return kinds;\n }\n\n #touch(id: string): void {\n this.#recency.delete(id);\n this.#recency.add(id);\n }\n\n #recordSeen(id: string, relayUrl: string): void {\n let url: string;\n try {\n url = normalizeURL(relayUrl);\n } catch {\n // A malformed relay label is metadata only: skip the seen-on record\n // instead of throwing after the event was already indexed.\n return;\n }\n let urls = this.#seenOn.get(id);\n if (!urls) {\n urls = [];\n this.#seenOn.set(id, urls);\n }\n if (!urls.includes(url) && urls.length < SEEN_ON_PER_ID) {\n urls.push(url);\n }\n trimOldest(this.#seenOn, this.#maxSeenOnEntries);\n }\n\n #onInsert(event: Event): void {\n this.#version += 1;\n this.#touch(event.id);\n this.#invalidateByEvent(event);\n this.#events.emit(\"insert\", event);\n }\n\n #onRemove(event: Event): void {\n this.#version += 1;\n this.#recency.delete(event.id);\n this.#invalidateByEvent(event);\n this.#events.emit(\"remove\", event);\n }\n\n #invalidateByEvent(event: Event): void {\n const byId = this.#idWatches.get(event.id);\n if (byId) {\n for (const watch of byId) {\n this.#markDirty(watch);\n }\n }\n const address = eventAddress(event);\n if (address !== undefined) {\n const byAddress = this.#addressWatches.get(address);\n if (byAddress) {\n for (const watch of byAddress) {\n this.#markDirty(watch);\n }\n }\n }\n // A kind-5 deletion marks its targets tombstoned even when they are not in\n // the index, so nothing else invalidates a watch keyed on the target.\n if (event.kind === Kind.EventDeletion) {\n for (const tag of event.tags) {\n if (tag[0] === \"e\" && tag[1] !== undefined) {\n const watches = this.#idWatches.get(tag[1].toLowerCase());\n if (watches) {\n for (const watch of watches) {\n this.#markDirty(watch);\n }\n }\n } else if (tag[0] === \"a\" && tag[1] !== undefined) {\n const coord = parseEventAddress(tag[1]);\n if (coord === undefined) {\n continue;\n }\n const watches = this.#addressWatches.get(\n formatEventAddress(coord.kind, coord.pubkey, coord.identifier),\n );\n if (watches) {\n for (const watch of watches) {\n this.#markDirty(watch);\n }\n }\n }\n }\n }\n // Candidates are the event's kind bucket plus any-kind watches — a watch sits\n // in at most one bucket per kind, so it is checked at most once per event.\n const byKind = this.#queryByKind.get(event.kind);\n if (byKind) {\n for (const watch of byKind) {\n if (watch.filters !== undefined && matchFilters(watch.filters, event)) {\n this.#markDirty(watch);\n }\n }\n }\n for (const watch of this.#queryAnyKind) {\n if (watch.filters !== undefined && matchFilters(watch.filters, event)) {\n this.#markDirty(watch);\n }\n }\n }\n\n #invalidateAll(): void {\n for (const watches of this.#idWatches.values()) {\n for (const watch of watches) {\n this.#markDirty(watch);\n }\n }\n for (const watches of this.#addressWatches.values()) {\n for (const watch of watches) {\n this.#markDirty(watch);\n }\n }\n for (const watches of this.#queryByKind.values()) {\n for (const watch of watches) {\n this.#markDirty(watch);\n }\n }\n for (const watch of this.#queryAnyKind) {\n this.#markDirty(watch);\n }\n this.#scheduleFlush();\n }\n\n #markDirty(watch: WatchHandle): void {\n watch.markDirty();\n this.#dirty.add(watch);\n this.#scheduleFlush();\n }\n\n #scheduleFlush(): void {\n if (this.#flushScheduled) {\n return;\n }\n this.#flushScheduled = true;\n queueMicrotask(() => {\n // Keep the flag set while flushing: writes re-entered from a subscriber\n // land in #dirty and are notified in the same pass. Bound the passes so an\n // always-writing subscriber cannot starve the microtask queue forever;\n // overflow continues on the next turn after a loud report.\n let passes = 0;\n while (this.#dirty.size > 0) {\n const batch = [...this.#dirty];\n this.#dirty.clear();\n for (const watch of batch) {\n watch.notify();\n }\n passes += 1;\n if (passes >= MAX_FLUSH_PASSES && this.#dirty.size > 0) {\n this.#flushScheduled = false;\n reportError(new Error(\"ReactiveEventStore flush overflow: a subscriber keeps writing\"));\n this.#scheduleFlush();\n return;\n }\n }\n this.#flushScheduled = false;\n });\n }\n\n #pin(ids: ReadonlyArray<string>): void {\n for (const id of ids) {\n this.#pinned.set(id, (this.#pinned.get(id) ?? 0) + 1);\n }\n }\n\n #unpin(ids: ReadonlyArray<string>): void {\n for (const id of ids) {\n const count = (this.#pinned.get(id) ?? 0) - 1;\n if (count <= 0) {\n this.#pinned.delete(id);\n } else {\n this.#pinned.set(id, count);\n }\n }\n }\n\n #evictIfNeeded(protectedId?: string): void {\n if (this.#index.size <= this.#maxEvents) {\n return;\n }\n const evicting: string[] = [];\n for (const id of this.#recency) {\n if (this.#index.size - evicting.length <= this.#maxEvents) {\n break;\n }\n if (this.#pinned.has(id) || id === protectedId) {\n continue;\n }\n const event = this.#index.get(id);\n if (event === undefined) {\n continue;\n }\n // Replaceable/addressable winners evict like any other event; the index\n // records a watermark so stale versions stay rejected afterwards.\n evicting.push(id);\n }\n this.#index.evict(evicting);\n for (const id of evicting) {\n this.#recency.delete(id);\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as verifyEvent } from "./verifier-
|
|
3
|
-
import { n as canonicalizeFilters, r as filterFingerprint, s as matchFilters, t as canonicalizeFilter } from "./filter-
|
|
4
|
-
import { n as createSubscriptionId, r as encodeClientMessage, s as parseRelayMessage, t as assertSubscriptionId } from "./message-
|
|
5
|
-
import { t as NoSignerError } from "./error-
|
|
1
|
+
import { C as MessageError, D as toError, m as nowSeconds, p as normalizeURL, w as NostrError } from "./util-BcRBO6o-.mjs";
|
|
2
|
+
import { n as verifyEvent } from "./verifier-DBSPHzGg.mjs";
|
|
3
|
+
import { n as canonicalizeFilters, r as filterFingerprint, s as matchFilters, t as canonicalizeFilter } from "./filter-BbShkmc4.mjs";
|
|
4
|
+
import { n as createSubscriptionId, r as encodeClientMessage, s as parseRelayMessage, t as assertSubscriptionId } from "./message-D6k16gtg.mjs";
|
|
5
|
+
import { t as NoSignerError } from "./error-CoP2myne.mjs";
|
|
6
6
|
import { i as throwIfAborted, n as onAbort, r as raceSignal, t as abortReason } from "./abort-DBq2STPj.mjs";
|
|
7
7
|
import { t as invokeSafely } from "./report-D2BM5dgn.mjs";
|
|
8
8
|
import { isAuthRequired, makeAuthEvent } from "./nips/nip42.mjs";
|
|
@@ -1829,4 +1829,4 @@ var Relay = class Relay {
|
|
|
1829
1829
|
//#endregion
|
|
1830
1830
|
export { RelayClosedError as a, RelayPublishError as c, subscriptionToAsyncIterable as i, RelaySuspendedError as l, Relay as n, RelayConnectionError as o, RelayStatus as r, RelayError as s, DEFAULT_REQUEST_TIMEOUT_MS as t, RelayTimeoutError as u };
|
|
1831
1831
|
|
|
1832
|
-
//# sourceMappingURL=relay-
|
|
1832
|
+
//# sourceMappingURL=relay-CbgToreY.mjs.map
|