@qntx/nostr 0.11.0 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/dist/{builder-GBddxj8i.d.mts → builder-BrmLOVj_.d.mts} +2 -2
  2. package/dist/{builder-BQbFDO6S.mjs → builder-Cf2N12CG.mjs} +2 -2
  3. package/dist/{builder-BQbFDO6S.mjs.map → builder-Cf2N12CG.mjs.map} +1 -1
  4. package/dist/{client-CTmp5w84.mjs → client-B3_vToDX.mjs} +5 -5
  5. package/dist/{client-CTmp5w84.mjs.map → client-B3_vToDX.mjs.map} +1 -1
  6. package/dist/client.d.mts +1 -1
  7. package/dist/client.mjs +1 -1
  8. package/dist/core.d.mts +2 -2
  9. package/dist/core.mjs +2 -2
  10. package/dist/{error-UHkqzWL7.mjs → error-D3VNRVhL.mjs} +6 -2
  11. package/dist/error-D3VNRVhL.mjs.map +1 -0
  12. package/dist/{gossip-BTMdlUI7.d.mts → gossip-u0iBlZ1U.d.mts} +2 -2
  13. package/dist/gossip.d.mts +1 -1
  14. package/dist/{index-CSsnE32i.d.mts → index-DxQO1eWA.d.mts} +6 -6
  15. package/dist/{index-N7HUGgwo.d.mts → index-SwMknboJ.d.mts} +53 -3
  16. package/dist/{index-BP94lABY.d.mts → index-yq9xkONo.d.mts} +5 -5
  17. package/dist/index.d.mts +8 -8
  18. package/dist/index.mjs +9 -9
  19. package/dist/{key-k8LDEPZh.mjs → key-B218_SoL.mjs} +32 -7
  20. package/dist/key-B218_SoL.mjs.map +1 -0
  21. package/dist/{key-CyKN1RTi.d.mts → key-CwMVo14x.d.mts} +16 -4
  22. package/dist/{loaders-Dp2nXEFk.mjs → loaders-Xdbx-K61.mjs} +2 -2
  23. package/dist/{loaders-Dp2nXEFk.mjs.map → loaders-Xdbx-K61.mjs.map} +1 -1
  24. package/dist/loaders.d.mts +1 -1
  25. package/dist/loaders.mjs +1 -1
  26. package/dist/{nip17-B7MI82Iq.d.mts → nip17-BM4hJYBq.d.mts} +4 -4
  27. package/dist/{nip51-O_vhOiK2.d.mts → nip51-D_bu8Hph.d.mts} +2 -2
  28. package/dist/{nip59-CdDoJptA.d.mts → nip59-CGWfoItR.d.mts} +14 -6
  29. package/dist/{nip65-BL79HI8N.d.mts → nip65-DaWMg943.d.mts} +2 -2
  30. package/dist/nips/blossom.d.mts +1 -1
  31. package/dist/nips/blossom.mjs +1 -1
  32. package/dist/nips/nip04.d.mts +10 -0
  33. package/dist/nips/nip04.mjs +38 -8
  34. package/dist/nips/nip04.mjs.map +1 -1
  35. package/dist/nips/nip10.d.mts +1 -1
  36. package/dist/nips/nip10.mjs +1 -1
  37. package/dist/nips/nip17.d.mts +1 -1
  38. package/dist/nips/nip17.mjs +2 -2
  39. package/dist/nips/nip17.mjs.map +1 -1
  40. package/dist/nips/nip44.d.mts +5 -0
  41. package/dist/nips/nip44.mjs +9 -2
  42. package/dist/nips/nip44.mjs.map +1 -1
  43. package/dist/nips/nip49.d.mts +7 -1
  44. package/dist/nips/nip49.mjs +16 -2
  45. package/dist/nips/nip49.mjs.map +1 -1
  46. package/dist/nips/nip51.d.mts +1 -1
  47. package/dist/nips/nip51.mjs +1 -1
  48. package/dist/nips/nip59.d.mts +1 -1
  49. package/dist/nips/nip59.mjs +47 -19
  50. package/dist/nips/nip59.mjs.map +1 -1
  51. package/dist/nips/nip65.d.mts +1 -1
  52. package/dist/nips/nip65.mjs +1 -1
  53. package/dist/nips/nip98.mjs +9 -1
  54. package/dist/nips/nip98.mjs.map +1 -1
  55. package/dist/{pool-BMUsw2un.mjs → pool-THuuOW4S.mjs} +2 -2
  56. package/dist/{pool-BMUsw2un.mjs.map → pool-THuuOW4S.mjs.map} +1 -1
  57. package/dist/{relay-DXtwUEdX.mjs → relay-BGPqiGKF.mjs} +2 -2
  58. package/dist/{relay-DXtwUEdX.mjs.map → relay-BGPqiGKF.mjs.map} +1 -1
  59. package/dist/relay.mjs +2 -2
  60. package/dist/{signer-CWtxpOxc.mjs → signer-CNTIeYH4.mjs} +169 -10
  61. package/dist/signer-CNTIeYH4.mjs.map +1 -0
  62. package/dist/signer.d.mts +2 -2
  63. package/dist/signer.mjs +3 -3
  64. package/dist/testing.mjs +2 -2
  65. package/package.json +1 -1
  66. package/dist/error-UHkqzWL7.mjs.map +0 -1
  67. package/dist/key-k8LDEPZh.mjs.map +0 -1
  68. package/dist/signer-CWtxpOxc.mjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"loaders-Dp2nXEFk.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 const promise = (async (): Promise<V> => {\n try {\n return await pending;\n } finally {\n this.#inflight.delete(cacheKey);\n }\n })();\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
