@qntx/nostr 0.13.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-YID-zPDF.d.mts → index-DYk24u9o.d.mts} +24 -18
- 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-DJcmC4nA.mjs → signer-DQzjm3pt.mjs} +119 -77
- 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-DJcmC4nA.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 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"client-COkEihj_.mjs","names":[],"sources":["../src/client/dm.ts","../src/client/types.ts","../src/client/gossip-io.ts","../src/client/sync.ts","../src/client/client.ts"],"sourcesContent":["import { onAbort, throwIfAborted } from \"../core/abort.ts\";\nimport type { EventBuilder } from \"../core/builder.ts\";\nimport type { Event } from \"../core/event.ts\";\nimport { itemCompare } from \"../core/event.ts\";\nimport { Kind } from \"../core/kind.ts\";\nimport { SerialQueue } from \"../core/serial.ts\";\nimport type { Gossip } from \"../gossip/gossip.ts\";\nimport {\n Nip17Error,\n buildChatMessageRumor,\n dmRelayListEventBuilder,\n normalizeRecipients,\n requireDmRelays,\n wrapDirectMessage,\n} from \"../nips/nip17.ts\";\nimport type { Recipient } from \"../nips/nip17.ts\";\nimport { unwrap } from \"../nips/nip59.ts\";\nimport type { Nip59Crypto } from \"../nips/nip59.ts\";\nimport type { Pool, PoolPublishResult } from \"../relay/pool.ts\";\nimport type { Closer } from \"../relay/subscription.ts\";\nimport type {\n FetchPrivateMessagesOptions,\n PrivateMessageSendResult,\n PublishOptions,\n ReceivedPrivateMessage,\n SendPrivateMessageOptions,\n SubscribePrivateMessagesOptions,\n} from \"./types.ts\";\n\nexport type DmDeps = {\n pool: Pool;\n gossip: Gossip;\n hydrateGossip: (pubkeys: ReadonlyArray<string>) => Promise<void>;\n ingest: (event: Event, relayUrl?: string) => void;\n markSeen: (id: string, relayUrl: string) => void;\n assertAlive: () => void;\n requireNip59Crypto: () => Nip59Crypto;\n publish: (\n eventOrBuilder: Event | EventBuilder,\n opts?: PublishOptions,\n ) => Promise<PoolPublishResult[]>;\n};\n\nexport function giftWrapRelays(gossip: Gossip, event: Event): ReadonlyArray<string> {\n const targets: string[] = [];\n for (const tag of event.tags) {\n if (tag[0] === \"p\" && tag[1] !== undefined && tag[1] !== \"\") {\n targets.push(tag[1].toLowerCase());\n }\n }\n if (targets.length === 0) {\n throw new Nip17Error(\"gift wrap has no p-tag recipient\");\n }\n const [target] = targets;\n if (targets.length !== 1 || target === undefined) {\n throw new Nip17Error(\"gift wrap must have exactly one p-tag recipient\");\n }\n const relays = gossip.dmRelays(target);\n if (relays.length === 0) {\n throw new Nip17Error(\"no kind 10050 in gossip; use sendPrivateMessage or pass relays\");\n }\n return relays;\n}\n\nexport async function setDmRelays(\n deps: DmDeps,\n relays: ReadonlyArray<string>,\n opts?: PublishOptions,\n): Promise<PoolPublishResult[]> {\n return deps.publish(dmRelayListEventBuilder(relays), { gossip: true, ...opts });\n}\n\nexport async function sendPrivateMessage(\n deps: DmDeps,\n recipients: string | Recipient | ReadonlyArray<string | Recipient>,\n content: string,\n opts?: SendPrivateMessageOptions,\n): Promise<PrivateMessageSendResult> {\n deps.assertAlive();\n const crypto = deps.requireNip59Crypto();\n const sender = await crypto.getPublicKey();\n const list = normalizeRecipients(recipients);\n if (list.length === 0) {\n throw new Nip17Error(\"recipients must not be empty\");\n }\n\n const targets = new Set<string>([sender.toLowerCase(), ...list.map((r) => r.pubkey)]);\n await deps.hydrateGossip([...targets]);\n for (const pk of targets) {\n requireDmRelays(pk, deps.gossip.dmRelays(pk));\n }\n\n const rumor = buildChatMessageRumor(sender, list, content, {\n created_at: opts?.created_at,\n subject: opts?.subject,\n replyTo: opts?.replyTo,\n });\n const wraps = await wrapDirectMessage(crypto, list, rumor, {\n expiration: opts?.expiration,\n ephemeral: opts?.ephemeral,\n });\n const sent = await Promise.all(\n wraps.map(async ({ recipient, wrap }) => {\n const relays = requireDmRelays(recipient, deps.gossip.dmRelays(recipient));\n const results = await deps.pool.publish(relays, wrap, { timeoutMs: opts?.timeoutMs });\n if (results.some((r) => r.status === \"ok\") && opts?.observe !== false) {\n deps.ingest(wrap);\n }\n return { recipient, wrap, results };\n }),\n );\n return { rumor, wraps: sent };\n}\n\nexport async function fetchPrivateMessages(\n deps: DmDeps,\n opts?: FetchPrivateMessagesOptions,\n): Promise<ReceivedPrivateMessage[]> {\n deps.assertAlive();\n const crypto = deps.requireNip59Crypto();\n const self = await crypto.getPublicKey();\n throwIfAborted(opts?.signal);\n await deps.hydrateGossip([self]);\n throwIfAborted(opts?.signal);\n const relays = requireDmRelays(self, deps.gossip.dmRelays(self));\n const urls = new Map<string, Set<string>>();\n const events = await deps.pool.fetch(\n relays,\n [\n {\n kinds: [Kind.GiftWrap],\n \"#p\": [self],\n since: opts?.since,\n until: opts?.until,\n },\n ],\n {\n timeoutMs: opts?.timeoutMs,\n signal: opts?.signal,\n onEvent: (event, relayUrl) => {\n let set = urls.get(event.id);\n if (!set) {\n urls.set(event.id, (set = new Set()));\n }\n set.add(relayUrl);\n },\n },\n );\n\n const byRumor = new Map<string, ReceivedPrivateMessage>();\n for (const wrap of events) {\n try {\n // oxlint-disable-next-line eslint/no-await-in-loop -- unwraps run serially so ingest/seenOn keep relay order\n const rumor = await unwrap(crypto, wrap);\n const wrapUrls = urls.get(wrap.id);\n const firstUrl = wrapUrls?.values().next().value;\n if (opts?.observe !== false) {\n deps.ingest(wrap, firstUrl);\n // Every other relay that delivered the same wrap is recorded too.\n if (wrapUrls) {\n for (const url of wrapUrls) {\n if (url !== firstUrl) {\n deps.markSeen(wrap.id, url);\n }\n }\n }\n }\n byRumor.set(rumor.id, {\n wrap,\n rumor,\n ...(firstUrl === undefined ? {} : { relayUrl: firstUrl }),\n });\n } catch {\n // junk / forgery / key mismatch — not stored\n }\n }\n\n return [...byRumor.values()].sort((a, b) => itemCompare(a.rumor, b.rumor));\n}\n\nexport async function subscribePrivateMessages(\n deps: DmDeps,\n opts?: SubscribePrivateMessagesOptions,\n): Promise<Closer> {\n deps.assertAlive();\n const crypto = deps.requireNip59Crypto();\n const self = await crypto.getPublicKey();\n throwIfAborted(opts?.signal);\n await deps.hydrateGossip([self]);\n throwIfAborted(opts?.signal);\n const relays = requireDmRelays(self, deps.gossip.dmRelays(self));\n\n const seen = new Set<string>();\n // Wrap ids processed (observed) already; receipts before that are buffered\n // so every delivering relay URL lands in seenOn once the wrap is indexed.\n const processed = new Set<string>();\n const pendingUrls = new Map<string, string[]>();\n const flushSeen = (wrapId: string): void => {\n processed.add(wrapId);\n const extra = pendingUrls.get(wrapId);\n if (extra === undefined) {\n return;\n }\n pendingUrls.delete(wrapId);\n for (const url of extra) {\n deps.markSeen(wrapId, url);\n }\n };\n const queue = new SerialQueue();\n let closed = false;\n let disposeAbort: (() => void) | undefined;\n const markClosed = (): void => {\n closed = true;\n disposeAbort?.();\n };\n if (opts?.signal?.aborted === true) {\n markClosed();\n } else {\n disposeAbort = onAbort(opts?.signal, markClosed);\n }\n\n let inner: Closer;\n try {\n inner = deps.pool.subscribe(\n relays,\n // 21059 is ephemeral (relays MUST NOT store); live inbox has to REQ it.\n [{ kinds: [Kind.GiftWrap, Kind.GiftWrapEphemeral], \"#p\": [self], since: opts?.since }],\n {\n signal: opts?.signal,\n onEose: opts?.onEose,\n onClose: (reason) => {\n markClosed();\n opts?.onClose?.(reason);\n },\n eoseTimeoutMs: opts?.eoseTimeoutMs,\n onEventSeen: (id, relayUrl) => {\n if (closed) {\n return;\n }\n if (processed.has(id)) {\n deps.markSeen(id, relayUrl);\n return;\n }\n const list = pendingUrls.get(id);\n if (list === undefined) {\n pendingUrls.set(id, [relayUrl]);\n } else if (!list.includes(relayUrl)) {\n list.push(relayUrl);\n }\n },\n onEvent: (wrap, relayUrl) => {\n if (closed) {\n return;\n }\n void queue.run(async () => {\n try {\n if (closed) {\n return;\n }\n const rumor = await unwrap(crypto, wrap);\n if (closed) {\n return;\n }\n if (seen.has(rumor.id)) {\n flushSeen(wrap.id);\n return;\n }\n seen.add(rumor.id);\n if (opts?.observe !== false) {\n deps.ingest(wrap, relayUrl);\n }\n flushSeen(wrap.id);\n opts?.onEvent?.({ wrap, rumor, relayUrl });\n } catch {\n // junk / forgery — not stored; keep the queue alive if a handler\n // throws, and still mark the wrap processed so its buffered relay\n // receipts flush instead of accumulating in pendingUrls forever.\n flushSeen(wrap.id);\n }\n });\n },\n },\n );\n } catch (error) {\n // A synchronous subscribe throw must not leave the abort listener armed.\n markClosed();\n throw error;\n }\n\n return {\n close: (reason?: string) => {\n markClosed();\n inner.close(reason);\n },\n };\n}\n","import { NostrError } from \"../core/error.ts\";\nimport type { Event } from \"../core/event.ts\";\nimport type { Gossip } from \"../gossip/gossip.ts\";\nimport type { ReplyTo } from \"../nips/nip17.ts\";\nimport type { Rumor } from \"../nips/nip59.ts\";\nimport type { PoolOptions, PoolPublishResult } from \"../relay/pool.ts\";\nimport type { NostrSigner } from \"../signer/types.ts\";\nimport type { StorageError } from \"../storage/error.ts\";\nimport type { EventStore } from \"../storage/types.ts\";\nimport type { ReactiveEventStore } from \"../store/reactive.ts\";\n\n/** Direction of a {@link Client.sync} run: upload, download, or both. */\nexport const SyncDirection = {\n Up: \"up\",\n Down: \"down\",\n Both: \"both\",\n} as const;\n\n/** Union of the {@link SyncDirection} values. */\nexport type SyncDirectionName = (typeof SyncDirection)[keyof typeof SyncDirection];\n\n/** Options for {@link Client.sync} (NIP-77 set reconciliation). */\nexport type SyncOptions = {\n relays?: ReadonlyArray<string> | undefined;\n direction?: SyncDirectionName | undefined;\n /**\n * Wall-clock deadline for the Negentropy reconciliation session (`NEG-OPEN` through `NEG-CLOSE`),\n * in milliseconds. One clock for the whole session — not reset per `NEG-MSG`. Default: the relay\n * `publishTimeoutMs`. Upload/download phases reuse this value as their per-call timeout.\n */\n timeoutMs?: number | undefined;\n signal?: AbortSignal | undefined;\n dryRun?: boolean | undefined;\n /** When false, skip observe/storage on downloaded events. Default true. */\n observe?: boolean | undefined;\n};\n\n/** Outcome of a {@link Client.sync} run: ids compared, sent, received, and failures. */\nexport type SyncSummary = {\n readonly local: ReadonlyArray<string>;\n readonly remote: ReadonlyArray<string>;\n readonly sent: ReadonlyArray<string>;\n readonly received: ReadonlyArray<string>;\n /** Publish failures by event id: relay messages or thrown-error text. */\n readonly sendFailures: Readonly<Record<string, string>>;\n /** PutMany throws by event id. */\n readonly persistFailures: Readonly<Record<string, Error>>;\n};\n\n/**\n * Options for the {@link Client} constructor. Pool fields are forwarded to {@link Pool}; the\n * client-only fields are documented here.\n */\nexport type ClientOptions = Omit<PoolOptions, \"automaticallyAuth\"> & {\n signer?: NostrSigner | undefined;\n relays?: ReadonlyArray<string> | undefined;\n /**\n * When true (default), answer NIP-42 AUTH automatically. The signer is read at challenge time, so\n * `setSigner()` applies to live connections; challenges received while no signer is set are\n * ignored.\n */\n automaticAuth?: boolean | undefined;\n gossip?: Gossip | undefined;\n /**\n * Local event store. Defaults to {@link MemoryEventStore}. Browser apps that want persistence must\n * pass {@link IndexedDbEventStore} and `await open()`.\n */\n storage?: EventStore | undefined;\n /**\n * Synchronous reactive index that mirrors every ingested event before callbacks and persistence.\n * Defaults to a new {@link ReactiveEventStore}.\n */\n index?: ReactiveEventStore | undefined;\n /**\n * When true (default), every ingested event is written to storage. Set false to disable automatic\n * persistence while keeping the store for manual use.\n */\n persistEvents?: boolean | undefined;\n};\n\n/** Typed event payloads for {@link Client.on}. */\nexport type ClientEventMap = {\n /**\n * Storage I/O failures: live `putMany` flush and `fetchEvents({ localFirst: true })` query. Those\n * paths do not throw.\n */\n storageerror: StorageError;\n};\n\nexport type FetchEventsOptions = {\n relays?: ReadonlyArray<string> | undefined;\n timeoutMs?: number | undefined;\n signal?: AbortSignal | undefined;\n gossip?: boolean | undefined;\n /** When true, query local storage first and merge with network results. Defaults to false. */\n localFirst?: boolean | undefined;\n /** When false, skip writing fetched events to storage/observe. Default true. */\n observe?: boolean | undefined;\n /**\n * Every event of every relay batch, including duplicates across relays. `relayUrl` is the\n * normalized URL of the relay that delivered it.\n */\n onEvent?: ((event: Event, relayUrl: string) => void) | undefined;\n};\n\n/** Options for {@link Client.fetchEach}: one-shot per-relay fetch, no gossip routing. */\nexport type FetchEachOptions = {\n relays?: ReadonlyArray<string> | undefined;\n timeoutMs?: number | undefined;\n signal?: AbortSignal | undefined;\n /** When false, skip writing fetched events to storage/observe. Default true. */\n observe?: boolean | undefined;\n};\n\nexport type SubscribeOptions = {\n relays?: ReadonlyArray<string> | undefined;\n /**\n * First receipt of each event only (deduped across relays). `relayUrl` is the normalized URL of\n * the relay that delivered it first.\n */\n onEvent?: ((event: Event, relayUrl: string) => void) | undefined;\n /** Every receipt from every relay, including duplicates skipped by dedupe. */\n onEventSeen?: ((id: string, relayUrl: string) => void) | undefined;\n onEose?: (() => void) | undefined;\n onClose?: ((reason: string) => void) | undefined;\n signal?: AbortSignal | undefined;\n id?: string | undefined;\n /**\n * If set, fire `onEose` once after this many ms if not all relays have EOSEd. Does not close the\n * subscription.\n */\n eoseTimeoutMs?: number | undefined;\n /** Fan out REQs via NIP-65 gossip routes when available. */\n gossip?: boolean | undefined;\n /** When false, skip writing received events to storage/observe. Default true. */\n observe?: boolean | undefined;\n};\n\nexport type PublishOptions = {\n relays?: ReadonlyArray<string> | undefined;\n timeoutMs?: number | undefined;\n gossip?: boolean | undefined;\n /** When false, skip writing the published event to storage/observe. Default true. */\n observe?: boolean | undefined;\n};\n\n/** Options for sending a NIP-17 private message. */\nexport type SendPrivateMessageOptions = {\n readonly subject?: string | undefined;\n readonly replyTo?: ReplyTo | undefined;\n readonly created_at?: number | undefined;\n readonly timeoutMs?: number | undefined;\n readonly observe?: boolean | undefined;\n /** NIP-17 disappearing-message expiry (unix seconds) on the seal and the gift wrap. */\n readonly expiration?: number | undefined;\n /** Wrap in the ephemeral kind 21059 (relays MUST NOT store). */\n readonly ephemeral?: boolean | undefined;\n};\n\n/** Result of sending a NIP-17 DM: the rumor plus per-recipient wrap publish results. */\nexport type PrivateMessageSendResult = {\n readonly rumor: Rumor;\n readonly wraps: ReadonlyArray<{\n readonly recipient: string;\n readonly wrap: Event;\n readonly results: ReadonlyArray<PoolPublishResult>;\n }>;\n};\n\n/** An unwrapped NIP-17 private message: the received gift wrap and its inner rumor. */\nexport type ReceivedPrivateMessage = {\n readonly wrap: Event;\n readonly rumor: Rumor;\n /** Normalized URL of the relay that delivered the wrap, when known. */\n readonly relayUrl?: string | undefined;\n};\n\n/** Options for fetching NIP-17 private-message history. */\nexport type FetchPrivateMessagesOptions = {\n readonly since?: number | undefined;\n readonly until?: number | undefined;\n readonly timeoutMs?: number | undefined;\n readonly signal?: AbortSignal | undefined;\n readonly observe?: boolean | undefined;\n};\n\n/** Options for a live NIP-17 private-message subscription. */\nexport type SubscribePrivateMessagesOptions = {\n readonly since?: number | undefined;\n readonly onEvent?: ((msg: ReceivedPrivateMessage) => void) | undefined;\n readonly onEose?: (() => void) | undefined;\n readonly onClose?: ((reason: string) => void) | undefined;\n readonly signal?: AbortSignal | undefined;\n readonly eoseTimeoutMs?: number | undefined;\n readonly observe?: boolean | undefined;\n};\n\n/** Client lifecycle, configuration, or abort failure (not cryptographic). */\nexport class ClientError extends NostrError {\n override name = \"ClientError\";\n}\n","import type { Event } from \"../core/event.ts\";\nimport type { Filter } from \"../core/filter.ts\";\nimport type { Gossip } from \"../gossip/gossip.ts\";\nimport { fanIn, fetchRouted } from \"../relay/fan-in.ts\";\nimport type { FanInOptions, RoutedJob } from \"../relay/fan-in.ts\";\nimport type { Pool } from \"../relay/pool.ts\";\nimport type { Closer } from \"../relay/subscription.ts\";\nimport { ClientError } from \"./types.ts\";\n\n/** Remainder is one job on defaults; throw before any REQ when defaults are empty. */\nexport function jobsForFilters(\n gossip: Gossip,\n filters: ReadonlyArray<Filter>,\n defaultRelays: () => ReadonlyArray<string>,\n): RoutedJob[] {\n const routed = filters.map((f) => gossip.route(f));\n const needsDefaults = routed.some((r) => r.remainder !== undefined);\n const defaults = needsDefaults ? defaultRelays() : undefined;\n const jobs: RoutedJob[] = [];\n for (const r of routed) {\n for (const [url, sub] of r.perRelay) {\n jobs.push({ urls: [url], filters: [sub] });\n }\n if (r.remainder !== undefined) {\n if (defaults === undefined) {\n throw new ClientError(\"unrouted remainder without default relays\");\n }\n jobs.push({ urls: defaults, filters: [r.remainder] });\n }\n }\n return jobs;\n}\n\nexport async function fetchGossip(\n pool: Pool,\n gossip: Gossip,\n filters: ReadonlyArray<Filter>,\n defaultRelays: () => ReadonlyArray<string>,\n opts?: {\n timeoutMs?: number | undefined;\n signal?: AbortSignal | undefined;\n onEvent?: ((event: Event, relayUrl: string) => void) | undefined;\n },\n): Promise<Event[]> {\n return fetchRouted(pool, jobsForFilters(gossip, filters, defaultRelays), {\n timeoutMs: opts?.timeoutMs,\n signal: opts?.signal,\n onEvent: opts?.onEvent,\n });\n}\n\nexport function subscribeGossip(\n pool: Pool,\n gossip: Gossip,\n filters: ReadonlyArray<Filter>,\n defaultRelays: () => ReadonlyArray<string>,\n opts: FanInOptions,\n): Closer {\n const jobs = jobsForFilters(gossip, filters, defaultRelays);\n if (jobs.length === 0) {\n queueMicrotask(() => opts.onEose?.());\n return {\n close: () => {\n /* empty */\n },\n };\n }\n return fanIn(pool, jobs, opts);\n}\n","import { throwIfAborted } from \"../core/abort.ts\";\nimport { errorMessage, toError } from \"../core/error.ts\";\nimport type { Event } from \"../core/event.ts\";\nimport { canonicalizeFilter } from \"../core/filter.ts\";\nimport type { Filter } from \"../core/filter.ts\";\nimport { storageFromItems } from \"../nips/nip77.ts\";\nimport type { NegentropyStorageVector } from \"../nips/nip77.ts\";\nimport type { Pool } from \"../relay/pool.ts\";\nimport type { EventStore, PutResult } from \"../storage/types.ts\";\nimport { SyncDirection } from \"./types.ts\";\nimport type { SyncOptions, SyncSummary } from \"./types.ts\";\n\nexport type SyncDeps = {\n pool: Pool;\n storage: EventStore;\n persistEvents: boolean;\n assertAlive: () => void;\n /**\n * The client's single ingest path: index (with relay URL) → meta → persistence. `persist: false`\n * when this function already wrote storage via an awaited `putMany`.\n */\n ingest: (event: Event, relayUrl?: string, opts?: { persist?: boolean }) => void;\n /** Record a relay sighting for an id already in the index. */\n markSeen: (id: string, relayUrl: string) => void;\n defaultRelays: (urls?: ReadonlyArray<string>) => ReadonlyArray<string>;\n};\n\nconst SYNC_ID_BATCH = 100;\nconst SYNC_UPLOAD_CONCURRENCY = 8;\n\nfunction uniqueIds(ids: ReadonlyArray<string>): string[] {\n return [...new Set(ids)];\n}\n\nfunction emptySummary(): SyncSummary {\n return {\n local: [],\n remote: [],\n sent: [],\n received: [],\n sendFailures: {},\n persistFailures: {},\n };\n}\n\nfunction mergeSyncSummary(into: SyncSummary, other: SyncSummary): SyncSummary {\n const sendFailures = { ...into.sendFailures, ...other.sendFailures };\n const persistFailures = { ...into.persistFailures, ...other.persistFailures };\n return {\n local: uniqueIds([...into.local, ...other.local]),\n remote: uniqueIds([...into.remote, ...other.remote]),\n sent: uniqueIds([...into.sent, ...other.sent]),\n received: uniqueIds([...into.received, ...other.received]),\n sendFailures,\n persistFailures,\n };\n}\n\n/**\n * NIP-77 sync against one relay: reconcile, then optionally upload local-only events and/or\n * download remote-only events. `observe: false` skips putMany and ingest; received ids are still\n * listed. `persistEvents: false` skips putMany, still ingests when observe is on.\n */\nexport async function syncToRelay(\n deps: SyncDeps,\n url: string,\n filter: Filter,\n opts?: Omit<SyncOptions, \"relays\">,\n): Promise<SyncSummary> {\n deps.assertAlive();\n throwIfAborted(opts?.signal);\n const direction = opts?.direction ?? SyncDirection.Down;\n const canon = canonicalizeFilter(filter);\n const items = await deps.storage.negentropyItems(canon);\n const storage: NegentropyStorageVector = storageFromItems(items);\n const relay = await deps.pool.ensureRelay(url, { signal: opts?.signal });\n const { have, need } = await relay.negReconcile(canon, storage, {\n timeoutMs: opts?.timeoutMs,\n signal: opts?.signal,\n });\n\n const sent: string[] = [];\n const received: string[] = [];\n const sendFailures: Record<string, string> = {};\n const persistFailures: Record<string, Error> = {};\n const summary: SyncSummary = {\n local: have,\n remote: need,\n sent,\n received,\n sendFailures,\n persistFailures,\n };\n\n if (opts?.dryRun === true) {\n return summary;\n }\n\n if ((direction === SyncDirection.Up || direction === SyncDirection.Both) && have.length > 0) {\n const found = await deps.storage.query([{ ids: have }]);\n const foundById = new Map(found.map((event) => [event.id, event]));\n for (const id of have) {\n if (!foundById.has(id)) {\n sendFailures[id] = \"event not found in local store\";\n }\n }\n for (let i = 0; i < found.length; i += SYNC_UPLOAD_CONCURRENCY) {\n const chunk = found.slice(i, i + SYNC_UPLOAD_CONCURRENCY);\n // Upload chunks are rate-limited serially.\n // oxlint-disable-next-line eslint/no-await-in-loop\n await Promise.all(\n chunk.map(async (event) => {\n try {\n const results = await deps.pool.publish([url], event, {\n timeoutMs: opts?.timeoutMs,\n });\n const ok = results.some((r) => r.status === \"ok\");\n if (ok) {\n sent.push(event.id);\n } else {\n const [first] = results;\n sendFailures[event.id] =\n first === undefined\n ? \"publish failed\"\n : first.status === \"failed\"\n ? errorMessage(first.error)\n : first.message;\n }\n } catch (error) {\n sendFailures[event.id] = errorMessage(error);\n }\n }),\n );\n }\n }\n\n if ((direction === SyncDirection.Down || direction === SyncDirection.Both) && need.length > 0) {\n const shouldObserve = opts?.observe !== false;\n for (let i = 0; i < need.length; i += SYNC_ID_BATCH) {\n const batch = need.slice(i, i + SYNC_ID_BATCH);\n throwIfAborted(opts?.signal);\n // Every relay that delivered an event is recorded once it is ingested.\n const urlsById = new Map<string, string[]>();\n // Id batches are fetched serially so backpressure stays bounded.\n // oxlint-disable-next-line eslint/no-await-in-loop\n const events = await deps.pool.fetch([url], [{ ids: batch }], {\n timeoutMs: opts?.timeoutMs,\n signal: opts?.signal,\n onEvent: shouldObserve\n ? (event, relayUrl) => {\n const urls = urlsById.get(event.id);\n if (urls === undefined) {\n urlsById.set(event.id, [relayUrl]);\n } else if (!urls.includes(relayUrl)) {\n urls.push(relayUrl);\n }\n }\n : undefined,\n });\n if (!shouldObserve) {\n for (const event of events) {\n received.push(event.id);\n }\n continue;\n }\n const ingestAll = (event: Event): void => {\n const urls = urlsById.get(event.id) ?? [];\n deps.ingest(event, urls[0], { persist: false });\n for (const url of urls.slice(1)) {\n deps.markSeen(event.id, url);\n }\n };\n if (!deps.persistEvents) {\n for (const event of events) {\n ingestAll(event);\n received.push(event.id);\n }\n continue;\n }\n // The awaited putMany reports persistFailures; accepted events then go\n // through the ingest path with persistence already done.\n let results: PutResult[];\n try {\n // Persisted serially per batch so failures land on the right ids.\n // oxlint-disable-next-line eslint/no-await-in-loop\n results = await deps.storage.putMany(events);\n } catch (error) {\n const thrown = toError(error);\n for (const event of events) {\n persistFailures[event.id] = thrown;\n }\n break;\n }\n for (const [j, event] of events.entries()) {\n if (results[j] === \"rejected\" || results[j] === \"invalid\") {\n continue;\n }\n ingestAll(event);\n received.push(event.id);\n }\n }\n }\n\n return summary;\n}\n\n/**\n * NIP-77 sync against the given relays (or Client default relays). Independent sessions run in\n * parallel. Fulfilled summaries are merged; if every relay rejects, throws the first rejection in\n * URL order.\n */\nexport async function sync(\n deps: SyncDeps,\n filter: Filter,\n opts?: SyncOptions,\n): Promise<SyncSummary> {\n deps.assertAlive();\n const urls = deps.defaultRelays(opts?.relays);\n const results = await Promise.allSettled(\n urls.map(async (url) => syncToRelay(deps, url, filter, opts)),\n );\n let merged = emptySummary();\n let fulfilled = 0;\n let firstRejection: unknown;\n for (const result of results) {\n if (result.status === \"fulfilled\") {\n fulfilled += 1;\n merged = mergeSyncSummary(merged, result.value);\n } else {\n firstRejection ??= result.reason;\n }\n }\n if (urls.length > 0 && fulfilled === 0) {\n throw firstRejection;\n }\n return merged;\n}\n","import { EventBuilder } from \"../core/builder.ts\";\nimport { Emitter } from \"../core/emitter.ts\";\nimport { EventValidationError } from \"../core/error.ts\";\nimport { sortedEvents } from \"../core/event.ts\";\nimport type { Event, EventTemplate, UnsignedEvent } from \"../core/event.ts\";\nimport { canonicalizeFilters, matchFilters } from \"../core/filter.ts\";\nimport type { Filter } from \"../core/filter.ts\";\nimport { normalizeURL } from \"../core/util.ts\";\nimport { verifyEvent } from \"../core/verifier.ts\";\nimport { Gossip } from \"../gossip/index.ts\";\nimport { createLoaders, OutboxFeed } from \"../loaders/index.ts\";\nimport type { Loaders } from \"../loaders/index.ts\";\nimport type { Recipient } from \"../nips/nip17.ts\";\nimport { isGiftWrapKind, requireNip59Crypto } from \"../nips/nip59.ts\";\nimport type { Nip59Crypto } from \"../nips/nip59.ts\";\nimport { Pool } from \"../relay/pool.ts\";\nimport type { PoolFetchResult, PoolPublishResult } from \"../relay/pool.ts\";\nimport type { Closer } from \"../relay/subscription.ts\";\nimport { NoSignerError } from \"../signer/error.ts\";\nimport type { NostrSigner } from \"../signer/types.ts\";\nimport { toStorageError } from \"../storage/error.ts\";\nimport { MemoryIndex } from \"../storage/memory-index.ts\";\nimport { MemoryEventStore } from \"../storage/memory.ts\";\nimport type { EventStore } from \"../storage/types.ts\";\nimport { ReactiveEventStore } from \"../store/reactive.ts\";\nimport {\n fetchPrivateMessages,\n giftWrapRelays,\n sendPrivateMessage,\n setDmRelays,\n subscribePrivateMessages,\n} from \"./dm.ts\";\nimport type { DmDeps } from \"./dm.ts\";\nimport { fetchGossip, subscribeGossip } from \"./gossip-io.ts\";\nimport { sync, syncToRelay } from \"./sync.ts\";\nimport type { SyncDeps } from \"./sync.ts\";\nimport { ClientError } from \"./types.ts\";\nimport type {\n ClientEventMap,\n ClientOptions,\n FetchEachOptions,\n FetchEventsOptions,\n FetchPrivateMessagesOptions,\n PrivateMessageSendResult,\n PublishOptions,\n ReceivedPrivateMessage,\n SendPrivateMessageOptions,\n SubscribeOptions,\n SubscribePrivateMessagesOptions,\n SyncOptions,\n SyncSummary,\n} from \"./types.ts\";\n\nfunction isFilterList(filter: Filter | ReadonlyArray<Filter>): filter is ReadonlyArray<Filter> {\n return Array.isArray(filter);\n}\n\nfunction toFilterList(filter: Filter | ReadonlyArray<Filter>): Filter[] {\n return canonicalizeFilters(isFilterList(filter) ? filter : [filter]);\n}\n\n/** Layer-5 facade: signer + default relays + pool + loaders + gossip + event store. */\nexport class Client {\n readonly pool: Pool;\n readonly loaders: Loaders;\n readonly gossip: Gossip;\n readonly storage: EventStore;\n readonly index: ReactiveEventStore;\n #signer: NostrSigner | undefined;\n #relays: string[];\n #shutdown = false;\n readonly #persistEvents: boolean;\n readonly #persistQueue: Event[] = [];\n #flushing: Promise<void> | undefined;\n readonly #events = new Emitter<ClientEventMap>();\n readonly #dmDeps: DmDeps;\n readonly #syncDeps: SyncDeps;\n\n constructor(opts: ClientOptions = {}) {\n const { signer, relays, automaticAuth, gossip, storage, index, persistEvents, ...poolOptions } =\n opts;\n this.#signer = signer;\n this.#relays = [];\n for (const raw of relays ?? []) {\n const url = normalizeURL(raw);\n if (!this.#relays.includes(url)) {\n this.#relays.push(url);\n }\n }\n this.gossip = gossip ?? new Gossip();\n this.storage = storage ?? new MemoryEventStore();\n this.index = index ?? new ReactiveEventStore();\n this.#persistEvents = persistEvents ?? true;\n this.pool = new Pool({\n ...poolOptions,\n enableReconnect: poolOptions.enableReconnect ?? true,\n // The sign function resolves the signer at challenge time, so\n // setSigner() takes effect on already-connected relays; challenges\n // without a signer are ignored by the relay (NoSignerError).\n automaticallyAuth:\n (automaticAuth ?? true)\n ? () => async (template) => {\n const signer = this.#signer;\n if (!signer) {\n throw new NoSignerError(\"no signer configured for AUTH\");\n }\n const pk = await signer.getPublicKey();\n return signer.signEvent({ ...template, pubkey: pk });\n }\n : undefined,\n });\n this.loaders = createLoaders({\n pool: this.pool,\n relays: this.#relays,\n index: this.index,\n ingest: (event, relayUrl) => this.#ingest(event, relayUrl),\n });\n this.#dmDeps = {\n pool: this.pool,\n gossip: this.gossip,\n hydrateGossip: async (pubkeys) => this.hydrateGossip(pubkeys),\n ingest: (event, relayUrl) => this.#ingest(event, relayUrl),\n markSeen: (id, relayUrl) => {\n this.index.markSeen(id, relayUrl);\n },\n assertAlive: () => this.#assertAlive(),\n requireNip59Crypto: () => this.#requireNip59Crypto(),\n publish: async (eventOrBuilder, opts) => this.publish(eventOrBuilder, opts),\n };\n this.#syncDeps = {\n pool: this.pool,\n storage: this.storage,\n persistEvents: this.#persistEvents,\n assertAlive: () => this.#assertAlive(),\n ingest: (event, relayUrl, opts) => this.#ingest(event, relayUrl, opts),\n markSeen: (id, relayUrl) => {\n this.index.markSeen(id, relayUrl);\n },\n defaultRelays: (urls) => this.#defaultRelays(urls),\n };\n }\n\n get signer(): NostrSigner | undefined {\n return this.#signer;\n }\n\n get relays(): ReadonlyArray<string> {\n return this.#relays;\n }\n\n get isShutdown(): boolean {\n return this.#shutdown;\n }\n\n setSigner(signer: NostrSigner | undefined): void {\n this.#signer = signer;\n // Re-arm relays whose AUTH challenge was ignored or rejected.\n this.pool.resetAuth();\n }\n\n addRelay(url: string): void {\n const normalized = normalizeURL(url);\n if (!this.#relays.includes(normalized)) {\n this.#relays.push(normalized);\n this.loaders.addRelay(normalized);\n }\n }\n\n removeRelay(url: string): void {\n const normalized = normalizeURL(url);\n this.#relays = this.#relays.filter((r) => r !== normalized);\n this.loaders.removeRelay(normalized);\n this.pool.closeRelays([normalized]);\n }\n\n /** Connect all configured relays (best-effort; failures are ignored). */\n async connect(opts?: { signal?: AbortSignal | undefined }): Promise<void> {\n this.#assertAlive();\n await Promise.allSettled(\n this.#relays.map(async (url) => this.pool.ensureRelay(url, { signal: opts?.signal })),\n );\n }\n\n async shutdown(): Promise<void> {\n this.#shutdown = true;\n // Stop inbound events first: a live subscription would otherwise keep\n // refilling the persistence queue and the drain below would never settle.\n this.pool.close();\n while (this.#flushing !== undefined) {\n // oxlint-disable-next-line eslint/no-await-in-loop -- waits for the current flush to settle\n await this.#flushing;\n }\n }\n\n /**\n * Listen for a client 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 ClientEventMap>(\n type: K,\n listener: (err: ClientEventMap[K]) => void,\n ): () => void {\n return this.#events.on(type, listener);\n }\n\n #assertAlive(): void {\n if (this.#shutdown) {\n throw new ClientError(\"client is shut down\");\n }\n }\n\n #defaultRelays(urls?: ReadonlyArray<string>): ReadonlyArray<string> {\n const list = urls ?? this.#relays;\n if (list.length === 0) {\n throw new ClientError(\"no relays configured\");\n }\n return list;\n }\n\n /**\n * Ingest one inbound event: index (with relay URL) → meta → persistence. The event must carry a\n * valid signature; {@link EventValidationError} is thrown otherwise. Events delivered by relay\n * subscriptions are already verified, so this is a WeakSet cache hit on those paths.\n */\n observe(event: Event, relayUrl?: string): void {\n if (!verifyEvent(event)) {\n throw new EventValidationError(\"observe requires a verified event\");\n }\n this.#ingest(event, relayUrl);\n }\n\n /**\n * Ingest many events (deduped by id, order preserved) as one persist batch. Every event must\n * carry a valid signature — all are verified before anything is ingested, so a batch is\n * all-or-nothing.\n */\n observeAll(events: ReadonlyArray<Event>): void {\n for (const event of events) {\n if (!verifyEvent(event)) {\n throw new EventValidationError(\"observe requires a verified event\");\n }\n }\n const seen = new Set<string>();\n for (const event of events) {\n if (seen.has(event.id)) {\n continue;\n }\n seen.add(event.id);\n this.#ingest(event);\n }\n }\n\n /**\n * The single inbound-event pipeline: reactive index add (with the source relay URL recorded in\n * seenOn), gossip routing meta, then the persistence decision. `persist: false` skips the storage\n * queue for callers that persist via their own awaited `putMany` (down-sync); `meta: false` is a\n * pure sighting/index add.\n */\n #ingest(event: Event, relayUrl?: string, opts?: { persist?: boolean; meta?: boolean }): void {\n this.index.add(event, relayUrl);\n if (opts?.meta !== false) {\n this.gossip.ingest(event);\n }\n if (opts?.persist !== false) {\n this.#queuePersist(event);\n }\n }\n\n /** Gossip + persistence for an event the index already holds — no redundant `index.add`. */\n #ingestMeta(event: Event): void {\n this.gossip.ingest(event);\n this.#queuePersist(event);\n }\n\n #queuePersist(event: Event): void {\n if (!this.#persistEvents || this.#shutdown) {\n return;\n }\n this.#persistQueue.push(event);\n this.#armFlush();\n }\n\n #armFlush(): void {\n if (this.#flushing) {\n return;\n }\n this.#flushing = this.#flushLoop();\n }\n\n async #flushLoop(): Promise<void> {\n try {\n await new Promise<void>((resolve) => {\n queueMicrotask(resolve);\n });\n while (this.#persistQueue.length > 0) {\n const batch = this.#persistQueue.splice(0);\n try {\n // oxlint-disable-next-line eslint/no-await-in-loop -- queued batches are persisted in order\n await this.storage.putMany(batch);\n } catch (error) {\n this.#events.emit(\"storageerror\", toStorageError(error));\n }\n }\n } finally {\n this.#flushing = undefined;\n if (this.#persistQueue.length > 0) {\n this.#armFlush();\n }\n }\n }\n\n /**\n * Explicitly load kind:10002 and kind:10050 lists for pubkeys and feed gossip. Does not\n * background-fetch unknown authors on every subscribe (predictable DX).\n */\n async hydrateGossip(\n pubkeys: ReadonlyArray<string>,\n opts?: { hints?: string[]; force?: boolean },\n ): Promise<void> {\n this.#assertAlive();\n await Promise.all(\n pubkeys.map(async (pk) => {\n const style = opts?.force === true ? \"force\" : \"default\";\n const [relayList, dmList] = await Promise.all([\n this.loaders.relayList(pk, { hints: opts?.hints, style }),\n this.loaders.dmRelayList(pk, { hints: opts?.hints, style }),\n ]);\n if (relayList.event) {\n this.observe(relayList.event);\n }\n if (dmList.event) {\n this.observe(dmList.event);\n }\n }),\n );\n }\n\n /**\n * Create an outbox-model feed for the given authors (NIP-65 write relays). Live events go through\n * {@link observe}; sync persists via applySync.\n */\n outbox(opts: {\n authors: ReadonlyArray<string>;\n kinds?: ReadonlyArray<number>;\n onEvent?: (event: Event) => void;\n maxRelaysPerAuthor?: number;\n }): OutboxFeed {\n this.#assertAlive();\n return new OutboxFeed({\n pool: this.pool,\n gossip: this.gossip,\n storage: this.storage,\n discoveryRelays: this.#relays,\n authors: opts.authors,\n kinds: opts.kinds,\n onEvent: opts.onEvent,\n maxRelaysPerAuthor: opts.maxRelaysPerAuthor,\n observe: (event, relayUrl) => this.observe(event, relayUrl),\n seen: (event, relayUrl) => this.#ingest(event, relayUrl, { persist: false, meta: false }),\n applySync: async (events) => {\n if (!this.#persistEvents) {\n for (const event of events) {\n this.#ingest(event, undefined, { persist: false });\n }\n return [];\n }\n const results = await this.storage.putMany(events);\n const applied: Event[] = [];\n for (const [i, event] of events.entries()) {\n const result = results[i];\n if (result === \"rejected\" || result === \"ephemeral\" || result === \"invalid\") {\n continue;\n }\n this.#ingest(event, undefined, { persist: false });\n applied.push(event);\n }\n return applied;\n },\n hydrate: async (pubkeys) => this.hydrateGossip(pubkeys),\n });\n }\n\n #requireSigner(): NostrSigner {\n const signer = this.#signer;\n if (!signer) {\n throw new ClientError(\"no signer configured\");\n }\n return signer;\n }\n\n async getPublicKey(): Promise<string> {\n return this.#requireSigner().getPublicKey();\n }\n\n async signEvent(unsigned: UnsignedEvent): Promise<Event> {\n return this.#requireSigner().signEvent(unsigned);\n }\n\n async signEventBuilder(builder: EventBuilder): Promise<Event> {\n return builder.sign(this.#requireSigner());\n }\n\n async signTemplate(template: EventTemplate): Promise<Event> {\n const signer = this.#requireSigner();\n const pubkey = await signer.getPublicKey();\n return signer.signEvent({ ...template, pubkey });\n }\n\n /**\n * Sign (if builder) and publish to relays. With `gossip: true`, prefers the author's NIP-65\n * outbox relays, tagged `p` inboxes, and up to 5 `e`/`a` tag relay hints when known. On any\n * successful OK, observes the event into storage/gossip.\n */\n async publish(\n eventOrBuilder: Event | EventBuilder,\n opts?: PublishOptions,\n ): Promise<PoolPublishResult[]> {\n this.#assertAlive();\n const event =\n eventOrBuilder instanceof EventBuilder\n ? await this.signEventBuilder(eventOrBuilder)\n : eventOrBuilder;\n\n let relays = opts?.relays;\n if (!relays && isGiftWrapKind(event.kind)) {\n relays = giftWrapRelays(this.gossip, event);\n } else if (!relays && opts?.gossip === true) {\n const urls: string[] = [];\n const add = (list: ReadonlyArray<string>) => {\n for (const url of list) {\n if (!urls.includes(url)) {\n urls.push(url);\n }\n }\n };\n add(this.gossip.outboxRelays(event.pubkey));\n for (const tag of event.tags) {\n if (tag[0] === \"p\" && tag[1] !== undefined) {\n add(this.gossip.inboxRelays(tag[1]));\n }\n }\n let hintCount = 0;\n for (const tag of event.tags) {\n if (hintCount >= 5) {\n break;\n }\n if ((tag[0] !== \"e\" && tag[0] !== \"a\") || tag[2] === undefined) {\n continue;\n }\n try {\n const url = normalizeURL(tag[2]);\n if (!urls.includes(url)) {\n urls.push(url);\n hintCount += 1;\n }\n } catch {\n // not a relay URL\n }\n }\n if (urls.length > 0) {\n relays = urls;\n }\n }\n const results = await this.pool.publish(this.#defaultRelays(relays), event, {\n timeoutMs: opts?.timeoutMs,\n });\n\n const anyOk = results.some((r) => r.status === \"ok\");\n if (anyOk && opts?.observe !== false) {\n this.observe(event);\n }\n return results;\n }\n\n /**\n * Fetch events. With `gossip: true`, routes filters by NIP-65 when possible. With `localFirst:\n * true`, merges storage hits with network results.\n */\n async fetchEvents(\n filter: Filter | ReadonlyArray<Filter>,\n opts?: FetchEventsOptions,\n ): Promise<Event[]> {\n this.#assertAlive();\n const filters = toFilterList(filter);\n const shouldObserve = opts?.observe !== false;\n const candidates: Event[] = [];\n\n if (opts?.localFirst === true) {\n // Persisted storage is merged with the index below; storage may hold a\n // stale replaceable version, so it is only a candidate — the index wins.\n try {\n const local = await this.storage.query(filters);\n for (const e of local) {\n candidates.push(e);\n if (shouldObserve) {\n this.#ingest(e, undefined, { persist: false, meta: false });\n }\n }\n } catch (error) {\n this.#events.emit(\"storageerror\", toStorageError(error));\n }\n }\n\n // Every inbound event lands in the index (with its relay URL) before the caller's\n // onEvent runs. `onEvent` fires once per relay delivery, so only the first sighting of\n // an id does a full index put — later relays are recorded via markSeen. The batch loop\n // below adds gossip meta and persistence without re-putting.\n const seenIds = new Set<string>();\n const onEvent = (event: Event, relayUrl: string) => {\n if (shouldObserve) {\n if (seenIds.has(event.id)) {\n this.index.markSeen(event.id, relayUrl);\n } else {\n seenIds.add(event.id);\n this.index.add(event, relayUrl);\n }\n }\n opts?.onEvent?.(event, relayUrl);\n };\n\n const batch =\n opts?.gossip !== true || opts.relays !== undefined\n ? await this.pool.fetch(this.#defaultRelays(opts?.relays), filters, {\n timeoutMs: opts?.timeoutMs,\n signal: opts?.signal,\n onEvent,\n })\n : await fetchGossip(this.pool, this.gossip, filters, () => this.#defaultRelays(), {\n timeoutMs: opts?.timeoutMs,\n signal: opts?.signal,\n onEvent,\n });\n for (const e of batch) {\n candidates.push(e);\n if (shouldObserve) {\n this.#ingestMeta(e);\n }\n }\n\n // Merge through a scratch index so NIP-01 replaceable winners, kind-5\n // deletions, dedupe and per-filter limits all apply to the result.\n // Ephemeral kinds are never stored; matching ones join the result directly.\n const tmp = new MemoryIndex();\n const ephemeral = new Map<string, Event>();\n const merged =\n opts?.localFirst === true ? [...this.index.query(filters), ...candidates] : candidates;\n for (const e of merged) {\n if (tmp.put(e) === \"ephemeral\" && matchFilters(filters, e)) {\n ephemeral.set(e.id, e);\n }\n }\n return sortedEvents([...tmp.query(filters), ...ephemeral.values()]);\n }\n\n /**\n * One-shot fetch with per-relay results: each entry reports its events and end reason (`eose`,\n * `closed`, `timeout`, or `failed` when the relay could not be reached). Events are ingested into\n * the index with each relay's URL in `seenOn` and persisted exactly like {@link fetchEvents}\n * (`observe` defaults to true). No gossip routing and no local merge.\n */\n async fetchEach(\n filter: Filter | ReadonlyArray<Filter>,\n opts?: FetchEachOptions,\n ): Promise<PoolFetchResult[]> {\n this.#assertAlive();\n const filters = toFilterList(filter);\n const results = await this.pool.fetchEach(this.#defaultRelays(opts?.relays), filters, {\n timeoutMs: opts?.timeoutMs,\n signal: opts?.signal,\n });\n if (opts?.observe !== false) {\n const unique: Event[] = [];\n const seen = new Set<string>();\n for (const result of results) {\n for (const event of result.events) {\n if (seen.has(event.id)) {\n this.index.markSeen(event.id, result.url);\n continue;\n }\n seen.add(event.id);\n this.index.add(event, result.url);\n unique.push(event);\n }\n }\n for (const event of unique) {\n this.#ingestMeta(event);\n }\n }\n return results;\n }\n\n /** Query local storage only (no network). */\n async queryLocal(filter: Filter | ReadonlyArray<Filter>): Promise<Event[]> {\n this.#assertAlive();\n const filters = toFilterList(filter);\n return this.storage.query(filters);\n }\n\n subscribe(filter: Filter | ReadonlyArray<Filter>, opts?: SubscribeOptions): Closer {\n this.#assertAlive();\n const filters = toFilterList(filter);\n const shouldObserve = opts?.observe !== false;\n\n const wrapEvent = (event: Event, relayUrl: string) => {\n if (shouldObserve) {\n this.observe(event, relayUrl);\n }\n opts?.onEvent?.(event, relayUrl);\n };\n\n const wrapReceived = (id: string, relayUrl: string) => {\n if (shouldObserve) {\n this.index.markSeen(id, relayUrl);\n }\n opts?.onEventSeen?.(id, relayUrl);\n };\n\n if (opts?.gossip !== true || opts.relays !== undefined) {\n return this.pool.subscribe(this.#defaultRelays(opts?.relays), filters, {\n onEvent: wrapEvent,\n onEventSeen: wrapReceived,\n onEose: opts?.onEose,\n onClose: opts?.onClose,\n signal: opts?.signal,\n id: opts?.id,\n eoseTimeoutMs: opts?.eoseTimeoutMs,\n });\n }\n\n return subscribeGossip(this.pool, this.gossip, filters, () => this.#defaultRelays(), {\n onEvent: wrapEvent,\n onEventSeen: wrapReceived,\n onEose: opts?.onEose,\n onClose: opts?.onClose,\n signal: opts?.signal,\n eoseTimeoutMs: opts?.eoseTimeoutMs,\n });\n }\n\n #requireNip59Crypto(): Nip59Crypto {\n return requireNip59Crypto(this.#requireSigner());\n }\n\n /** Publish a kind:10050 NIP-17 DM relay list (`relay` tags). Not kind 10002. */\n async setDmRelays(\n relays: ReadonlyArray<string>,\n opts?: PublishOptions,\n ): Promise<PoolPublishResult[]> {\n return setDmRelays(this.#dmDeps, relays, opts);\n }\n\n async sendPrivateMessage(\n recipients: string | Recipient | ReadonlyArray<string | Recipient>,\n content: string,\n opts?: SendPrivateMessageOptions,\n ): Promise<PrivateMessageSendResult> {\n return sendPrivateMessage(this.#dmDeps, recipients, content, opts);\n }\n\n async fetchPrivateMessages(\n opts?: FetchPrivateMessagesOptions,\n ): Promise<ReceivedPrivateMessage[]> {\n return fetchPrivateMessages(this.#dmDeps, opts);\n }\n\n async subscribePrivateMessages(opts?: SubscribePrivateMessagesOptions): Promise<Closer> {\n return subscribePrivateMessages(this.#dmDeps, opts);\n }\n\n /**\n * NIP-77 sync against one relay: reconcile, then optionally upload local-only events and/or\n * download remote-only events. `observe: false` skips putMany and ingest; received ids are still\n * listed. `persistEvents: false` skips putMany, still ingests when observe is on.\n */\n async syncToRelay(\n url: string,\n filter: Filter,\n opts?: Omit<SyncOptions, \"relays\">,\n ): Promise<SyncSummary> {\n return syncToRelay(this.#syncDeps, url, filter, opts);\n }\n\n /**\n * NIP-77 sync against the given relays (or Client default relays). Independent sessions run in\n * parallel. Fulfilled summaries are merged; if every relay rejects, throws the first rejection in\n * URL order.\n */\n async sync(filter: Filter, opts?: SyncOptions): Promise<SyncSummary> {\n return sync(this.#syncDeps, filter, opts);\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"event-DvM1f03b.mjs","names":[],"sources":["../src/core/tag.ts","../src/core/event.ts"],"sourcesContent":["import { isAddressableKind, isReplaceableKind } from \"./kind.ts\";\nimport { isHex32 } from \"./util.ts\";\n\n/** A NIP-01 tag: first element is the name, rest are values. */\nexport type Tag = ReadonlyArray<string>;\n\n/** Mutable tag builder input. */\nexport type TagInput = string[];\n\n/** Type guard: a non-empty array of strings. */\nexport function isTag(value: unknown): value is Tag {\n if (!Array.isArray(value) || value.length === 0) {\n return false;\n }\n for (const item of value) {\n if (typeof item !== \"string\") {\n return false;\n }\n }\n return true;\n}\n\n/** First element of a tag (its name). */\nexport function tagName(tag: Tag): string {\n return tag[0] ?? \"\";\n}\n\n/** Second element of a tag (its primary value), or undefined for a bare name tag. */\nexport function tagValue(tag: Tag): string | undefined {\n return tag[1];\n}\n\n/**\n * Push positional tag values: \"\" for absent slots before a present one, trailing absent slots\n * omitted.\n */\nfunction pushPositions(t: string[], positions: Array<string | undefined>): void {\n let end = positions.length;\n while (end > 0 && positions[end - 1] === undefined) {\n end -= 1;\n }\n for (let i = 0; i < end; i += 1) {\n t.push(positions[i] ?? \"\");\n }\n}\n\n/** Construct common tags. */\nexport const Tag = {\n e(id: string, relay?: string, marker?: string, pubkey?: string): Tag {\n const t: string[] = [\"e\", id.toLowerCase()];\n pushPositions(t, [relay, marker, pubkey?.toLowerCase()]);\n return t;\n },\n p(pubkey: string, relay?: string, petname?: string): Tag {\n const t: string[] = [\"p\", pubkey.toLowerCase()];\n pushPositions(t, [relay, petname]);\n return t;\n },\n a(coordinate: string, relay?: string): Tag {\n const t: string[] = [\"a\", coordinate];\n if (relay !== undefined) {\n t.push(relay);\n }\n return t;\n },\n d(identifier: string): Tag {\n return [\"d\", identifier];\n },\n t(hashtag: string): Tag {\n return [\"t\", hashtag];\n },\n r(url: string, marker?: string): Tag {\n const t: string[] = [\"r\", url];\n if (marker !== undefined) {\n t.push(marker);\n }\n return t;\n },\n k(kind: number | string): Tag {\n return [\"k\", String(kind)];\n },\n} as const;\n\n/** First tag value with the given name, or undefined when no such tag has a value. */\nexport function firstTagValue(tags: ReadonlyArray<Tag>, name: string): string | undefined {\n for (const tag of tags) {\n if (tag[0] === name && tag[1] !== undefined) {\n return tag[1];\n }\n }\n return undefined;\n}\n\n/** First `d` tag value on an event, if any. */\nexport function getDTag(tags: ReadonlyArray<Tag>): string | undefined {\n return firstTagValue(tags, \"d\");\n}\n\n/** NIP-01 `a` tag: `kind:pubkey:identifier` (identifier may be empty and may contain extra colons). */\nexport type EventAddress = {\n kind: number;\n pubkey: string;\n identifier: string;\n};\n\n/** Parse a NIP-01 addressable/replaceable coordinate. */\nexport function parseEventAddress(value: string): EventAddress | undefined {\n const first = value.indexOf(\":\");\n if (first <= 0) {\n return undefined;\n }\n const second = value.indexOf(\":\", first + 1);\n if (second === -1) {\n return undefined;\n }\n const kindText = value.slice(0, first);\n // Strict decimal: Number() also accepts \"1e4\", \"0x10\", \" 1\", \"+1\" — none are NIP-19 kinds.\n if (!/^\\d{1,5}$/.test(kindText)) {\n return undefined;\n }\n const kind = Number(kindText);\n if (kind > 65535) {\n return undefined;\n }\n const pubkey = value.slice(first + 1, second).toLowerCase();\n if (!isHex32(pubkey)) {\n return undefined;\n }\n return { kind, pubkey, identifier: value.slice(second + 1) };\n}\n\n/** Format an address as a `kind:pubkey:dTag` coordinate string. */\nexport function formatEventAddress(kind: number, pubkey: string, identifier = \"\"): string {\n return `${kind}:${pubkey.toLowerCase()}:${identifier}`;\n}\n\n/**\n * Coordinate for replaceable (`kind:pubkey:`) and addressable (`kind:pubkey:d`) events. Undefined\n * for regular/ephemeral kinds.\n */\nexport function eventAddress(event: {\n kind: number;\n pubkey: string;\n tags: ReadonlyArray<Tag>;\n}): string | undefined {\n if (isAddressableKind(event.kind)) {\n return formatEventAddress(event.kind, event.pubkey, getDTag(event.tags) ?? \"\");\n }\n if (isReplaceableKind(event.kind)) {\n return formatEventAddress(event.kind, event.pubkey, \"\");\n }\n return undefined;\n}\n","import { sha256 } from \"@noble/hashes/sha2.js\";\n\nimport { EventValidationError } from \"./error.ts\";\nimport type { Tag } from \"./tag.ts\";\nimport { isTag } from \"./tag.ts\";\nimport { bytesToHex, hasLoneSurrogate, isHex32, isHex64, isRecord, utf8Encoder } from \"./util.ts\";\n\n/** Wire event template before pubkey/id/sig. */\nexport type EventTemplate = {\n readonly kind: number;\n readonly tags: ReadonlyArray<Tag>;\n readonly content: string;\n readonly created_at: number;\n};\n\n/** Event with pubkey, ready to hash and sign. */\nexport type UnsignedEvent = EventTemplate & {\n readonly pubkey: string;\n};\n\n/**\n * Fully formed NIP-01 event (JSON-friendly plain object). Fields are readonly at the type level;\n * wire interop remains plain JSON.\n */\nexport type Event = UnsignedEvent & {\n readonly id: string;\n readonly sig: string;\n};\n\n/** Cache of events whose signatures have been verified successfully. */\nconst verifiedEvents = new WeakSet<Event>();\n\n/** Cache of events known to fail verification. */\nconst failedEvents = new WeakSet<Event>();\n\n/** Cache of object identities that already passed {@link validateSignedEvent}. */\nconst validatedEvents = new WeakSet<Record<string, unknown>>();\n\nexport function markVerified(event: Event): void {\n verifiedEvents.add(event);\n failedEvents.delete(event);\n}\n\nexport function markUnverified(event: Event): void {\n failedEvents.add(event);\n verifiedEvents.delete(event);\n}\n\nexport function isMarkedVerified(event: Event): boolean {\n return verifiedEvents.has(event);\n}\n\nexport function isMarkedFailed(event: Event): boolean {\n return failedEvents.has(event);\n}\n\n/** Structural validation of an unsigned event (no crypto). */\nexport function validateEvent(event: unknown): event is UnsignedEvent {\n if (!isRecord(event)) {\n return false;\n }\n if (\n typeof event[\"kind\"] !== \"number\" ||\n !Number.isSafeInteger(event[\"kind\"]) ||\n event[\"kind\"] < 0 ||\n event[\"kind\"] > 65535\n ) {\n return false;\n }\n // Lone surrogates are rejected per NIP-01: after JSON.parse they differ from\n // what serde_json (and thus Rust) can express, so the event id would diverge.\n if (typeof event[\"content\"] !== \"string\" || hasLoneSurrogate(event[\"content\"])) {\n return false;\n }\n if (\n typeof event[\"created_at\"] !== \"number\" ||\n !Number.isSafeInteger(event[\"created_at\"]) ||\n event[\"created_at\"] < 0\n ) {\n return false;\n }\n if (typeof event[\"pubkey\"] !== \"string\" || !isHex32(event[\"pubkey\"])) {\n return false;\n }\n if (!Array.isArray(event[\"tags\"])) {\n return false;\n }\n for (const tag of event[\"tags\"]) {\n if (!isTag(tag)) {\n return false;\n }\n for (const item of tag) {\n if (hasLoneSurrogate(item)) {\n return false;\n }\n }\n }\n return true;\n}\n\n/** Structural validation of a full event including id and sig hex shape. */\nexport function validateSignedEvent(event: unknown): event is Event {\n if (isRecord(event) && validatedEvents.has(event)) {\n return true;\n }\n if (!validateEvent(event)) {\n return false;\n }\n const e = event as Record<string, unknown>;\n if (typeof e[\"id\"] !== \"string\" || !isHex32(e[\"id\"])) {\n return false;\n }\n if (typeof e[\"sig\"] !== \"string\" || !isHex64(e[\"sig\"])) {\n return false;\n }\n validatedEvents.add(e);\n return true;\n}\n\n/**\n * Canonical NIP-01 serialization used for hashing: JSON array `[0, pubkey, created_at, kind, tags,\n * content]` with no extra whitespace.\n */\nexport function serializeEvent(event: UnsignedEvent): string {\n if (!validateEvent(event)) {\n throw new EventValidationError(\"cannot serialize event with invalid shape\");\n }\n return serializeValidatedEvent(event);\n}\n\n/**\n * Canonical serialization for an event already known valid (after {@link validateEvent} or\n * {@link validateSignedEvent}); identical output to {@link serializeEvent} without re-walking the\n * structural checks.\n */\nexport function serializeValidatedEvent(event: UnsignedEvent): string {\n return JSON.stringify([0, event.pubkey, event.created_at, event.kind, event.tags, event.content]);\n}\n\n/** SHA-256 of the canonical serialization, lowercase hex. */\nexport function getEventHash(event: UnsignedEvent): string {\n const serialized = serializeEvent(event);\n return bytesToHex(sha256(utf8Encoder.encode(serialized)));\n}\n\n/** Id tie-break: plain code-unit order (ids are canonical lowercase hex, so `<` is lexical). */\nfunction compareIds(a: { id: string }, b: { id: string }): number {\n if (a.id < b.id) {\n return -1;\n }\n if (a.id > b.id) {\n return 1;\n }\n return 0;\n}\n\n/**\n * NIP-01 newest-first order: `created_at` descending, then `id` in plain code-unit order (ids are\n * canonical lowercase hex, so `<` is lexical order).\n */\nexport function compareEventsDesc(\n a: { id: string; created_at: number },\n b: { id: string; created_at: number },\n): number {\n if (a.created_at !== b.created_at) {\n return b.created_at - a.created_at;\n }\n return compareIds(a, b);\n}\n\n/**\n * Sort events reverse-chronologically by `created_at`, then by `id` lexicographically. Mutates and\n * returns the array.\n */\nexport function sortEvents(events: Event[]): Event[] {\n return events.sort(compareEventsDesc);\n}\n\n/** Non-mutating sort; returns a new array. */\nexport function sortedEvents(events: ReadonlyArray<Event>): Event[] {\n return [...events].sort(compareEventsDesc);\n}\n\n/**\n * Negentropy item order: `created_at` ascending, then `id` lexicographic (`<`/`>`). Time axis is\n * opposite {@link sortEvents}; id tie-break is the same direction.\n */\nexport function itemCompare(\n a: { id: string; created_at: number },\n b: { id: string; created_at: number },\n): number {\n if (a.created_at !== b.created_at) {\n return a.created_at - b.created_at;\n }\n return compareIds(a, b);\n}\n\n/**\n * NIP-01 replaceable/addressable winner: higher `created_at`, or equal timestamp and\n * lexicographically lower `id`.\n */\nexport function isReplaceableWinner(\n candidate: { created_at: number; id: string },\n incumbent: { created_at: number; id: string },\n): boolean {\n if (candidate.created_at !== incumbent.created_at) {\n return candidate.created_at > incumbent.created_at;\n }\n return candidate.id < incumbent.id;\n}\n\n/** True when a signed event is the signed form of `unsigned` (kind/tags/content/time/pubkey). */\nexport function signedMatchesUnsigned(signed: Event, unsigned: UnsignedEvent): boolean {\n if (signed.kind !== unsigned.kind) {\n return false;\n }\n if (signed.content !== unsigned.content) {\n return false;\n }\n if (signed.created_at !== unsigned.created_at) {\n return false;\n }\n if (JSON.stringify(signed.tags) !== JSON.stringify(unsigned.tags)) {\n return false;\n }\n if (unsigned.pubkey && signed.pubkey.toLowerCase() !== unsigned.pubkey.toLowerCase()) {\n return false;\n }\n return true;\n}\n"],"mappings":";;;;;AAUA,SAAgB,MAAM,OAA8B;CAClD,IAAI,CAAC,MAAM,QAAQ,KAAK,KAAK,MAAM,WAAW,GAC5C,OAAO;CAET,KAAK,MAAM,QAAQ,OACjB,IAAI,OAAO,SAAS,UAClB,OAAO;CAGX,OAAO;AACT;;AAGA,SAAgB,QAAQ,KAAkB;CACxC,OAAO,IAAI,MAAM;AACnB;;AAGA,SAAgB,SAAS,KAA8B;CACrD,OAAO,IAAI;AACb;;;;;AAMA,SAAS,cAAc,GAAa,WAA4C;CAC9E,IAAI,MAAM,UAAU;CACpB,OAAO,MAAM,KAAK,UAAU,MAAM,OAAO,KAAA,GACvC,OAAO;CAET,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,KAAK,GAC5B,EAAE,KAAK,UAAU,MAAM,EAAE;AAE7B;;AAGA,MAAa,MAAM;CACjB,EAAE,IAAY,OAAgB,QAAiB,QAAsB;EACnE,MAAM,IAAc,CAAC,KAAK,GAAG,YAAY,CAAC;EAC1C,cAAc,GAAG;GAAC;GAAO;GAAQ,QAAQ,YAAY;EAAC,CAAC;EACvD,OAAO;CACT;CACA,EAAE,QAAgB,OAAgB,SAAuB;EACvD,MAAM,IAAc,CAAC,KAAK,OAAO,YAAY,CAAC;EAC9C,cAAc,GAAG,CAAC,OAAO,OAAO,CAAC;EACjC,OAAO;CACT;CACA,EAAE,YAAoB,OAAqB;EACzC,MAAM,IAAc,CAAC,KAAK,UAAU;EACpC,IAAI,UAAU,KAAA,GACZ,EAAE,KAAK,KAAK;EAEd,OAAO;CACT;CACA,EAAE,YAAyB;EACzB,OAAO,CAAC,KAAK,UAAU;CACzB;CACA,EAAE,SAAsB;EACtB,OAAO,CAAC,KAAK,OAAO;CACtB;CACA,EAAE,KAAa,QAAsB;EACnC,MAAM,IAAc,CAAC,KAAK,GAAG;EAC7B,IAAI,WAAW,KAAA,GACb,EAAE,KAAK,MAAM;EAEf,OAAO;CACT;CACA,EAAE,MAA4B;EAC5B,OAAO,CAAC,KAAK,OAAO,IAAI,CAAC;CAC3B;AACF;;AAGA,SAAgB,cAAc,MAA0B,MAAkC;CACxF,KAAK,MAAM,OAAO,MAChB,IAAI,IAAI,OAAO,QAAQ,IAAI,OAAO,KAAA,GAChC,OAAO,IAAI;AAIjB;;AAGA,SAAgB,QAAQ,MAA8C;CACpE,OAAO,cAAc,MAAM,GAAG;AAChC;;AAUA,SAAgB,kBAAkB,OAAyC;CACzE,MAAM,QAAQ,MAAM,QAAQ,GAAG;CAC/B,IAAI,SAAS,GACX;CAEF,MAAM,SAAS,MAAM,QAAQ,KAAK,QAAQ,CAAC;CAC3C,IAAI,WAAW,IACb;CAEF,MAAM,WAAW,MAAM,MAAM,GAAG,KAAK;CAErC,IAAI,CAAC,YAAY,KAAK,QAAQ,GAC5B;CAEF,MAAM,OAAO,OAAO,QAAQ;CAC5B,IAAI,OAAO,OACT;CAEF,MAAM,SAAS,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,YAAY;CAC1D,IAAI,CAAC,QAAQ,MAAM,GACjB;CAEF,OAAO;EAAE;EAAM;EAAQ,YAAY,MAAM,MAAM,SAAS,CAAC;CAAE;AAC7D;;AAGA,SAAgB,mBAAmB,MAAc,QAAgB,aAAa,IAAY;CACxF,OAAO,GAAG,KAAK,GAAG,OAAO,YAAY,EAAE,GAAG;AAC5C;;;;;AAMA,SAAgB,aAAa,OAIN;CACrB,IAAI,kBAAkB,MAAM,IAAI,GAC9B,OAAO,mBAAmB,MAAM,MAAM,MAAM,QAAQ,QAAQ,MAAM,IAAI,KAAK,EAAE;CAE/E,IAAI,kBAAkB,MAAM,IAAI,GAC9B,OAAO,mBAAmB,MAAM,MAAM,MAAM,QAAQ,EAAE;AAG1D;;;;AC1HA,MAAM,iCAAiB,IAAI,QAAe;;AAG1C,MAAM,+BAAe,IAAI,QAAe;;AAGxC,MAAM,kCAAkB,IAAI,QAAiC;AAE7D,SAAgB,aAAa,OAAoB;CAC/C,eAAe,IAAI,KAAK;CACxB,aAAa,OAAO,KAAK;AAC3B;AAEA,SAAgB,eAAe,OAAoB;CACjD,aAAa,IAAI,KAAK;CACtB,eAAe,OAAO,KAAK;AAC7B;AAEA,SAAgB,iBAAiB,OAAuB;CACtD,OAAO,eAAe,IAAI,KAAK;AACjC;AAEA,SAAgB,eAAe,OAAuB;CACpD,OAAO,aAAa,IAAI,KAAK;AAC/B;;AAGA,SAAgB,cAAc,OAAwC;CACpE,IAAI,CAAC,SAAS,KAAK,GACjB,OAAO;CAET,IACE,OAAO,MAAM,YAAY,YACzB,CAAC,OAAO,cAAc,MAAM,OAAO,KACnC,MAAM,UAAU,KAChB,MAAM,UAAU,OAEhB,OAAO;CAIT,IAAI,OAAO,MAAM,eAAe,YAAY,iBAAiB,MAAM,UAAU,GAC3E,OAAO;CAET,IACE,OAAO,MAAM,kBAAkB,YAC/B,CAAC,OAAO,cAAc,MAAM,aAAa,KACzC,MAAM,gBAAgB,GAEtB,OAAO;CAET,IAAI,OAAO,MAAM,cAAc,YAAY,CAAC,QAAQ,MAAM,SAAS,GACjE,OAAO;CAET,IAAI,CAAC,MAAM,QAAQ,MAAM,OAAO,GAC9B,OAAO;CAET,KAAK,MAAM,OAAO,MAAM,SAAS;EAC/B,IAAI,CAAC,MAAM,GAAG,GACZ,OAAO;EAET,KAAK,MAAM,QAAQ,KACjB,IAAI,iBAAiB,IAAI,GACvB,OAAO;CAGb;CACA,OAAO;AACT;;AAGA,SAAgB,oBAAoB,OAAgC;CAClE,IAAI,SAAS,KAAK,KAAK,gBAAgB,IAAI,KAAK,GAC9C,OAAO;CAET,IAAI,CAAC,cAAc,KAAK,GACtB,OAAO;CAET,MAAM,IAAI;CACV,IAAI,OAAO,EAAE,UAAU,YAAY,CAAC,QAAQ,EAAE,KAAK,GACjD,OAAO;CAET,IAAI,OAAO,EAAE,WAAW,YAAY,CAAC,QAAQ,EAAE,MAAM,GACnD,OAAO;CAET,gBAAgB,IAAI,CAAC;CACrB,OAAO;AACT;;;;;AAMA,SAAgB,eAAe,OAA8B;CAC3D,IAAI,CAAC,cAAc,KAAK,GACtB,MAAM,IAAI,qBAAqB,2CAA2C;CAE5E,OAAO,wBAAwB,KAAK;AACtC;;;;;;AAOA,SAAgB,wBAAwB,OAA8B;CACpE,OAAO,KAAK,UAAU;EAAC;EAAG,MAAM;EAAQ,MAAM;EAAY,MAAM;EAAM,MAAM;EAAM,MAAM;CAAO,CAAC;AAClG;;AAGA,SAAgB,aAAa,OAA8B;CACzD,MAAM,aAAa,eAAe,KAAK;CACvC,OAAO,WAAW,OAAO,YAAY,OAAO,UAAU,CAAC,CAAC;AAC1D;;AAGA,SAAS,WAAW,GAAmB,GAA2B;CAChE,IAAI,EAAE,KAAK,EAAE,IACX,OAAO;CAET,IAAI,EAAE,KAAK,EAAE,IACX,OAAO;CAET,OAAO;AACT;;;;;AAMA,SAAgB,kBACd,GACA,GACQ;CACR,IAAI,EAAE,eAAe,EAAE,YACrB,OAAO,EAAE,aAAa,EAAE;CAE1B,OAAO,WAAW,GAAG,CAAC;AACxB;;;;;AAMA,SAAgB,WAAW,QAA0B;CACnD,OAAO,OAAO,KAAK,iBAAiB;AACtC;;AAGA,SAAgB,aAAa,QAAuC;CAClE,OAAO,CAAC,GAAG,MAAM,CAAC,CAAC,KAAK,iBAAiB;AAC3C;;;;;AAMA,SAAgB,YACd,GACA,GACQ;CACR,IAAI,EAAE,eAAe,EAAE,YACrB,OAAO,EAAE,aAAa,EAAE;CAE1B,OAAO,WAAW,GAAG,CAAC;AACxB;;;;;AAMA,SAAgB,oBACd,WACA,WACS;CACT,IAAI,UAAU,eAAe,UAAU,YACrC,OAAO,UAAU,aAAa,UAAU;CAE1C,OAAO,UAAU,KAAK,UAAU;AAClC;;AAGA,SAAgB,sBAAsB,QAAe,UAAkC;CACrF,IAAI,OAAO,SAAS,SAAS,MAC3B,OAAO;CAET,IAAI,OAAO,YAAY,SAAS,SAC9B,OAAO;CAET,IAAI,OAAO,eAAe,SAAS,YACjC,OAAO;CAET,IAAI,KAAK,UAAU,OAAO,IAAI,MAAM,KAAK,UAAU,SAAS,IAAI,GAC9D,OAAO;CAET,IAAI,SAAS,UAAU,OAAO,OAAO,YAAY,MAAM,SAAS,OAAO,YAAY,GACjF,OAAO;CAET,OAAO;AACT"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"filter-3jOh4uLN.mjs","names":[],"sources":["../src/core/filter.ts"],"sourcesContent":["import type { Event } from \"./event.ts\";\nimport { isAddressableKind, isReplaceableKind } from \"./kind.ts\";\n\n/**\n * NIP-01 filter object (immutable-friendly; callers may still pass plain objects). Filters are\n * treated as immutable once used: `matchFilter` and the event stores cache a compiled form keyed on\n * the object, so mutating a filter after it has been matched yields stale results.\n */\nexport type Filter = {\n readonly ids?: ReadonlyArray<string> | undefined;\n readonly kinds?: ReadonlyArray<number> | undefined;\n readonly authors?: ReadonlyArray<string> | undefined;\n readonly since?: number | undefined;\n readonly until?: number | undefined;\n readonly limit?: number | undefined;\n /** NIP-50 full-text search. Relays interpret this. Local `matchFilter` / `query` ignore it. */\n readonly search?: string | undefined;\n readonly [key: `#${string}`]: ReadonlyArray<string> | undefined;\n};\n\n/** NIP-01 only defines single-letter tag conditions. */\nconst SINGLE_LETTER_TAG = /^#[a-zA-Z]$/;\n\ntype CompiledTagCondition = {\n readonly name: string;\n readonly values: ReadonlySet<string>;\n};\n\n/** `matchFilter` condition form: `Set` lookups instead of per-element scans and `toLowerCase`. */\ntype CompiledFilter = {\n readonly ids: ReadonlySet<string> | undefined;\n readonly kinds: ReadonlySet<number> | undefined;\n readonly authors: ReadonlySet<string> | undefined;\n readonly tags: ReadonlyArray<CompiledTagCondition>;\n readonly since: number | undefined;\n readonly until: number | undefined;\n};\n\nconst compiledFilters = new WeakMap<Filter, CompiledFilter>();\n\n/**\n * Single-letter `#x` conditions shared by `matchFilter` and the event stores: `#e`/`#p` values are\n * lowercased (they hold hex ids, same as `encodeClientMessage`), other tag values stay verbatim —\n * relays match tag values literally. Multi-letter `#` keys are outside NIP-01. Internal to the\n * package; not exported from the index.\n */\nexport function filterTagConditions(\n filter: Filter,\n): Array<{ name: string; values: ReadonlyArray<string> }> {\n const conditions: Array<{ name: string; values: ReadonlyArray<string> }> = [];\n for (const key of Object.keys(filter)) {\n if (!SINGLE_LETTER_TAG.test(key)) {\n continue;\n }\n const name = key.slice(1);\n const values = filter[`#${name}`];\n if (values === undefined) {\n continue;\n }\n conditions.push({\n name,\n values: name === \"e\" || name === \"p\" ? values.map((v) => v.toLowerCase()) : values,\n });\n }\n return conditions;\n}\n\nfunction compileFilter(filter: Filter): CompiledFilter {\n return {\n ids: filter.ids ? new Set(filter.ids.map((id) => id.toLowerCase())) : undefined,\n kinds: filter.kinds ? new Set(filter.kinds) : undefined,\n authors: filter.authors ? new Set(filter.authors.map((pk) => pk.toLowerCase())) : undefined,\n tags: filterTagConditions(filter).map(({ name, values }) => ({\n name,\n values: new Set(values),\n })),\n since: filter.since,\n until: filter.until,\n };\n}\n\n/** Local NIP-01 match. `search` is ignored; relays interpret NIP-50. */\nexport function matchFilter(filter: Filter, event: Event): boolean {\n let compiled = compiledFilters.get(filter);\n if (compiled === undefined) {\n compiled = compileFilter(filter);\n compiledFilters.set(filter, compiled);\n }\n if (compiled.ids !== undefined && !compiled.ids.has(event.id)) {\n return false;\n }\n if (compiled.kinds !== undefined && !compiled.kinds.has(event.kind)) {\n return false;\n }\n if (compiled.authors !== undefined && !compiled.authors.has(event.pubkey)) {\n return false;\n }\n\n for (const { name, values } of compiled.tags) {\n const hit = event.tags.some((tag) => {\n const [tagKey, tagValue] = tag;\n return tagKey === name && tagValue !== undefined && values.has(tagValue);\n });\n if (!hit) {\n return false;\n }\n }\n\n if (compiled.since !== undefined && event.created_at < compiled.since) {\n return false;\n }\n if (compiled.until !== undefined && event.created_at > compiled.until) {\n return false;\n }\n\n return true;\n}\n\n/** True when the event matches any of the filters (NIP-01 OR semantics). */\nexport function matchFilters(filters: ReadonlyArray<Filter>, event: Event): boolean {\n for (const filter of filters) {\n if (matchFilter(filter, event)) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Intrinsic upper bound implied by the filter alone. Returns a positive integer, or `Infinity` when\n * unbounded.\n */\nexport function getFilterLimit(filter: Filter): number {\n if (filter.ids && filter.ids.length === 0) {\n return 0;\n }\n if (filter.kinds && filter.kinds.length === 0) {\n return 0;\n }\n if (filter.authors && filter.authors.length === 0) {\n return 0;\n }\n\n let limit = Number.POSITIVE_INFINITY;\n if (filter.ids) {\n limit = Math.min(limit, new Set(filter.ids.map((id) => id.toLowerCase())).size);\n }\n if (filter.limit !== undefined) {\n limit = Math.min(limit, filter.limit);\n }\n\n if (filter.kinds && filter.authors) {\n const kinds = new Set(filter.kinds);\n const allReplaceable = [...kinds].every((k) => isReplaceableKind(k) || isAddressableKind(k));\n if (allReplaceable) {\n const dTags = filter[\"#d\"];\n const perAuthor = dTags && dTags.length > 0 ? new Set(dTags).size : 1;\n const authors = new Set(filter.authors.map((pk) => pk.toLowerCase())).size;\n limit = Math.min(limit, authors * kinds.size * perAuthor);\n }\n }\n\n return limit;\n}\n\nconst HEX_LIST_KEYS = new Set([\"ids\", \"authors\", \"#e\", \"#p\"]);\n\n/** NIP-01 known fields; everything else (incl. multi-letter `#` keys) is dropped. */\nconst KNOWN_FILTER_KEYS = new Set([\"ids\", \"authors\", \"kinds\", \"since\", \"until\", \"limit\", \"search\"]);\n\n/**\n * Lowercase hex lists, sort and dedupe every array, and keep only the NIP-01 known fields plus\n * single-letter `#` keys — unknown keys and multi-letter `#` keys are dropped. Omits undefined so\n * `[]` stays distinct from missing.\n */\nexport function canonicalizeFilter(filter: Filter): Filter {\n const raw = filter as Record<string, unknown>;\n const out: Record<string, unknown> = {};\n for (const key of Object.keys(raw).sort()) {\n if (key.startsWith(\"#\")) {\n if (!SINGLE_LETTER_TAG.test(key)) {\n continue;\n }\n } else if (!KNOWN_FILTER_KEYS.has(key)) {\n continue;\n }\n const value = raw[key];\n // omit undefined so a missing key is not `[]` / null\n if (value === undefined) {\n continue;\n }\n if (Array.isArray(value)) {\n if (HEX_LIST_KEYS.has(key)) {\n out[key] = [...new Set(value.map((v) => String(v).toLowerCase()))].sort();\n } else if (key === \"kinds\") {\n out[key] = [...new Set(value.map(Number))].sort((a, b) => a - b);\n } else {\n out[key] = [...new Set(value.map(String))].sort();\n }\n } else {\n out[key] = value;\n }\n }\n // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- built key by key above; the record is filter-shaped by construction\n return out as Filter;\n}\n\n/** Apply {@link canonicalizeFilter} to each filter. */\nexport function canonicalizeFilters(filters: ReadonlyArray<Filter>): Filter[] {\n return filters.map(canonicalizeFilter);\n}\n\n/**\n * Canonical identity for live REQ coalescing. Relays treat a missing list key as unconstrained and\n * `[]` as match-nothing.\n */\nexport function filterFingerprint(filters: ReadonlyArray<Filter>): string {\n const parts = filters.map((filter) => JSON.stringify(canonicalizeFilter(filter)));\n parts.sort();\n return `[${parts.join(\",\")}]`;\n}\n"],"mappings":";;;AAqBA,MAAM,oBAAoB;AAiB1B,MAAM,kCAAkB,IAAI,QAAgC;;;;;;;AAQ5D,SAAgB,oBACd,QACwD;CACxD,MAAM,aAAqE,CAAC;CAC5E,KAAK,MAAM,OAAO,OAAO,KAAK,MAAM,GAAG;EACrC,IAAI,CAAC,kBAAkB,KAAK,GAAG,GAC7B;EAEF,MAAM,OAAO,IAAI,MAAM,CAAC;EACxB,MAAM,SAAS,OAAO,IAAI;EAC1B,IAAI,WAAW,KAAA,GACb;EAEF,WAAW,KAAK;GACd;GACA,QAAQ,SAAS,OAAO,SAAS,MAAM,OAAO,KAAK,MAAM,EAAE,YAAY,CAAC,IAAI;EAC9E,CAAC;CACH;CACA,OAAO;AACT;AAEA,SAAS,cAAc,QAAgC;CACrD,OAAO;EACL,KAAK,OAAO,MAAM,IAAI,IAAI,OAAO,IAAI,KAAK,OAAO,GAAG,YAAY,CAAC,CAAC,IAAI,KAAA;EACtE,OAAO,OAAO,QAAQ,IAAI,IAAI,OAAO,KAAK,IAAI,KAAA;EAC9C,SAAS,OAAO,UAAU,IAAI,IAAI,OAAO,QAAQ,KAAK,OAAO,GAAG,YAAY,CAAC,CAAC,IAAI,KAAA;EAClF,MAAM,oBAAoB,MAAM,CAAC,CAAC,KAAK,EAAE,MAAM,cAAc;GAC3D;GACA,QAAQ,IAAI,IAAI,MAAM;EACxB,EAAE;EACF,OAAO,OAAO;EACd,OAAO,OAAO;CAChB;AACF;;AAGA,SAAgB,YAAY,QAAgB,OAAuB;CACjE,IAAI,WAAW,gBAAgB,IAAI,MAAM;CACzC,IAAI,aAAa,KAAA,GAAW;EAC1B,WAAW,cAAc,MAAM;EAC/B,gBAAgB,IAAI,QAAQ,QAAQ;CACtC;CACA,IAAI,SAAS,QAAQ,KAAA,KAAa,CAAC,SAAS,IAAI,IAAI,MAAM,EAAE,GAC1D,OAAO;CAET,IAAI,SAAS,UAAU,KAAA,KAAa,CAAC,SAAS,MAAM,IAAI,MAAM,IAAI,GAChE,OAAO;CAET,IAAI,SAAS,YAAY,KAAA,KAAa,CAAC,SAAS,QAAQ,IAAI,MAAM,MAAM,GACtE,OAAO;CAGT,KAAK,MAAM,EAAE,MAAM,YAAY,SAAS,MAKtC,IAAI,CAJQ,MAAM,KAAK,MAAM,QAAQ;EACnC,MAAM,CAAC,QAAQ,YAAY;EAC3B,OAAO,WAAW,QAAQ,aAAa,KAAA,KAAa,OAAO,IAAI,QAAQ;CACzE,CACO,GACL,OAAO;CAIX,IAAI,SAAS,UAAU,KAAA,KAAa,MAAM,aAAa,SAAS,OAC9D,OAAO;CAET,IAAI,SAAS,UAAU,KAAA,KAAa,MAAM,aAAa,SAAS,OAC9D,OAAO;CAGT,OAAO;AACT;;AAGA,SAAgB,aAAa,SAAgC,OAAuB;CAClF,KAAK,MAAM,UAAU,SACnB,IAAI,YAAY,QAAQ,KAAK,GAC3B,OAAO;CAGX,OAAO;AACT;;;;;AAMA,SAAgB,eAAe,QAAwB;CACrD,IAAI,OAAO,OAAO,OAAO,IAAI,WAAW,GACtC,OAAO;CAET,IAAI,OAAO,SAAS,OAAO,MAAM,WAAW,GAC1C,OAAO;CAET,IAAI,OAAO,WAAW,OAAO,QAAQ,WAAW,GAC9C,OAAO;CAGT,IAAI,QAAQ,OAAO;CACnB,IAAI,OAAO,KACT,QAAQ,KAAK,IAAI,OAAO,IAAI,IAAI,OAAO,IAAI,KAAK,OAAO,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC,IAAI;CAEhF,IAAI,OAAO,UAAU,KAAA,GACnB,QAAQ,KAAK,IAAI,OAAO,OAAO,KAAK;CAGtC,IAAI,OAAO,SAAS,OAAO,SAAS;EAClC,MAAM,QAAQ,IAAI,IAAI,OAAO,KAAK;EAElC,IADuB,CAAC,GAAG,KAAK,CAAC,CAAC,OAAO,MAAM,kBAAkB,CAAC,KAAK,kBAAkB,CAAC,CACzE,GAAG;GAClB,MAAM,QAAQ,OAAO;GACrB,MAAM,YAAY,SAAS,MAAM,SAAS,IAAI,IAAI,IAAI,KAAK,CAAC,CAAC,OAAO;GACpE,MAAM,UAAU,IAAI,IAAI,OAAO,QAAQ,KAAK,OAAO,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC;GACtE,QAAQ,KAAK,IAAI,OAAO,UAAU,MAAM,OAAO,SAAS;EAC1D;CACF;CAEA,OAAO;AACT;AAEA,MAAM,gCAAgB,IAAI,IAAI;CAAC;CAAO;CAAW;CAAM;AAAI,CAAC;;AAG5D,MAAM,oCAAoB,IAAI,IAAI;CAAC;CAAO;CAAW;CAAS;CAAS;CAAS;CAAS;AAAQ,CAAC;;;;;;AAOlG,SAAgB,mBAAmB,QAAwB;CACzD,MAAM,MAAM;CACZ,MAAM,MAA+B,CAAC;CACtC,KAAK,MAAM,OAAO,OAAO,KAAK,GAAG,CAAC,CAAC,KAAK,GAAG;EACzC,IAAI,IAAI,WAAW,GAAG,GAChB;OAAA,CAAC,kBAAkB,KAAK,GAAG,GAC7B;EAAA,OAEG,IAAI,CAAC,kBAAkB,IAAI,GAAG,GACnC;EAEF,MAAM,QAAQ,IAAI;EAElB,IAAI,UAAU,KAAA,GACZ;EAEF,IAAI,MAAM,QAAQ,KAAK,GAAG;GACxB,IAAI,cAAc,IAAI,GAAG,GACvB,IAAI,OAAO,CAAC,GAAG,IAAI,IAAI,MAAM,KAAK,MAAM,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK;QACnE,IAAI,QAAQ,SACjB,IAAI,OAAO,CAAC,GAAG,IAAI,IAAI,MAAM,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,MAAM,IAAI,CAAC;QAE/D,IAAI,OAAO,CAAC,GAAG,IAAI,IAAI,MAAM,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK;EAEpD,OACE,IAAI,OAAO;CAEf;CAEA,OAAO;AACT;;AAGA,SAAgB,oBAAoB,SAA0C;CAC5E,OAAO,QAAQ,IAAI,kBAAkB;AACvC;;;;;AAMA,SAAgB,kBAAkB,SAAwC;CACxE,MAAM,QAAQ,QAAQ,KAAK,WAAW,KAAK,UAAU,mBAAmB,MAAM,CAAC,CAAC;CAChF,MAAM,KAAK;CACX,OAAO,IAAI,MAAM,KAAK,GAAG,EAAE;AAC7B"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"gossip-CRyhK6E6.mjs","names":[],"sources":["../src/gossip/gossip.ts"],"sourcesContent":["import { touchKey, trimOldest } from \"../core/collections.ts\";\nimport type { Event } from \"../core/event.ts\";\nimport { isReplaceableWinner } from \"../core/event.ts\";\nimport type { Filter } from \"../core/filter.ts\";\nimport { Kind } from \"../core/kind.ts\";\nimport { normalizeRelayUrls, normalizeURL, nowSeconds } from \"../core/util.ts\";\nimport { parseDmRelayList } from \"../nips/nip17.ts\";\nimport { parseRelayList } from \"../nips/nip65.ts\";\nimport type { RelayListItem } from \"../nips/nip65.ts\";\n\n/** Relay routing state for one pubkey: NIP-65 outbox/inbox plus NIP-17 DM relays. */\nexport type PubkeyRoutes = {\n /** Relays the user writes to (outbox). NIP-65. */\n readonly write: ReadonlyArray<string>;\n /** Relays the user reads from (inbox). NIP-65. */\n readonly read: ReadonlyArray<string>;\n /** Relays for NIP-17 gift-wrap delivery. Kind 10050. */\n readonly dm: ReadonlyArray<string>;\n /** `created_at` of the last accepted kind:10002 list. */\n readonly updatedAt: number;\n /** `created_at` of the last accepted kind:10050 list. */\n readonly dmUpdatedAt: number;\n /** Event id of the last accepted kind:10002 (NIP-01 equal-timestamp tie-break). */\n readonly relayListId?: string | undefined;\n /** Event id of the last accepted kind:10050. */\n readonly dmListId?: string | undefined;\n};\n\n/** A filter split by gossip routes: per-relay narrowed filters plus the unrouted remainder. */\nexport type RoutedFilter = {\n /** Url → already-narrowed filter. Empty when nothing routed. */\n readonly perRelay: ReadonlyMap<string, Filter>;\n /**\n * Unrouted work for the caller:\n *\n * - No authors and no #p: original filter (\"generic\")\n * - Authors/#p all unrouted: original filter (\"orphan\")\n * - Mixed: narrowed leftover (\"fallback\")\n * - All routed: undefined\n */\n readonly remainder?: Filter | undefined;\n};\n\nexport type GossipOptions = {\n /**\n * Max relays kept per direction; the first N in list order win (NIP-65 defines no ranking).\n * Default 4.\n */\n maxRelaysPerPubkey?: number | undefined;\n /**\n * Max pubkeys with tracked routes; the map is LRU — writes and lookups refresh recency, and\n * inserting beyond the cap drops the oldest entry. Default 10_000.\n */\n maxPubkeys?: number | undefined;\n};\n\nfunction emptyRoutes(): PubkeyRoutes {\n return {\n write: [],\n read: [],\n dm: [],\n updatedAt: 0,\n dmUpdatedAt: 0,\n };\n}\n\n/**\n * Routing table for NIP-65 (10002) and NIP-17 DM relays (10050). Ingest replaceable list events,\n * then route filters into per-relay REQs.\n */\nexport class Gossip {\n readonly #routes = new Map<string, PubkeyRoutes>();\n readonly #maxRelays: number;\n readonly #maxPubkeys: number;\n\n constructor(opts: GossipOptions = {}) {\n this.#maxRelays = opts.maxRelaysPerPubkey ?? 4;\n this.#maxPubkeys = opts.maxPubkeys ?? 10_000;\n }\n\n /**\n * Ingest a routing list event.\n *\n * - Kind:10002 → write/read (NIP-65)\n * - Kind:10050 → dm (NIP-17)\n *\n * Returns true if routes for that list type were updated.\n */\n ingest(event: Event): boolean {\n if (event.kind === Kind.RelayList) {\n let items: RelayListItem[];\n try {\n items = parseRelayList(event);\n } catch {\n return false;\n }\n return this.setRoutes(event.pubkey, items, event.created_at, event.id);\n }\n if (event.kind === Kind.DirectMessageRelaysList) {\n let relays: string[];\n try {\n relays = parseDmRelayList(event);\n } catch {\n return false;\n }\n return this.setDmRoutes(event.pubkey, relays, event.created_at, event.id);\n }\n return false;\n }\n\n setRoutes(\n pubkey: string,\n items: ReadonlyArray<RelayListItem>,\n updatedAt: number = nowSeconds(),\n eventId?: string,\n ): boolean {\n const pk = pubkey.toLowerCase();\n const prev = this.#lookup(pk) ?? emptyRoutes();\n if (prev.updatedAt > updatedAt) {\n return false;\n }\n if (\n eventId !== undefined &&\n prev.relayListId !== undefined &&\n prev.updatedAt === updatedAt &&\n !isReplaceableWinner(\n { created_at: updatedAt, id: eventId },\n { created_at: prev.updatedAt, id: prev.relayListId },\n )\n ) {\n return false;\n }\n\n const write: string[] = [];\n const read: string[] = [];\n for (const item of items) {\n let url: string;\n try {\n url = normalizeURL(item.url);\n } catch {\n continue;\n }\n if (item.marker !== \"read\" && !write.includes(url)) {\n write.push(url);\n }\n if (item.marker !== \"write\" && !read.includes(url)) {\n read.push(url);\n }\n }\n\n this.#put(pk, {\n write: write.slice(0, this.#maxRelays),\n read: read.slice(0, this.#maxRelays),\n dm: prev.dm,\n updatedAt,\n dmUpdatedAt: prev.dmUpdatedAt,\n relayListId: eventId ?? prev.relayListId,\n dmListId: prev.dmListId,\n });\n return true;\n }\n\n setDmRoutes(\n pubkey: string,\n relays: ReadonlyArray<string>,\n updatedAt: number = nowSeconds(),\n eventId?: string,\n ): boolean {\n const pk = pubkey.toLowerCase();\n const prev = this.#lookup(pk) ?? emptyRoutes();\n if (prev.dmUpdatedAt > updatedAt) {\n return false;\n }\n if (\n eventId !== undefined &&\n prev.dmListId !== undefined &&\n prev.dmUpdatedAt === updatedAt &&\n !isReplaceableWinner(\n { created_at: updatedAt, id: eventId },\n { created_at: prev.dmUpdatedAt, id: prev.dmListId },\n )\n ) {\n return false;\n }\n\n const dm = normalizeRelayUrls(relays);\n\n this.#put(pk, {\n write: prev.write,\n read: prev.read,\n dm: dm.slice(0, this.#maxRelays),\n updatedAt: prev.updatedAt,\n dmUpdatedAt: updatedAt,\n relayListId: prev.relayListId,\n dmListId: eventId ?? prev.dmListId,\n });\n return true;\n }\n\n getRoutes(pubkey: string): Readonly<PubkeyRoutes> | undefined {\n return this.#lookup(pubkey.toLowerCase());\n }\n\n outboxRelays(pubkey: string): ReadonlyArray<string> {\n return this.#lookup(pubkey.toLowerCase())?.write ?? [];\n }\n\n inboxRelays(pubkey: string): ReadonlyArray<string> {\n return this.#lookup(pubkey.toLowerCase())?.read ?? [];\n }\n\n /** NIP-17 kind:10050 delivery relays for gift-wraps. */\n dmRelays(pubkey: string): ReadonlyArray<string> {\n return this.#lookup(pubkey.toLowerCase())?.dm ?? [];\n }\n\n clear(pubkey?: string): void {\n if (pubkey !== undefined && pubkey !== \"\") {\n this.#routes.delete(pubkey.toLowerCase());\n } else {\n this.#routes.clear();\n }\n }\n\n get size(): number {\n return this.#routes.size;\n }\n\n /**\n * Route a user-facing filter into per-relay sub-filters plus leftover.\n *\n * - Authors → outbox relays, filter narrowed per relay's authors\n * - #p only → inbox relays of those pubkeys\n * - Neither / all unrouted → remainder is the original filter\n * - Mixed → remainder is the narrowed leftover; all routed → no remainder\n */\n route(filter: Filter): RoutedFilter {\n const authors = filter.authors?.map((a) => a.toLowerCase());\n const pTags = filter[\"#p\"]?.map((p) => p.toLowerCase());\n\n if ((!authors || authors.length === 0) && (!pTags || pTags.length === 0)) {\n return { perRelay: new Map(), remainder: filter };\n }\n\n if (authors && authors.length > 0 && (!pTags || pTags.length === 0)) {\n return this.#breakAuthors(filter, authors, \"write\");\n }\n\n if (pTags && pTags.length > 0 && (!authors || authors.length === 0)) {\n const { authors: _authors, ...base } = filter;\n return this.#breakAuthors(base, pTags, \"read\", (b, pubkeys) => ({ ...b, \"#p\": pubkeys }));\n }\n\n // both authors and #p: send full filter to union of routes\n const unionKeys = new Set([...(authors ?? []), ...(pTags ?? [])]);\n const relays = new Set<string>();\n let anyUnrouted = false;\n for (const pk of unionKeys) {\n const r = this.#lookup(pk);\n const urls = r ? [...r.write, ...r.read] : [];\n if (urls.length === 0) {\n anyUnrouted = true;\n continue;\n }\n for (const u of urls) {\n relays.add(u);\n }\n }\n if (relays.size === 0) {\n return { perRelay: new Map(), remainder: filter };\n }\n const map = new Map<string, Filter>();\n for (const url of relays) {\n map.set(url, { ...filter });\n }\n return anyUnrouted ? { perRelay: map, remainder: filter } : { perRelay: map };\n }\n\n #breakAuthors(\n filter: Filter,\n pubkeys: string[],\n direction: \"read\" | \"write\",\n narrow: (base: Filter, pubkeys: string[]) => Filter = (base, pks) => ({\n ...base,\n authors: pks,\n }),\n ): RoutedFilter {\n const perRelay = new Map<string, string[]>();\n const unrouted: string[] = [];\n let anyRoute = false;\n\n for (const pk of pubkeys) {\n const routes = this.#lookup(pk);\n const urls = direction === \"write\" ? routes?.write : routes?.read;\n if (!urls || urls.length === 0) {\n unrouted.push(pk);\n continue;\n }\n anyRoute = true;\n for (const url of urls) {\n const list = perRelay.get(url) ?? [];\n list.push(pk);\n perRelay.set(url, list);\n }\n }\n\n if (!anyRoute) {\n return { perRelay: new Map(), remainder: filter };\n }\n\n const map = new Map<string, Filter>();\n for (const [url, pks] of perRelay) {\n map.set(url, narrow(filter, pks));\n }\n return unrouted.length > 0\n ? { perRelay: map, remainder: narrow(filter, unrouted) }\n : { perRelay: map };\n }\n\n /** Read that refreshes LRU recency. */\n #lookup(pk: string): PubkeyRoutes | undefined {\n const routes = this.#routes.get(pk);\n if (routes !== undefined) {\n touchKey(this.#routes, pk, routes);\n }\n return routes;\n }\n\n /** Write at the newest LRU end, then trim the oldest beyond the cap. */\n #put(pk: string, routes: PubkeyRoutes): void {\n touchKey(this.#routes, pk, routes);\n trimOldest(this.#routes, this.#maxPubkeys);\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"key-D_oEutQ6.mjs","names":["hexToBytes","bytesToHex"],"sources":["../src/core/key.ts"],"sourcesContent":["import { schnorr } from \"@noble/curves/secp256k1.js\";\nimport { sha256 } from \"@noble/hashes/sha2.js\";\nimport { randomBytes } from \"@noble/hashes/utils.js\";\n\nimport { CryptoError, EventValidationError } from \"./error.ts\";\nimport { markVerified, serializeValidatedEvent, validateEvent } from \"./event.ts\";\nimport type { Event, EventTemplate, UnsignedEvent } from \"./event.ts\";\nimport { assertHex32, assertSecretKeyBytes, bytesToHex, hexToBytes, utf8Encoder } from \"./util.ts\";\n\n/** 32-byte secret key held as bytes; prefer zeroize when done. */\nexport class SecretKey {\n #bytes: Uint8Array | undefined;\n\n private constructor(bytes: Uint8Array) {\n assertSecretKeyBytes(bytes);\n this.#bytes = new Uint8Array(bytes);\n }\n\n static generate(): SecretKey {\n return new SecretKey(schnorr.utils.randomSecretKey());\n }\n\n static fromBytes(bytes: Uint8Array): SecretKey {\n return new SecretKey(bytes);\n }\n\n static fromHex(hex: string): SecretKey {\n return new SecretKey(hexToBytes(assertHex32(hex, \"secret key\")));\n }\n\n get bytes(): Uint8Array {\n if (!this.#bytes) {\n throw new CryptoError(\"secret key has been zeroized\");\n }\n return new Uint8Array(this.#bytes);\n }\n\n toHex(): string {\n return bytesToHex(this.bytes);\n }\n\n /** Best-effort wipe of the internal buffer. */\n zeroize(): void {\n if (this.#bytes) {\n this.#bytes.fill(0);\n this.#bytes = undefined;\n }\n }\n}\n\n/** Anything a secret key can be given as: instance, raw bytes, or hex. */\nexport type SecretKeyInput = SecretKey | Uint8Array | string;\n\n/** Coerce a {@link SecretKeyInput} to {@link SecretKey}. */\nexport function toSecretKey(input: SecretKeyInput): SecretKey {\n if (input instanceof SecretKey) {\n return input;\n }\n if (typeof input === \"string\") {\n return SecretKey.fromHex(input);\n }\n return SecretKey.fromBytes(input);\n}\n\n/** Lowercase hex-encoded secp256k1 public key (x-only, 64 chars). */\nexport type PublicKey = string;\n\n/** Validate and normalize a hex public key; throws {@link HexError} on bad input. */\nexport function publicKeyFromHex(hex: string): PublicKey {\n return assertHex32(hex, \"public key\");\n}\n\n/**\n * BIP-340 signing backend. `publicKey` returns the 32-byte x-only public key of a 32-byte secret\n * key; `sign` returns the 64-byte BIP-340 signature of a 32-byte `id` using 32 bytes of auxiliary\n * randomness. Both throw on invalid input (for example an out-of-range secret key).\n */\nexport type SigningBackend = {\n publicKey: (secretKey: Uint8Array) => Uint8Array;\n sign: (id: Uint8Array, secretKey: Uint8Array, auxRand: Uint8Array) => Uint8Array;\n};\n\nconst nobleSigning: SigningBackend = {\n publicKey: (secretKey) => schnorr.getPublicKey(secretKey),\n sign: (id, secretKey, auxRand) => schnorr.sign(id, secretKey, auxRand),\n};\n\n/** Derive the public key for a secret key given as SecretKey, bytes, or hex. */\nexport function getPublicKey(secretKey: SecretKeyInput): PublicKey {\n return bytesToHex(schnorr.getPublicKey(toSecretKey(secretKey).bytes));\n}\n\n/** Keypair convenience wrapper. */\nexport class Keys {\n readonly secretKey: SecretKey;\n readonly publicKey: PublicKey;\n /**\n * BIP-340 backend that derived {@link publicKey} and signs in {@link finalizeEvent}/\n * {@link signEvent}. Defaults to noble; pass a loaded `NostrWasm` module or a native backend to\n * delegate signing.\n */\n readonly backend: SigningBackend;\n\n private constructor(secretKey: SecretKey, backend: SigningBackend) {\n this.secretKey = secretKey;\n this.backend = backend;\n const secretBytes = secretKey.bytes;\n let pubkey: Uint8Array;\n try {\n pubkey = backend.publicKey(secretBytes);\n } finally {\n secretBytes.fill(0);\n }\n if (pubkey.length !== 32) {\n throw new CryptoError(\"signing backend returned an invalid public key\");\n }\n this.publicKey = bytesToHex(pubkey);\n }\n\n static generate(backend: SigningBackend = nobleSigning): Keys {\n return new Keys(SecretKey.generate(), backend);\n }\n\n static fromSecretKey(secretKey: SecretKeyInput, backend: SigningBackend = nobleSigning): Keys {\n return new Keys(toSecretKey(secretKey), backend);\n }\n}\n\nfunction resolveKeys(secretKey: SecretKeyInput | Keys): Keys {\n return secretKey instanceof Keys ? secretKey : Keys.fromSecretKey(secretKey);\n}\n\n/**\n * Fill pubkey/id/sig on a template and return a signed event.\n *\n * `auxRand` is the 32-byte BIP-340 auxiliary randomness. It defaults to a fresh `randomBytes(32)`\n * draw; an explicit value makes the signature reproducible, which is required for vectors and\n * tests. Never reuse aux randomness in production.\n */\nexport function finalizeEvent(\n template: EventTemplate,\n secretKey: SecretKeyInput | Keys,\n auxRand?: Uint8Array,\n): Event {\n const keys = resolveKeys(secretKey);\n const unsigned: UnsignedEvent = {\n kind: template.kind,\n tags: template.tags,\n content: template.content,\n created_at: template.created_at,\n pubkey: keys.publicKey,\n };\n return signEvent(unsigned, keys, auxRand);\n}\n\n/**\n * Sign an already-assembled unsigned event. Rejects when `unsigned.pubkey` does not match the\n * secret key.\n *\n * `auxRand` is the 32-byte BIP-340 auxiliary randomness. It defaults to a fresh `randomBytes(32)`\n * draw; an explicit value makes the signature reproducible, which is required for vectors and\n * tests. Never reuse aux randomness in production.\n */\nexport function signEvent(\n unsigned: UnsignedEvent,\n secretKey: SecretKeyInput | Keys,\n auxRand?: Uint8Array,\n): Event {\n const keys = resolveKeys(secretKey);\n if (auxRand !== undefined && auxRand.length !== 32) {\n throw new CryptoError(\"auxRand must be 32 bytes\");\n }\n if (!validateEvent(unsigned)) {\n throw new EventValidationError(\"cannot sign invalid unsigned event\");\n }\n\n if (unsigned.pubkey !== keys.publicKey) {\n throw new CryptoError(\"unsigned event pubkey does not match secret key\");\n }\n\n const normalized: UnsignedEvent = {\n kind: unsigned.kind,\n tags: unsigned.tags,\n content: unsigned.content,\n created_at: unsigned.created_at,\n pubkey: keys.publicKey,\n };\n\n const id = bytesToHex(sha256(utf8Encoder.encode(serializeValidatedEvent(normalized))));\n const secretBytes = keys.secretKey.bytes;\n let sig: Uint8Array;\n try {\n sig = keys.backend.sign(hexToBytes(id), secretBytes, auxRand ?? randomBytes(32));\n } finally {\n secretBytes.fill(0);\n }\n if (sig.length !== 64) {\n throw new CryptoError(\"signing backend returned an invalid signature\");\n }\n const event: Event = { ...normalized, id, sig: bytesToHex(sig) };\n markVerified(event);\n return event;\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"loaders-BRh2riV9.mjs","names":[],"sources":["../src/loaders/context.ts","../src/loaders/event.ts","../src/loaders/lists.ts","../src/loaders/profile.ts","../src/loaders/dataloader.ts","../src/loaders/replaceable.ts","../src/loaders/outbox.ts","../src/loaders/index.ts"],"sourcesContent":["import type { Event } from \"../core/event.ts\";\nimport { nowSeconds } from \"../core/util.ts\";\nimport type { Pool } from \"../relay/pool.ts\";\nimport { DEFAULT_REQUEST_TIMEOUT_MS } from \"../relay/relay.ts\";\nimport type { ReactiveEventStore } from \"../store/reactive.ts\";\n\nexport type LoaderContextOptions = {\n pool: Pool;\n /** Fallback / discovery relays when no per-user routing is known. */\n relays: ReadonlyArray<string>;\n /** The reactive index loaders read from and feed fetched events into. */\n index: ReactiveEventStore;\n /**\n * Inbound-event sink for fetched events. Defaults to `index.add`; Client supplies its single\n * ingest path so loader fetches get gossip meta and persistence like every other inbound event.\n */\n ingest?: ((event: Event, relayUrl: string) => void) | undefined;\n /** Max age (seconds) before a fetched replaceable is considered stale. Default 2 days. */\n staleAfterSec?: number | undefined;\n fetchTimeoutMs?: number | undefined;\n};\n\n/** Internal dependency bag for loaders — never a module-level singleton. */\nexport class LoaderContext {\n readonly pool: Pool;\n readonly index: ReactiveEventStore;\n readonly ingest: (event: Event, relayUrl: string) => void;\n readonly staleAfterSec: number;\n readonly fetchTimeoutMs: number;\n #relays: string[];\n\n constructor(opts: LoaderContextOptions) {\n this.pool = opts.pool;\n this.#relays = [...opts.relays];\n this.index = opts.index;\n this.ingest = opts.ingest ?? ((event, relayUrl) => this.index.add(event, relayUrl));\n this.staleAfterSec = opts.staleAfterSec ?? 60 * 60 * 24 * 2;\n this.fetchTimeoutMs = opts.fetchTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;\n }\n\n /** Snapshot of discovery/fallback relays. */\n get relays(): ReadonlyArray<string> {\n return this.#relays;\n }\n\n addRelay(url: string): void {\n if (!this.#relays.includes(url)) {\n this.#relays.push(url);\n }\n }\n\n removeRelay(url: string): void {\n this.#relays = this.#relays.filter((r) => r !== url);\n }\n\n setRelays(urls: ReadonlyArray<string>): void {\n this.#relays = [...urls];\n }\n\n isFresh(fetchedAt: number, now: number = nowSeconds()): boolean {\n return now - fetchedAt < this.staleAfterSec;\n }\n}\n","import type { Event } from \"../core/event.ts\";\nimport { isAddressableKind } from \"../core/kind.ts\";\nimport { formatEventAddress } from \"../core/tag.ts\";\nimport { isHex32 } from \"../core/util.ts\";\nimport { decode, Nip19Error } from \"../nips/nip19.ts\";\nimport type { AddressPointer, EventPointer } from \"../nips/nip19.ts\";\nimport type { LoaderContext } from \"./context.ts\";\n\nexport type EventRef = string | EventPointer | AddressPointer;\n\ntype ParsedRef = {\n filter: { ids?: string[]; authors?: string[]; kinds?: number[]; \"#d\"?: string[] };\n hints: ReadonlyArray<string>;\n /** Resolve the fetched event from the index after the network round. */\n lookup: (ctx: LoaderContext) => Event | undefined;\n cacheKey: string;\n};\n\nfunction dFilter(kind: number, identifier: string): { \"#d\"?: string[] } {\n if (isAddressableKind(kind)) {\n return { \"#d\": [identifier] };\n }\n return {};\n}\n\nfunction addressRef(kind: number, pubkey: string, identifier: string): string {\n return formatEventAddress(kind, pubkey.toLowerCase(), identifier);\n}\n\nfunction parseRef(ref: EventRef): ParsedRef {\n if (typeof ref === \"string\") {\n if (isHex32(ref.toLowerCase())) {\n const id = ref.toLowerCase();\n return {\n filter: { ids: [id] },\n hints: [],\n lookup: (ctx) => ctx.index.get(id),\n cacheKey: `id:${id}`,\n };\n }\n const decoded = decode(ref);\n switch (decoded.type) {\n case \"note\":\n return {\n filter: { ids: [decoded.data] },\n hints: [],\n lookup: (ctx) => ctx.index.get(decoded.data),\n cacheKey: `id:${decoded.data}`,\n };\n case \"nevent\":\n return {\n filter: { ids: [decoded.data.id] },\n hints: decoded.data.relays ?? [],\n lookup: (ctx) => ctx.index.get(decoded.data.id),\n cacheKey: `id:${decoded.data.id}`,\n };\n case \"naddr\": {\n const addr = addressRef(decoded.data.kind, decoded.data.pubkey, decoded.data.identifier);\n return {\n filter: {\n authors: [decoded.data.pubkey.toLowerCase()],\n kinds: [decoded.data.kind],\n ...dFilter(decoded.data.kind, decoded.data.identifier),\n },\n hints: decoded.data.relays ?? [],\n lookup: (ctx) => ctx.index.getByAddress(addr),\n cacheKey: `addr:${addr}`,\n };\n }\n default:\n throw new Nip19Error(`cannot load event from ${decoded.type}`);\n }\n }\n if (\"id\" in ref) {\n const id = ref.id.toLowerCase();\n return {\n filter: { ids: [id] },\n hints: ref.relays ?? [],\n lookup: (ctx) => ctx.index.get(id),\n cacheKey: `id:${id}`,\n };\n }\n const addr = addressRef(ref.kind, ref.pubkey, ref.identifier);\n return {\n filter: {\n authors: [ref.pubkey.toLowerCase()],\n kinds: [ref.kind],\n ...dFilter(ref.kind, ref.identifier),\n },\n hints: ref.relays ?? [],\n lookup: (ctx) => ctx.index.getByAddress(addr),\n cacheKey: `addr:${addr}`,\n };\n}\n\n/**\n * Resolve an event reference (hex id, note, nevent, naddr, or pointer object) through the reactive\n * index, fetching from relays on a miss. The index is the durable store — the loader keeps only\n * in-flight coalescing, so a miss never blocks a later retry.\n */\nexport type EventLoader = {\n load: (ref: EventRef) => Promise<Event | undefined>;\n};\n\nexport function createEventLoader(ctx: LoaderContext): EventLoader {\n const inflight = new Map<string, Promise<Event | undefined>>();\n return {\n // oxlint-disable-next-line typescript/promise-function-async -- parseRef must throw synchronously on malformed refs\n load(ref: EventRef): Promise<Event | undefined> {\n const parsed = parseRef(ref);\n const hit = parsed.lookup(ctx);\n if (hit !== undefined) {\n return Promise.resolve(hit);\n }\n const pending = inflight.get(parsed.cacheKey);\n if (pending !== undefined) {\n return pending;\n }\n const p = (async (): Promise<Event | undefined> => {\n try {\n const relays = [...new Set([...parsed.hints, ...ctx.relays])];\n if (relays.length === 0) {\n return undefined;\n }\n await ctx.pool.fetch(relays, [parsed.filter], {\n timeoutMs: ctx.fetchTimeoutMs,\n onEvent: (event, relayUrl) => ctx.ingest(event, relayUrl),\n });\n return parsed.lookup(ctx);\n } finally {\n inflight.delete(parsed.cacheKey);\n }\n })();\n inflight.set(parsed.cacheKey, p);\n return p;\n },\n };\n}\n","import type { Event } from \"../core/event.ts\";\nimport { Kind } from \"../core/kind.ts\";\nimport { isHex32 } from \"../core/util.ts\";\nimport { parseDmRelayList } from \"../nips/nip17.ts\";\nimport { parseMuteList } from \"../nips/nip51.ts\";\nimport type { MuteItem } from \"../nips/nip51.ts\";\nimport type { RelayListItem } from \"../nips/nip65.ts\";\nimport { parseRelayList } from \"../nips/nip65.ts\";\nimport type { LoadStyle, ReplaceableLoader } from \"./replaceable.ts\";\n\n/** Result of a list loader: the source event, its decoded items, and freshness. */\nexport type ListResult<T> = {\n readonly event: Event | undefined;\n readonly items: ReadonlyArray<T>;\n readonly fresh: boolean;\n};\n\nfunction fromTags<T>(\n event: Event | undefined,\n map: (tag: ReadonlyArray<string>) => T | undefined,\n): T[] {\n if (event === undefined) {\n return [];\n }\n const out: T[] = [];\n for (const tag of event.tags) {\n const item = map(tag);\n if (item !== undefined) {\n out.push(item);\n }\n }\n return out;\n}\n\ntype ListLoaderOpts = { hints?: string[] | undefined; style?: LoadStyle | undefined };\n\nexport type ListLoaders = {\n follows: (pubkey: string, opts?: ListLoaderOpts) => Promise<ListResult<string>>;\n muteList: (pubkey: string, opts?: ListLoaderOpts) => Promise<ListResult<MuteItem>>;\n relayList: (pubkey: string, opts?: ListLoaderOpts) => Promise<ListResult<RelayListItem>>;\n dmRelayList: (pubkey: string, opts?: ListLoaderOpts) => Promise<ListResult<string>>;\n};\n\nexport function createListLoaders(replaceable: (kind: number) => ReplaceableLoader): ListLoaders {\n const followsLoader = replaceable(Kind.Contacts);\n const muteLoader = replaceable(Kind.MuteList);\n const relayListLoader = replaceable(Kind.RelayList);\n const dmRelayListLoader = replaceable(Kind.DirectMessageRelaysList);\n\n return {\n async follows(pubkey: string, opts?: ListLoaderOpts): Promise<ListResult<string>> {\n const { event, fresh } = await followsLoader(pubkey, opts);\n return {\n event,\n fresh,\n items: fromTags(event, (tag) =>\n tag[0] === \"p\" && tag[1] !== undefined && isHex32(tag[1].toLowerCase())\n ? tag[1].toLowerCase()\n : undefined,\n ),\n };\n },\n\n async muteList(pubkey: string, opts?: ListLoaderOpts): Promise<ListResult<MuteItem>> {\n const { event, fresh } = await muteLoader(pubkey, opts);\n let items: MuteItem[] = [];\n if (event) {\n try {\n items = parseMuteList(event);\n } catch {\n items = [];\n }\n }\n return { event, fresh, items };\n },\n\n async relayList(pubkey: string, opts?: ListLoaderOpts): Promise<ListResult<RelayListItem>> {\n const { event, fresh } = await relayListLoader(pubkey, opts);\n let items: RelayListItem[] = [];\n if (event) {\n try {\n items = parseRelayList(event);\n } catch {\n items = [];\n }\n }\n return { event, fresh, items };\n },\n\n async dmRelayList(pubkey: string, opts?: ListLoaderOpts): Promise<ListResult<string>> {\n const { event, fresh } = await dmRelayListLoader(pubkey, opts);\n let items: string[] = [];\n if (event) {\n try {\n items = parseDmRelayList(event);\n } catch {\n items = [];\n }\n }\n return { event, fresh, items };\n },\n };\n}\n","import type { ProfileMetadata } from \"../core/builder.ts\";\nimport type { Event } from \"../core/event.ts\";\nimport { Kind } from \"../core/kind.ts\";\nimport { isRecord } from \"../core/util.ts\";\nimport { npubEncode } from \"../nips/nip19.ts\";\nimport type { LoadStyle, ReplaceableLoader } from \"./replaceable.ts\";\n\n/** A profile-card view of a pubkey: identity fields plus the kind:0 event. */\nexport type NostrUser = {\n readonly pubkey: string;\n readonly npub: string;\n readonly shortName: string;\n readonly image?: string | undefined;\n readonly metadata: ProfileMetadata;\n readonly lastUpdated: number;\n readonly event: Event | undefined;\n readonly fresh: boolean;\n};\n\n/** A {@link NostrUser} with no metadata — used when no kind:0 event exists. */\nexport function bareNostrUser(pubkey: string): NostrUser {\n const pk = pubkey.toLowerCase();\n let npub: string;\n try {\n npub = npubEncode(pk);\n } catch {\n npub = pk;\n }\n return {\n pubkey: pk,\n npub,\n shortName: npub.startsWith(\"npub1\") ? `${npub.slice(0, 8)}…${npub.slice(-4)}` : pk.slice(0, 8),\n metadata: {},\n lastUpdated: 0,\n event: undefined,\n fresh: false,\n };\n}\n\nfunction nonEmpty(value: unknown): string | undefined {\n return typeof value === \"string\" && value !== \"\" ? value : undefined;\n}\n\nconst PROFILE_METADATA_KEYS: ReadonlyArray<keyof ProfileMetadata> = [\n \"name\",\n \"display_name\",\n \"about\",\n \"picture\",\n \"banner\",\n \"website\",\n \"nip05\",\n \"lud06\",\n \"lud16\",\n];\n\nfunction parseMetadata(content: string): ProfileMetadata {\n try {\n const obj: unknown = JSON.parse(content);\n if (!isRecord(obj)) {\n return {};\n }\n const out: Record<string, string> = {};\n for (const key of PROFILE_METADATA_KEYS) {\n const value = obj[key];\n if (typeof value === \"string\") {\n out[key] = value;\n }\n }\n return out;\n } catch {\n return {};\n }\n}\n\nexport type ProfileLoader = {\n load: (\n pubkey: string,\n opts?: { hints?: string[] | undefined; style?: LoadStyle | undefined },\n ) => Promise<NostrUser>;\n};\n\nexport function createProfileLoader(\n replaceable: (kind: number) => ReplaceableLoader,\n): ProfileLoader {\n const loader = replaceable(Kind.Metadata);\n\n return {\n async load(\n pubkey: string,\n opts?: { hints?: string[] | undefined; style?: LoadStyle | undefined },\n ): Promise<NostrUser> {\n const base = bareNostrUser(pubkey);\n const { event, fresh } = await loader(pubkey, opts);\n if (!event) {\n return { ...base, fresh };\n }\n\n const metadata = parseMetadata(event.content);\n const display = nonEmpty(metadata.display_name) ?? nonEmpty(metadata.name);\n return {\n ...base,\n shortName: display ?? base.shortName,\n ...(metadata.picture === undefined ? {} : { image: metadata.picture }),\n metadata,\n lastUpdated: event.created_at,\n event,\n fresh,\n };\n },\n };\n}\n","import { NostrError } from \"../core/error.ts\";\n\nexport class LoaderError extends NostrError {\n override name = \"LoaderError\";\n}\n\n/** Minimal request coalescer. Batches keys within a microtask; no global state. */\nexport type BatchLoadFn<K, V> = (keys: ReadonlyArray<K>) => Promise<ReadonlyArray<V | Error>>;\n\nexport type DataLoaderOptions<K, C = K> = {\n cacheKeyFn?: (key: K) => C;\n maxBatchSize?: number;\n /** When false, only coalesces in-flight requests (no durable memoization). Default true. */\n cache?: boolean;\n};\n\nexport class DataLoader<K, V, C = K> {\n readonly #batchLoadFn: BatchLoadFn<K, V>;\n readonly #cacheKeyFn: (key: K) => C;\n readonly #maxBatchSize: number;\n readonly #useCache: boolean;\n readonly #cache = new Map<C, Promise<V>>();\n readonly #inflight = new Map<C, Promise<V>>();\n readonly #queue: Array<{\n key: K;\n resolve: (v: V) => void;\n reject: (e: unknown) => void;\n }> = [];\n #scheduled = false;\n\n constructor(batchLoadFn: BatchLoadFn<K, V>, options: DataLoaderOptions<K, C> = {}) {\n this.#batchLoadFn = batchLoadFn;\n // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- the default C = K makes this the identity for callers that omit cacheKeyFn\n this.#cacheKeyFn = options.cacheKeyFn ?? ((k: K) => k as unknown as C);\n this.#maxBatchSize = options.maxBatchSize ?? Number.POSITIVE_INFINITY;\n this.#useCache = options.cache !== false;\n }\n\n async load(key: K): Promise<V> {\n const cacheKey = this.#cacheKeyFn(key);\n if (this.#useCache) {\n const cached = this.#cache.get(cacheKey);\n if (cached) {\n return cached;\n }\n }\n const inflight = this.#inflight.get(cacheKey);\n if (inflight) {\n return inflight;\n }\n\n const pending = new Promise<V>((resolve, reject) => {\n this.#queue.push({ key, resolve, reject });\n if (this.#queue.length >= this.#maxBatchSize) {\n this.#dispatch();\n } else if (!this.#scheduled) {\n this.#scheduled = true;\n queueMicrotask(() => this.#dispatch());\n }\n });\n // Identity guard: clear()+re-load may have registered a fresh entry under\n // this key; the stale settle must not evict it.\n const cleanup = async (): Promise<V> => {\n try {\n return await pending;\n } finally {\n if (this.#inflight.get(cacheKey) === promise) {\n this.#inflight.delete(cacheKey);\n }\n }\n };\n const promise = cleanup();\n\n this.#inflight.set(cacheKey, promise);\n if (this.#useCache) {\n this.#cache.set(cacheKey, promise);\n }\n return promise;\n }\n\n clear(key: K): void {\n this.#cache.delete(this.#cacheKeyFn(key));\n this.#inflight.delete(this.#cacheKeyFn(key));\n }\n\n clearAll(): void {\n this.#cache.clear();\n this.#inflight.clear();\n }\n\n #dispatch(): void {\n this.#scheduled = false;\n const batch = this.#queue.splice(0, this.#maxBatchSize);\n if (batch.length === 0) {\n return;\n }\n\n const keys = batch.map((b) => b.key);\n void (async () => {\n let values: ReadonlyArray<V | Error>;\n try {\n values = await this.#batchLoadFn(keys);\n } catch (error: unknown) {\n for (const item of batch) {\n item.reject(error);\n }\n return;\n }\n if (values.length !== keys.length) {\n const err = new LoaderError(\n `DataLoader batch function must return array of length ${keys.length}, got ${values.length}`,\n );\n for (const item of batch) {\n item.reject(err);\n }\n return;\n }\n for (const [i, value] of values.entries()) {\n const item = batch[i];\n if (item === undefined) {\n continue;\n }\n if (value instanceof Error) {\n item.reject(value);\n } else {\n item.resolve(value);\n }\n }\n })();\n\n if (this.#queue.length > 0) {\n this.#scheduled = true;\n queueMicrotask(() => this.#dispatch());\n }\n }\n}\n","import { touchKey, trimOldest } from \"../core/collections.ts\";\nimport type { Event } from \"../core/event.ts\";\nimport type { Filter } from \"../core/filter.ts\";\nimport { formatEventAddress } from \"../core/tag.ts\";\nimport { nowSeconds } from \"../core/util.ts\";\nimport type { LoaderContext } from \"./context.ts\";\nimport { DataLoader } from \"./dataloader.ts\";\n\n/** Result of a replaceable load: the winning event (or none) and freshness. */\nexport type ReplaceableLoadResult = {\n readonly event: Event | undefined;\n readonly fresh: boolean;\n};\n\n/** `default`: fetch when stale or unfetched; `force`: always fetch; `cache-only`: index only. */\nexport type LoadStyle = \"default\" | \"force\" | \"cache-only\";\n\n/** Loads the winning replaceable/addressable event of one kind for a pubkey. */\nexport type ReplaceableLoader = (\n pubkey: string,\n opts?: { hints?: string[] | undefined; style?: LoadStyle | undefined },\n) => Promise<ReplaceableLoadResult>;\n\n/** FIFO cap on per-address fetch records (misses included). */\nconst MAX_FETCHED_AT = 10_000;\n\n/**\n * Address -> last network fetch. `hit` is true when the index held a winner right after that fetch;\n * a fresh `hit` record whose winner was evicted since must refetch instead of reporting a miss.\n */\ntype FetchedAt = Map<string, { at: number; hit: boolean }>;\n\n/**\n * Batch-fetch replaceable events (kind + authors) via the pool. Fetched events are written into the\n * reactive index (`onEvent` records `seenOn`) and the winner is read back from it; the index — not\n * the loader — owns the stored value and winner selection. The loader only tracks per-address\n * `fetchedAt` timestamps so a recent fetch (including a miss) is not repeated within\n * `staleAfterSec`.\n */\nexport function createReplaceableLoader(ctx: LoaderContext, kind: number): ReplaceableLoader {\n type Key = { pubkey: string; hints?: string[] | undefined };\n\n const fetchedAt: FetchedAt = new Map();\n const markFetched = (addr: string, hit: boolean, now: number): void => {\n touchKey(fetchedAt, addr, { at: now, hit });\n trimOldest(fetchedAt, MAX_FETCHED_AT);\n };\n\n const loader = new DataLoader<Key, ReplaceableLoadResult, string>(\n async (keys) => {\n const authors = [...new Set(keys.map((k) => k.pubkey))];\n const relays = [...new Set([...keys.flatMap((k) => k.hints ?? []), ...ctx.relays])];\n if (relays.length > 0) {\n const filter: Filter = { kinds: [kind], authors };\n await ctx.pool.fetch(relays, [filter], {\n timeoutMs: ctx.fetchTimeoutMs,\n onEvent: (event, relayUrl) => ctx.ingest(event, relayUrl),\n });\n }\n const now = nowSeconds();\n return keys.map((k) => {\n const addr = formatEventAddress(kind, k.pubkey, \"\");\n const event = ctx.index.getByAddress(addr);\n // Record the fetch even when nothing came back: a recent miss must\n // not be retried on every `default` load.\n markFetched(addr, event !== undefined, now);\n return { event, fresh: true };\n });\n },\n {\n cache: false,\n cacheKeyFn: (k) => `${kind}:${k.pubkey}:${(k.hints ?? []).join(\",\")}`,\n maxBatchSize: 50,\n },\n );\n\n return async (pubkey, opts) => {\n const pk = pubkey.toLowerCase();\n const address = formatEventAddress(kind, pk, \"\");\n const style = opts?.style ?? \"default\";\n const current = (): Event | undefined => ctx.index.getByAddress(address);\n\n if (style === \"cache-only\") {\n return { event: current(), fresh: false };\n }\n if (style === \"default\") {\n const rec = fetchedAt.get(address);\n const event = current();\n // A fresh record short-circuits the fetch, unless it was a hit whose\n // winner has since been evicted — that would report a stale miss.\n if (rec !== undefined && ctx.isFresh(rec.at) && (!rec.hit || event !== undefined)) {\n return { event, fresh: false };\n }\n }\n return loader.load({ pubkey: pk, hints: opts?.hints });\n };\n}\n","import { throwIfAborted } from \"../core/abort.ts\";\nimport { NostrError } from \"../core/error.ts\";\nimport type { Event } from \"../core/event.ts\";\nimport { sortedEvents } from \"../core/event.ts\";\nimport type { Filter } from \"../core/filter.ts\";\nimport { Kind } from \"../core/kind.ts\";\nimport { invokeSafely } from \"../core/report.ts\";\nimport type { Mutable } from \"../core/util.ts\";\nimport { normalizeRelayUrls, nowSeconds } from \"../core/util.ts\";\nimport type { Gossip } from \"../gossip/gossip.ts\";\nimport type { Pool } from \"../relay/pool.ts\";\nimport { DEFAULT_REQUEST_TIMEOUT_MS } from \"../relay/relay.ts\";\nimport type { Closer } from \"../relay/subscription.ts\";\nimport { toStorageError } from \"../storage/error.ts\";\nimport { outboxBoundKey } from \"../storage/put.ts\";\nimport type { EventStore, OutboxBound } from \"../storage/types.ts\";\n\nexport type { OutboxBound } from \"../storage/types.ts\";\n\n/** Outbox feed lifecycle error (e.g. use after close). */\nexport class OutboxError extends NostrError {\n override name = \"OutboxError\";\n}\n\n/** Trailing-flush delay for live bound persistence: one write per dirty bound per window. */\nexport const BOUND_FLUSH_MS = 1000;\n\n/** Options for {@link OutboxFeed}. */\nexport type OutboxFeedOptions = {\n pool: Pool;\n gossip: Gossip;\n storage: EventStore;\n /** Fallback relays when an author has no known outbox routes. */\n discoveryRelays: ReadonlyArray<string>;\n authors: ReadonlyArray<string>;\n /** Event kinds to fetch. Default `[Kind.TextNote]`. */\n kinds?: ReadonlyArray<number> | undefined;\n /** Max write-relays to use per author. Default 3. */\n maxRelaysPerAuthor?: number | undefined;\n fetchTimeoutMs?: number | undefined;\n /** Called for live events after observe, and for sync events returned by applySync. */\n onEvent?: ((event: Event) => void) | undefined;\n /** Live ingest (e.g. `client.observe`) with the source relay URL. StartLive never putManys. */\n observe?: ((event: Event, relayUrl: string) => void) | undefined;\n /**\n * Every receipt from every relay — live (including duplicates the feed's own dedupe skips) and\n * sync fetch batches — for seenOn bookkeeping.\n */\n seen?: ((event: Event, relayUrl: string) => void) | undefined;\n /**\n * Sync path only. Awaited. Return events that should advance bounds. Throw → sync throws, bounds\n * unchanged.\n */\n applySync?: ((events: ReadonlyArray<Event>) => Promise<ReadonlyArray<Event>>) | undefined;\n /** Load kind:10002 for authors before sync when routes are missing. Requires a relay-list loader. */\n hydrate?: ((pubkeys: ReadonlyArray<string>) => Promise<void>) | undefined;\n};\n\n/**\n * Group authors by outbox relay (discovery fallback). `prefer` reorders existing candidates only;\n * it never appends a URL that is not already in the author's outbox or discovery list. Returns\n * Map<relayUrl, authors[]>.\n */\nexport function groupAuthorsByOutboxRelay(\n authors: ReadonlyArray<string>,\n gossip: Gossip,\n discoveryRelays: ReadonlyArray<string>,\n maxRelaysPerAuthor = 3,\n prefer: ReadonlyArray<string> = [],\n): Map<string, string[]> {\n const leftover = new Set(\n (gossip.route({ authors: authors.map((a) => a.toLowerCase()) }).remainder?.authors ?? []).map(\n (a) => a.toLowerCase(),\n ),\n );\n const preferSet = new Set(normalizeRelayUrls(prefer));\n const discovery = normalizeRelayUrls(discoveryRelays);\n const map = new Map<string, string[]>();\n\n for (const author of authors) {\n const pk = author.toLowerCase();\n const urls = leftover.has(pk) ? discovery : gossip.outboxRelays(pk);\n const preferred: string[] = [];\n const rest: string[] = [];\n for (const url of urls) {\n if (preferSet.has(url)) {\n preferred.push(url);\n } else {\n rest.push(url);\n }\n }\n for (const url of [...preferred, ...rest].slice(0, maxRelaysPerAuthor)) {\n const list = map.get(url) ?? [];\n if (!list.includes(pk)) {\n list.push(pk);\n }\n map.set(url, list);\n }\n }\n return map;\n}\n\n/**\n * Outbox-model feed: history sync + live subscription for a set of authors, routed via NIP-65 write\n * relays (with discovery fallback).\n */\nexport class OutboxFeed {\n readonly #pool: Pool;\n readonly #gossip: Gossip;\n readonly #storage: EventStore;\n readonly #discovery: string[];\n readonly #kinds: number[];\n readonly #maxRelays: number;\n readonly #timeoutMs: number;\n readonly #onEvent: ((event: Event) => void) | undefined;\n readonly #observe: ((event: Event, relayUrl: string) => void) | undefined;\n readonly #seen: ((event: Event, relayUrl: string) => void) | undefined;\n readonly #applySync:\n | ((events: ReadonlyArray<Event>) => Promise<ReadonlyArray<Event>>)\n | undefined;\n readonly #hydrate: ((pubkeys: ReadonlyArray<string>) => Promise<void>) | undefined;\n readonly #bounds = new Map<string, Mutable<OutboxBound>>();\n /** Bound keys dirtied by live events since the last flush. */\n readonly #dirtyBounds = new Set<string>();\n #boundTimer: ReturnType<typeof setTimeout> | undefined;\n #authors: string[];\n #liveCloser: Closer | undefined;\n #closed = false;\n\n constructor(opts: OutboxFeedOptions) {\n this.#pool = opts.pool;\n this.#gossip = opts.gossip;\n this.#storage = opts.storage;\n this.#discovery = [...opts.discoveryRelays];\n this.#authors = [...new Set(opts.authors.map((a) => a.toLowerCase()))];\n this.#kinds = [...(opts.kinds ?? [Kind.TextNote])];\n this.#maxRelays = opts.maxRelaysPerAuthor ?? 3;\n this.#timeoutMs = opts.fetchTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;\n this.#onEvent = opts.onEvent;\n this.#observe = opts.observe;\n this.#seen = opts.seen;\n this.#applySync = opts.applySync;\n this.#hydrate = opts.hydrate;\n }\n\n get authors(): ReadonlyArray<string> {\n return this.#authors;\n }\n\n get kinds(): ReadonlyArray<number> {\n return this.#kinds;\n }\n\n /** Replace the author set (does not auto-restart live). */\n setAuthors(authors: ReadonlyArray<string>): void {\n this.#authors = [...new Set(authors.map((a) => a.toLowerCase()))];\n }\n\n getBound(pubkey: string, kind: number): OutboxBound | undefined {\n return this.#bounds.get(outboxBoundKey(pubkey, kind));\n }\n\n /** Load NIP-65 relay lists for authors that have no outbox routes yet. */\n async hydrate(): Promise<void> {\n this.#assertOpen();\n if (!this.#hydrate) {\n return;\n }\n const missing = this.#authors.filter((pk) => this.#gossip.outboxRelays(pk).length === 0);\n if (missing.length === 0) {\n return;\n }\n await this.#hydrate(missing);\n }\n\n /** One-shot history pull. Persistence is `applySync`; bounds move only for events it returns. */\n async sync(opts?: {\n limit?: number | undefined;\n since?: number | undefined;\n until?: number | undefined;\n signal?: AbortSignal | undefined;\n /** Skip hydrate() even if provided. */\n skipHydrate?: boolean | undefined;\n }): Promise<Event[]> {\n this.#assertOpen();\n if (opts?.skipHydrate !== true) {\n await this.hydrate();\n }\n await this.#hydrateBoundsFromStore();\n\n const byRelay = groupAuthorsByOutboxRelay(\n this.#authors,\n this.#gossip,\n this.#discovery,\n this.#maxRelays,\n this.#pool.connectedUrls(),\n );\n\n if (byRelay.size === 0) {\n return [];\n }\n\n const byId = new Map<string, Event>();\n await Promise.all(\n [...byRelay.entries()].map(async ([url, authors]) => {\n throwIfAborted(opts?.signal);\n\n const filters = this.#syncFilters(authors, opts);\n try {\n const batch = await this.#pool.fetch([url], filters, {\n timeoutMs: this.#timeoutMs,\n signal: opts?.signal,\n onEvent: (event, relayUrl) => invokeSafely(() => this.#seen?.(event, relayUrl)),\n });\n for (const event of batch) {\n byId.set(event.id, event);\n }\n } catch (error) {\n // An abort rejects the whole sync; per-relay failures are skipped.\n if (opts?.signal?.aborted === true) {\n throw error;\n }\n }\n }),\n );\n\n const unique = [...byId.values()];\n if (unique.length === 0) {\n return [];\n }\n\n let applied: ReadonlyArray<Event> = [];\n if (this.#applySync) {\n try {\n applied = await this.#applySync(unique);\n } catch (error) {\n throw toStorageError(error);\n }\n }\n const dirty = new Set<string>();\n for (const event of applied) {\n this.#updateBounds(event);\n dirty.add(outboxBoundKey(event.pubkey, event.kind));\n invokeSafely(() => this.#onEvent?.(event));\n }\n await this.#persistBounds(dirty);\n return sortedEvents(unique);\n }\n\n /**\n * Start a live subscription across outbox relays for the current authors. Closes any previous\n * live subscription.\n */\n startLive(opts?: { signal?: AbortSignal | undefined; since?: number | undefined }): Closer {\n this.#assertOpen();\n this.#liveCloser?.close(\"restart\");\n\n const byRelay = groupAuthorsByOutboxRelay(\n this.#authors,\n this.#gossip,\n this.#discovery,\n this.#maxRelays,\n this.#pool.connectedUrls(),\n );\n\n const seen = new Set<string>();\n const closers: Closer[] = [];\n const since = opts?.since ?? nowSeconds() - 60;\n\n for (const [url, authors] of byRelay) {\n const filter: Filter = {\n authors,\n kinds: this.#kinds,\n since,\n };\n closers.push(\n this.#pool.subscribe([url], [filter], {\n signal: opts?.signal,\n onEvent: (event, relayUrl) => {\n invokeSafely(() => this.#seen?.(event, relayUrl));\n if (seen.has(event.id)) {\n return;\n }\n seen.add(event.id);\n this.#noteEvent(event, relayUrl);\n },\n }),\n );\n }\n\n const closer = {\n close: (reason?: string) => {\n for (const c of closers) {\n c.close(reason);\n }\n if (this.#liveCloser === closer) {\n this.#liveCloser = undefined;\n }\n },\n };\n this.#liveCloser = closer;\n return closer;\n }\n\n /** Convenience: hydrate + sync + startLive. */\n async start(opts?: {\n limit?: number | undefined;\n signal?: AbortSignal | undefined;\n }): Promise<Closer & { events: Event[] }> {\n const events = await this.sync({ limit: opts?.limit, signal: opts?.signal });\n const live = this.startLive({ signal: opts?.signal });\n return { close: (r) => live.close(r), events };\n }\n\n close(): void {\n this.#closed = true;\n this.#liveCloser?.close(\"feed closed\");\n this.#liveCloser = undefined;\n if (this.#boundTimer !== undefined) {\n clearTimeout(this.#boundTimer);\n this.#boundTimer = undefined;\n }\n void this.#flushBounds();\n }\n\n #assertOpen(): void {\n if (this.#closed) {\n throw new OutboxError(\"OutboxFeed is closed\");\n }\n }\n\n async #hydrateBoundsFromStore(): Promise<void> {\n await Promise.all(\n this.#authors.flatMap((pk) =>\n this.#kinds.map(async (kind) => {\n const key = outboxBoundKey(pk, kind);\n if (this.#bounds.has(key)) {\n return;\n }\n const bound = await this.#storage.getOutboxBound(pk, kind);\n if (!bound) {\n return;\n }\n await this.#storage.setOutboxBound(pk, kind, bound);\n this.#bounds.set(key, { oldest: bound.oldest, newest: bound.newest });\n }),\n ),\n );\n }\n\n #syncFilters(\n authors: string[],\n opts?: { limit?: number | undefined; since?: number | undefined; until?: number | undefined },\n ): Filter[] {\n const base: Filter = {\n kinds: this.#kinds,\n limit: opts?.limit ?? 50,\n ...(opts?.until === undefined ? {} : { until: opts.until }),\n };\n\n if (opts?.since !== undefined) {\n return [{ ...base, authors, since: opts.since }];\n }\n\n const bounded: string[] = [];\n const unbounded: string[] = [];\n let minBoundedNewest: number | undefined;\n\n for (const pk of authors) {\n let newest: number | undefined;\n let complete = true;\n for (const kind of this.#kinds) {\n const b = this.#bounds.get(outboxBoundKey(pk, kind));\n if (!b) {\n complete = false;\n break;\n }\n newest = Math.min(newest ?? b.newest, b.newest);\n }\n if (complete && newest !== undefined) {\n bounded.push(pk);\n if (minBoundedNewest === undefined || newest < minBoundedNewest) {\n minBoundedNewest = newest;\n }\n } else {\n unbounded.push(pk);\n }\n }\n\n if (bounded.length === 0 || minBoundedNewest === undefined) {\n return [{ ...base, authors }];\n }\n // A bound far in the future (bad clock, hostile stored data) must not push `since` past wall\n // clock — clamp at this single consumer point so live-updated, persisted, and store-derived\n // bounds are all covered. Worst case is re-delivery, never loss.\n const since = Math.max(0, Math.min(minBoundedNewest, nowSeconds()) - 1);\n if (unbounded.length === 0) {\n return [{ ...base, authors, since }];\n }\n return [\n { ...base, authors: bounded, since },\n { ...base, authors: unbounded },\n ];\n }\n\n #noteEvent(event: Event, relayUrl: string): void {\n this.#updateBounds(event);\n const key = outboxBoundKey(event.pubkey, event.kind);\n if (this.#bounds.has(key)) {\n this.#dirtyBounds.add(key);\n this.#boundTimer ??= setTimeout(() => {\n this.#boundTimer = undefined;\n void this.#flushBounds();\n }, BOUND_FLUSH_MS);\n }\n invokeSafely(() => this.#observe?.(event, relayUrl));\n invokeSafely(() => this.#onEvent?.(event));\n }\n\n /** Persist all bounds dirtied since the last flush. Best-effort: errors are swallowed. */\n async #flushBounds(): Promise<void> {\n const keys = [...this.#dirtyBounds];\n this.#dirtyBounds.clear();\n try {\n await this.#persistBounds(keys);\n } catch {\n // Bound persistence is best-effort.\n }\n }\n\n async #persistBounds(keys: Iterable<string>): Promise<void> {\n const writes: Array<Promise<void>> = [];\n for (const key of keys) {\n const bound = this.#bounds.get(key);\n if (!bound) {\n continue;\n }\n const sep = key.lastIndexOf(\":\");\n writes.push(\n this.#storage.setOutboxBound(key.slice(0, sep), Number(key.slice(sep + 1)), {\n oldest: bound.oldest,\n newest: bound.newest,\n }),\n );\n }\n if (writes.length === 0) {\n return;\n }\n try {\n await Promise.all(writes);\n } catch (error) {\n throw toStorageError(error);\n }\n }\n\n #updateBounds(event: Event): void {\n const key = outboxBoundKey(event.pubkey, event.kind);\n const prev = this.#bounds.get(key);\n if (!prev) {\n this.#bounds.set(key, { oldest: event.created_at, newest: event.created_at });\n return;\n }\n if (event.created_at < prev.oldest) {\n prev.oldest = event.created_at;\n }\n if (event.created_at > prev.newest) {\n prev.newest = event.created_at;\n }\n }\n}\n","import type { Event } from \"../core/event.ts\";\nimport type { Pool } from \"../relay/pool.ts\";\nimport type { ReactiveEventStore } from \"../store/reactive.ts\";\nimport { LoaderContext } from \"./context.ts\";\nimport { createEventLoader } from \"./event.ts\";\nimport type { EventLoader } from \"./event.ts\";\nimport { createListLoaders } from \"./lists.ts\";\nimport type { ListLoaders } from \"./lists.ts\";\nimport { createProfileLoader } from \"./profile.ts\";\nimport type { ProfileLoader } from \"./profile.ts\";\nimport { createReplaceableLoader } from \"./replaceable.ts\";\nimport type { ReplaceableLoader } from \"./replaceable.ts\";\n\nexport {\n type LoadStyle,\n type ReplaceableLoader,\n type ReplaceableLoadResult,\n} from \"./replaceable.ts\";\nexport { type ListResult, type ListLoaders } from \"./lists.ts\";\nexport { bareNostrUser, type NostrUser, type ProfileLoader } from \"./profile.ts\";\nexport { type EventLoader, type EventRef } from \"./event.ts\";\nexport {\n OutboxError,\n OutboxFeed,\n groupAuthorsByOutboxRelay,\n type OutboxBound,\n type OutboxFeedOptions,\n} from \"./outbox.ts\";\n\n/** The loader surface returned by {@link createLoaders}. */\nexport type Loaders = {\n follows: ListLoaders[\"follows\"];\n muteList: ListLoaders[\"muteList\"];\n relayList: ListLoaders[\"relayList\"];\n dmRelayList: ListLoaders[\"dmRelayList\"];\n profile: ProfileLoader[\"load\"];\n event: EventLoader[\"load\"];\n /** Generic replaceable loader for any kind; memoized per kind. */\n replaceable: (kind: number) => ReplaceableLoader;\n addRelay: (url: string) => void;\n removeRelay: (url: string) => void;\n};\n\n/** Options for {@link createLoaders}. */\nexport type CreateLoadersOptions = {\n pool: Pool;\n relays: ReadonlyArray<string>;\n /** The reactive index loaders read from and feed fetched events into. */\n index: ReactiveEventStore;\n /**\n * Inbound-event sink for fetched events; defaults to `index.add`. Client wires its single ingest\n * path so loader fetches get gossip meta and persistence like every other inbound event.\n */\n ingest?: ((event: Event, relayUrl: string) => void) | undefined;\n staleAfterSec?: number | undefined;\n fetchTimeoutMs?: number | undefined;\n};\n\n/** Build an instance-scoped loader suite (no module globals). */\nexport function createLoaders(opts: CreateLoadersOptions): Loaders {\n const context = new LoaderContext(opts);\n const byKind = new Map<number, ReplaceableLoader>();\n const replaceable = (kind: number): ReplaceableLoader => {\n let loader = byKind.get(kind);\n if (loader === undefined) {\n loader = createReplaceableLoader(context, kind);\n byKind.set(kind, loader);\n }\n return loader;\n };\n const lists = createListLoaders(replaceable);\n const profile = createProfileLoader(replaceable);\n const event = createEventLoader(context);\n return {\n follows: async (pubkey, o) => lists.follows(pubkey, o),\n muteList: async (pubkey, o) => lists.muteList(pubkey, o),\n relayList: async (pubkey, o) => lists.relayList(pubkey, o),\n dmRelayList: async (pubkey, o) => lists.dmRelayList(pubkey, o),\n profile: async (pubkey, o) => profile.load(pubkey, o),\n // oxlint-disable-next-line typescript/promise-function-async -- must pass through load's synchronous throw on malformed refs\n event: (ref) => event.load(ref),\n replaceable,\n addRelay: (url) => context.addRelay(url),\n removeRelay: (url) => context.removeRelay(url),\n };\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"memory-index-3GSrPIWj.mjs","names":[],"sources":["../src/storage/sorted-events.ts","../src/storage/memory-index.ts"],"sourcesContent":["import type { Event } from \"../core/event.ts\";\n\n/**\n * Chunk bounds: a chunk splice moves at most `CHUNK_MAX` element refs and a chunk merge at\n * `CHUNK_MIN` keeps chunk count proportional to `size` under removals.\n */\nconst CHUNK_MAX = 1024;\nconst CHUNK_MIN = 128;\n\n/** Reverse of `compareEventsDesc`: `created_at` asc, `id` desc — the physical chunk order. */\nfunction compareAsc(\n a: { id: string; created_at: number },\n b: { id: string; created_at: number },\n): number {\n if (a.created_at !== b.created_at) {\n return a.created_at - b.created_at;\n }\n if (a.id < b.id) {\n return 1;\n }\n if (a.id > b.id) {\n return -1;\n }\n return 0;\n}\n\nfunction mid(lo: number, hi: number): number {\n return lo + Math.floor((hi - lo) / 2);\n}\n\n/** A read cursor over a `SortedEvents` range in canonical order; `value` is the current event. */\nexport class EventCursor {\n readonly #chunks: Event[][];\n #chunk: number;\n #index: number;\n readonly #endChunk: number;\n readonly #endIndex: number;\n\n constructor(chunks: Event[][], chunk: number, index: number, endChunk: number, endIndex: number) {\n this.#chunks = chunks;\n this.#chunk = chunk;\n this.#index = index;\n this.#endChunk = endChunk;\n this.#endIndex = endIndex;\n }\n\n get value(): Event | undefined {\n const chunk = this.#chunk;\n const index = this.#index;\n if (chunk < this.#endChunk || (chunk === this.#endChunk && index <= this.#endIndex)) {\n return undefined;\n }\n return this.#chunks[chunk]?.[index];\n }\n\n next(): void {\n this.#index -= 1;\n if (this.#index < 0) {\n this.#chunk -= 1;\n this.#index = (this.#chunks[this.#chunk]?.length ?? 1) - 1;\n }\n }\n}\n\n/**\n * Event refs sorted in canonical order (`compareEventsDesc`: `created_at` desc, `id` asc) stored as\n * chunks of ≤ `CHUNK_MAX` elements in ascending physical order so insertion and removal\n * binary-search a chunk and then move a bounded number of refs — appending newer events (live\n * ingest, ascending fills) pushes at the end, and appending older ones (relay backfill) moves at\n * most one chunk's worth of refs. Internal to the memory index; not exported.\n */\nexport class SortedEvents {\n #chunks: Event[][] = [];\n #size = 0;\n\n get size(): number {\n return this.#size;\n }\n\n clear(): void {\n this.#chunks = [];\n this.#size = 0;\n }\n\n /** Index of the chunk `event` belongs to: the last one whose first element is not newer. */\n #chunkOf(event: Event): number {\n const chunks = this.#chunks;\n let lo = 0;\n let hi = chunks.length;\n while (lo < hi) {\n const m = mid(lo, hi);\n const first = chunks[m]?.[0];\n if (first !== undefined && compareAsc(first, event) <= 0) {\n lo = m + 1;\n } else {\n hi = m;\n }\n }\n return lo - 1;\n }\n\n insert(event: Event): void {\n const chunks = this.#chunks;\n if (chunks.length === 0) {\n chunks.push([event]);\n this.#size = 1;\n return;\n }\n const chunkIndex = Math.max(this.#chunkOf(event), 0);\n const chunk = chunks[chunkIndex];\n if (chunk === undefined) {\n return;\n }\n // Upper bound inside the chunk: insert after all elements that sort before `event`.\n let a = 0;\n let b = chunk.length;\n while (a < b) {\n const m = mid(a, b);\n const element = chunk[m];\n if (element !== undefined && compareAsc(element, event) <= 0) {\n a = m + 1;\n } else {\n b = m;\n }\n }\n chunk.splice(a, 0, event);\n this.#size += 1;\n if (chunk.length > CHUNK_MAX) {\n const half = chunk.splice(Math.floor(CHUNK_MAX / 2));\n chunks.splice(chunkIndex + 1, 0, half);\n }\n }\n\n remove(event: Event): boolean {\n const chunks = this.#chunks;\n const chunkIndex = this.#chunkOf(event);\n if (chunkIndex < 0) {\n return false;\n }\n const chunk = chunks[chunkIndex];\n if (chunk === undefined) {\n return false;\n }\n let a = 0;\n let b = chunk.length;\n while (a < b) {\n const m = mid(a, b);\n const element = chunk[m];\n if (element !== undefined && compareAsc(element, event) < 0) {\n a = m + 1;\n } else {\n b = m;\n }\n }\n const found = chunk[a];\n if (found === undefined || compareAsc(found, event) !== 0) {\n return false;\n }\n chunk.splice(a, 1);\n this.#size -= 1;\n if (chunk.length === 0) {\n chunks.splice(chunkIndex, 1);\n } else if (chunk.length < CHUNK_MIN) {\n // Absorb a thin chunk into a neighbor when it fits; a small chunk left between\n // fuller ones is still sorted and correct.\n const next = chunks[chunkIndex + 1];\n const prev = chunks[chunkIndex - 1];\n if (next !== undefined && chunk.length + next.length <= CHUNK_MAX) {\n chunk.push(...next);\n chunks.splice(chunkIndex + 1, 1);\n } else if (prev !== undefined && prev.length + chunk.length <= CHUNK_MAX) {\n prev.push(...chunk);\n chunks.splice(chunkIndex, 1);\n }\n }\n return true;\n }\n\n /**\n * Cursor over the [since, until] `created_at` window in canonical (desc) order: iteration starts\n * at the newest element with `created_at <= until` and stops before the first with `created_at <\n * since`. Chunks and their elements are stored ascending; the cursor walks back.\n */\n cursor(since?: number, until?: number): EventCursor {\n const chunks = this.#chunks;\n // Start position: last element with created_at <= until.\n let chunk = chunks.length - 1;\n let index = -1;\n if (until === undefined) {\n index = (chunks[chunk]?.length ?? 1) - 1;\n } else {\n // Last chunk holding an element with created_at <= until.\n let lo = 0;\n let hi = chunks.length;\n while (lo < hi) {\n const m = mid(lo, hi);\n const first = chunks[m]?.[0];\n if (first !== undefined && first.created_at <= until) {\n lo = m + 1;\n } else {\n hi = m;\n }\n }\n chunk = lo - 1;\n if (chunk >= 0) {\n const bucket = chunks[chunk];\n let a = 0;\n let b = bucket?.length ?? 0;\n while (a < b) {\n const m = mid(a, b);\n const element = bucket?.[m];\n if (element !== undefined && element.created_at <= until) {\n a = m + 1;\n } else {\n b = m;\n }\n }\n index = a - 1;\n }\n }\n // End position: last element with created_at < since (walk stops once past it).\n let endChunk = -1;\n let endIndex = -1;\n if (since !== undefined) {\n // First chunk holding an element with created_at >= since; the position before the\n // first such element is the boundary.\n let lo = 0;\n let hi = chunks.length;\n while (lo < hi) {\n const m = mid(lo, hi);\n const bucket = chunks[m];\n const last = bucket?.at(-1);\n if (last !== undefined && last.created_at >= since) {\n hi = m;\n } else {\n lo = m + 1;\n }\n }\n if (lo === chunks.length) {\n // Everything is older than since: empty range.\n endChunk = Number.MAX_SAFE_INTEGER;\n } else {\n const bucket = chunks[lo];\n let a = 0;\n let b = bucket?.length ?? 0;\n while (a < b) {\n const m = mid(a, b);\n const element = bucket?.[m];\n if (element !== undefined && element.created_at >= since) {\n b = m;\n } else {\n a = m + 1;\n }\n }\n if (a === 0) {\n endChunk = lo - 1;\n endIndex = (chunks[endChunk]?.length ?? 1) - 1;\n } else {\n endChunk = lo;\n endIndex = a - 1;\n }\n }\n }\n return new EventCursor(chunks, chunk, index, endChunk, endIndex);\n }\n}\n","import { trimOldest } from \"../core/collections.ts\";\nimport type { Event } from \"../core/event.ts\";\nimport { compareEventsDesc, itemCompare, sortEvents } from \"../core/event.ts\";\nimport type { Filter } from \"../core/filter.ts\";\nimport { filterTagConditions, matchFilter } from \"../core/filter.ts\";\nimport { eventAddress, formatEventAddress, parseEventAddress } from \"../core/tag.ts\";\nimport { DeletionState } from \"./deletion.ts\";\nimport { applyPutMemory, decidePut, outboxBoundKey } from \"./put.ts\";\nimport type { PutLookup } from \"./put.ts\";\nimport { SortedEvents } from \"./sorted-events.ts\";\nimport type { EventCursor } from \"./sorted-events.ts\";\nimport type { NegentropyItem, OutboxBound, PutResult } from \"./types.ts\";\n\n/**\n * NIP-01 only defines single-letter tag conditions; `#e`/`#p` hold hex ids (filter side\n * lowercased).\n */\nconst SINGLE_ASCII_LETTER = /^[a-zA-Z]$/;\n\nexport type MemoryIndexOptions = {\n /**\n * FIFO cap applied independently to each deletion-state collection: tombstoned event ids, pending\n * e-tag ids, and coordinate tombstones. Default unbounded. Trade-off: once an entry is trimmed, a\n * re-arriving old deleted event or replaceable version can be accepted again.\n */\n maxTombstones?: number | undefined;\n /**\n * FIFO cap on replaceable/addressable winner watermarks recorded by {@link evict}. A watermark\n * keeps only `{ id, created_at }` per address so stale versions stay rejected after the winner's\n * body is evicted, and a re-put of the watermarked id re-inserts it. Default 0 (no watermark\n * retention); callers that evict should pass a bound. Once a watermark is trimmed, an older\n * evicted version can be accepted again — the same trade-off as deletion tombstones.\n */\n maxWatermarks?: number | undefined;\n /** Fires after every physical index insert (accept, replace, deletion event). */\n onInsert?: ((event: Event) => void) | undefined;\n /** Fires after every physical index remove (replace, delete, evict, remove, clear). */\n onRemove?: ((event: Event) => void) | undefined;\n};\n\n/**\n * Synchronous in-memory event index with NIP-01 replaceable / addressable / ephemeral handling and\n * NIP-09 deletion (kind 5) application. Shared by {@link MemoryEventStore} (async facade) and\n * `ReactiveEventStore`.\n */\nexport class MemoryIndex {\n readonly #byId = new Map<string, Event>();\n readonly #all = new SortedEvents();\n readonly #byPubkey = new Map<string, SortedEvents>();\n readonly #byKind = new Map<number, SortedEvents>();\n readonly #byKindPubkey = new Map<string, SortedEvents>(); // `${kind}:${pubkey}` bucket\n readonly #byTag = new Map<string, SortedEvents>(); // `${name}:${value}` for single-letter tag names (event-side verbatim)\n readonly #replaceable = new Map<string, string>(); // address -> event id\n // address -> last evicted winner; insertion order is the FIFO trim order\n readonly #watermarks = new Map<string, { id: string; created_at: number }>();\n // winner id -> address reverse index for O(1) watermark drops on deletion\n readonly #watermarkIds = new Map<string, string>();\n readonly #deletion = new DeletionState();\n readonly #outboxBounds = new Map<string, OutboxBound>();\n readonly #maxTombstones: number | undefined;\n readonly #maxWatermarks: number;\n readonly #onInsert: ((event: Event) => void) | undefined;\n readonly #onRemove: ((event: Event) => void) | undefined;\n\n constructor(opts?: MemoryIndexOptions) {\n this.#maxTombstones = opts?.maxTombstones;\n this.#maxWatermarks = opts?.maxWatermarks ?? 0;\n this.#onInsert = opts?.onInsert;\n this.#onRemove = opts?.onRemove;\n }\n\n put(raw: Event): PutResult {\n const lookup: PutLookup = {\n deletion: this.#deletion,\n getById: (id) => this.#byId.get(id),\n getReplaceable: (addr) => {\n const id = this.#replaceable.get(addr);\n const ev = id === undefined ? undefined : this.#byId.get(id);\n if (ev) {\n return { id: ev.id, created_at: ev.created_at };\n }\n const watermark = this.#watermarks.get(addr);\n return watermark === undefined ? undefined : { ...watermark, evicted: true };\n },\n };\n const decision = decidePut(raw, lookup);\n if (decision.action === \"delete\") {\n // Pending e-tag targets and coordinate tombstones never reach\n // indexRemove — drop their watermarks here.\n for (const p of decision.plan.pendingIds) {\n this.#dropWatermarkId(p.id);\n }\n for (const c of decision.plan.coordinates) {\n this.#dropWatermark(c.key);\n }\n }\n const result = applyPutMemory(\n {\n deletion: this.#deletion,\n indexInsert: (e) => this.#indexInsert(e),\n indexRemove: (id) => this.#indexRemove(id),\n },\n decision,\n );\n this.#trimTombstones();\n return result;\n }\n\n /** Sequential `put` in input order. */\n putMany(events: ReadonlyArray<Event>): PutResult[] {\n const results: PutResult[] = [];\n for (const event of events) {\n results.push(this.put(event));\n }\n return results;\n }\n\n get(id: string): Event | undefined {\n const key = id.toLowerCase();\n if (this.#deletion.ids.has(key)) {\n return undefined;\n }\n return this.#byId.get(key);\n }\n\n /** Current event at a `kind:pubkey:d` coordinate (replaceable or addressable). */\n getByAddress(address: string): Event | undefined {\n const coord = parseEventAddress(address);\n if (!coord) {\n return undefined;\n }\n const id = this.#replaceable.get(\n formatEventAddress(coord.kind, coord.pubkey, coord.identifier),\n );\n if (id === undefined) {\n return undefined;\n }\n if (this.#deletion.ids.has(id)) {\n return undefined;\n }\n return this.#byId.get(id);\n }\n\n query(filters: ReadonlyArray<Filter>): Event[] {\n const seen = new Set<string>();\n const events: Event[] = [];\n for (const filter of filters) {\n for (const event of this.#matchedEvents(filter)) {\n if (seen.has(event.id)) {\n continue;\n }\n seen.add(event.id);\n events.push(event);\n }\n }\n sortEvents(events);\n return events;\n }\n\n count(filters: ReadonlyArray<Filter>): number {\n const seen = new Set<string>();\n for (const filter of filters) {\n for (const event of this.#matchedEvents(filter)) {\n seen.add(event.id);\n }\n }\n return seen.size;\n }\n\n negentropyItems(filter: Filter): NegentropyItem[] {\n if (filter.limit === 0) {\n return [];\n }\n const items: NegentropyItem[] = [];\n const { limit } = filter;\n if (filter.ids) {\n const seen = new Set<string>();\n for (const raw of filter.ids) {\n const event = this.#byId.get(raw.toLowerCase());\n if (!event || seen.has(event.id)) {\n continue;\n }\n seen.add(event.id);\n if (this.#deletion.covers(event) || !matchFilter(filter, event)) {\n continue;\n }\n items.push({ id: event.id, created_at: event.created_at });\n }\n } else {\n const cursor = this.#candidates(filter);\n while (cursor.value !== undefined) {\n const event = cursor.value;\n cursor.next();\n if (this.#deletion.covers(event) || !matchFilter(filter, event)) {\n continue;\n }\n items.push({ id: event.id, created_at: event.created_at });\n if (limit !== undefined && items.length >= limit) {\n break;\n }\n }\n }\n if (filter.limit !== undefined) {\n // The ids path collected matches in filter order; cursor results are already canonical.\n if (filter.ids) {\n items.sort(compareEventsDesc);\n }\n if (items.length > filter.limit) {\n items.length = filter.limit;\n }\n }\n items.sort(itemCompare);\n return items;\n }\n\n /** Remove by id and tombstone (like a local NIP-09). Returns removed count. */\n remove(ids: ReadonlyArray<string>): number {\n let n = 0;\n for (const raw of ids) {\n const id = raw.toLowerCase();\n if (this.#indexRemove(id)) {\n n += 1;\n }\n this.#deletion.ids.add(id);\n this.#deletion.pending.delete(id);\n }\n this.#trimTombstones();\n return n;\n }\n\n /**\n * Remove by id without writing a tombstone (LRU eviction). When the evicted event is the current\n * winner of a replaceable/addressable address, a `{ id, created_at }` watermark is recorded so\n * stale versions stay rejected while the body is gone; the watermarks are FIFO-bounded by\n * `maxWatermarks`.\n */\n evict(ids: ReadonlyArray<string>): number {\n let n = 0;\n for (const raw of ids) {\n const id = raw.toLowerCase();\n const event = this.#byId.get(id);\n if (event === undefined) {\n continue;\n }\n const addr = eventAddress(event);\n const winner = addr !== undefined && this.#replaceable.get(addr) === id;\n if (this.#indexRemove(id)) {\n n += 1;\n }\n if (winner) {\n this.#setWatermark(addr, { id, created_at: event.created_at });\n }\n }\n return n;\n }\n\n /**\n * True when the id is tombstoned, or the coordinate has a deletion marker that still covers the\n * current event at that address: a replacement published after the marker's `until` clears the\n * deletion. Coordinates are matched on the canonical lowercase pubkey.\n */\n isDeleted(idOrAddress: string): boolean {\n if (this.#deletion.ids.has(idOrAddress.toLowerCase())) {\n return true;\n }\n const coord = parseEventAddress(idOrAddress);\n if (!coord) {\n return false;\n }\n const address = formatEventAddress(coord.kind, coord.pubkey, coord.identifier);\n const until = this.#deletion.coordinates.get(address);\n if (until === undefined) {\n return false;\n }\n const id = this.#replaceable.get(address);\n if (id === undefined) {\n return true;\n }\n const event = this.#byId.get(id);\n return event === undefined || event.created_at <= until;\n }\n\n getOutboxBound(pubkey: string, kind: number): OutboxBound | undefined {\n const persisted = this.#outboxBounds.get(outboxBoundKey(pubkey, kind));\n if (persisted) {\n return { oldest: persisted.oldest, newest: persisted.newest };\n }\n return this.#deriveOutboxBound(pubkey, kind);\n }\n\n setOutboxBound(pubkey: string, kind: number, bound: OutboxBound): void {\n this.#outboxBounds.set(outboxBoundKey(pubkey, kind), {\n oldest: bound.oldest,\n newest: bound.newest,\n });\n }\n\n clear(): void {\n if (this.#onRemove) {\n for (const event of this.#byId.values()) {\n this.#onRemove(event);\n }\n }\n this.#byId.clear();\n this.#all.clear();\n this.#byPubkey.clear();\n this.#byKind.clear();\n this.#byKindPubkey.clear();\n this.#byTag.clear();\n this.#replaceable.clear();\n this.#watermarks.clear();\n this.#watermarkIds.clear();\n this.#deletion.clear();\n this.#outboxBounds.clear();\n }\n\n get size(): number {\n return this.#byId.size;\n }\n\n #indexInsert(event: Event): void {\n this.#byId.set(event.id, event);\n const { pubkey } = event;\n this.#all.insert(event);\n insertBucket(this.#byPubkey, pubkey, event);\n insertBucket(this.#byKind, event.kind, event);\n insertBucket(this.#byKindPubkey, `${event.kind}:${pubkey}`, event);\n for (const key of tagKeys(event)) {\n insertBucket(this.#byTag, key, event);\n }\n const addr = eventAddress(event);\n if (addr !== undefined) {\n this.#replaceable.set(addr, event.id);\n this.#dropWatermark(addr);\n }\n this.#onInsert?.(event);\n }\n\n #indexRemove(id: string): boolean {\n // A removed/deleted id must not keep a stale watermark alive; eviction\n // re-records its winner watermark right after this call.\n this.#dropWatermarkId(id);\n const event = this.#byId.get(id);\n if (!event) {\n return false;\n }\n this.#byId.delete(id);\n const { pubkey } = event;\n this.#all.remove(event);\n removeBucket(this.#byPubkey, pubkey, event);\n removeBucket(this.#byKind, event.kind, event);\n removeBucket(this.#byKindPubkey, `${event.kind}:${pubkey}`, event);\n for (const key of tagKeys(event)) {\n removeBucket(this.#byTag, key, event);\n }\n const addr = eventAddress(event);\n if (addr !== undefined && this.#replaceable.get(addr) === id) {\n this.#replaceable.delete(addr);\n }\n this.#onRemove?.(event);\n return true;\n }\n\n #matchedEvents(filter: Filter): Event[] {\n if (filter.limit === 0) {\n return [];\n }\n const { limit } = filter;\n const matched: Event[] = [];\n if (filter.ids) {\n const seen = new Set<string>();\n for (const raw of filter.ids) {\n const event = this.#byId.get(raw.toLowerCase());\n if (!event || seen.has(event.id)) {\n continue;\n }\n seen.add(event.id);\n if (this.#deletion.covers(event) || !matchFilter(filter, event)) {\n continue;\n }\n matched.push(event);\n }\n sortEvents(matched);\n return limit === undefined ? matched : matched.slice(0, limit);\n }\n const cursor = this.#candidates(filter);\n while (cursor.value !== undefined) {\n const event = cursor.value;\n cursor.next();\n if (this.#deletion.covers(event) || !matchFilter(filter, event)) {\n continue;\n }\n matched.push(event);\n if (limit !== undefined && matched.length >= limit) {\n break;\n }\n }\n return matched;\n }\n\n /**\n * Smallest-total bucket set for the filter's usable indexes: `authors`×`kinds` (both present),\n * `authors`, `kinds`, each single-letter `#x` condition (a bucket per value; `#e`/`#p` filter\n * values lowercased like `matchFilter`, others verbatim), or `all` as the fallback.\n */\n #plan(filter: Filter): SortedEvents[] {\n const { authors, kinds } = filter;\n const tagConds = filterTagConditions(filter);\n\n let best: SortedEvents[] | undefined;\n let bestTotal = Number.POSITIVE_INFINITY;\n const consider = (buckets: Array<SortedEvents | undefined>): void => {\n let total = 0;\n const present: SortedEvents[] = [];\n for (const bucket of buckets) {\n if (bucket !== undefined) {\n present.push(bucket);\n total += bucket.size;\n }\n }\n if (total < bestTotal) {\n bestTotal = total;\n best = present;\n }\n };\n\n if (authors !== undefined && kinds !== undefined) {\n const buckets: Array<SortedEvents | undefined> = [];\n for (const pk of authors) {\n const pubkey = pk.toLowerCase();\n for (const kind of kinds) {\n buckets.push(this.#byKindPubkey.get(`${kind}:${pubkey}`));\n }\n }\n consider(buckets);\n }\n if (authors !== undefined) {\n consider(authors.map((pk) => this.#byPubkey.get(pk.toLowerCase())));\n }\n if (kinds !== undefined) {\n consider(kinds.map((kind) => this.#byKind.get(kind)));\n }\n for (const { name, values } of tagConds) {\n consider(values.map((v) => this.#byTag.get(`${name}:${v}`)));\n }\n if (best === undefined || bestTotal > this.#all.size) {\n return [this.#all];\n }\n return best;\n }\n\n /** Canonical-order candidates from the planned buckets, each clipped to `since`/`until`. */\n #candidates(filter: Filter): CandidateCursor {\n const buckets = this.#plan(filter);\n const cursors: EventCursor[] = [];\n for (const bucket of buckets) {\n const cursor = bucket.cursor(filter.since, filter.until);\n if (cursor.value !== undefined) {\n cursors.push(cursor);\n }\n }\n if (cursors.length === 0) {\n return EMPTY_CURSOR;\n }\n if (cursors.length === 1) {\n return cursors[0] ?? EMPTY_CURSOR;\n }\n return new CursorHeap(cursors);\n }\n\n #deriveOutboxBound(pubkey: string, kind: number): OutboxBound | undefined {\n const bucket = this.#byKindPubkey.get(`${kind}:${pubkey.toLowerCase()}`);\n if (bucket === undefined) {\n return undefined;\n }\n // Canonical order: first non-deleted event is the newest, last the oldest.\n let oldest: number | undefined;\n let newest: number | undefined;\n const cursor = bucket.cursor();\n while (cursor.value !== undefined) {\n const event = cursor.value;\n cursor.next();\n if (this.#deletion.covers(event)) {\n continue;\n }\n newest ??= event.created_at;\n oldest = event.created_at;\n }\n if (oldest === undefined || newest === undefined) {\n return undefined;\n }\n return { oldest, newest };\n }\n\n #setWatermark(addr: string, watermark: { id: string; created_at: number }): void {\n if (this.#maxWatermarks === 0) {\n return;\n }\n this.#dropWatermark(addr);\n this.#watermarks.set(addr, watermark);\n this.#watermarkIds.set(watermark.id, addr);\n trimOldest(this.#watermarks, this.#maxWatermarks, (a) => this.#dropWatermark(a));\n }\n\n #dropWatermark(addr: string): void {\n const watermark = this.#watermarks.get(addr);\n if (watermark === undefined) {\n return;\n }\n this.#watermarks.delete(addr);\n this.#watermarkIds.delete(watermark.id);\n }\n\n #dropWatermarkId(id: string): void {\n const addr = this.#watermarkIds.get(id);\n if (addr === undefined) {\n return;\n }\n this.#watermarkIds.delete(id);\n this.#watermarks.delete(addr);\n }\n\n #trimTombstones(): void {\n const cap = this.#maxTombstones;\n if (cap === undefined) {\n return;\n }\n trimOldest(this.#deletion.ids, cap, (id) => {\n this.#deletion.pending.delete(id);\n });\n trimOldest(this.#deletion.pending, cap);\n trimOldest(this.#deletion.coordinates, cap);\n }\n}\n\ntype CandidateCursor = {\n readonly value: Event | undefined;\n next: () => void;\n};\n\nconst EMPTY_CURSOR: CandidateCursor = {\n value: undefined,\n next: () => undefined,\n};\n\n/**\n * Binary-heap k-way merge of sorted cursors. `next()` advances every cursor sitting on the emitted\n * event, which dedupes an event present in several buckets (multi-value `#x` filters); copies\n * compare equal so they always surface adjacent. Entries pair each cursor with its head event so\n * heap comparisons never read `undefined`.\n */\nclass CursorHeap implements CandidateCursor {\n #heap: Array<{ cursor: EventCursor; event: Event }>;\n\n constructor(cursors: EventCursor[]) {\n const heap: Array<{ cursor: EventCursor; event: Event }> = [];\n for (const cursor of cursors) {\n const event = cursor.value;\n if (event !== undefined) {\n heap.push({ cursor, event });\n }\n }\n this.#heap = heap;\n for (let i = Math.floor(heap.length / 2) - 1; i >= 0; i--) {\n this.#siftDown(i);\n }\n }\n\n get value(): Event | undefined {\n return this.#heap[0]?.event;\n }\n\n next(): void {\n const emitted = this.#heap[0]?.event;\n if (emitted === undefined) {\n return;\n }\n while (this.#heap[0]?.event === emitted) {\n const [root] = this.#heap;\n if (root === undefined) {\n return;\n }\n root.cursor.next();\n const nextEvent = root.cursor.value;\n if (nextEvent === undefined) {\n const last = this.#heap.pop();\n if (this.#heap.length > 0 && last !== undefined) {\n this.#heap[0] = last;\n this.#siftDown(0);\n }\n } else {\n root.event = nextEvent;\n this.#siftDown(0);\n }\n }\n }\n\n #less(a: number, b: number): boolean {\n const x = this.#heap[a];\n const y = this.#heap[b];\n return x !== undefined && y !== undefined && compareEventsDesc(x.event, y.event) < 0;\n }\n\n #siftDown(start: number): void {\n const heap = this.#heap;\n let i = start;\n for (;;) {\n let min = i;\n if (this.#less(2 * i + 1, min)) {\n min = 2 * i + 1;\n }\n if (this.#less(2 * i + 2, min)) {\n min = 2 * i + 2;\n }\n if (min === i) {\n return;\n }\n const tmp = heap[i];\n const swap = heap[min];\n if (tmp === undefined || swap === undefined) {\n return;\n }\n heap[i] = swap;\n heap[min] = tmp;\n i = min;\n }\n }\n}\n\n/** Distinct `${name}:${value}` bucket keys for every ASCII single-letter tag name on the event. */\nfunction tagKeys(event: Event): string[] {\n const keys = new Set<string>();\n for (const tag of event.tags) {\n const [name, value] = tag;\n if (name === undefined || value === undefined || !SINGLE_ASCII_LETTER.test(name)) {\n continue;\n }\n keys.add(`${name}:${value}`);\n }\n return [...keys];\n}\n\nfunction insertBucket<K>(map: Map<K, SortedEvents>, key: K, event: Event): void {\n let bucket = map.get(key);\n if (bucket === undefined) {\n bucket = new SortedEvents();\n map.set(key, bucket);\n }\n bucket.insert(event);\n}\n\nfunction removeBucket<K>(map: Map<K, SortedEvents>, key: K, event: Event): void {\n const bucket = map.get(key);\n if (bucket === undefined || !bucket.remove(event)) {\n return;\n }\n if (bucket.size === 0) {\n map.delete(key);\n }\n}\n"],"mappings":"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"}
|