+ {"version":3,"file":"loaders-Xdbx-K61.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 const promise = (async (): Promise<V> => {\n try {\n return await pending;\n } finally {\n this.#inflight.delete(cacheKey);\n }\n })();\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,3 +1,3 @@
1
1
  import { r as OutboxBound } from "./types-CqhnT-Mx.mjs";
2
- import { _ as EventRef, a as OutboxFeed, c as NostrUser, d as ListLoaders, f as ListResult, g as EventLoader, h as ReplaceableLoader, i as OutboxError, l as ProfileLoader, m as ReplaceableLoadResult, n as Loaders, o as OutboxFeedOptions, p as LoadStyle, r as createLoaders, s as groupAuthorsByOutboxRelay, t as CreateLoadersOptions, u as bareNostrUser } from "./index-BP94lABY.mjs";
2
+ import { _ as EventRef, a as OutboxFeed, c as NostrUser, d as ListLoaders, f as ListResult, g as EventLoader, h as ReplaceableLoader, i as OutboxError, l as ProfileLoader, m as ReplaceableLoadResult, n as Loaders, o as OutboxFeedOptions, p as LoadStyle, r as createLoaders, s as groupAuthorsByOutboxRelay, t as CreateLoadersOptions, u as bareNostrUser } from "./index-yq9xkONo.mjs";
3
3
  export { CreateLoadersOptions, type EventLoader, type EventRef, type ListLoaders, type ListResult, type LoadStyle, Loaders, type NostrUser, type OutboxBound, OutboxError, OutboxFeed, type OutboxFeedOptions, type ProfileLoader, type ReplaceableLoadResult, type ReplaceableLoader, bareNostrUser, createLoaders, groupAuthorsByOutboxRelay };
package/dist/loaders.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { a as bareNostrUser, i as groupAuthorsByOutboxRelay, n as OutboxError, r as OutboxFeed, t as createLoaders } from "./loaders-Dp2nXEFk.mjs";
1
+ import { a as bareNostrUser, i as groupAuthorsByOutboxRelay, n as OutboxError, r as OutboxFeed, t as createLoaders } from "./loaders-Xdbx-K61.mjs";
2
2
  export { OutboxError, OutboxFeed, bareNostrUser, createLoaders, groupAuthorsByOutboxRelay };
@@ -1,7 +1,7 @@
1
1
  import { h as Tag, t as Event } from "./event-j3BwI5dF.mjs";
2
- import { t as EventBuilder } from "./builder-GBddxj8i.mjs";
2
+ import { t as EventBuilder } from "./builder-BrmLOVj_.mjs";
3
3
  import { a as NostrError } from "./error-BquXDmIN.mjs";
4
- import { a as Rumor, l as WrapOptions, r as Nip59Crypto } from "./nip59-CdDoJptA.mjs";
4
+ import { a as Rumor, l as WrapOptions, r as Nip59Crypto } from "./nip59-CGWfoItR.mjs";
5
5
  //#region src/nips/nip17.d.ts
6
6
  declare class Nip17Error extends NostrError {
7
7
  name: string;
@@ -28,11 +28,11 @@ declare function dmRelayListEventBuilder(relays: ReadonlyArray<string>): EventBu
28
28
  /** Accept a hex pubkey, a Recipient, or a readonly array of either. Dedup by pubkey. */
29
29
  declare function normalizeRecipients(input: string | Recipient | ReadonlyArray<string | Recipient>): Recipient[];
30
30
  declare function buildChatMessageRumor(senderPubkey: string, recipients: ReadonlyArray<Recipient>, content: string, opts?: ChatMessageOptions): Rumor;
31
- declare function wrapDirectMessage(crypto: Nip59Crypto, recipients: ReadonlyArray<Recipient>, rumor: Rumor, opts?: Pick<WrapOptions, "now" | "randomInt" | "timestamps" | "expiration" | "ephemeral" | "randomize">): Promise<ReadonlyArray<{
31
+ declare function wrapDirectMessage(crypto: Nip59Crypto, recipients: ReadonlyArray<Recipient>, rumor: Rumor, opts?: Pick<WrapOptions, "now" | "randomBytes" | "timestamps" | "expiration" | "ephemeral" | "randomize">): Promise<ReadonlyArray<{
32
32
  recipient: string;
33
33
  wrap: Event;
34
34
  }>>;
35
35
  declare function requireDmRelays(pubkey: string, relays: ReadonlyArray<string>): string[];
36
36
  //#endregion
37
37
  export { buildChatMessageRumor as a, normalizeRecipients as c, wrapDirectMessage as d, ReplyTo as i, parseDmRelayList as l, Nip17Error as n, dmRelayListEventBuilder as o, Recipient as r, dmRelayListToTags as s, ChatMessageOptions as t, requireDmRelays as u };
38
- //# sourceMappingURL=nip17-B7MI82Iq.d.mts.map
38
+ //# sourceMappingURL=nip17-BM4hJYBq.d.mts.map
@@ -1,5 +1,5 @@
1
1
  import { t as Event } from "./event-j3BwI5dF.mjs";
2
- import { t as EventBuilder } from "./builder-GBddxj8i.mjs";
2
+ import { t as EventBuilder } from "./builder-BrmLOVj_.mjs";
3
3
  //#region src/nips/nip51.d.ts
4
4
  /**
5
5
  * Structural crypto used by NIP-51 private tags. Satisfied by NostrSigner when
@@ -84,4 +84,4 @@ declare function parseFollowPack(event: Pick<Event, "kind" | "tags">): {
84
84
  };
85
85
  //#endregion
86
86
  export { encryptPrivateTags as a, parseEmojiSet as c, parseMuteList as d, parseMuteListPrivate as f, pinListEventBuilder as g, parseUserEmojiList as h, decryptPrivateTags as i, parseFavoriteRelays as l, parseRelaySet as m, Nip51Crypto as n, muteListEventBuilder as o, parsePinList as p, bookmarkListEventBuilder as r, parseBookmarkList as s, MuteItem as t, parseFollowPack as u };
87
- //# sourceMappingURL=nip51-O_vhOiK2.d.mts.map
87
+ //# sourceMappingURL=nip51-D_bu8Hph.d.mts.map
@@ -27,9 +27,17 @@ type TimestampRandomize = "wrap" | "seal+wrap";
27
27
  type WrapOptions = {
28
28
  /** Unix seconds used as the randomization window end. Default: floor(Date.now()/1000). */
29
29
  readonly now?: number | undefined;
30
- /** Uniform integer in [0, maxExclusive). Default: CSPRNG via @noble/hashes randomBytes. */
31
- readonly randomInt?: ((maxExclusive: number) => number) | undefined;
32
- /** When set, used as-is. Overrides now/randomInt for this call. */
30
+ /**
31
+ * Entropy source replacing @noble/hashes `randomBytes` for everything `wrap`/`createGiftWrap`
32
+ * draws — in stream order: the ephemeral secret key (32-byte draws, redrawn while not a valid
33
+ * scalar), the timestamp offset (4-byte big-endian u32 draws under the [0, 2 days) rejection
34
+ * bound), the NIP-44 nonce (32 bytes), and the BIP-340 auxiliary randomness (32 bytes). In
35
+ * `createSeal` only the timestamp offset is drawn here; the seal's nonce and aux belong to the
36
+ * injected {@link Nip59Crypto}. `wrap` = seal draws then gift-wrap draws. Default: noble
37
+ * `randomBytes`.
38
+ */
39
+ readonly randomBytes?: ((n: number) => Uint8Array) | undefined;
40
+ /** When set, used as-is. Overrides now/randomBytes for this call. */
33
41
  readonly timestamps?: GiftWrapTimestamps | undefined;
34
42
  readonly relayHint?: string | undefined;
35
43
  /** Appended after the required wrap `p` tag. Never applied to the seal. */
@@ -47,7 +55,7 @@ type WrapOptions = {
47
55
  */
48
56
  readonly randomize?: TimestampRandomize | undefined;
49
57
  };
50
- type SealOptions = Pick<WrapOptions, "now" | "randomInt" | "timestamps" | "expiration" | "randomize">;
58
+ type SealOptions = Pick<WrapOptions, "now" | "randomBytes" | "timestamps" | "expiration" | "randomize">;
51
59
  declare class Nip59Error extends NostrError {
52
60
  name: string;
53
61
  }
@@ -68,7 +76,7 @@ declare function rumorToJson(rumor: Rumor): string;
68
76
  declare function eventToJson(event: Event): string;
69
77
  declare function randomPastTimestamp(opts?: {
70
78
  now?: number | undefined;
71
- randomInt?: ((maxExclusive: number) => number) | undefined;
79
+ randomBytes?: ((n: number) => Uint8Array) | undefined;
72
80
  }): number;
73
81
  declare function createRumor(pubkey: string, template: {
74
82
  kind: number;
@@ -82,4 +90,4 @@ declare function wrap(crypto: Nip59Crypto, recipient: string, rumor: Rumor, opts
82
90
  declare function unwrap(crypto: Nip44Decryptor, giftWrap: Event): Promise<Rumor>;
83
91
  //#endregion
84
92
  export { requireNip59Crypto as _, Rumor as a, wrap as b, TimestampRandomize as c, createRumor as d, createSeal as f, requireNip44Decryptor as g, randomPastTimestamp as h, Nip59Error as i, WrapOptions as l, isGiftWrapKind as m, Nip44Decryptor as n, SealOptions as o, eventToJson as p, Nip59Crypto as r, TWO_DAYS_SECS as s, GiftWrapTimestamps as t, createGiftWrap as u, rumorToJson as v, unwrap as y };
85
- //# sourceMappingURL=nip59-CdDoJptA.d.mts.map
93
+ //# sourceMappingURL=nip59-CGWfoItR.d.mts.map
@@ -1,5 +1,5 @@
1
1
  import { h as Tag, t as Event } from "./event-j3BwI5dF.mjs";
2
- import { t as EventBuilder } from "./builder-GBddxj8i.mjs";
2
+ import { t as EventBuilder } from "./builder-BrmLOVj_.mjs";
3
3
  //#region src/nips/nip65.d.ts
4
4
  /** NIP-65 `r` tag marker: read-only, write-only, or unmarked (both). */
5
5
  type RelayMarker = "read" | "write" | "both";
@@ -20,4 +20,4 @@ declare function readRelays(items: ReadonlyArray<RelayListItem>): string[];
20
20
  declare function writeRelays(items: ReadonlyArray<RelayListItem>): string[];
21
21
  //#endregion
22
22
  export { relayListEventBuilder as a, readRelays as i, RelayMarker as n, relayListToTags as o, parseRelayList as r, writeRelays as s, RelayListItem as t };
23
- //# sourceMappingURL=nip65-BL79HI8N.d.mts.map
23
+ //# sourceMappingURL=nip65-DaWMg943.d.mts.map
@@ -1,5 +1,5 @@
1
1
  import { n as EventTemplate, t as Event } from "../event-j3BwI5dF.mjs";
2
- import { t as EventBuilder } from "../builder-GBddxj8i.mjs";
2
+ import { t as EventBuilder } from "../builder-BrmLOVj_.mjs";
3
3
  import { a as NostrError } from "../error-BquXDmIN.mjs";
4
4
  import { t as ManualFetch } from "../http-kwWG-V1X.mjs";
5
5
  //#region src/nips/blossom.d.ts
@@ -1,6 +1,6 @@
1
1
  import { f as nowSeconds, g as EventValidationError, i as bytesToHex, l as isRecord, m as utf8Encoder, n as assertHex32, y as NostrError } from "../util-DDpPkiED.mjs";
2
2
  import { t as Kind } from "../kind-D8fENiiL.mjs";
3
- import { t as EventBuilder } from "../builder-BQbFDO6S.mjs";
3
+ import { t as EventBuilder } from "../builder-Cf2N12CG.mjs";
4
4
  import { n as requireGlobalFetch, r as sendManual, t as fetchManual } from "../http-CuazNRIP.mjs";
5
5
  import { sha256 } from "@noble/hashes/sha2.js";
6
6
  import { base64urlnopad } from "@scure/base";
@@ -1,7 +1,17 @@
1
1
  //#region src/nips/nip04.d.ts
2
2
  /** Encrypt plaintext to a peer pubkey (NIP-04). */
3
3
  export declare function encrypt(secretKey: string | Uint8Array, pubkey: string, text: string): string;
4
+ /**
5
+ * Encrypt with the raw 32-byte ECDH x-coordinate instead of a secret key, for key holders that only
6
+ * expose ECDH ({@link NostrKeyOperations}).
7
+ */
8
+ export declare function encryptWithSharedSecret(sharedX: Uint8Array, text: string): string;
4
9
  /** Decrypt a NIP-04 payload from a peer pubkey. */
5
10
  export declare function decrypt(secretKey: string | Uint8Array, pubkey: string, data: string): string;
11
+ /**
12
+ * Decrypt a NIP-04 payload with the raw 32-byte ECDH x-coordinate instead of a secret key, for key
13
+ * holders that only expose ECDH ({@link NostrKeyOperations}).
14
+ */
15
+ export declare function decryptWithSharedSecret(sharedX: Uint8Array, data: string): string;
6
16
  //#endregion
7
17
  //# sourceMappingURL=nip04.d.mts.map
@@ -1,4 +1,4 @@
1
- import { h as CryptoError, m as utf8Encoder, n as assertHex32, o as hexToBytes$1, p as utf8Decoder, r as assertSecretKeyBytes } from "../util-DDpPkiED.mjs";
1
+ import { h as CryptoError, m as utf8Encoder, n as assertHex32, o as hexToBytes$1, p as utf8Decoder, r as assertSecretKeyBytes, t as assertByteLength } from "../util-DDpPkiED.mjs";
2
2
  import { randomBytes } from "@noble/hashes/utils.js";
3
3
  import { secp256k1 } from "@noble/curves/secp256k1.js";
4
4
  import { cbc } from "@noble/ciphers/aes.js";
@@ -21,27 +21,57 @@ function resolveSecret(secretKey) {
21
21
  /** Encrypt plaintext to a peer pubkey (NIP-04). */
22
22
  function encrypt(secretKey, pubkey, text) {
23
23
  const normalizedKey = normalizeSharedSecret(resolveSecret(secretKey), pubkey);
24
+ try {
25
+ return encryptWithSharedSecret(normalizedKey, text);
26
+ } finally {
27
+ normalizedKey.fill(0);
28
+ }
29
+ }
30
+ /**
31
+ * Encrypt with the raw 32-byte ECDH x-coordinate instead of a secret key, for key holders that only
32
+ * expose ECDH ({@link NostrKeyOperations}).
33
+ */
34
+ function encryptWithSharedSecret(sharedX, text) {
35
+ assertByteLength(sharedX, 32, "shared secret");
24
36
  const iv = randomBytes(16);
25
37
  const plaintext = utf8Encoder.encode(text);
26
- const ciphertext = cbc(normalizedKey, iv).encrypt(plaintext);
38
+ const ciphertext = cbc(sharedX, iv).encrypt(plaintext);
27
39
  return `${base64.encode(ciphertext)}?iv=${base64.encode(iv)}`;
28
40
  }
29
41
  /** Decrypt a NIP-04 payload from a peer pubkey. */
30
42
  function decrypt(secretKey, pubkey, data) {
31
43
  const privkey = resolveSecret(secretKey);
32
- const parts = data.split("?iv=");
33
- if (parts.length !== 2 || parts[0] === void 0 || parts[0] === "" || parts[1] === void 0 || parts[1] === "") throw new CryptoError("invalid NIP-04 payload: missing iv");
44
+ splitPayload(data);
34
45
  const normalizedKey = normalizeSharedSecret(privkey, pubkey);
35
46
  try {
36
- const iv = base64.decode(parts[1]);
37
- const ciphertext = base64.decode(parts[0]);
38
- const plaintext = cbc(normalizedKey, iv).decrypt(ciphertext);
47
+ return decryptWithSharedSecret(normalizedKey, data);
48
+ } finally {
49
+ normalizedKey.fill(0);
50
+ }
51
+ }
52
+ function splitPayload(data) {
53
+ const parts = data.split("?iv=");
54
+ const [ciphertext, iv] = parts;
55
+ if (parts.length !== 2 || ciphertext === void 0 || ciphertext === "" || iv === void 0 || iv === "") throw new CryptoError("invalid NIP-04 payload: missing iv");
56
+ return [ciphertext, iv];
57
+ }
58
+ /**
59
+ * Decrypt a NIP-04 payload with the raw 32-byte ECDH x-coordinate instead of a secret key, for key
60
+ * holders that only expose ECDH ({@link NostrKeyOperations}).
61
+ */
62
+ function decryptWithSharedSecret(sharedX, data) {
63
+ assertByteLength(sharedX, 32, "shared secret");
64
+ const [ciphertextPart, ivPart] = splitPayload(data);
65
+ try {
66
+ const iv = base64.decode(ivPart);
67
+ const ciphertext = base64.decode(ciphertextPart);
68
+ const plaintext = cbc(sharedX, iv).decrypt(ciphertext);
39
69
  return utf8Decoder.decode(plaintext);
40
70
  } catch (error) {
41
71
  throw new CryptoError("invalid NIP-04 payload", { cause: error });
42
72
  }
43
73
  }
44
74
  //#endregion
45
- export { decrypt, encrypt };
75
+ export { decrypt, decryptWithSharedSecret, encrypt, encryptWithSharedSecret };
46
76
 
47
77
  //# sourceMappingURL=nip04.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"nip04.mjs","names":["hexToBytes"],"sources":["../../src/nips/nip04.ts"],"sourcesContent":["/** NIP-04 legacy encrypted direct messages. Prefer NIP-44 for new applications. */\nimport { cbc } from \"@noble/ciphers/aes.js\";\nimport { secp256k1 } from \"@noble/curves/secp256k1.js\";\nimport { randomBytes } from \"@noble/hashes/utils.js\";\nimport { base64 } from \"@scure/base\";\n\nimport { CryptoError } from \"../core/error.ts\";\nimport {\n assertHex32,\n assertSecretKeyBytes,\n hexToBytes,\n utf8Decoder,\n utf8Encoder,\n} from \"../core/util.ts\";\n\nfunction normalizeSharedSecret(privkey: Uint8Array, pubkey: string): Uint8Array {\n assertHex32(pubkey, \"public key\");\n try {\n const key = secp256k1.getSharedSecret(privkey, hexToBytes(`02${pubkey.toLowerCase()}`));\n return key.slice(1, 33);\n } catch (error) {\n throw new CryptoError(\"invalid NIP-04 public key\", { cause: error });\n }\n}\n\nfunction resolveSecret(secretKey: string | Uint8Array): Uint8Array {\n if (typeof secretKey === \"string\") {\n return hexToBytes(assertHex32(secretKey, \"secret key\"));\n }\n assertSecretKeyBytes(secretKey);\n return secretKey;\n}\n\n/** Encrypt plaintext to a peer pubkey (NIP-04). */\nexport function encrypt(secretKey: string | Uint8Array, pubkey: string, text: string): string {\n const privkey = resolveSecret(secretKey);\n const normalizedKey = normalizeSharedSecret(privkey, pubkey);\n const iv = randomBytes(16);\n const plaintext = utf8Encoder.encode(text);\n const ciphertext = cbc(normalizedKey, iv).encrypt(plaintext);\n return `${base64.encode(ciphertext)}?iv=${base64.encode(iv)}`;\n}\n\n/** Decrypt a NIP-04 payload from a peer pubkey. */\nexport function decrypt(secretKey: string | Uint8Array, pubkey: string, data: string): string {\n const privkey = resolveSecret(secretKey);\n const parts = data.split(\"?iv=\");\n if (\n parts.length !== 2 ||\n parts[0] === undefined ||\n parts[0] === \"\" ||\n parts[1] === undefined ||\n parts[1] === \"\"\n ) {\n throw new CryptoError(\"invalid NIP-04 payload: missing iv\");\n }\n const normalizedKey = normalizeSharedSecret(privkey, pubkey);\n try {\n const iv = base64.decode(parts[1]);\n const ciphertext = base64.decode(parts[0]);\n const plaintext = cbc(normalizedKey, iv).decrypt(ciphertext);\n return utf8Decoder.decode(plaintext);\n } catch (error) {\n throw new CryptoError(\"invalid NIP-04 payload\", { cause: error });\n }\n}\n"],"mappings":";;;;;;;AAeA,SAAS,sBAAsB,SAAqB,QAA4B;CAC9E,YAAY,QAAQ,YAAY;CAChC,IAAI;EAEF,OADY,UAAU,gBAAgB,SAASA,aAAW,KAAK,OAAO,YAAY,GAAG,CAC5E,CAAC,CAAC,MAAM,GAAG,EAAE;CACxB,SAAS,OAAO;EACd,MAAM,IAAI,YAAY,6BAA6B,EAAE,OAAO,MAAM,CAAC;CACrE;AACF;AAEA,SAAS,cAAc,WAA4C;CACjE,IAAI,OAAO,cAAc,UACvB,OAAOA,aAAW,YAAY,WAAW,YAAY,CAAC;CAExD,qBAAqB,SAAS;CAC9B,OAAO;AACT;;AAGA,SAAgB,QAAQ,WAAgC,QAAgB,MAAsB;CAE5F,MAAM,gBAAgB,sBADN,cAAc,SACoB,GAAG,MAAM;CAC3D,MAAM,KAAK,YAAY,EAAE;CACzB,MAAM,YAAY,YAAY,OAAO,IAAI;CACzC,MAAM,aAAa,IAAI,eAAe,EAAE,CAAC,CAAC,QAAQ,SAAS;CAC3D,OAAO,GAAG,OAAO,OAAO,UAAU,EAAE,MAAM,OAAO,OAAO,EAAE;AAC5D;;AAGA,SAAgB,QAAQ,WAAgC,QAAgB,MAAsB;CAC5F,MAAM,UAAU,cAAc,SAAS;CACvC,MAAM,QAAQ,KAAK,MAAM,MAAM;CAC/B,IACE,MAAM,WAAW,KACjB,MAAM,OAAO,KAAA,KACb,MAAM,OAAO,MACb,MAAM,OAAO,KAAA,KACb,MAAM,OAAO,IAEb,MAAM,IAAI,YAAY,oCAAoC;CAE5D,MAAM,gBAAgB,sBAAsB,SAAS,MAAM;CAC3D,IAAI;EACF,MAAM,KAAK,OAAO,OAAO,MAAM,EAAE;EACjC,MAAM,aAAa,OAAO,OAAO,MAAM,EAAE;EACzC,MAAM,YAAY,IAAI,eAAe,EAAE,CAAC,CAAC,QAAQ,UAAU;EAC3D,OAAO,YAAY,OAAO,SAAS;CACrC,SAAS,OAAO;EACd,MAAM,IAAI,YAAY,0BAA0B,EAAE,OAAO,MAAM,CAAC;CAClE;AACF"}
1
+ {"version":3,"file":"nip04.mjs","names":["hexToBytes"],"sources":["../../src/nips/nip04.ts"],"sourcesContent":["/** NIP-04 legacy encrypted direct messages. Prefer NIP-44 for new applications. */\nimport { cbc } from \"@noble/ciphers/aes.js\";\nimport { secp256k1 } from \"@noble/curves/secp256k1.js\";\nimport { randomBytes } from \"@noble/hashes/utils.js\";\nimport { base64 } from \"@scure/base\";\n\nimport { CryptoError } from \"../core/error.ts\";\nimport {\n assertByteLength,\n assertHex32,\n assertSecretKeyBytes,\n hexToBytes,\n utf8Decoder,\n utf8Encoder,\n} from \"../core/util.ts\";\n\nfunction normalizeSharedSecret(privkey: Uint8Array, pubkey: string): Uint8Array {\n assertHex32(pubkey, \"public key\");\n try {\n const key = secp256k1.getSharedSecret(privkey, hexToBytes(`02${pubkey.toLowerCase()}`));\n return key.slice(1, 33);\n } catch (error) {\n throw new CryptoError(\"invalid NIP-04 public key\", { cause: error });\n }\n}\n\nfunction resolveSecret(secretKey: string | Uint8Array): Uint8Array {\n if (typeof secretKey === \"string\") {\n return hexToBytes(assertHex32(secretKey, \"secret key\"));\n }\n assertSecretKeyBytes(secretKey);\n return secretKey;\n}\n\n/** Encrypt plaintext to a peer pubkey (NIP-04). */\nexport function encrypt(secretKey: string | Uint8Array, pubkey: string, text: string): string {\n const privkey = resolveSecret(secretKey);\n const normalizedKey = normalizeSharedSecret(privkey, pubkey);\n try {\n return encryptWithSharedSecret(normalizedKey, text);\n } finally {\n normalizedKey.fill(0);\n }\n}\n\n/**\n * Encrypt with the raw 32-byte ECDH x-coordinate instead of a secret key, for key holders that only\n * expose ECDH ({@link NostrKeyOperations}).\n */\nexport function encryptWithSharedSecret(sharedX: Uint8Array, text: string): string {\n assertByteLength(sharedX, 32, \"shared secret\");\n const iv = randomBytes(16);\n const plaintext = utf8Encoder.encode(text);\n const ciphertext = cbc(sharedX, iv).encrypt(plaintext);\n return `${base64.encode(ciphertext)}?iv=${base64.encode(iv)}`;\n}\n\n/** Decrypt a NIP-04 payload from a peer pubkey. */\nexport function decrypt(secretKey: string | Uint8Array, pubkey: string, data: string): string {\n const privkey = resolveSecret(secretKey);\n splitPayload(data);\n const normalizedKey = normalizeSharedSecret(privkey, pubkey);\n try {\n return decryptWithSharedSecret(normalizedKey, data);\n } finally {\n normalizedKey.fill(0);\n }\n}\n\nfunction splitPayload(data: string): [ciphertext: string, iv: string] {\n const parts = data.split(\"?iv=\");\n const [ciphertext, iv] = parts;\n if (\n parts.length !== 2 ||\n ciphertext === undefined ||\n ciphertext === \"\" ||\n iv === undefined ||\n iv === \"\"\n ) {\n throw new CryptoError(\"invalid NIP-04 payload: missing iv\");\n }\n return [ciphertext, iv];\n}\n\n/**\n * Decrypt a NIP-04 payload with the raw 32-byte ECDH x-coordinate instead of a secret key, for key\n * holders that only expose ECDH ({@link NostrKeyOperations}).\n */\nexport function decryptWithSharedSecret(sharedX: Uint8Array, data: string): string {\n assertByteLength(sharedX, 32, \"shared secret\");\n const [ciphertextPart, ivPart] = splitPayload(data);\n try {\n const iv = base64.decode(ivPart);\n const ciphertext = base64.decode(ciphertextPart);\n const plaintext = cbc(sharedX, iv).decrypt(ciphertext);\n return utf8Decoder.decode(plaintext);\n } catch (error) {\n throw new CryptoError(\"invalid NIP-04 payload\", { cause: error });\n }\n}\n"],"mappings":";;;;;;;AAgBA,SAAS,sBAAsB,SAAqB,QAA4B;CAC9E,YAAY,QAAQ,YAAY;CAChC,IAAI;EAEF,OADY,UAAU,gBAAgB,SAASA,aAAW,KAAK,OAAO,YAAY,GAAG,CAC5E,CAAC,CAAC,MAAM,GAAG,EAAE;CACxB,SAAS,OAAO;EACd,MAAM,IAAI,YAAY,6BAA6B,EAAE,OAAO,MAAM,CAAC;CACrE;AACF;AAEA,SAAS,cAAc,WAA4C;CACjE,IAAI,OAAO,cAAc,UACvB,OAAOA,aAAW,YAAY,WAAW,YAAY,CAAC;CAExD,qBAAqB,SAAS;CAC9B,OAAO;AACT;;AAGA,SAAgB,QAAQ,WAAgC,QAAgB,MAAsB;CAE5F,MAAM,gBAAgB,sBADN,cAAc,SACoB,GAAG,MAAM;CAC3D,IAAI;EACF,OAAO,wBAAwB,eAAe,IAAI;CACpD,UAAU;EACR,cAAc,KAAK,CAAC;CACtB;AACF;;;;;AAMA,SAAgB,wBAAwB,SAAqB,MAAsB;CACjF,iBAAiB,SAAS,IAAI,eAAe;CAC7C,MAAM,KAAK,YAAY,EAAE;CACzB,MAAM,YAAY,YAAY,OAAO,IAAI;CACzC,MAAM,aAAa,IAAI,SAAS,EAAE,CAAC,CAAC,QAAQ,SAAS;CACrD,OAAO,GAAG,OAAO,OAAO,UAAU,EAAE,MAAM,OAAO,OAAO,EAAE;AAC5D;;AAGA,SAAgB,QAAQ,WAAgC,QAAgB,MAAsB;CAC5F,MAAM,UAAU,cAAc,SAAS;CACvC,aAAa,IAAI;CACjB,MAAM,gBAAgB,sBAAsB,SAAS,MAAM;CAC3D,IAAI;EACF,OAAO,wBAAwB,eAAe,IAAI;CACpD,UAAU;EACR,cAAc,KAAK,CAAC;CACtB;AACF;AAEA,SAAS,aAAa,MAAgD;CACpE,MAAM,QAAQ,KAAK,MAAM,MAAM;CAC/B,MAAM,CAAC,YAAY,MAAM;CACzB,IACE,MAAM,WAAW,KACjB,eAAe,KAAA,KACf,eAAe,MACf,OAAO,KAAA,KACP,OAAO,IAEP,MAAM,IAAI,YAAY,oCAAoC;CAE5D,OAAO,CAAC,YAAY,EAAE;AACxB;;;;;AAMA,SAAgB,wBAAwB,SAAqB,MAAsB;CACjF,iBAAiB,SAAS,IAAI,eAAe;CAC7C,MAAM,CAAC,gBAAgB,UAAU,aAAa,IAAI;CAClD,IAAI;EACF,MAAM,KAAK,OAAO,OAAO,MAAM;EAC/B,MAAM,aAAa,OAAO,OAAO,cAAc;EAC/C,MAAM,YAAY,IAAI,SAAS,EAAE,CAAC,CAAC,QAAQ,UAAU;EACrD,OAAO,YAAY,OAAO,SAAS;CACrC,SAAS,OAAO;EACd,MAAM,IAAI,YAAY,0BAA0B,EAAE,OAAO,MAAM,CAAC;CAClE;AACF"}
@@ -1,5 +1,5 @@
1
1
  import { h as Tag, t as Event } from "../event-j3BwI5dF.mjs";
2
- import { t as EventBuilder } from "../builder-GBddxj8i.mjs";
2
+ import { t as EventBuilder } from "../builder-BrmLOVj_.mjs";
3
3
  import { f as ProfilePointer, i as EventPointer, t as AddressPointer } from "../nip19-DpPN17bY.mjs";
4
4
  //#region src/nips/nip10.d.ts
5
5
  /** Parsed NIP-10 thread references from an event's `e`/`q`/`p` tags. */
@@ -1,7 +1,7 @@
1
1
  import { g as EventValidationError, s as isHex32 } from "../util-DDpPkiED.mjs";
2
2
  import { t as Kind } from "../kind-D8fENiiL.mjs";
3
3
  import { S as parseEventAddress, g as Tag, y as formatEventAddress } from "../event-B3ua2_oL.mjs";
4
- import { t as EventBuilder } from "../builder-BQbFDO6S.mjs";
4
+ import { t as EventBuilder } from "../builder-Cf2N12CG.mjs";
5
5
  //#region src/nips/nip10.ts
6
6
  /**
7
7
  * NIP-10: Text Notes and Threads
@@ -1,2 +1,2 @@
1
- import { a as buildChatMessageRumor, c as normalizeRecipients, d as wrapDirectMessage, i as ReplyTo, l as parseDmRelayList, n as Nip17Error, o as dmRelayListEventBuilder, r as Recipient, s as dmRelayListToTags, t as ChatMessageOptions, u as requireDmRelays } from "../nip17-B7MI82Iq.mjs";
1
+ import { a as buildChatMessageRumor, c as normalizeRecipients, d as wrapDirectMessage, i as ReplyTo, l as parseDmRelayList, n as Nip17Error, o as dmRelayListEventBuilder, r as Recipient, s as dmRelayListToTags, t as ChatMessageOptions, u as requireDmRelays } from "../nip17-BM4hJYBq.mjs";
2
2
  export { ChatMessageOptions, Nip17Error, Recipient, ReplyTo, buildChatMessageRumor, dmRelayListEventBuilder, dmRelayListToTags, normalizeRecipients, parseDmRelayList, requireDmRelays, wrapDirectMessage };
@@ -1,7 +1,7 @@
1
1
  import { g as EventValidationError, n as assertHex32, u as normalizeRelayUrls, y as NostrError } from "../util-DDpPkiED.mjs";
2
2
  import { t as Kind } from "../kind-D8fENiiL.mjs";
3
3
  import { g as Tag } from "../event-B3ua2_oL.mjs";
4
- import { t as EventBuilder } from "../builder-BQbFDO6S.mjs";
4
+ import { t as EventBuilder } from "../builder-Cf2N12CG.mjs";
5
5
  import { createGiftWrap, createRumor, createSeal } from "./nip59.mjs";
6
6
  //#region src/nips/nip17.ts
7
7
  /**
@@ -96,7 +96,7 @@ async function wrapDirectMessage(crypto, recipients, rumor, opts) {
96
96
  const out = [];
97
97
  const timeOpts = opts ? {
98
98
  now: opts.now,
99
- randomInt: opts.randomInt,
99
+ randomBytes: opts.randomBytes,
100
100
  timestamps: opts.timestamps,
101
101
  expiration: opts.expiration,
102
102
  randomize: opts.randomize