@qntx/nostr 0.14.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/dist/{builder-CGgM_QHT.mjs → builder-70CC2EBq.mjs} +4 -4
  2. package/dist/{builder-CGgM_QHT.mjs.map → builder-70CC2EBq.mjs.map} +1 -1
  3. package/dist/{builder-C72nJITG.d.mts → builder-Dk00TdNf.d.mts} +2 -2
  4. package/dist/{client-COkEihj_.mjs → client-BXmvZ7vU.mjs} +45 -30
  5. package/dist/client-BXmvZ7vU.mjs.map +1 -0
  6. package/dist/client.d.mts +1 -1
  7. package/dist/client.mjs +1 -2
  8. package/dist/core.d.mts +8 -7
  9. package/dist/core.mjs +8 -8
  10. package/dist/{error-Dyb89ahb.mjs → error-CoP2myne.mjs} +2 -2
  11. package/dist/{error-Dyb89ahb.mjs.map → error-CoP2myne.mjs.map} +1 -1
  12. package/dist/{error-BPqAU5RB.d.mts → error-DE1UCZ2J.d.mts} +6 -2
  13. package/dist/{error-BjSUsx6M.d.mts → error-IHMcLy8-.d.mts} +2 -2
  14. package/dist/{error-QyECUDIp.mjs → error-VDn7Nv-6.mjs} +2 -2
  15. package/dist/{error-QyECUDIp.mjs.map → error-VDn7Nv-6.mjs.map} +1 -1
  16. package/dist/{event-DvM1f03b.mjs → event-DHtcm0jM.mjs} +17 -6
  17. package/dist/event-DHtcm0jM.mjs.map +1 -0
  18. package/dist/{filter-3jOh4uLN.mjs → filter-BbShkmc4.mjs} +34 -28
  19. package/dist/filter-BbShkmc4.mjs.map +1 -0
  20. package/dist/{filter-oYG2zjJ3.d.mts → filter-w1R-mU63.d.mts} +8 -6
  21. package/dist/{gossip-BvCItMVZ.d.mts → gossip-C-7WhJ7v.d.mts} +3 -3
  22. package/dist/{gossip-CRyhK6E6.mjs → gossip-CejMm_QM.mjs} +13 -11
  23. package/dist/gossip-CejMm_QM.mjs.map +1 -0
  24. package/dist/gossip.d.mts +1 -1
  25. package/dist/gossip.mjs +1 -2
  26. package/dist/{index-B2JlP2Jf.d.mts → index-11znTLM0.d.mts} +4 -4
  27. package/dist/{index-DzHmhHqD.d.mts → index-B7nMjbyt.d.mts} +3 -3
  28. package/dist/{index-muydfuYD.d.mts → index-BuNI8JH_.d.mts} +13 -13
  29. package/dist/{index-Dpj_MUxb.d.mts → index-C5QomO2D.d.mts} +10 -3
  30. package/dist/{index-DCobP2Jj.d.mts → index-DDZjqV48.d.mts} +10 -10
  31. package/dist/{index-CcgmG3F6.d.mts → index-DYk24u9o.d.mts} +12 -7
  32. package/dist/index.d.mts +21 -20
  33. package/dist/index.mjs +19 -19
  34. package/dist/{key-D_oEutQ6.mjs → key-Bt0ow7mI.mjs} +33 -11
  35. package/dist/key-Bt0ow7mI.mjs.map +1 -0
  36. package/dist/{key-DojW2QZl.d.mts → key-E0AJKMRB.d.mts} +19 -11
  37. package/dist/{loaders-BRh2riV9.mjs → loaders-COGGrZY4.mjs} +7 -7
  38. package/dist/loaders-COGGrZY4.mjs.map +1 -0
  39. package/dist/loaders.d.mts +2 -2
  40. package/dist/loaders.mjs +1 -2
  41. package/dist/{memory-D_GdEQYG.mjs → memory-Dq4dg-U4.mjs} +2 -2
  42. package/dist/{memory-D_GdEQYG.mjs.map → memory-Dq4dg-U4.mjs.map} +1 -1
  43. package/dist/{memory-index-3GSrPIWj.mjs → memory-index-DhlmbqWx.mjs} +19 -18
  44. package/dist/memory-index-DhlmbqWx.mjs.map +1 -0
  45. package/dist/{message-BMfreCQc.mjs → message-D6k16gtg.mjs} +20 -20
  46. package/dist/message-D6k16gtg.mjs.map +1 -0
  47. package/dist/{verifier-DhAbqVJF.d.mts → message-u_7KOq6Q.d.mts} +3 -22
  48. package/dist/{nip17-BZvFQLcs.d.mts → nip17--YwFSuph.d.mts} +4 -4
  49. package/dist/{nip19-D4RKn6GC.d.mts → nip19-qtyc5sCl.d.mts} +2 -2
  50. package/dist/{nip46-i8L8_nIm.d.mts → nip46-C_ZFQprM.d.mts} +2 -2
  51. package/dist/{nip51-C_kTYcud.d.mts → nip51-yi9w8FsM.d.mts} +2 -2
  52. package/dist/{nip59-DfJ6Moeu.d.mts → nip59-B6Ey8L9z.d.mts} +12 -3
  53. package/dist/{nip65-CPNE0SV0.d.mts → nip65-XrBjV9WM.d.mts} +2 -2
  54. package/dist/{nip77-DhpoL7d8.d.mts → nip77-BAXijIt_.d.mts} +2 -2
  55. package/dist/nips/blossom.d.mts +3 -2
  56. package/dist/nips/blossom.mjs +11 -11
  57. package/dist/nips/blossom.mjs.map +1 -1
  58. package/dist/nips/nip04.d.mts +3 -2
  59. package/dist/nips/nip04.mjs +10 -12
  60. package/dist/nips/nip04.mjs.map +1 -1
  61. package/dist/nips/nip05.d.mts +2 -2
  62. package/dist/nips/nip05.mjs +9 -8
  63. package/dist/nips/nip05.mjs.map +1 -1
  64. package/dist/nips/nip10.d.mts +3 -2
  65. package/dist/nips/nip10.mjs +19 -19
  66. package/dist/nips/nip10.mjs.map +1 -1
  67. package/dist/nips/nip11.d.mts +2 -1
  68. package/dist/nips/nip11.mjs +6 -4
  69. package/dist/nips/nip11.mjs.map +1 -1
  70. package/dist/nips/nip13.d.mts +1 -1
  71. package/dist/nips/nip13.mjs +2 -2
  72. package/dist/nips/nip13.mjs.map +1 -1
  73. package/dist/nips/nip17.d.mts +1 -1
  74. package/dist/nips/nip17.mjs +6 -6
  75. package/dist/nips/nip17.mjs.map +1 -1
  76. package/dist/nips/nip19.d.mts +1 -1
  77. package/dist/nips/nip19.mjs +1 -1
  78. package/dist/nips/nip21.d.mts +2 -2
  79. package/dist/nips/nip21.mjs +3 -3
  80. package/dist/nips/nip21.mjs.map +1 -1
  81. package/dist/nips/nip27.d.mts +3 -3
  82. package/dist/nips/nip27.mjs +308 -85
  83. package/dist/nips/nip27.mjs.map +1 -1
  84. package/dist/nips/nip42.mjs +1 -1
  85. package/dist/nips/nip44.d.mts +2 -1
  86. package/dist/nips/nip44.mjs +4 -4
  87. package/dist/nips/nip44.mjs.map +1 -1
  88. package/dist/nips/nip46.d.mts +1 -1
  89. package/dist/nips/nip46.mjs +9 -8
  90. package/dist/nips/nip46.mjs.map +1 -1
  91. package/dist/nips/nip49.d.mts +1 -1
  92. package/dist/nips/nip49.mjs +1 -1
  93. package/dist/nips/nip51.d.mts +1 -1
  94. package/dist/nips/nip51.mjs +14 -14
  95. package/dist/nips/nip51.mjs.map +1 -1
  96. package/dist/nips/nip57.mjs +10 -10
  97. package/dist/nips/nip57.mjs.map +1 -1
  98. package/dist/nips/nip59.d.mts +1 -1
  99. package/dist/nips/nip59.mjs +14 -11
  100. package/dist/nips/nip59.mjs.map +1 -1
  101. package/dist/nips/nip65.d.mts +1 -1
  102. package/dist/nips/nip65.mjs +2 -2
  103. package/dist/nips/nip77.d.mts +1 -1
  104. package/dist/nips/nip77.mjs +2 -2
  105. package/dist/nips/nip77.mjs.map +1 -1
  106. package/dist/nips/nip98.d.mts +1 -1
  107. package/dist/nips/nip98.mjs +5 -5
  108. package/dist/nips/nip98.mjs.map +1 -1
  109. package/dist/{pool-MehgIIzm.d.mts → pool-BMfTQZjM.d.mts} +5 -4
  110. package/dist/{pool-lMT_mvzn.mjs → pool-D13eVCqc.mjs} +5 -5
  111. package/dist/{pool-lMT_mvzn.mjs.map → pool-D13eVCqc.mjs.map} +1 -1
  112. package/dist/{put-BEM5j8xK.mjs → put-DuymazkT.mjs} +5 -5
  113. package/dist/put-DuymazkT.mjs.map +1 -0
  114. package/dist/{reactive-B7lPFgTS.d.mts → reactive-BkzOsSwU.d.mts} +3 -3
  115. package/dist/{reactive-DFHY-f00.mjs → reactive-C0-rTqN9.mjs} +10 -9
  116. package/dist/reactive-C0-rTqN9.mjs.map +1 -0
  117. package/dist/{relay-DIR77xQ2.mjs → relay-CbgToreY.mjs} +6 -6
  118. package/dist/{relay-DIR77xQ2.mjs.map → relay-CbgToreY.mjs.map} +1 -1
  119. package/dist/relay.d.mts +3 -3
  120. package/dist/relay.mjs +2 -2
  121. package/dist/{signer-Bha9gQy8.mjs → signer-DQzjm3pt.mjs} +28 -14
  122. package/dist/signer-DQzjm3pt.mjs.map +1 -0
  123. package/dist/signer.d.mts +2 -2
  124. package/dist/signer.mjs +2 -2
  125. package/dist/{storage-n2BE1bdY.mjs → storage-DvDAuxzC.mjs} +26 -41
  126. package/dist/storage-DvDAuxzC.mjs.map +1 -0
  127. package/dist/storage.d.mts +3 -3
  128. package/dist/storage.mjs +4 -4
  129. package/dist/store.d.mts +1 -1
  130. package/dist/store.mjs +1 -2
  131. package/dist/testing.d.mts +2 -2
  132. package/dist/testing.mjs +7 -7
  133. package/dist/{types-DiVVyf1e.d.mts → types-COupAqtN.d.mts} +2 -2
  134. package/dist/{types-BJ4wZ7JJ.d.mts → types-DF8yT6EG.d.mts} +7 -1
  135. package/dist/{util-DfsyJ9nz.mjs → util-BcRBO6o-.mjs} +18 -3
  136. package/dist/util-BcRBO6o-.mjs.map +1 -0
  137. package/dist/{verifier-DEHUy4AD.mjs → verifier-DBSPHzGg.mjs} +6 -15
  138. package/dist/verifier-DBSPHzGg.mjs.map +1 -0
  139. package/dist/verifier-Or2nDvHb.d.mts +16 -0
  140. package/package.json +3 -3
  141. package/dist/client-COkEihj_.mjs.map +0 -1
  142. package/dist/event-DvM1f03b.mjs.map +0 -1
  143. package/dist/filter-3jOh4uLN.mjs.map +0 -1
  144. package/dist/gossip-CRyhK6E6.mjs.map +0 -1
  145. package/dist/key-D_oEutQ6.mjs.map +0 -1
  146. package/dist/loaders-BRh2riV9.mjs.map +0 -1
  147. package/dist/memory-index-3GSrPIWj.mjs.map +0 -1
  148. package/dist/message-BMfreCQc.mjs.map +0 -1
  149. package/dist/put-BEM5j8xK.mjs.map +0 -1
  150. package/dist/reactive-DFHY-f00.mjs.map +0 -1
  151. package/dist/signer-Bha9gQy8.mjs.map +0 -1
  152. package/dist/storage-n2BE1bdY.mjs.map +0 -1
  153. package/dist/util-DfsyJ9nz.mjs.map +0 -1
  154. package/dist/verifier-DEHUy4AD.mjs.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"message-BMfreCQc.mjs","names":[],"sources":["../src/core/message.ts"],"sourcesContent":["import { MessageError } from \"./error.ts\";\nimport type { Event } from \"./event.ts\";\nimport { validateSignedEvent } from \"./event.ts\";\nimport { canonicalizeFilter, canonicalizeFilters } from \"./filter.ts\";\nimport type { Filter } from \"./filter.ts\";\nimport { SUBSCRIPTION_ID_MAX_CHARS } from \"./limits.ts\";\nimport type { Mutable } from \"./util.ts\";\nimport { bytesToHex, hexToBytes, isRecord } from \"./util.ts\";\n\n/** NIP-01 subscription id: 1..64 chars. */\nexport type SubscriptionId = string;\n\n/** Validate a subscription id; throws {@link MessageError} unless 1..64 Unicode scalar values. */\nexport function assertSubscriptionId(id: string): SubscriptionId {\n // Iterating a string yields code points, so astral chars count once (NIP-01's \"chars\").\n let length = 0;\n for (const _ of id) {\n length += 1;\n }\n if (length === 0 || length > SUBSCRIPTION_ID_MAX_CHARS) {\n throw new MessageError(`subscription id length must be 1..${SUBSCRIPTION_ID_MAX_CHARS}`);\n }\n return id;\n}\n\n/** Validate the given id, or mint a random 16-hex-char subscription id. */\nexport function createSubscriptionId(id?: string): SubscriptionId {\n if (id !== undefined) {\n return assertSubscriptionId(id);\n }\n const bytes = new Uint8Array(8);\n crypto.getRandomValues(bytes);\n return bytesToHex(bytes);\n}\n\n/** Client → relay messages (NIP-01 + NIP-42 AUTH + NIP-45 COUNT + NIP-77). */\nexport type ClientMessage =\n | [\"EVENT\", Event]\n | [\"REQ\", SubscriptionId, ...Filter[]]\n | [\"CLOSE\", SubscriptionId]\n | [\"AUTH\", Event]\n | [\"COUNT\", SubscriptionId, ...Filter[]]\n | [\"NEG-OPEN\", SubscriptionId, Filter, string]\n | [\"NEG-MSG\", SubscriptionId, string]\n | [\"NEG-CLOSE\", SubscriptionId];\n\n/** Relay → client messages. */\nexport type RelayMessage =\n | [\"EVENT\", SubscriptionId, Event]\n | [\"OK\", string, boolean, string]\n | [\"EOSE\", SubscriptionId]\n | [\"CLOSED\", SubscriptionId, string]\n | [\"NOTICE\", string]\n | [\"AUTH\", string]\n | [\"COUNT\", SubscriptionId, CountResult]\n | [\"NEG-MSG\", SubscriptionId, string]\n | [\"NEG-ERR\", SubscriptionId, string];\n\n/** NIP-45 COUNT reply payload. */\nexport type CountResult = {\n readonly count: number;\n readonly approximate?: boolean | undefined;\n /**\n * Optional 512-char hex HyperLogLog sketch (256 registers). Merge sketches with `mergeCountHll`.\n * Estimation is unspecified by NIP-45 and not provided.\n */\n readonly hll?: string | undefined;\n};\n\nconst HLL_BYTES = 256; // 512 hex chars (NIP-45; hex only, not base64)\nconst HLL_HEX_LEN = HLL_BYTES * 2;\nconst HEX_RE = /^[0-9a-fA-F]+$/;\n\n/**\n * Register-wise max of NIP-45 HyperLogLog sketches. Output is always lowercase 512 hex. Empty input\n * is the zero sketch (identity).\n */\nexport function mergeCountHll(hexes: ReadonlyArray<string>): string {\n const merged = new Uint8Array(HLL_BYTES);\n for (const hex of hexes) {\n if (hex.length !== HLL_HEX_LEN || !HEX_RE.test(hex)) {\n throw new MessageError(\"invalid NIP-45 HLL sketch: expected 512-char hex\");\n }\n const bytes = hexToBytes(hex);\n for (const [i, b] of bytes.entries()) {\n const m = merged[i];\n if (m !== undefined && b > m) {\n merged[i] = b;\n }\n }\n }\n return bytesToHex(merged);\n}\n\n/** Serialize a client->relay message to its NIP-01 JSON wire form. */\nexport function encodeClientMessage(message: ClientMessage): string {\n // REQ/COUNT/NEG-OPEN filters are canonicalized so the emitted wire form is\n // the canonical filter `filterFingerprint` uses for subscription identity.\n switch (message[0]) {\n case \"REQ\":\n case \"COUNT\": {\n const [type, id, ...filters] = message;\n return JSON.stringify([type, id, ...canonicalizeFilters(filters)]);\n }\n case \"NEG-OPEN\":\n return JSON.stringify([\"NEG-OPEN\", message[1], canonicalizeFilter(message[2]), message[3]]);\n default:\n return JSON.stringify(message);\n }\n}\n\n/** Serialize a relay->client message to its NIP-01 JSON wire form. */\nexport function encodeRelayMessage(message: RelayMessage): string {\n return JSON.stringify(message);\n}\n\n/** Parse a client->relay JSON message; throws {@link MessageError} on malformed input. */\nexport function parseClientMessage(raw: string): ClientMessage {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw new MessageError(\"client message is not valid JSON\");\n }\n if (!Array.isArray(parsed) || parsed.length === 0 || typeof parsed[0] !== \"string\") {\n throw new MessageError(\"client message must be a non-empty JSON array\");\n }\n const items: unknown[] = parsed;\n const [type] = items;\n switch (type) {\n case \"EVENT\":\n if (items.length !== 2 || !validateSignedEvent(items[1])) {\n throw new MessageError(\"invalid EVENT client message\");\n }\n return [\"EVENT\", items[1]];\n\n case \"REQ\":\n if (items.length < 3 || typeof items[1] !== \"string\") {\n throw new MessageError(\"invalid REQ client message\");\n }\n return [\"REQ\", assertSubscriptionId(items[1]), ...parseWireFilters(items.slice(2), \"REQ\")];\n\n case \"CLOSE\":\n if (items.length !== 2 || typeof items[1] !== \"string\") {\n throw new MessageError(\"invalid CLOSE client message\");\n }\n return [\"CLOSE\", assertSubscriptionId(items[1])];\n\n case \"AUTH\":\n if (items.length !== 2 || !validateSignedEvent(items[1])) {\n throw new MessageError(\"invalid AUTH client message\");\n }\n return [\"AUTH\", items[1]];\n\n case \"COUNT\":\n if (items.length < 3 || typeof items[1] !== \"string\") {\n throw new MessageError(\"invalid COUNT client message\");\n }\n return [\n \"COUNT\",\n assertSubscriptionId(items[1]),\n ...parseWireFilters(items.slice(2), \"COUNT\"),\n ];\n\n case \"NEG-OPEN\":\n if (items.length === 5) {\n throw new MessageError(\"obsolete 5-element NEG-OPEN; expected [NEG-OPEN, id, filter, hex]\");\n }\n if (\n items.length !== 4 ||\n typeof items[1] !== \"string\" ||\n !isRecord(items[2]) ||\n !isNegHex(items[3])\n ) {\n throw new MessageError(\"invalid NEG-OPEN client message\");\n }\n return [\n \"NEG-OPEN\",\n assertSubscriptionId(items[1]),\n parseWireFilter(items[2], \"NEG-OPEN\"),\n items[3].toLowerCase(),\n ];\n\n case \"NEG-MSG\":\n if (items.length !== 3 || typeof items[1] !== \"string\" || !isNegHex(items[2])) {\n throw new MessageError(\"invalid NEG-MSG client message\");\n }\n return [\"NEG-MSG\", assertSubscriptionId(items[1]), items[2].toLowerCase()];\n\n case \"NEG-CLOSE\":\n if (items.length !== 2 || typeof items[1] !== \"string\") {\n throw new MessageError(\"invalid NEG-CLOSE client message\");\n }\n return [\"NEG-CLOSE\", assertSubscriptionId(items[1])];\n\n default:\n throw new MessageError(`unknown client message type: ${String(type)}`);\n }\n}\n\n/** Parse a relay->client JSON message; throws {@link MessageError} on malformed input. */\nexport function parseRelayMessage(raw: string): RelayMessage {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw new MessageError(\"relay message is not valid JSON\");\n }\n if (!Array.isArray(parsed) || parsed.length === 0 || typeof parsed[0] !== \"string\") {\n throw new MessageError(\"relay message must be a non-empty JSON array\");\n }\n const items: unknown[] = parsed;\n const [type] = items;\n switch (type) {\n case \"EVENT\":\n if (items.length !== 3 || typeof items[1] !== \"string\" || !validateSignedEvent(items[2])) {\n throw new MessageError(\"invalid EVENT relay message\");\n }\n return [\"EVENT\", assertSubscriptionId(items[1]), items[2]];\n\n case \"OK\":\n if (\n items.length !== 4 ||\n typeof items[1] !== \"string\" ||\n !HEX64_LOWER_RE.test(items[1]) ||\n typeof items[2] !== \"boolean\" ||\n typeof items[3] !== \"string\"\n ) {\n throw new MessageError(\"invalid OK relay message\");\n }\n return [\"OK\", items[1], items[2], items[3]];\n\n case \"EOSE\":\n if (items.length !== 2 || typeof items[1] !== \"string\") {\n throw new MessageError(\"invalid EOSE relay message\");\n }\n return [\"EOSE\", assertSubscriptionId(items[1])];\n\n case \"CLOSED\":\n if (items.length !== 3 || typeof items[1] !== \"string\" || typeof items[2] !== \"string\") {\n throw new MessageError(\"invalid CLOSED relay message\");\n }\n return [\"CLOSED\", assertSubscriptionId(items[1]), items[2]];\n\n case \"NOTICE\":\n if (items.length !== 2 || typeof items[1] !== \"string\") {\n throw new MessageError(\"invalid NOTICE relay message\");\n }\n return [\"NOTICE\", items[1]];\n\n case \"AUTH\":\n if (items.length !== 2 || typeof items[1] !== \"string\") {\n throw new MessageError(\"invalid AUTH relay message\");\n }\n return [\"AUTH\", items[1]];\n\n case \"COUNT\": {\n const payload = items.at(2);\n const count = isRecord(payload) ? payload[\"count\"] : undefined;\n if (\n items.length !== 3 ||\n typeof items[1] !== \"string\" ||\n !isRecord(payload) ||\n typeof count !== \"number\" ||\n !Number.isSafeInteger(count) ||\n count < 0\n ) {\n throw new MessageError(\"invalid COUNT relay message\");\n }\n const result: Mutable<CountResult> = { count };\n if (typeof payload[\"approximate\"] === \"boolean\") {\n result.approximate = payload[\"approximate\"];\n }\n const hll = parseCountHll(payload[\"hll\"]);\n if (hll !== undefined) {\n result.hll = hll;\n }\n return [\"COUNT\", assertSubscriptionId(items[1]), result];\n }\n case \"NEG-MSG\":\n if (items.length !== 3 || typeof items[1] !== \"string\" || !isNegHex(items[2])) {\n throw new MessageError(\"invalid NEG-MSG relay message\");\n }\n return [\"NEG-MSG\", assertSubscriptionId(items[1]), items[2].toLowerCase()];\n\n case \"NEG-ERR\":\n if (items.length !== 3 || typeof items[1] !== \"string\" || typeof items[2] !== \"string\") {\n throw new MessageError(\"invalid NEG-ERR relay message\");\n }\n return [\"NEG-ERR\", assertSubscriptionId(items[1]), items[2]];\n\n default:\n throw new MessageError(`unknown relay message type: ${String(type)}`);\n }\n}\n\nfunction parseCountHll(value: unknown): string | undefined {\n if (typeof value !== \"string\" || value.length !== HLL_HEX_LEN || !HEX_RE.test(value)) {\n return undefined;\n }\n return value.toLowerCase();\n}\n\nconst HEX64_RE = /^[0-9a-fA-F]{64}$/;\nconst HEX64_LOWER_RE = /^[0-9a-f]{64}$/;\n\n/**\n * Validates a wire filter per NIP-01: `ids`/`authors` are 64-hex arrays (normalized lowercase),\n * `kinds` are integers in 0..=65535, `since`/`until`/`limit` non-negative integers, `search` a\n * string, and `#<single letter>` arrays of strings (`#e`/`#p` lowercased). Multi-letter `#` keys\n * and unknown non-`#` keys are dropped; wrong types throw {@link MessageError}. Duplicate keys and\n * integer spellings (`1e3`, `1.0`, `-0`) need no handling: `JSON.parse` already applied last-wins\n * and normalized the numbers before this runs.\n */\nfunction parseWireFilter(value: unknown, kind: string): Filter {\n if (!isRecord(value)) {\n throw new MessageError(`invalid ${kind} filter`);\n }\n const fail = (): MessageError => new MessageError(`invalid ${kind} filter`);\n const out: Mutable<Filter> = {};\n for (const [key, v] of Object.entries(value)) {\n if (key.startsWith(\"#\")) {\n if (!/^#[a-zA-Z]$/.test(key)) {\n continue;\n }\n if (!Array.isArray(v) || !v.every((x): x is string => typeof x === \"string\")) {\n throw fail();\n }\n out[`#${key.slice(1)}`] = key === \"#e\" || key === \"#p\" ? v.map((x) => x.toLowerCase()) : v;\n } else if (key === \"ids\" || key === \"authors\") {\n if (\n !Array.isArray(v) ||\n !v.every((x): x is string => typeof x === \"string\" && HEX64_RE.test(x))\n ) {\n throw fail();\n }\n out[key] = v.map((x) => x.toLowerCase());\n } else if (key === \"kinds\") {\n if (\n !Array.isArray(v) ||\n !v.every(\n (x): x is number =>\n typeof x === \"number\" && Number.isSafeInteger(x) && x >= 0 && x <= 65535,\n )\n ) {\n throw fail();\n }\n out.kinds = v;\n } else if (key === \"since\" || key === \"until\" || key === \"limit\") {\n if (typeof v !== \"number\" || !Number.isSafeInteger(v) || v < 0) {\n throw fail();\n }\n out[key] = v;\n } else if (key === \"search\") {\n if (typeof v !== \"string\") {\n throw fail();\n }\n out.search = v;\n }\n }\n return out;\n}\n\nfunction parseWireFilters(items: unknown[], kind: string): Filter[] {\n return items.map((item) => parseWireFilter(item, kind));\n}\n\nfunction isNegHex(value: unknown): value is string {\n return (\n typeof value === \"string\" && value.length > 0 && value.length % 2 === 0 && HEX_RE.test(value)\n );\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"put-BEM5j8xK.mjs","names":[],"sources":["../src/storage/deletion.ts","../src/storage/put.ts"],"sourcesContent":["import type { Event } from \"../core/event.ts\";\nimport { Kind } from \"../core/kind.ts\";\nimport { eventAddress, parseEventAddress } from \"../core/tag.ts\";\nimport { isHex32 } from \"../core/util.ts\";\n\nexport type DeletionPlan = {\n /** Stored events validated as same-pubkey (not kind 5) and to be removed now. */\n readonly removeIds: ReadonlyArray<string>;\n /** Referenced ids not in the store; remember deletion pubkey until the event arrives. */\n readonly pendingIds: ReadonlyArray<{ readonly id: string; readonly pubkey: string }>;\n /** Replaceable/addressable coordinates tombstoned up to `until` (deletion created_at). */\n readonly coordinates: ReadonlyArray<{ readonly key: string; readonly until: number }>;\n};\n\n/**\n * NIP-09 application plan for a kind 5 event.\n *\n * - `e` tags: delete only when the target exists, is not itself kind 5, and shares pubkey.\n * - Unknown `e` ids are pending until the event arrives (client MUST validate pubkey).\n * - `a` tags: tombstone that coordinate up to this deletion's `created_at` when pubkey matches.\n * - Deleting a deletion request is a no-op.\n */\nexport function planDeletion(\n deletion: Pick<Event, \"pubkey\" | \"created_at\" | \"tags\">,\n getById: (id: string) => Pick<Event, \"id\" | \"pubkey\" | \"kind\"> | undefined,\n): DeletionPlan {\n const { pubkey } = deletion;\n const removeIds: string[] = [];\n const pendingIds: Array<{ id: string; pubkey: string }> = [];\n const coordinates: Array<{ key: string; until: number }> = [];\n const seenIds = new Set<string>();\n const seenCoords = new Set<string>();\n\n for (const tag of deletion.tags) {\n if (tag[0] === \"e\" && tag[1] !== undefined && isHex32(tag[1].toLowerCase())) {\n const id = tag[1].toLowerCase();\n if (seenIds.has(id)) {\n continue;\n }\n seenIds.add(id);\n const existing = getById(id);\n if (!existing) {\n pendingIds.push({ id, pubkey });\n continue;\n }\n if (existing.kind === Kind.EventDeletion) {\n continue;\n }\n if (existing.pubkey !== pubkey) {\n continue;\n }\n removeIds.push(id);\n continue;\n }\n\n if (tag[0] === \"a\" && tag[1] !== undefined) {\n const coord = parseEventAddress(tag[1]);\n if (!coord || coord.pubkey !== pubkey) {\n continue;\n }\n const key = `${coord.kind}:${coord.pubkey}:${coord.identifier}`;\n if (seenCoords.has(key)) {\n continue;\n }\n seenCoords.add(key);\n coordinates.push({ key, until: deletion.created_at });\n }\n }\n\n return { removeIds, pendingIds, coordinates };\n}\n\n/** In-memory NIP-09 tombstones shared by MemoryEventStore and IndexedDbEventStore. */\nexport class DeletionState {\n readonly ids: Set<string> = new Set<string>();\n readonly pending: Map<string, string> = new Map<string, string>();\n readonly coordinates: Map<string, number> = new Map<string, number>();\n\n clear(): void {\n this.ids.clear();\n this.pending.clear();\n this.coordinates.clear();\n }\n\n covers(event: Pick<Event, \"id\" | \"pubkey\" | \"kind\" | \"created_at\" | \"tags\">): boolean {\n if (this.ids.has(event.id)) {\n return true;\n }\n if (event.kind === Kind.EventDeletion) {\n return false;\n }\n const pendingPk = this.pending.get(event.id);\n if (pendingPk !== undefined && event.pubkey === pendingPk) {\n return true;\n }\n const addr = eventAddress(event);\n if (addr === undefined) {\n return false;\n }\n const until = this.coordinates.get(addr);\n return until !== undefined && event.created_at <= until;\n }\n\n absorb(plan: DeletionPlan): void {\n for (const id of plan.removeIds) {\n this.ids.add(id);\n this.pending.delete(id);\n }\n for (const p of plan.pendingIds) {\n if (!this.ids.has(p.id)) {\n this.pending.set(p.id, p.pubkey);\n }\n }\n for (const c of plan.coordinates) {\n const prev = this.coordinates.get(c.key) ?? Number.NEGATIVE_INFINITY;\n // Re-absorbing a coordinate moves it to the newest end so FIFO\n // tombstone caps evict the oldest marker, not the freshest.\n this.coordinates.delete(c.key);\n this.coordinates.set(c.key, Math.max(prev, c.until));\n }\n }\n}\n\nexport function coordinateRemovals(\n coordinates: ReadonlyArray<{ key: string; until: number }>,\n getCurrent: (key: string) => Pick<Event, \"id\" | \"created_at\"> | undefined,\n): string[] {\n const ids: string[] = [];\n for (const c of coordinates) {\n const current = getCurrent(c.key);\n if (current && current.created_at <= c.until) {\n ids.push(current.id);\n }\n }\n return ids;\n}\n","import type { Event } from \"../core/event.ts\";\nimport { isMarkedVerified, isReplaceableWinner, validateSignedEvent } from \"../core/event.ts\";\nimport { isEphemeralKind, Kind } from \"../core/kind.ts\";\nimport { eventAddress } from \"../core/tag.ts\";\nimport { coordinateRemovals, planDeletion } from \"./deletion.ts\";\nimport type { DeletionPlan, DeletionState } from \"./deletion.ts\";\nimport type { PutResult } from \"./types.ts\";\n\nexport function outboxBoundKey(pubkey: string, kind: number): string {\n return `${pubkey.toLowerCase()}:${kind}`;\n}\n\nexport type PutDecision =\n | {\n readonly action: \"skip\";\n readonly result: \"duplicate\" | \"ephemeral\" | \"rejected\" | \"invalid\";\n readonly event: Event;\n }\n | { readonly action: \"tombstone\"; readonly result: \"duplicate\"; readonly event: Event }\n | {\n readonly action: \"delete\";\n readonly result: \"deleted\";\n readonly event: Event;\n readonly plan: DeletionPlan;\n readonly coordIds: ReadonlyArray<string>;\n }\n | {\n readonly action: \"insert\";\n readonly result: \"accepted\" | \"replaced\";\n readonly event: Event;\n readonly address?: string | undefined;\n readonly replaceId?: string | undefined;\n };\n\nexport type PutLookup = {\n deletion: DeletionState;\n getById: (\n id: string,\n ) => Pick<Event, \"id\" | \"pubkey\" | \"kind\" | \"created_at\" | \"tags\"> | undefined;\n /**\n * Current replaceable winner for an address. `evicted: true` marks an eviction watermark: the\n * winning event body was dropped, but its id/created_at still reject older versions, and a re-put\n * of the watermarked id re-inserts it.\n */\n getReplaceable: (\n address: string,\n ) => (Pick<Event, \"id\" | \"created_at\"> & { evicted?: boolean }) | undefined;\n};\n\nexport function decidePut(raw: Event, lookup: PutLookup): PutDecision {\n // Canonical-input precondition: non-canonical events are never indexed. An\n // already-verified event has necessarily passed validateSignedEvent.\n if (!isMarkedVerified(raw) && !validateSignedEvent(raw)) {\n return { action: \"skip\", result: \"invalid\", event: raw };\n }\n const event = raw;\n if (lookup.deletion.ids.has(event.id) || lookup.getById(event.id)) {\n return { action: \"skip\", result: \"duplicate\", event };\n }\n if (event.kind === Kind.EventDeletion) {\n const plan = planDeletion(event, lookup.getById);\n const coordIds = coordinateRemovals(plan.coordinates, lookup.getReplaceable);\n return { action: \"delete\", result: \"deleted\", event, plan, coordIds };\n }\n if (lookup.deletion.covers(event)) {\n return { action: \"tombstone\", result: \"duplicate\", event };\n }\n if (isEphemeralKind(event.kind)) {\n return { action: \"skip\", result: \"ephemeral\", event };\n }\n const address = eventAddress(event);\n if (address !== undefined) {\n const prev = lookup.getReplaceable(address);\n // A stored incumbent rejects stale versions; an eviction watermark does\n // the same, except a re-put of the watermarked id re-inserts it.\n if (prev && prev.id !== event.id && !isReplaceableWinner(event, prev)) {\n return { action: \"skip\", result: \"rejected\", event };\n }\n if (prev !== undefined && prev.evicted !== true) {\n return { action: \"insert\", result: \"replaced\", event, address, replaceId: prev.id };\n }\n return { action: \"insert\", result: \"accepted\", event, address };\n }\n return { action: \"insert\", result: \"accepted\", event };\n}\n\nexport function applyPutMemory(\n s: {\n deletion: DeletionState;\n indexInsert: (event: Event) => void;\n indexRemove: (id: string) => boolean;\n },\n d: PutDecision,\n): PutResult {\n if (d.action === \"skip\") {\n return d.result;\n }\n if (d.action === \"tombstone\") {\n s.deletion.ids.add(d.event.id);\n s.deletion.pending.delete(d.event.id);\n return \"duplicate\";\n }\n if (d.action === \"delete\") {\n s.deletion.pending.delete(d.event.id);\n s.deletion.absorb(d.plan);\n for (const id of d.plan.removeIds) {\n s.indexRemove(id);\n }\n for (const id of d.coordIds) {\n s.deletion.ids.add(id);\n s.indexRemove(id);\n }\n s.indexInsert(d.event);\n return \"deleted\";\n }\n if (d.replaceId !== undefined) {\n s.indexRemove(d.replaceId);\n }\n s.indexInsert(d.event);\n return d.result;\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"reactive-DFHY-f00.mjs","names":[],"sources":["../src/store/reactive.ts"],"sourcesContent":["import { addToSetMap, removeFromSetMap, trimOldest } from \"../core/collections.ts\";\nimport { Emitter } from \"../core/emitter.ts\";\nimport { itemCompare } from \"../core/event.ts\";\nimport type { Event } from \"../core/event.ts\";\nimport type { Filter } from \"../core/filter.ts\";\nimport { filterFingerprint, matchFilters } from \"../core/filter.ts\";\nimport { Kind } from \"../core/kind.ts\";\nimport { invokeSafely, reportError } from \"../core/report.ts\";\nimport { formatEventAddress, eventAddress, parseEventAddress } from \"../core/tag.ts\";\nimport { normalizeURL } from \"../core/util.ts\";\nimport { MemoryIndex } from \"../storage/memory-index.ts\";\nimport type { PutResult } from \"../storage/types.ts\";\n\n/** A reactive snapshot source compatible with `useSyncExternalStore`. */\nexport type Watch<T> = {\n subscribe: (onChange: () => void) => () => void;\n getSnapshot: () => T;\n};\n\n/** Capacity options for {@link ReactiveEventStore}. */\nexport type ReactiveEventStoreOptions = {\n /** Max stored events before LRU eviction. Default 50_000. */\n maxEvents?: number | undefined;\n /** Max ids tracked by `seenOn`. Default 20_000. */\n maxSeenOnEntries?: number | undefined;\n /** FIFO cap on tombstoned deletion ids. Default 100_000. */\n maxTombstones?: number | undefined;\n};\n\n/** Typed event payloads for {@link ReactiveEventStore.on}. */\nexport type ReactiveEventStoreEventMap = {\n /** Every physical index insert, fired synchronously after the update. */\n insert: Event;\n /** Every physical index remove, fired synchronously after watch invalidation. */\n remove: Event;\n};\n\nconst SEEN_ON_PER_ID = 16;\n\n/** Notify passes per microtask; overflow reschedules so a writing subscriber cannot livelock. */\nconst MAX_FLUSH_PASSES = 32;\n\n/** Category of a {@link Watch} subscription. */\ntype WatchKind = \"event\" | \"replaceable\" | \"query\";\n\n/** Structural handle the store needs for invalidation; implemented by WatchImpl. */\ntype WatchHandle = {\n readonly kind: WatchKind;\n readonly key: string;\n readonly filters: ReadonlyArray<Filter> | undefined;\n readonly subscribed: boolean;\n readonly pinnedIds: ReadonlyArray<string>;\n markDirty: () => void;\n notify: () => void;\n};\n\n/** Store internals exposed to a {@link WatchImpl}: version clock and registry/pin bookkeeping. */\ntype WatchHost = {\n version: () => number;\n register: (watch: WatchHandle) => void;\n unregister: (watch: WatchHandle) => void;\n /** Refresh pinned ids after a registered watch's snapshot changed. No-op when unregistered. */\n repin: (watch: WatchHandle) => void;\n};\n\nclass WatchImpl<T> implements Watch<T>, WatchHandle {\n readonly host: WatchHost;\n readonly kind: WatchKind;\n readonly key: string;\n readonly filters: ReadonlyArray<Filter> | undefined;\n readonly compute: () => T;\n readonly equal: (a: T, b: T) => boolean;\n readonly snapshotIds: (snapshot: T) => ReadonlyArray<string>;\n readonly #subscribers = new Set<() => void>();\n #snapshot: T;\n #version = -1;\n #dirty = false;\n #pendingRemove = false;\n\n constructor(\n host: WatchHost,\n kind: WatchKind,\n key: string,\n compute: () => T,\n equal: (a: T, b: T) => boolean,\n snapshotIds: (snapshot: T) => ReadonlyArray<string>,\n filters?: ReadonlyArray<Filter>,\n ) {\n this.host = host;\n this.kind = kind;\n this.key = key;\n this.filters = filters;\n this.compute = compute;\n this.equal = equal;\n this.snapshotIds = snapshotIds;\n this.#snapshot = compute();\n this.#version = host.version();\n }\n\n get subscribed(): boolean {\n return this.#subscribers.size > 0;\n }\n\n get pinnedIds(): ReadonlyArray<string> {\n return this.snapshotIds(this.#snapshot);\n }\n\n subscribe(onChange: () => void): () => void {\n const wasUnsubscribed = this.#subscribers.size === 0;\n this.#subscribers.add(onChange);\n this.#pendingRemove = false;\n // Writes that landed while unregistered must not be lost.\n if (wasUnsubscribed && this.#version !== this.host.version()) {\n this.#dirty = true;\n }\n this.host.register(this);\n return () => {\n this.#subscribers.delete(onChange);\n if (this.#subscribers.size > 0 || this.#pendingRemove) {\n return;\n }\n this.#pendingRemove = true;\n queueMicrotask(() => {\n if (this.#pendingRemove) {\n this.host.unregister(this);\n }\n });\n };\n }\n\n getSnapshot(): T {\n if (this.subscribed ? this.#dirty : this.#version !== this.host.version()) {\n const next = this.compute();\n if (!this.equal(next, this.#snapshot)) {\n this.#snapshot = next;\n this.host.repin(this);\n }\n this.#version = this.host.version();\n this.#dirty = false;\n }\n return this.#snapshot;\n }\n\n markDirty(): void {\n this.#dirty = true;\n }\n\n notify(): void {\n // Do not sync #version here: subscribers will call getSnapshot, which\n // recomputes against the bumped store version and keeps the reference\n // when the result is element-wise identical. A throwing subscriber is\n // reported and never aborts the remaining notifications.\n for (const onChange of this.#subscribers) {\n invokeSafely(onChange);\n }\n }\n}\n\nconst EVENT_IDS = (e: Event | undefined): ReadonlyArray<string> => (e ? [e.id] : []);\nconst LIST_IDS = (events: ReadonlyArray<Event>): ReadonlyArray<string> => events.map((e) => e.id);\n\nfunction sameRef(a: Event | undefined, b: Event | undefined): boolean {\n return a === b;\n}\n\nfunction sameList(a: ReadonlyArray<Event>, b: ReadonlyArray<Event>): boolean {\n if (a.length !== b.length) {\n return false;\n }\n for (let i = 0; i < a.length; i++) {\n if (a[i] !== b[i]) {\n return false;\n }\n }\n return true;\n}\n\n/**\n * Synchronous reactive in-memory event store backed by {@link MemoryIndex}. The single read source\n * for Nostr events on the UI side: `Watch` snapshots keep referential stability for\n * `useSyncExternalStore` and are invalidated on matching writes, deletions and tombstones. Writes\n * batch notifications into one microtask flush.\n */\nexport class ReactiveEventStore {\n readonly #index: MemoryIndex;\n readonly #maxEvents: number;\n readonly #maxSeenOnEntries: number;\n\n #version = 0;\n\n // id -> recency-ordered access order (delete+add moves to the newest end)\n readonly #recency = new Set<string>();\n // id -> relay urls (≤ SEEN_ON_PER_ID); Map insertion order for eviction\n readonly #seenOn = new Map<string, string[]>();\n\n readonly #idWatches = new Map<string, Set<WatchHandle>>();\n readonly #addressWatches = new Map<string, Set<WatchHandle>>();\n readonly #eventCache = new Map<string, WatchImpl<Event | undefined>>();\n readonly #addressCache = new Map<string, WatchImpl<Event | undefined>>();\n readonly #queryCache = new Map<string, WatchImpl<ReadonlyArray<Event>>>();\n // Query watches indexed by filter kinds; a filter without kinds matches any kind.\n readonly #queryByKind = new Map<number, Set<WatchHandle>>();\n readonly #queryAnyKind = new Set<WatchHandle>();\n\n // Event-id → pin refcount across registered watch snapshots; eviction skips pinned ids.\n readonly #pinned = new Map<string, number>();\n // Registered watch → ids it currently pins (repin/unpin need the old set).\n readonly #watchPins = new Map<WatchHandle, ReadonlyArray<string>>();\n\n readonly #dirty = new Set<WatchHandle>();\n #flushScheduled = false;\n readonly #events = new Emitter<ReactiveEventStoreEventMap>();\n\n readonly #watchHost: WatchHost = {\n version: () => this.#version,\n register: (watch) => {\n this.#registerWatch(watch);\n if (!this.#watchPins.has(watch)) {\n const ids = watch.pinnedIds;\n this.#watchPins.set(watch, ids);\n this.#pin(ids);\n }\n },\n unregister: (watch) => {\n if (watch.subscribed) {\n return;\n }\n this.#unregisterWatch(watch);\n const ids = this.#watchPins.get(watch);\n if (ids !== undefined) {\n this.#watchPins.delete(watch);\n this.#unpin(ids);\n }\n },\n repin: (watch) => {\n const old = this.#watchPins.get(watch);\n if (old === undefined) {\n return;\n }\n const next = watch.pinnedIds;\n this.#unpin(old);\n this.#pin(next);\n this.#watchPins.set(watch, next);\n },\n };\n\n constructor(opts?: ReactiveEventStoreOptions) {\n this.#maxEvents = opts?.maxEvents ?? 50_000;\n this.#maxSeenOnEntries = opts?.maxSeenOnEntries ?? 20_000;\n this.#index = new MemoryIndex({\n maxTombstones: opts?.maxTombstones ?? 100_000,\n // Keep evicted replaceable winners rejectable while their body is gone.\n maxWatermarks: this.#maxEvents,\n onInsert: (event) => this.#onInsert(event),\n onRemove: (event) => this.#onRemove(event),\n });\n }\n\n add(event: Event, relayUrl?: string): PutResult {\n const result = this.#index.put(event);\n // Stored events are canonical; seenOn keys are their lowercase ids.\n const { id } = event;\n if (\n relayUrl !== undefined &&\n result !== \"ephemeral\" &&\n result !== \"rejected\" &&\n result !== \"invalid\"\n ) {\n this.#recordSeen(id, relayUrl);\n }\n // The just-inserted event must survive its own eviction pass even when\n // every older entry is pinned by a subscribed watch snapshot.\n this.#evictIfNeeded(id);\n return result;\n }\n\n addMany(events: ReadonlyArray<Event>, relayUrl?: string): PutResult[] {\n const results: PutResult[] = [];\n for (const event of events) {\n results.push(this.add(event, relayUrl));\n }\n return results;\n }\n\n /** Record a relay sighting for an id already in the index. No notification. */\n markSeen(id: string, relayUrl: string): boolean {\n const key = id.toLowerCase();\n if (this.#index.get(key) === undefined) {\n return false;\n }\n this.#recordSeen(key, relayUrl);\n return true;\n }\n\n /** Bulk load (initial hydration): no seenOn, one batched notification. */\n hydrate(events: ReadonlyArray<Event>): void {\n // Insert oldest-first so recency ends with the newest entries hottest.\n const sorted = [...events].sort(itemCompare);\n for (const event of sorted) {\n this.#index.put(event);\n }\n this.#evictIfNeeded();\n }\n\n remove(ids: ReadonlyArray<string>): number {\n return this.#index.remove(ids);\n }\n\n clear(): void {\n this.#index.clear();\n this.#seenOn.clear();\n this.#recency.clear();\n this.#version += 1;\n this.#invalidateAll();\n }\n\n get(id: string): Event | undefined {\n const event = this.#index.get(id);\n if (event) {\n this.#touch(event.id);\n }\n return event;\n }\n\n getReplaceable(kind: number, pubkey: string, d?: string): Event | undefined {\n return this.getByAddress(formatEventAddress(kind, pubkey, d ?? \"\"));\n }\n\n getByAddress(address: string): Event | undefined {\n const event = this.#index.getByAddress(address);\n if (event) {\n this.#touch(event.id);\n }\n return event;\n }\n\n query(filters: ReadonlyArray<Filter>): ReadonlyArray<Event> {\n const events = this.#index.query(filters);\n // Results come back newest-first; touch oldest→newest so the newest\n // entries end up hottest in the recency order.\n for (const event of [...events].reverse()) {\n this.#touch(event.id);\n }\n return events;\n }\n\n isDeleted(idOrAddress: string): boolean {\n return this.#index.isDeleted(idOrAddress);\n }\n\n seenOn(id: string): ReadonlyArray<string> {\n return this.#seenOn.get(id.toLowerCase()) ?? [];\n }\n\n get size(): number {\n return this.#index.size;\n }\n\n watchEvent(id: string): Watch<Event | undefined> {\n const key = id.toLowerCase();\n let watch = this.#eventCache.get(key);\n if (!watch) {\n watch = new WatchImpl(this.#watchHost, \"event\", key, () => this.get(key), sameRef, EVENT_IDS);\n this.#eventCache.set(key, watch);\n }\n return watch;\n }\n\n watchReplaceable(kind: number, pubkey: string, d?: string): Watch<Event | undefined> {\n const address = formatEventAddress(kind, pubkey, d ?? \"\");\n let watch = this.#addressCache.get(address);\n if (!watch) {\n watch = new WatchImpl(\n this.#watchHost,\n \"replaceable\",\n address,\n () => this.getByAddress(address),\n sameRef,\n EVENT_IDS,\n );\n this.#addressCache.set(address, watch);\n }\n return watch;\n }\n\n watchQuery(filters: ReadonlyArray<Filter>): Watch<ReadonlyArray<Event>> {\n const key = filterFingerprint(filters);\n let watch = this.#queryCache.get(key);\n if (!watch) {\n const list = [...filters];\n watch = new WatchImpl(\n this.#watchHost,\n \"query\",\n key,\n () => this.query(list),\n sameList,\n LIST_IDS,\n list,\n );\n this.#queryCache.set(key, watch);\n }\n return watch;\n }\n\n /**\n * Listen for a store event. Multiple listeners per type are allowed and fire in registration\n * order; a throwing listener is reported and does not break the dispatch. Returns an unsubscribe\n * function.\n */\n on<K extends keyof ReactiveEventStoreEventMap>(\n type: K,\n listener: (payload: ReactiveEventStoreEventMap[K]) => void,\n ): () => void {\n return this.#events.on(type, listener);\n }\n\n #registerWatch(watch: WatchHandle): void {\n switch (watch.kind) {\n case \"event\":\n addToSetMap(this.#idWatches, watch.key, watch);\n return;\n case \"replaceable\":\n addToSetMap(this.#addressWatches, watch.key, watch);\n return;\n case \"query\": {\n const kinds = this.#queryKinds(watch);\n if (kinds === undefined) {\n this.#queryAnyKind.add(watch);\n return;\n }\n for (const kind of kinds) {\n addToSetMap(this.#queryByKind, kind, watch);\n }\n }\n }\n }\n\n #unregisterWatch(watch: WatchHandle): void {\n switch (watch.kind) {\n case \"event\":\n removeFromSetMap(this.#idWatches, watch.key, watch);\n if (this.#eventCache.get(watch.key) === watch) {\n this.#eventCache.delete(watch.key);\n }\n return;\n case \"replaceable\":\n removeFromSetMap(this.#addressWatches, watch.key, watch);\n if (this.#addressCache.get(watch.key) === watch) {\n this.#addressCache.delete(watch.key);\n }\n return;\n case \"query\": {\n const kinds = this.#queryKinds(watch);\n if (kinds === undefined) {\n this.#queryAnyKind.delete(watch);\n } else {\n for (const kind of kinds) {\n removeFromSetMap(this.#queryByKind, kind, watch);\n }\n }\n if (this.#queryCache.get(watch.key) === watch) {\n this.#queryCache.delete(watch.key);\n }\n }\n }\n }\n\n /**\n * Kind buckets a query watch belongs to (deduped across filters), or undefined when any filter\n * lacks a non-empty `kinds` and so can match every kind.\n */\n #queryKinds(watch: WatchHandle): Set<number> | undefined {\n const kinds = new Set<number>();\n for (const filter of watch.filters ?? []) {\n if (filter.kinds === undefined || filter.kinds.length === 0) {\n return undefined;\n }\n for (const kind of filter.kinds) {\n kinds.add(kind);\n }\n }\n return kinds;\n }\n\n #touch(id: string): void {\n this.#recency.delete(id);\n this.#recency.add(id);\n }\n\n #recordSeen(id: string, relayUrl: string): void {\n let url: string;\n try {\n url = normalizeURL(relayUrl);\n } catch {\n // A malformed relay label is metadata only: skip the seen-on record\n // instead of throwing after the event was already indexed.\n return;\n }\n let urls = this.#seenOn.get(id);\n if (!urls) {\n urls = [];\n this.#seenOn.set(id, urls);\n }\n if (!urls.includes(url) && urls.length < SEEN_ON_PER_ID) {\n urls.push(url);\n }\n trimOldest(this.#seenOn, this.#maxSeenOnEntries);\n }\n\n #onInsert(event: Event): void {\n this.#version += 1;\n this.#touch(event.id);\n this.#invalidateByEvent(event);\n this.#events.emit(\"insert\", event);\n }\n\n #onRemove(event: Event): void {\n this.#version += 1;\n this.#recency.delete(event.id);\n this.#invalidateByEvent(event);\n this.#events.emit(\"remove\", event);\n }\n\n #invalidateByEvent(event: Event): void {\n const byId = this.#idWatches.get(event.id);\n if (byId) {\n for (const watch of byId) {\n this.#markDirty(watch);\n }\n }\n const address = eventAddress(event);\n if (address !== undefined) {\n const byAddress = this.#addressWatches.get(address);\n if (byAddress) {\n for (const watch of byAddress) {\n this.#markDirty(watch);\n }\n }\n }\n // A kind-5 deletion marks its targets tombstoned even when they are not in\n // the index, so nothing else invalidates a watch keyed on the target.\n if (event.kind === Kind.EventDeletion) {\n for (const tag of event.tags) {\n if (tag[0] === \"e\" && tag[1] !== undefined) {\n const watches = this.#idWatches.get(tag[1].toLowerCase());\n if (watches) {\n for (const watch of watches) {\n this.#markDirty(watch);\n }\n }\n } else if (tag[0] === \"a\" && tag[1] !== undefined) {\n const coord = parseEventAddress(tag[1]);\n if (coord === undefined) {\n continue;\n }\n const watches = this.#addressWatches.get(\n formatEventAddress(coord.kind, coord.pubkey, coord.identifier),\n );\n if (watches) {\n for (const watch of watches) {\n this.#markDirty(watch);\n }\n }\n }\n }\n }\n // Candidates are the event's kind bucket plus any-kind watches — a watch sits\n // in at most one bucket per kind, so it is checked at most once per event.\n const byKind = this.#queryByKind.get(event.kind);\n if (byKind) {\n for (const watch of byKind) {\n if (watch.filters !== undefined && matchFilters(watch.filters, event)) {\n this.#markDirty(watch);\n }\n }\n }\n for (const watch of this.#queryAnyKind) {\n if (watch.filters !== undefined && matchFilters(watch.filters, event)) {\n this.#markDirty(watch);\n }\n }\n }\n\n #invalidateAll(): void {\n for (const watches of this.#idWatches.values()) {\n for (const watch of watches) {\n this.#markDirty(watch);\n }\n }\n for (const watches of this.#addressWatches.values()) {\n for (const watch of watches) {\n this.#markDirty(watch);\n }\n }\n for (const watches of this.#queryByKind.values()) {\n for (const watch of watches) {\n this.#markDirty(watch);\n }\n }\n for (const watch of this.#queryAnyKind) {\n this.#markDirty(watch);\n }\n this.#scheduleFlush();\n }\n\n #markDirty(watch: WatchHandle): void {\n watch.markDirty();\n this.#dirty.add(watch);\n this.#scheduleFlush();\n }\n\n #scheduleFlush(): void {\n if (this.#flushScheduled) {\n return;\n }\n this.#flushScheduled = true;\n queueMicrotask(() => {\n // Keep the flag set while flushing: writes re-entered from a subscriber\n // land in #dirty and are notified in the same pass. Bound the passes so an\n // always-writing subscriber cannot starve the microtask queue forever;\n // overflow continues on the next turn after a loud report.\n let passes = 0;\n while (this.#dirty.size > 0) {\n const batch = [...this.#dirty];\n this.#dirty.clear();\n for (const watch of batch) {\n watch.notify();\n }\n passes += 1;\n if (passes >= MAX_FLUSH_PASSES && this.#dirty.size > 0) {\n this.#flushScheduled = false;\n reportError(new Error(\"ReactiveEventStore flush overflow: a subscriber keeps writing\"));\n this.#scheduleFlush();\n return;\n }\n }\n this.#flushScheduled = false;\n });\n }\n\n #pin(ids: ReadonlyArray<string>): void {\n for (const id of ids) {\n this.#pinned.set(id, (this.#pinned.get(id) ?? 0) + 1);\n }\n }\n\n #unpin(ids: ReadonlyArray<string>): void {\n for (const id of ids) {\n const count = (this.#pinned.get(id) ?? 0) - 1;\n if (count <= 0) {\n this.#pinned.delete(id);\n } else {\n this.#pinned.set(id, count);\n }\n }\n }\n\n #evictIfNeeded(protectedId?: string): void {\n if (this.#index.size <= this.#maxEvents) {\n return;\n }\n const evicting: string[] = [];\n for (const id of this.#recency) {\n if (this.#index.size - evicting.length <= this.#maxEvents) {\n break;\n }\n if (this.#pinned.has(id) || id === protectedId) {\n continue;\n }\n const event = this.#index.get(id);\n if (event === undefined) {\n continue;\n }\n // Replaceable/addressable winners evict like any other event; the index\n // records a watermark so stale versions stay rejected afterwards.\n evicting.push(id);\n }\n this.#index.evict(evicting);\n for (const id of evicting) {\n this.#recency.delete(id);\n }\n }\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"signer-Bha9gQy8.mjs","names":["hexToBytes","bytesToHex","nip44.getConversationKeyFromSharedSecret","nip44.encrypt","nip44.decrypt","nip04.encrypt","nip04.decrypt","nip04.encryptToPubkey","nip04.decryptFromPubkey","nip44.getConversationKey","nip44.encrypt","nip44.decrypt","bytesToHex"],"sources":["../src/signer/delegated.ts","../src/signer/keys.ts","../src/signer/nip07.ts","../src/signer/nip46.ts"],"sourcesContent":["import { schnorr } from \"@noble/curves/secp256k1.js\";\n\nimport { CryptoError, EventValidationError } from \"../core/error.ts\";\nimport type { Event, UnsignedEvent } from \"../core/event.ts\";\nimport { getEventHash, markVerified, validateEvent } from \"../core/event.ts\";\nimport { assertHex32, bytesToHex, hexToBytes, isHex32 } from \"../core/util.ts\";\nimport * as nip04 from \"../nips/nip04.ts\";\nimport * as nip44 from \"../nips/nip44.ts\";\nimport { SignerDisposedError } from \"./error.ts\";\nimport type { NostrSigner } from \"./types.ts\";\n\n/**\n * The three operations an external key holder (wallet keyring, NFC card, platform secure store)\n * provides. The secret itself never enters this process.\n */\nexport type NostrKeyOperations = {\n /** Lowercase 64-char hex x-only public key. */\n getPublicKey: () => Promise<string>;\n /** 64-byte BIP-340 signature over the 32-byte event id. */\n signEventId: (id: Uint8Array) => Promise<Uint8Array>;\n /**\n * Optional. 32-byte x-coordinate of `ECDH(secret, lift_x(peer))`, unhashed. Enables NIP-04 and\n * NIP-44.\n */\n sharedSecret?: (peer: string) => Promise<Uint8Array>;\n};\n\n/**\n * {@link NostrSigner} backed by a {@link NostrKeyOperations} key holder. The signer verifies\n * everything the holder returns: the public key shape, the signature length, and the BIP-340\n * signature itself — a misbehaving holder never produces an event relays would reject.\n */\nexport class DelegatedSigner implements NostrSigner {\n readonly #operations: NostrKeyOperations;\n readonly #convKeys = new Map<string, Uint8Array>();\n #pubkey: string | undefined;\n #disposed = false;\n\n constructor(operations: NostrKeyOperations) {\n this.#operations = operations;\n }\n\n #assertUsable(): void {\n if (this.#disposed) {\n throw new SignerDisposedError(\"signer has been disposed\");\n }\n }\n\n async #publicKey(): Promise<string> {\n let pubkey = this.#pubkey;\n if (pubkey === undefined) {\n pubkey = await this.#operations.getPublicKey();\n this.#assertUsable();\n if (!isHex32(pubkey)) {\n throw new CryptoError(\"key holder returned an invalid public key\");\n }\n this.#pubkey = pubkey;\n }\n return pubkey;\n }\n\n async getPublicKey(): Promise<string> {\n this.#assertUsable();\n return this.#publicKey();\n }\n\n async signEvent(unsigned: UnsignedEvent): Promise<Event> {\n this.#assertUsable();\n if (!validateEvent(unsigned)) {\n throw new EventValidationError(\"cannot sign invalid unsigned event\");\n }\n const pubkey = await this.#publicKey();\n if (unsigned.pubkey !== pubkey) {\n throw new CryptoError(\"unsigned event pubkey does not match the key holder's public key\");\n }\n const id = getEventHash(unsigned);\n const sig = await this.#operations.signEventId(hexToBytes(id));\n this.#assertUsable();\n if (sig.length !== 64) {\n throw new CryptoError(\"key holder returned an invalid signature length\");\n }\n if (!schnorr.verify(sig, hexToBytes(id), hexToBytes(pubkey))) {\n throw new CryptoError(\"key holder returned a signature that fails verification\");\n }\n const event: Event = { ...unsigned, id, sig: bytesToHex(sig) };\n markVerified(event);\n return event;\n }\n\n /**\n * The normalized lowercase peer pubkey is handed to the holder; the returned 32-byte shared\n * secret is the caller's to wipe.\n */\n async #sharedSecret(peer: string): Promise<Uint8Array> {\n const { sharedSecret } = this.#operations;\n if (!sharedSecret) {\n throw new CryptoError(\"key holder does not support ECDH shared secrets\");\n }\n const shared = await sharedSecret(assertHex32(peer, \"peer public key\"));\n if (this.#disposed) {\n shared.fill(0);\n throw new SignerDisposedError(\"signer has been disposed\");\n }\n if (shared.length !== 32) {\n shared.fill(0);\n throw new CryptoError(\"key holder returned an invalid shared secret\");\n }\n return shared;\n }\n\n async #conversationKey(peer: string): Promise<Uint8Array> {\n const pk = assertHex32(peer, \"peer public key\");\n let key = this.#convKeys.get(pk);\n if (!key) {\n const shared = await this.#sharedSecret(pk);\n try {\n key = nip44.getConversationKeyFromSharedSecret(shared);\n } finally {\n shared.fill(0);\n }\n this.#convKeys.set(pk, key);\n }\n return key;\n }\n\n async nip44Encrypt(peer: string, plaintext: string): Promise<string> {\n this.#assertUsable();\n return nip44.encrypt(plaintext, await this.#conversationKey(peer));\n }\n\n async nip44Decrypt(peer: string, payload: string): Promise<string> {\n this.#assertUsable();\n return nip44.decrypt(payload, await this.#conversationKey(peer));\n }\n\n async nip04Encrypt(peer: string, plaintext: string): Promise<string> {\n this.#assertUsable();\n const shared = await this.#sharedSecret(peer);\n try {\n return nip04.encrypt(plaintext, shared);\n } finally {\n shared.fill(0);\n }\n }\n\n async nip04Decrypt(peer: string, ciphertext: string): Promise<string> {\n this.#assertUsable();\n const shared = await this.#sharedSecret(peer);\n try {\n return nip04.decrypt(ciphertext, shared);\n } finally {\n shared.fill(0);\n }\n }\n\n /**\n * Zero-fill and clear every cached NIP-44 conversation key. All later calls reject with\n * {@link SignerDisposedError} and never reach the key holder. Idempotent.\n */\n dispose(): void {\n for (const key of this.#convKeys.values()) {\n key.fill(0);\n }\n this.#convKeys.clear();\n this.#disposed = true;\n }\n}\n","import type { Event, UnsignedEvent } from \"../core/event.ts\";\nimport type { SecretKeyInput } from \"../core/key.ts\";\nimport { Keys, signEvent } from \"../core/key.ts\";\nimport * as nip04 from \"../nips/nip04.ts\";\nimport * as nip44 from \"../nips/nip44.ts\";\nimport { SignerDisposedError } from \"./error.ts\";\nimport type { NostrSigner } from \"./types.ts\";\n\n/** Local secret-key signer with NIP-04 (legacy) and NIP-44 support. */\nexport class KeysSigner implements NostrSigner {\n readonly #keys: Keys;\n readonly #convKeys = new Map<string, Uint8Array>();\n #disposed = false;\n\n constructor(secretKey: SecretKeyInput | Keys) {\n this.#keys = secretKey instanceof Keys ? secretKey : Keys.fromSecretKey(secretKey);\n }\n\n /**\n * The live {@link Keys} instance holding the secret material. {@link KeysSigner.dispose} zeroizes\n * it, so a reference obtained earlier reports a zeroized secret afterwards.\n */\n get keys(): Keys {\n return this.#keys;\n }\n\n #assertUsable(): void {\n if (this.#disposed) {\n throw new SignerDisposedError(\"signer has been disposed\");\n }\n }\n\n async getPublicKey(): Promise<string> {\n this.#assertUsable();\n return this.#keys.publicKey;\n }\n\n async signEvent(unsigned: UnsignedEvent): Promise<Event> {\n this.#assertUsable();\n return signEvent(unsigned, this.#keys);\n }\n\n async nip04Encrypt(peer: string, plaintext: string): Promise<string> {\n this.#assertUsable();\n const secret = this.#keys.secretKey.bytes;\n try {\n return nip04.encryptToPubkey(plaintext, secret, peer);\n } finally {\n secret.fill(0);\n }\n }\n\n async nip04Decrypt(peer: string, ciphertext: string): Promise<string> {\n this.#assertUsable();\n const secret = this.#keys.secretKey.bytes;\n try {\n return nip04.decryptFromPubkey(ciphertext, secret, peer);\n } finally {\n secret.fill(0);\n }\n }\n\n #conversationKey(peer: string): Uint8Array {\n const pk = peer.toLowerCase();\n let key = this.#convKeys.get(pk);\n if (!key) {\n const secret = this.#keys.secretKey.bytes;\n try {\n key = nip44.getConversationKey(secret, pk);\n } finally {\n secret.fill(0);\n }\n this.#convKeys.set(pk, key);\n }\n return key;\n }\n\n async nip44Encrypt(peer: string, plaintext: string): Promise<string> {\n this.#assertUsable();\n return nip44.encrypt(plaintext, this.#conversationKey(peer));\n }\n\n async nip44Decrypt(peer: string, payload: string): Promise<string> {\n this.#assertUsable();\n return nip44.decrypt(payload, this.#conversationKey(peer));\n }\n\n /**\n * Zeroize the wrapped secret key and every cached NIP-44 conversation key. All later calls reject\n * with {@link SignerDisposedError}. Idempotent.\n */\n dispose(): void {\n this.#keys.secretKey.zeroize();\n for (const key of this.#convKeys.values()) {\n key.fill(0);\n }\n this.#convKeys.clear();\n this.#disposed = true;\n }\n}\n","import { CryptoError } from \"../core/error.ts\";\nimport type { Event, EventTemplate, UnsignedEvent } from \"../core/event.ts\";\nimport { signedMatchesUnsigned, validateSignedEvent } from \"../core/event.ts\";\nimport { assertHex32 } from \"../core/util.ts\";\nimport { verifyEvent } from \"../core/verifier.ts\";\nimport type { NostrSigner } from \"./types.ts\";\n\n/**\n * Minimal NIP-07 provider surface (browser extension `window.nostr`).\n *\n * @see https://github.com/nostr-protocol/nips/blob/master/07.md\n */\nexport type WindowNostr = {\n getPublicKey: () => Promise<string>;\n signEvent: (event: EventTemplate) => Promise<Event>;\n nip04?: {\n encrypt: (pubkey: string, plaintext: string) => Promise<string>;\n decrypt: (pubkey: string, ciphertext: string) => Promise<string>;\n };\n nip44?: {\n encrypt: (pubkey: string, plaintext: string) => Promise<string>;\n decrypt: (pubkey: string, ciphertext: string) => Promise<string>;\n };\n};\n\ntype GlobalWithNostr = typeof globalThis & {\n nostr?: WindowNostr;\n window?: { nostr?: WindowNostr };\n};\n\n/** Resolve `window.nostr` / `globalThis.nostr` when present. */\nexport function getWindowNostr(): WindowNostr | undefined {\n const g = globalThis as GlobalWithNostr;\n return g.nostr ?? g.window?.nostr;\n}\n\n/** True when a NIP-07 provider is injected in the current environment. */\nexport function isNip07Available(): boolean {\n const n = getWindowNostr();\n return Boolean(n && typeof n.getPublicKey === \"function\" && typeof n.signEvent === \"function\");\n}\n\n/**\n * Signer backed by a NIP-07 browser extension (or any injected provider). Never holds secret keys\n * in-process.\n */\nexport class Nip07Signer implements NostrSigner {\n readonly #provider: WindowNostr | undefined;\n\n constructor(provider?: WindowNostr) {\n this.#provider = provider;\n }\n\n #resolve(): WindowNostr {\n const provider = this.#provider ?? getWindowNostr();\n if (!provider) {\n throw new CryptoError(\"NIP-07 provider not available (window.nostr missing)\");\n }\n return provider;\n }\n\n async getPublicKey(): Promise<string> {\n const pk = await this.#resolve().getPublicKey();\n try {\n return assertHex32(pk, \"pubkey\");\n } catch (error) {\n throw new CryptoError(\"NIP-07 getPublicKey returned an invalid pubkey\", {\n cause: error,\n });\n }\n }\n\n async signEvent(unsigned: UnsignedEvent): Promise<Event> {\n const provider = this.#resolve();\n const template: EventTemplate = {\n kind: unsigned.kind,\n tags: unsigned.tags.map((t) => {\n const [name, ...rest] = t;\n if (name === undefined) {\n throw new CryptoError(\"unsigned event has an empty tag\");\n }\n return [name, ...rest];\n }),\n content: unsigned.content,\n created_at: unsigned.created_at,\n };\n\n const signed = await provider.signEvent(template);\n if (!validateSignedEvent(signed)) {\n throw new CryptoError(\"NIP-07 signEvent returned an invalid event\");\n }\n if (!verifyEvent(signed)) {\n throw new CryptoError(\"NIP-07 signed event failed signature verification\");\n }\n if (!signedMatchesUnsigned(signed, unsigned)) {\n throw new CryptoError(\"NIP-07 signed event does not match unsigned template\");\n }\n return signed;\n }\n\n async nip04Encrypt(peer: string, plaintext: string): Promise<string> {\n const { nip04 } = this.#resolve();\n if (!nip04?.encrypt) {\n throw new CryptoError(\"NIP-07 provider does not support nip04.encrypt\");\n }\n return nip04.encrypt(peer, plaintext);\n }\n\n async nip04Decrypt(peer: string, ciphertext: string): Promise<string> {\n const { nip04 } = this.#resolve();\n if (!nip04?.decrypt) {\n throw new CryptoError(\"NIP-07 provider does not support nip04.decrypt\");\n }\n return nip04.decrypt(peer, ciphertext);\n }\n\n async nip44Encrypt(peer: string, plaintext: string): Promise<string> {\n const { nip44 } = this.#resolve();\n if (!nip44?.encrypt) {\n throw new CryptoError(\"NIP-07 provider does not support nip44.encrypt\");\n }\n return nip44.encrypt(peer, plaintext);\n }\n\n async nip44Decrypt(peer: string, payload: string): Promise<string> {\n const { nip44 } = this.#resolve();\n if (!nip44?.decrypt) {\n throw new CryptoError(\"NIP-07 provider does not support nip44.decrypt\");\n }\n return nip44.decrypt(peer, payload);\n }\n}\n","import { randomBytes } from \"@noble/hashes/utils.js\";\n\nimport { throwIfAborted } from \"../core/abort.ts\";\nimport { errorMessage } from \"../core/error.ts\";\nimport type { Event, EventTemplate, UnsignedEvent } from \"../core/event.ts\";\nimport { signedMatchesUnsigned, validateSignedEvent } from \"../core/event.ts\";\nimport type { Filter } from \"../core/filter.ts\";\nimport { Kind } from \"../core/kind.ts\";\nimport { invokeSafely } from \"../core/report.ts\";\nimport { Tag } from \"../core/tag.ts\";\nimport { bytesToHex, isHex32, nowSeconds } from \"../core/util.ts\";\nimport { verifyEvent } from \"../core/verifier.ts\";\nimport {\n Nip46Error,\n decodeNip46Response,\n encodeNip46Request,\n parseBunkerURL,\n parseNostrConnectURI,\n} from \"../nips/nip46.ts\";\nimport type { BunkerPointer, ClientMetadata, Nip46Request } from \"../nips/nip46.ts\";\nimport type { NostrSigner } from \"./types.ts\";\n\n/** Subscribe options used by NIP-46 transport (structural subset of Pool). */\nexport type Nip46SubscribeOptions = {\n signal?: AbortSignal | undefined;\n onEvent?: ((event: Event) => void) | undefined;\n onClose?: ((reason: string) => void) | undefined;\n};\n\n/**\n * Structural relay transport for NIP-46 RPC. Satisfied by {@link import(\"../relay/pool.ts\").Pool};\n * constructed above the signer layer so `signer` never imports `relay` (see AGENTS.md).\n */\nexport type Nip46Transport = {\n subscribe: (\n relays: ReadonlyArray<string>,\n filters: ReadonlyArray<Filter>,\n opts?: Nip46SubscribeOptions,\n ) => { close: (reason?: string) => void };\n publish: (\n relays: ReadonlyArray<string>,\n event: Event,\n ) => Promise<\n ReadonlyArray<{\n readonly status: \"ok\" | \"rejected\" | \"failed\";\n readonly message?: string;\n readonly error?: Error;\n }>\n >;\n close: () => void;\n};\n\n/** Options for {@link Nip46Signer}: transport, timeouts, and relay hints. */\nexport type Nip46SignerOptions = {\n /** Shared transport. When set, the signer does not close it on {@link Nip46Signer.close}. */\n pool?: Nip46Transport | undefined;\n /**\n * Factory for a private transport when `pool` is omitted. Typical: `() => new Pool({\n * websocketImplementation, enableReconnect: true })`.\n */\n createPool?: (() => Nip46Transport) | undefined;\n /**\n * Relays used when the bunker pointer has none, and merged uniquely onto a nonempty pointer\n * (pointer first).\n */\n relays?: ReadonlyArray<string> | undefined;\n /** Bunker connection secret when the pointer or bunker:// URL has none. */\n secret?: string | undefined;\n /**\n * Local client identity used to encrypt and sign RPC (not the remote user key). Must implement\n * `nip44Encrypt`/`nip44Decrypt` — the NIP-46 encryption layer is NIP-44. Construction rejects a\n * signer without them.\n */\n clientSigner: NostrSigner;\n /** Called when bunker returns `auth_url` for a pending request. */\n onAuthUrl?: ((url: string) => void) | undefined;\n /** Per-request timeout in ms. Default 30s. */\n timeoutMs?: number | undefined;\n /**\n * Timeout in ms applied after the bunker replies `auth_url`: the request keeps waiting for the\n * real response until this elapses. Default 300s.\n */\n authTimeoutMs?: number | undefined;\n /** Requested permissions sent with `connect` (`method[:kind]` list). */\n perms?: ReadonlyArray<string> | undefined;\n /** Client metadata sent with bunker-initiated `connect`. */\n metadata?: ClientMetadata | undefined;\n};\n\n/** The client-signer surface NIP-46 needs: signing plus the NIP-44 encryption layer. */\ntype ClientSigner = Pick<NostrSigner, \"getPublicKey\" | \"signEvent\"> &\n Required<Pick<NostrSigner, \"nip44Encrypt\" | \"nip44Decrypt\">>;\n\n/**\n * Validate `opts.clientSigner` up front — a missing signer or one without NIP-44 cannot run the RPC\n * encryption layer, so construction fails closed with {@link Nip46Error} rather than timing out\n * mid-handshake.\n */\nfunction requireClientSigner(opts: Nip46SignerOptions): ClientSigner {\n const signer = opts.clientSigner;\n if (signer === undefined) {\n throw new Nip46Error(\"Nip46Signer requires clientSigner\");\n }\n const { nip44Encrypt, nip44Decrypt } = signer;\n if (typeof nip44Encrypt !== \"function\" || typeof nip44Decrypt !== \"function\") {\n throw new Nip46Error(\"clientSigner must implement nip44Encrypt and nip44Decrypt\");\n }\n // Member calls keep the signer's `this` (e.g. KeysSigner private state).\n return {\n getPublicKey: async () => signer.getPublicKey(),\n signEvent: async (unsigned) => signer.signEvent(unsigned),\n nip44Encrypt: async (peer, plaintext) => nip44Encrypt.call(signer, peer, plaintext),\n nip44Decrypt: async (peer, payload) => nip44Decrypt.call(signer, peer, payload),\n };\n}\n\nasync function clientPublicKeyOf(signer: ClientSigner): Promise<string> {\n const reported = await signer.getPublicKey();\n const pubkey = reported.toLowerCase();\n if (!isHex32(pubkey)) {\n throw new Nip46Error(\"clientSigner returned an invalid public key\");\n }\n return pubkey;\n}\n\nfunction resolveTransport(opts: Nip46SignerOptions): { pool: Nip46Transport; ownsPool: boolean } {\n if (opts.pool) {\n return { pool: opts.pool, ownsPool: false };\n }\n if (opts.createPool) {\n return { pool: opts.createPool(), ownsPool: true };\n }\n throw new Nip46Error(\n \"Nip46Signer requires pool or createPool (inject Pool from @qntx/nostr/relay)\",\n );\n}\n\nfunction applyPointerOpts(pointer: BunkerPointer, opts: Nip46SignerOptions): BunkerPointer {\n let next: BunkerPointer = {\n pubkey: pointer.pubkey.toLowerCase(),\n relays: [...pointer.relays],\n secret: pointer.secret,\n };\n if (opts.secret !== undefined && next.secret === undefined) {\n next = { ...next, secret: opts.secret };\n }\n\n const relays = next.relays.length > 0 ? next.relays : [...(opts.relays ?? [])];\n if (relays.length === 0) {\n throw new Nip46Error(\"no relays for bunker connection\");\n }\n if (next.relays.length > 0 && opts.relays !== undefined && opts.relays.length > 0) {\n const merged = [...next.relays];\n for (const r of opts.relays) {\n if (!merged.includes(r)) {\n merged.push(r);\n }\n }\n return { ...next, relays: merged };\n }\n return { ...next, relays };\n}\n\nfunction resolveBunkerPointer(input: string | BunkerPointer): BunkerPointer {\n if (typeof input !== \"string\") {\n return {\n pubkey: input.pubkey.toLowerCase(),\n relays: [...input.relays],\n secret: input.secret,\n };\n }\n\n const bunker = parseBunkerURL(input);\n if (bunker) {\n return bunker;\n }\n\n throw new Nip46Error(\"invalid bunker input (expected bunker:// URL or BunkerPointer)\");\n}\n\ntype PendingRequest = {\n resolve: (v: string) => void;\n reject: (e: Error) => void;\n method: string;\n timer?: ReturnType<typeof setTimeout>;\n};\n\n/**\n * NIP-46 remote signer (bunker / nostrconnect). Implements {@link NostrSigner}; never holds the\n * remote user's secret key. Network I/O is injected via {@link Nip46Transport} (no relay import).\n */\nexport class Nip46Signer implements NostrSigner {\n readonly #pool: Nip46Transport;\n readonly #ownsPool: boolean;\n readonly #clientSigner: ClientSigner;\n readonly #clientPubkey: string;\n readonly #pointer: BunkerPointer;\n readonly #onAuthUrl: ((url: string) => void) | undefined;\n readonly #timeoutMs: number;\n readonly #authTimeoutMs: number;\n readonly #perms: ReadonlyArray<string> | undefined;\n readonly #metadata: ClientMetadata | undefined;\n #relays: string[];\n readonly #listeners = new Map<string, PendingRequest>();\n #sub: { close: (reason?: string) => void } | undefined;\n #open = false;\n #cachedRemotePubkey: string | undefined;\n\n private constructor(\n clientSigner: ClientSigner,\n clientPubkey: string,\n pointer: BunkerPointer,\n pool: Nip46Transport,\n ownsPool: boolean,\n opts: Nip46SignerOptions,\n ) {\n if (pointer.relays.length === 0) {\n throw new Nip46Error(\"bunker pointer has no relays\");\n }\n this.#clientSigner = clientSigner;\n this.#clientPubkey = clientPubkey;\n this.#pointer = {\n pubkey: pointer.pubkey.toLowerCase(),\n relays: [...pointer.relays],\n secret: pointer.secret,\n };\n this.#relays = [...pointer.relays];\n this.#pool = pool;\n this.#ownsPool = ownsPool;\n this.#onAuthUrl = opts.onAuthUrl;\n this.#timeoutMs = opts.timeoutMs ?? 30_000;\n this.#authTimeoutMs = opts.authTimeoutMs ?? 300_000;\n this.#perms = opts.perms;\n this.#metadata = opts.metadata;\n }\n\n get bunker(): Readonly<BunkerPointer> {\n return {\n pubkey: this.#pointer.pubkey,\n relays: [...this.#relays],\n secret: this.#pointer.secret,\n };\n }\n\n get clientPublicKey(): string {\n return this.#clientPubkey;\n }\n\n /**\n * Subscribe to an already-known bunker pointer. Does not send the `connect` RPC (reconnect\n * without a new handshake). Requires `clientSigner` — reconnect must reuse the original client\n * identity. Async because the client public key comes from the signer.\n */\n static async fromBunker(pointer: BunkerPointer, opts: Nip46SignerOptions): Promise<Nip46Signer> {\n const clientSigner = requireClientSigner(opts);\n const clientPubkey = await clientPublicKeyOf(clientSigner);\n const resolved = applyPointerOpts(pointer, opts);\n const { pool, ownsPool } = resolveTransport(opts);\n const signer = new Nip46Signer(clientSigner, clientPubkey, resolved, pool, ownsPool, opts);\n signer.#startSubscription();\n return signer;\n }\n\n /**\n * Connect using a `bunker://` URL or pre-parsed pointer. Invalid strings throw. A NIP-05\n * identifier is not a bunker pointer. When both pointer and `opts.relays` are nonempty, unique\n * `opts.relays` are appended.\n */\n static async connect(\n input: string | BunkerPointer,\n opts: Nip46SignerOptions,\n ): Promise<Nip46Signer> {\n const pointer = resolveBunkerPointer(input);\n const signer = await Nip46Signer.fromBunker(pointer, opts);\n try {\n await signer.connectRemote();\n try {\n await signer.switchRelays();\n } catch {\n // Bunker may not implement switch_relays.\n }\n await signer.getPublicKey();\n return signer;\n } catch (error) {\n await signer.close();\n throw error;\n }\n }\n\n /**\n * Wait for a bunker to complete a client-initiated `nostrconnect://` handshake. Requires\n * `clientSigner` whose pubkey matches the URI client pubkey (build the URI with\n * {@link createNostrConnectURI} and the signer's public key).\n */\n static async fromNostrConnectURI(\n connectionURI: string,\n opts: Nip46SignerOptions & {\n signal?: AbortSignal;\n handshakeTimeoutMs?: number;\n },\n ): Promise<Nip46Signer> {\n throwIfAborted(opts.signal);\n const clientSigner = requireClientSigner(opts);\n const params = parseNostrConnectURI(connectionURI);\n\n const { pool, ownsPool } = resolveTransport(opts);\n const handshakeTimeoutMs = opts.handshakeTimeoutMs ?? 300_000;\n\n // The signer's pubkey resolves async while the handshake subscription is already live —\n // subscribing must stay synchronous so early handshake events are not missed.\n const clientPubkeyPromise = clientPublicKeyOf(clientSigner);\n\n return new Promise((resolve, reject) => {\n let settled = false;\n let handshaking = false;\n // `let` + later assignment: pool.subscribe may fire onClose synchronously (pre-aborted\n // signal via fanIn) before a const initializer would complete.\n // oxlint-disable-next-line eslint/prefer-const\n let sub: { close: (reason?: string) => void } | undefined;\n const finish = (err?: Error, signer?: Nip46Signer) => {\n if (settled) {\n return;\n }\n settled = true;\n clearTimeout(timer);\n sub?.close(err ? \"failed\" : \"connected\");\n if (err) {\n if (ownsPool) {\n pool.close();\n }\n reject(err);\n } else if (signer) {\n resolve(signer);\n }\n };\n\n const timer = setTimeout(() => {\n finish(new Nip46Error(\"nostrconnect handshake timed out\"));\n }, handshakeTimeoutMs);\n\n // The URI's client pubkey gates the handshake: the signer must own that key.\n void (async () => {\n try {\n const pubkey = await clientPubkeyPromise;\n if (pubkey !== params.clientPubkey) {\n finish(\n new Nip46Error(\"clientSigner public key does not match nostrconnect client pubkey\"),\n );\n }\n } catch (error) {\n finish(error instanceof Error ? error : new Nip46Error(String(error)));\n }\n })();\n\n sub = pool.subscribe(\n params.relays,\n [{ kinds: [Kind.NostrConnect], \"#p\": [params.clientPubkey], limit: 0 }],\n {\n signal: opts.signal,\n onEvent: (event) => {\n void (async () => {\n try {\n const clientPubkey = await clientPubkeyPromise;\n if (settled || clientPubkey !== params.clientPubkey) {\n return;\n }\n const payload = await clientSigner.nip44Decrypt(event.pubkey, event.content);\n const response = decodeNip46Response(payload);\n if (response.result !== params.secret) {\n return;\n }\n // A duplicate connect response must not spin up a second signer.\n if (handshaking) {\n return;\n }\n handshaking = true;\n\n const pointer: BunkerPointer = {\n pubkey: event.pubkey.toLowerCase(),\n relays: params.relays,\n secret: params.secret,\n };\n const signer = new Nip46Signer(\n clientSigner,\n clientPubkey,\n pointer,\n pool,\n ownsPool,\n opts,\n );\n signer.#startSubscription();\n try {\n await signer.switchRelays();\n } catch {\n // Relay switch at connect time is best-effort.\n }\n try {\n await signer.getPublicKey();\n finish(undefined, signer);\n } catch (error: unknown) {\n // Do not leak the signer's subscription on a shared pool.\n await signer.close();\n finish(error instanceof Error ? error : new Nip46Error(String(error)));\n }\n } catch {\n // ignore non-matching events\n }\n })();\n },\n onClose: (reason) => {\n if (reason !== \"connected\" && reason !== \"failed\") {\n finish(new Nip46Error(`nostrconnect subscription closed: ${reason}`));\n }\n },\n },\n );\n });\n }\n\n #startSubscription(): void {\n this.#sub?.close();\n this.#sub = this.#pool.subscribe(\n this.#relays,\n [\n {\n kinds: [Kind.NostrConnect],\n authors: [this.#pointer.pubkey],\n \"#p\": [this.#clientPubkey],\n limit: 0,\n },\n ],\n {\n onEvent: (event) => {\n void this.#handleResponseEvent(event);\n },\n onClose: () => {\n this.#sub = undefined;\n },\n },\n );\n this.#open = true;\n }\n\n async #handleResponseEvent(event: Event): Promise<void> {\n try {\n const payload = await this.#clientSigner.nip44Decrypt(event.pubkey, event.content);\n const { id, result, error } = decodeNip46Response(payload);\n\n if (result === \"auth_url\" && this.#listeners.has(id)) {\n const listener = this.#listeners.get(id);\n if (!listener) {\n return;\n }\n if (error !== undefined && error !== \"\") {\n invokeSafely(() => this.#onAuthUrl?.(error));\n }\n clearTimeout(listener.timer);\n listener.timer = this.#requestTimer(id, listener, this.#authTimeoutMs);\n return;\n }\n\n const listener = this.#dropRequest(id);\n if (!listener) {\n return;\n }\n if (error !== undefined && error !== \"\") {\n listener.reject(new Nip46Error(error));\n } else if (result === undefined) {\n listener.reject(new Nip46Error(\"empty NIP-46 response\"));\n } else {\n listener.resolve(result);\n }\n } catch {\n // ignore decrypt failures from unrelated events\n }\n }\n\n async connectRemote(overrides?: {\n metadata?: ClientMetadata | undefined;\n perms?: ReadonlyArray<string> | undefined;\n }): Promise<void> {\n const metadata = overrides?.metadata ?? this.#metadata;\n const perms = overrides?.perms ?? this.#perms;\n const params = [this.#pointer.pubkey, this.#pointer.secret ?? \"\"];\n const hasPerms = perms !== undefined && perms.length > 0;\n if (hasPerms || metadata !== undefined) {\n params.push(hasPerms ? perms.join(\",\") : \"\");\n }\n if (metadata !== undefined) {\n params.push(JSON.stringify(metadata));\n }\n const result = await this.#sendRequest(\"connect\", params);\n if (result === \"ack\") {\n return;\n }\n const { secret } = this.#pointer;\n if (secret !== undefined && secret !== \"\" && result === secret) {\n return;\n }\n throw new Nip46Error(`connect result is not ack or secret: ${result}`);\n }\n\n /**\n * Ask the bunker for its preferred relay list and resubscribe when it changes. Spec result is a\n * JSON array of URLs, or `null` when nothing changes.\n */\n async switchRelays(): Promise<string[] | undefined> {\n const resp = await this.#sendRequest(\"switch_relays\", []);\n if (resp === \"null\") {\n return undefined;\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(resp);\n } catch (error) {\n throw new Nip46Error(\"invalid switch_relays JSON\", {\n cause: error,\n });\n }\n if (\n !Array.isArray(parsed) ||\n parsed.length === 0 ||\n !parsed.every((x) => typeof x === \"string\")\n ) {\n throw new Nip46Error(\"invalid switch_relays result\");\n }\n this.#relays = parsed;\n this.#startSubscription();\n return parsed;\n }\n\n /** Courtesy session teardown. Always closes the local signer afterwards. */\n async logout(): Promise<void> {\n try {\n const resp = await this.#sendRequest(\"logout\", []);\n if (resp !== \"ack\") {\n throw new Nip46Error(`logout result is not ack: ${resp}`);\n }\n } finally {\n await this.close();\n }\n }\n\n async ping(): Promise<void> {\n const resp = await this.#sendRequest(\"ping\", []);\n if (resp !== \"pong\") {\n throw new Nip46Error(`ping result is not pong: ${resp}`);\n }\n }\n\n async getPublicKey(): Promise<string> {\n if (this.#cachedRemotePubkey === undefined) {\n const pk = await this.#sendRequest(\"get_public_key\", []);\n if (!isHex32(pk.toLowerCase())) {\n throw new Nip46Error(\"bunker returned invalid pubkey\");\n }\n this.#cachedRemotePubkey = pk.toLowerCase();\n }\n return this.#cachedRemotePubkey;\n }\n\n async signEvent(unsigned: UnsignedEvent): Promise<Event> {\n const template: EventTemplate = {\n kind: unsigned.kind,\n tags: unsigned.tags,\n content: unsigned.content,\n created_at: unsigned.created_at,\n };\n const resp = await this.#sendRequest(\"sign_event\", [JSON.stringify(template)]);\n let signed: unknown;\n try {\n signed = JSON.parse(resp);\n } catch (error) {\n throw new Nip46Error(\"bunker returned invalid sign_event JSON\", {\n cause: error,\n });\n }\n if (!validateSignedEvent(signed) || !verifyEvent(signed)) {\n throw new Nip46Error(\"bunker returned improperly signed event\");\n }\n if (!signedMatchesUnsigned(signed, unsigned)) {\n throw new Nip46Error(\"signed event does not match unsigned template\");\n }\n return signed;\n }\n\n async nip04Encrypt(peer: string, plaintext: string): Promise<string> {\n return this.#sendRequest(\"nip04_encrypt\", [peer, plaintext]);\n }\n\n async nip04Decrypt(peer: string, ciphertext: string): Promise<string> {\n return this.#sendRequest(\"nip04_decrypt\", [peer, ciphertext]);\n }\n\n async nip44Encrypt(peer: string, plaintext: string): Promise<string> {\n return this.#sendRequest(\"nip44_encrypt\", [peer, plaintext]);\n }\n\n async nip44Decrypt(peer: string, payload: string): Promise<string> {\n return this.#sendRequest(\"nip44_decrypt\", [peer, payload]);\n }\n\n async close(): Promise<void> {\n this.#open = false;\n for (const [, listener] of this.#listeners) {\n clearTimeout(listener.timer);\n listener.reject(new Nip46Error(\"signer closed\"));\n }\n this.#listeners.clear();\n this.#sub?.close(\"signer closed\");\n this.#sub = undefined;\n if (this.#ownsPool) {\n this.#pool.close();\n }\n }\n\n async #sendRequest(method: string, params: string[]): Promise<string> {\n if (!this.#open) {\n throw new Nip46Error(\"signer is closed\");\n }\n if (!this.#sub) {\n this.#startSubscription();\n }\n\n const id = bytesToHex(randomBytes(16));\n const req: Nip46Request = { id, method, params };\n const encrypted = await this.#clientSigner.nip44Encrypt(\n this.#pointer.pubkey,\n encodeNip46Request(req),\n );\n\n const unsigned: UnsignedEvent = {\n kind: Kind.NostrConnect,\n tags: [Tag.p(this.#pointer.pubkey)],\n content: encrypted,\n created_at: nowSeconds(),\n pubkey: this.#clientPubkey,\n };\n const event = await this.#clientSigner.signEvent(unsigned);\n // The client signer is untrusted code: check it actually signed the request we sent.\n if (!verifyEvent(event)) {\n throw new Nip46Error(\"clientSigner returned an improperly signed request event\");\n }\n if (!signedMatchesUnsigned(event, unsigned)) {\n throw new Nip46Error(\"clientSigner signed an event that does not match the request\");\n }\n\n const resultPromise = new Promise<string>((resolve, reject) => {\n const listener: PendingRequest = { resolve, reject, method };\n listener.timer = this.#requestTimer(id, listener, this.#timeoutMs);\n this.#listeners.set(id, listener);\n });\n\n let replies;\n try {\n replies = await this.#pool.publish(this.#relays, event);\n } catch (error) {\n this.#dropRequest(id);\n throw error;\n }\n if (!replies.some((reply) => reply.status === \"ok\")) {\n const listener = this.#dropRequest(id);\n const detail = replies\n .map((reply) => (reply.status === \"failed\" ? errorMessage(reply.error) : reply.message))\n .filter((message): message is string => Boolean(message))\n .join(\"; \");\n listener?.reject(new Nip46Error(`request not accepted by any relay: ${detail}`));\n }\n return resultPromise;\n }\n\n #dropRequest(id: string): PendingRequest | undefined {\n const listener = this.#listeners.get(id);\n if (!listener) {\n return undefined;\n }\n clearTimeout(listener.timer);\n this.#listeners.delete(id);\n return listener;\n }\n\n #requestTimer(id: string, listener: PendingRequest, ms: number): ReturnType<typeof setTimeout> {\n return setTimeout(() => {\n this.#dropRequest(id);\n listener.reject(new Nip46Error(`NIP-46 request timed out: ${listener.method}`));\n }, ms);\n }\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"storage-n2BE1bdY.mjs","names":[],"sources":["../src/storage/idb-types.ts","../src/storage/idb-helpers.ts","../src/storage/idb-query.ts","../src/storage/idb-schema.ts","../src/storage/indexeddb.ts","../src/storage/sqlite.ts"],"sourcesContent":["// oxlint-disable typescript/no-restricted-types -- the *Like types mirror IndexedDB, whose request/transaction fields and event handlers are null-valued\nexport const EVENTS = \"events\";\nexport const TAG_REFS = \"tag_refs\";\nexport const ADDRESSES = \"addresses\";\nexport const TOMBSTONES = \"tombstones\";\nexport const OUTBOX_BOUNDS = \"outbox_bounds\";\nexport const WRITE_STORES: string[] = [EVENTS, TAG_REFS, ADDRESSES, TOMBSTONES];\n\nexport type IDBCursorDirectionLike = \"next\" | \"prev\";\n\nexport type IDBKeyRangeLike = {\n lower: unknown;\n upper: unknown;\n lowerOpen: boolean;\n upperOpen: boolean;\n};\n\nexport type IDBCursorLike = {\n value?: unknown;\n key: unknown;\n primaryKey: unknown;\n continue: () => void;\n};\n\nexport type IDBIndexLike = {\n openCursor: (range?: IDBKeyRangeLike, direction?: IDBCursorDirectionLike) => IDBRequestLike;\n openKeyCursor: (range?: IDBKeyRangeLike, direction?: IDBCursorDirectionLike) => IDBRequestLike;\n};\n\nexport type IDBRequestLike = {\n result: unknown;\n error: Error | null;\n onsuccess: ((ev: unknown) => void) | null;\n onerror: ((ev: unknown) => void) | null;\n};\n\nexport type IDBObjectStoreLike = {\n put: (value: unknown) => unknown;\n get: (key: string) => IDBRequestLike;\n delete: (key: string) => unknown;\n clear: () => unknown;\n getAll: () => IDBRequestLike;\n createIndex: (name: string, keyPath: string | string[]) => IDBIndexLike;\n index: (name: string) => IDBIndexLike;\n openCursor: (range?: IDBKeyRangeLike, direction?: IDBCursorDirectionLike) => IDBRequestLike;\n};\n\nexport type IDBTransactionLike = {\n objectStore: (name: string) => IDBObjectStoreLike;\n oncomplete: ((ev: unknown) => void) | null;\n onerror: ((ev: unknown) => void) | null;\n onabort: ((ev: unknown) => void) | null;\n error: Error | null;\n abort: () => void;\n};\n\nexport type IDBDatabaseLike = {\n objectStoreNames: { contains: (name: string) => boolean };\n createObjectStore: (name: string, options?: { keyPath?: string }) => IDBObjectStoreLike;\n deleteObjectStore: (name: string) => void;\n transaction: (\n storeNames: string | string[],\n mode?: \"readonly\" | \"readwrite\",\n ) => IDBTransactionLike;\n onversionchange: ((ev: unknown) => void) | null;\n close: () => void;\n};\n\nexport type IDBVersionChangeEventLike = {\n oldVersion: number;\n newVersion: number | null;\n target: { result: IDBDatabaseLike; transaction: IDBTransactionLike };\n};\n\nexport type IDBFactoryLike = {\n open: (name: string, version?: number) => IDBOpenRequestLike;\n};\n\nexport type IDBOpenRequestLike = IDBRequestLike & {\n onupgradeneeded: ((ev: IDBVersionChangeEventLike) => void) | null;\n onblocked: ((ev: unknown) => void) | null;\n result: IDBDatabaseLike;\n};\n\nexport type TagRef = {\n readonly key: string;\n readonly name: string;\n readonly value: string;\n readonly id: string;\n readonly created_at: number;\n};\n\nexport type AddressRow = {\n readonly address: string;\n readonly id: string;\n readonly created_at: number;\n};\n\nexport type Tombstone =\n | { readonly key: `id:${string}`; readonly type: \"id\" }\n | { readonly key: `pending:${string}`; readonly type: \"pending\"; readonly pubkey: string }\n | { readonly key: `coord:${string}`; readonly type: \"coord\"; readonly until: number };\n\nexport type OutboxBoundRow = {\n readonly key: string;\n readonly oldest: number;\n readonly newest: number;\n};\n","// oxlint-disable unicorn/prefer-add-event-listener -- the *Like driver interfaces model only the `on*` handler surface\nimport type { Event } from \"../core/event.ts\";\nimport { isRecord } from \"../core/util.ts\";\nimport type { DeletionPlan, DeletionState } from \"./deletion.ts\";\nimport { StorageError } from \"./error.ts\";\nimport { ADDRESSES, EVENTS, TAG_REFS, TOMBSTONES } from \"./idb-types.ts\";\nimport type {\n AddressRow,\n IDBCursorDirectionLike,\n IDBCursorLike,\n IDBKeyRangeLike,\n IDBObjectStoreLike,\n IDBRequestLike,\n IDBTransactionLike,\n TagRef,\n Tombstone,\n} from \"./idb-types.ts\";\nimport type { PutDecision } from \"./put.ts\";\nimport type { PutResult } from \"./types.ts\";\n\nexport async function reqOf<T>(req: IDBRequestLike): Promise<T> {\n return new Promise((resolve, reject) => {\n // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- request results are trusted as T at each call site\n req.onsuccess = () => resolve(req.result as T);\n req.onerror = () => reject(req.error ?? new StorageError(\"IndexedDB request failed\"));\n });\n}\n\nexport async function txDone(tx: IDBTransactionLike): Promise<void> {\n return new Promise((resolve, reject) => {\n tx.oncomplete = () => resolve();\n tx.onerror = () => reject(tx.error ?? new StorageError(\"IndexedDB transaction failed\"));\n });\n}\n\nexport async function walkCursor(\n source: {\n openCursor: (range?: IDBKeyRangeLike, direction?: IDBCursorDirectionLike) => IDBRequestLike;\n },\n range: IDBKeyRangeLike | undefined,\n direction: IDBCursorDirectionLike,\n visit: (cursor: IDBCursorLike) => boolean,\n): Promise<void> {\n return new Promise((resolve, reject) => {\n const req = source.openCursor(range, direction);\n req.onerror = () => reject(req.error ?? new StorageError(\"IndexedDB cursor failed\"));\n req.onsuccess = () => {\n // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- openCursor resolves with a cursor or null\n const cursor = (req.result ?? undefined) as IDBCursorLike | undefined;\n if (cursor === undefined) {\n resolve();\n return;\n }\n if (visit(cursor)) {\n resolve();\n return;\n }\n cursor.continue();\n };\n });\n}\n\nexport function tagRefKey(name: string, value: string, id: string): string {\n return `${name}:${value}:${id}`;\n}\n\nexport function writeTagRefs(store: IDBObjectStoreLike, event: Event): void {\n for (const tag of event.tags) {\n if ((tag[0] !== \"e\" && tag[0] !== \"p\") || tag[1] === undefined) {\n continue;\n }\n const { 1: value } = tag;\n const { id } = event;\n store.put({\n key: tagRefKey(tag[0], value, id),\n name: tag[0],\n value,\n id,\n created_at: event.created_at,\n } satisfies TagRef);\n }\n}\n\nexport function deleteStoredEvent(\n tx: IDBTransactionLike,\n event: Event,\n addressRow?: AddressRow,\n): void {\n const tagRefs = tx.objectStore(TAG_REFS);\n for (const tag of event.tags) {\n if ((tag[0] === \"e\" || tag[0] === \"p\") && tag[1] !== undefined) {\n tagRefs.delete(tagRefKey(tag[0], tag[1], event.id));\n }\n }\n if (addressRow?.id === event.id) {\n tx.objectStore(ADDRESSES).delete(addressRow.address);\n }\n tx.objectStore(EVENTS).delete(event.id);\n}\n\nexport function persistPlanTombstones(\n store: IDBObjectStoreLike,\n plan: DeletionPlan,\n coordIds: ReadonlyArray<string>,\n deletion: DeletionState,\n): void {\n for (const id of plan.removeIds) {\n store.put({ key: `id:${id}`, type: \"id\" } satisfies Tombstone);\n store.delete(`pending:${id}`);\n }\n for (const id of coordIds) {\n store.put({ key: `id:${id}`, type: \"id\" } satisfies Tombstone);\n store.delete(`pending:${id}`);\n }\n for (const p of plan.pendingIds) {\n if (deletion.ids.has(p.id)) {\n continue;\n }\n store.put({\n key: `pending:${p.id}`,\n type: \"pending\",\n pubkey: p.pubkey,\n } satisfies Tombstone);\n }\n for (const c of plan.coordinates) {\n const prev = deletion.coordinates.get(c.key) ?? Number.NEGATIVE_INFINITY;\n store.put({\n key: `coord:${c.key}`,\n type: \"coord\",\n until: Math.max(prev, c.until),\n } satisfies Tombstone);\n }\n}\n\nexport function tombstonesToPlan(rows: unknown[]): DeletionPlan {\n const removeIds: string[] = [];\n const pendingIds: Array<{ id: string; pubkey: string }> = [];\n const coordinates: Array<{ key: string; until: number }> = [];\n for (const row of rows) {\n if (!isRecord(row)) {\n continue;\n }\n const r = row;\n if (r[\"type\"] === \"id\" && typeof r[\"key\"] === \"string\" && r[\"key\"].startsWith(\"id:\")) {\n removeIds.push(r[\"key\"].slice(3));\n continue;\n }\n if (\n r[\"type\"] === \"pending\" &&\n typeof r[\"key\"] === \"string\" &&\n r[\"key\"].startsWith(\"pending:\") &&\n typeof r[\"pubkey\"] === \"string\"\n ) {\n pendingIds.push({ id: r[\"key\"].slice(8), pubkey: r[\"pubkey\"] });\n continue;\n }\n if (\n r[\"type\"] === \"coord\" &&\n typeof r[\"key\"] === \"string\" &&\n r[\"key\"].startsWith(\"coord:\") &&\n typeof r[\"until\"] === \"number\"\n ) {\n coordinates.push({ key: r[\"key\"].slice(6), until: r[\"until\"] });\n }\n }\n return { removeIds, pendingIds, coordinates };\n}\n\nexport function applyPutIndexedDb(\n tx: IDBTransactionLike,\n s: {\n deletion: DeletionState;\n replaceable: Map<string, string>;\n },\n d: PutDecision,\n): PutResult {\n const events = tx.objectStore(EVENTS);\n const tagRefs = tx.objectStore(TAG_REFS);\n const addresses = tx.objectStore(ADDRESSES);\n const tombstones = tx.objectStore(TOMBSTONES);\n if (d.action === \"skip\") {\n return d.result;\n }\n if (d.action === \"tombstone\") {\n tombstones.put({ key: `id:${d.event.id}`, type: \"id\" } satisfies Tombstone);\n tombstones.delete(`pending:${d.event.id}`);\n s.deletion.ids.add(d.event.id);\n s.deletion.pending.delete(d.event.id);\n return \"duplicate\";\n }\n if (d.action === \"delete\") {\n s.deletion.pending.delete(d.event.id);\n persistPlanTombstones(tombstones, d.plan, d.coordIds, s.deletion);\n s.deletion.absorb(d.plan);\n for (const id of d.coordIds) {\n s.deletion.ids.add(id);\n }\n events.put(d.event);\n writeTagRefs(tagRefs, d.event);\n return \"deleted\";\n }\n events.put(d.event);\n writeTagRefs(tagRefs, d.event);\n if (d.address !== undefined) {\n addresses.put({\n address: d.address,\n id: d.event.id,\n created_at: d.event.created_at,\n });\n s.replaceable.set(d.address, d.event.id);\n }\n return d.result;\n}\n","// oxlint-disable unicorn/prefer-add-event-listener -- the *Like driver interfaces model only the `on*` handler surface\nimport type { Event } from \"../core/event.ts\";\nimport { compareEventsDesc, sortEvents } from \"../core/event.ts\";\nimport type { Filter } from \"../core/filter.ts\";\nimport { StorageError } from \"./error.ts\";\nimport { reqOf } from \"./idb-helpers.ts\";\nimport { EVENTS, TAG_REFS } from \"./idb-types.ts\";\nimport type {\n IDBCursorDirectionLike,\n IDBCursorLike,\n IDBIndexLike,\n IDBKeyRangeLike,\n IDBObjectStoreLike,\n IDBRequestLike,\n IDBTransactionLike,\n TagRef,\n} from \"./idb-types.ts\";\n\nexport function prefixRange(\n prefix: ReadonlyArray<string | number>,\n since?: number,\n until?: number,\n): IDBKeyRangeLike {\n return idbKeyRange().bound(\n [...prefix, since ?? 0],\n [...prefix, until ?? Number.MAX_SAFE_INTEGER],\n );\n}\n\nfunction createdAtRange(since?: number, until?: number): IDBKeyRangeLike {\n return idbKeyRange().bound(since ?? 0, until ?? Number.MAX_SAFE_INTEGER);\n}\n\nfunction idbKeyRange(): {\n bound: (\n lower: unknown,\n upper: unknown,\n lowerOpen?: boolean,\n upperOpen?: boolean,\n ) => IDBKeyRangeLike;\n} {\n // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- globalThis lookup of the platform IDBKeyRange\n return (globalThis as unknown as { IDBKeyRange: ReturnType<typeof idbKeyRange> }).IDBKeyRange;\n}\n\nexport function epTagPrefixes(filter: Filter): Array<{ name: \"e\" | \"p\"; value: string }> {\n const out: Array<{ name: \"e\" | \"p\"; value: string }> = [];\n for (const name of [\"e\", \"p\"] as const) {\n const values = filter[`#${name}`];\n if (values === undefined) {\n continue;\n }\n for (const value of values) {\n out.push({ name, value: value.toLowerCase() });\n }\n }\n return out;\n}\n\ntype MergeOpener = {\n open: () => IDBRequestLike;\n read: (\n cursor: IDBCursorLike,\n ok: (event: Event | undefined) => void,\n err: (error: Error) => void,\n ) => void;\n};\n\nfunction eventCursor(\n source: {\n openCursor: (range?: IDBKeyRangeLike, direction?: IDBCursorDirectionLike) => IDBRequestLike;\n },\n range: IDBKeyRangeLike,\n): MergeOpener {\n return {\n open: () => source.openCursor(range, \"prev\"),\n read: (cursor, ok) => {\n // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- index cursor values are stored Event rows\n ok(cursor.value as Event);\n },\n };\n}\n\nfunction tagCursor(\n index: IDBIndexLike,\n events: IDBObjectStoreLike,\n range: IDBKeyRangeLike,\n): MergeOpener {\n return {\n open: () => index.openCursor(range, \"prev\"),\n read: (cursor, ok, err) => {\n // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- tag_refs cursor values are TagRef rows written by writeTagRefs\n const row = cursor.value as TagRef;\n const req = events.get(row.id);\n req.onerror = () => err(req.error ?? new StorageError(\"IndexedDB get failed\"));\n // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- get resolves with a stored Event row or undefined\n req.onsuccess = () => ok(req.result as Event | undefined);\n },\n };\n}\n\nexport async function scanIds(\n tx: IDBTransactionLike,\n filter: Filter,\n accept: (event: Event) => boolean,\n take: (event: Event) => boolean,\n): Promise<void> {\n const ids = filter.ids ?? [];\n const events = tx.objectStore(EVENTS);\n const reqs = ids.map((id) => events.get(id.toLowerCase()));\n const rows = await Promise.all(reqs.map(async (req) => reqOf<Event | undefined>(req)));\n const matched: Event[] = [];\n const seen = new Set<string>();\n for (const event of rows) {\n if (event === undefined || seen.has(event.id) || !accept(event)) {\n continue;\n }\n seen.add(event.id);\n matched.push(event);\n }\n sortEvents(matched);\n const out = filter.limit === undefined ? matched : matched.slice(0, filter.limit);\n for (const event of out) {\n if (take(event)) {\n break;\n }\n }\n}\n\n/** IDB auto-commits when onsuccess returns with no outstanding requests. */\nexport async function kWayMerge(\n openers: ReadonlyArray<MergeOpener>,\n accept: (event: Event) => boolean,\n take: (event: Event) => boolean,\n): Promise<void> {\n return new Promise((resolve, reject) => {\n if (openers.length === 0) {\n resolve();\n return;\n }\n\n type Slot = { cursor: IDBCursorLike | undefined; head: Event | undefined };\n const slots: Slot[] = openers.map(() => ({ cursor: undefined, head: undefined }));\n const seen = new Set<string>();\n let inflight = 0;\n let phase: \"merge\" | \"drain\" | \"done\" = \"merge\";\n let drainT = 0;\n let drainBuf: Event[] = [];\n\n const finish = () => {\n if (phase === \"done\") {\n return;\n }\n phase = \"done\";\n resolve();\n };\n const fail = (error: Error) => {\n if (phase === \"done\") {\n return;\n }\n phase = \"done\";\n reject(error);\n };\n\n const stepCursor = (i: number) => {\n const { cursor } = slots.at(i) ?? { cursor: undefined };\n if (cursor === undefined) {\n return;\n }\n inflight++;\n cursor.continue();\n };\n\n const emitDrain = () => {\n sortEvents(drainBuf);\n for (const event of drainBuf) {\n if (seen.has(event.id) || !accept(event)) {\n continue;\n }\n seen.add(event.id);\n if (take(event)) {\n finish();\n return;\n }\n }\n drainBuf = [];\n phase = \"merge\";\n pump();\n };\n\n const pump = () => {\n if (phase === \"done\" || inflight > 0) {\n return;\n }\n if (phase === \"drain\") {\n emitDrain();\n return;\n }\n let best: Event | undefined;\n for (const slot of slots) {\n const event = slot.head;\n if (event === undefined) {\n continue;\n }\n if (best === undefined || compareEventsDesc(event, best) < 0) {\n best = event;\n }\n }\n if (best === undefined) {\n finish();\n return;\n }\n phase = \"drain\";\n drainT = best.created_at;\n drainBuf = [];\n for (const [i, slot] of slots.entries()) {\n const event = slot.head;\n if (event === undefined || event.created_at !== drainT) {\n continue;\n }\n drainBuf.push(event);\n slot.head = undefined;\n stepCursor(i);\n }\n if (inflight === 0) {\n pump();\n }\n };\n\n const onEvent = (i: number, event: Event | undefined) => {\n if (phase === \"done\") {\n return;\n }\n const slot = slots.at(i);\n if (slot === undefined) {\n return;\n }\n const { cursor } = slot;\n if (event === undefined) {\n if (cursor === undefined) {\n pump();\n } else {\n inflight++;\n cursor.continue();\n }\n return;\n }\n if (phase === \"drain\") {\n if (event.created_at === drainT) {\n drainBuf.push(event);\n if (cursor === undefined) {\n pump();\n } else {\n inflight++;\n cursor.continue();\n }\n return;\n }\n slot.head = event;\n pump();\n return;\n }\n slot.head = event;\n pump();\n };\n\n const openSlot = (i: number, opener: MergeOpener): void => {\n const req = opener.open();\n req.onerror = () => fail(req.error ?? new StorageError(\"IndexedDB cursor failed\"));\n inflight++;\n req.onsuccess = () => {\n inflight--;\n if (phase === \"done\") {\n return;\n }\n // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- openCursor resolves with a cursor or null\n const cursor = (req.result ?? undefined) as IDBCursorLike | undefined;\n const slot = slots.at(i);\n if (slot === undefined) {\n return;\n }\n if (cursor === undefined) {\n slot.cursor = undefined;\n slot.head = undefined;\n pump();\n return;\n }\n slot.cursor = cursor;\n inflight++;\n opener.read(\n cursor,\n (event) => {\n inflight--;\n onEvent(i, event);\n },\n (error) => {\n inflight--;\n fail(error);\n },\n );\n };\n };\n for (const [i, opener] of openers.entries()) {\n openSlot(i, opener);\n }\n });\n}\n\n/**\n * Cap on cursors opened per filter: beyond it a k-way merge degenerates into one request per match\n * candidate. Past the cap the planner opens fewer, wider cursors and lets `accept` (matchFilter)\n * enforce the remaining terms, so results and per-filter limits are unchanged.\n */\nexport const MAX_MERGE_CURSORS = 64;\n\nexport async function scanFilter(\n tx: IDBTransactionLike,\n filter: Filter,\n accept: (event: Event) => boolean,\n take: (event: Event) => boolean,\n): Promise<void> {\n if (filter.limit === 0) {\n return;\n }\n if (filter.since !== undefined && filter.until !== undefined && filter.since > filter.until) {\n return;\n }\n\n if (filter.ids) {\n return scanIds(tx, filter, accept, take);\n }\n\n const events = tx.objectStore(EVENTS);\n const fullScan: MergeOpener = eventCursor(\n events.index(\"created_at\"),\n createdAtRange(filter.since, filter.until),\n );\n const openers: MergeOpener[] = [];\n if (filter.authors && filter.kinds) {\n if (filter.authors.length * filter.kinds.length > MAX_MERGE_CURSORS) {\n // One cursor per kind keeps the bound tight when the kind list is\n // small; otherwise a single created_at scan still fits under the cap.\n if (filter.kinds.length <= MAX_MERGE_CURSORS) {\n const index = events.index(\"kind_created_at\");\n for (const kind of filter.kinds) {\n openers.push(eventCursor(index, prefixRange([kind], filter.since, filter.until)));\n }\n } else {\n openers.push(fullScan);\n }\n } else {\n const index = events.index(\"kind_pubkey_created_at\");\n for (const kind of filter.kinds) {\n for (const pk of filter.authors) {\n openers.push(\n eventCursor(index, prefixRange([kind, pk.toLowerCase()], filter.since, filter.until)),\n );\n }\n }\n }\n } else if (filter.authors) {\n if (filter.authors.length > MAX_MERGE_CURSORS) {\n openers.push(fullScan);\n } else {\n const index = events.index(\"pubkey_created_at\");\n for (const pk of filter.authors) {\n openers.push(\n eventCursor(index, prefixRange([pk.toLowerCase()], filter.since, filter.until)),\n );\n }\n }\n } else if (filter.kinds) {\n if (filter.kinds.length > MAX_MERGE_CURSORS) {\n openers.push(fullScan);\n } else {\n const index = events.index(\"kind_created_at\");\n for (const kind of filter.kinds) {\n openers.push(eventCursor(index, prefixRange([kind], filter.since, filter.until)));\n }\n }\n } else {\n const tags = epTagPrefixes(filter);\n if (tags.length > MAX_MERGE_CURSORS) {\n openers.push(fullScan);\n } else if (tags.length > 0) {\n const index = tx.objectStore(TAG_REFS).index(\"name_value_created\");\n for (const tag of tags) {\n openers.push(\n tagCursor(index, events, prefixRange([tag.name, tag.value], filter.since, filter.until)),\n );\n }\n } else {\n openers.push(fullScan);\n }\n }\n return kWayMerge(openers, accept, take);\n}\n","// oxlint-disable unicorn/prefer-add-event-listener -- the *Like driver interfaces model only the `on*` handler surface\nimport { StorageError } from \"./error.ts\";\nimport { ADDRESSES, EVENTS, OUTBOX_BOUNDS, TAG_REFS, TOMBSTONES } from \"./idb-types.ts\";\nimport type { IDBDatabaseLike, IDBFactoryLike } from \"./idb-types.ts\";\n\nexport const IDB_VERSION = 5;\n\nconst ALL_STORES = [EVENTS, TAG_REFS, ADDRESSES, TOMBSTONES, OUTBOX_BOUNDS];\n\nfunction createSchema(db: IDBDatabaseLike): void {\n const events = db.createObjectStore(EVENTS, { keyPath: \"id\" });\n events.createIndex(\"created_at\", \"created_at\");\n events.createIndex(\"kind_created_at\", [\"kind\", \"created_at\"]);\n events.createIndex(\"pubkey_created_at\", [\"pubkey\", \"created_at\"]);\n events.createIndex(\"kind_pubkey_created_at\", [\"kind\", \"pubkey\", \"created_at\"]);\n db.createObjectStore(TAG_REFS, { keyPath: \"key\" }).createIndex(\"name_value_created\", [\n \"name\",\n \"value\",\n \"created_at\",\n ]);\n db.createObjectStore(ADDRESSES, { keyPath: \"address\" });\n db.createObjectStore(TOMBSTONES, { keyPath: \"key\" });\n db.createObjectStore(OUTBOX_BOUNDS, { keyPath: \"key\" });\n}\n\nexport async function openDb(dbName: string): Promise<IDBDatabaseLike> {\n return new Promise((resolve, reject) => {\n // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- reads the platform global, which has no ambient types here\n const factory = (globalThis as unknown as { indexedDB: IDBFactoryLike }).indexedDB;\n const req = factory.open(dbName, IDB_VERSION);\n req.onupgradeneeded = (ev) => {\n const db = req.result;\n if (ev.oldVersion !== 0) {\n // The stores are caches of relay data: a non-current schema is dropped\n // and recreated rather than migrated.\n for (const name of ALL_STORES) {\n if (db.objectStoreNames.contains(name)) {\n db.deleteObjectStore(name);\n }\n }\n }\n createSchema(db);\n };\n let settled = false;\n req.onsuccess = () => {\n const db = req.result;\n if (settled) {\n // The blocked rejection already fired; do not leak this late connection.\n db.close();\n return;\n }\n settled = true;\n // Yield to any newer-version open instead of blocking it forever.\n db.onversionchange = () => db.close();\n resolve(db);\n };\n req.onerror = () => {\n settled = true;\n reject(req.error ?? new StorageError(\"IndexedDB open failed\"));\n };\n req.onblocked = () => {\n settled = true;\n reject(new StorageError(\"IndexedDB open blocked by another connection\"));\n };\n });\n}\n","// oxlint-disable unicorn/prefer-add-event-listener -- the *Like driver interfaces model only the `on*` handler surface\n// oxlint-disable eslint/no-await-in-loop -- requests inside one IndexedDB transaction are issued sequentially; awaiting a no-request promise auto-commits the tx\nimport type { Event } from \"../core/event.ts\";\nimport { itemCompare, sortEvents, validateSignedEvent } from \"../core/event.ts\";\nimport type { Filter } from \"../core/filter.ts\";\nimport { matchFilter } from \"../core/filter.ts\";\nimport { Kind } from \"../core/kind.ts\";\nimport { SerialQueue } from \"../core/serial.ts\";\nimport { eventAddress, parseEventAddress } from \"../core/tag.ts\";\nimport { DeletionState } from \"./deletion.ts\";\nimport { StorageError, toStorageError } from \"./error.ts\";\nimport {\n applyPutIndexedDb,\n deleteStoredEvent,\n reqOf,\n tombstonesToPlan,\n txDone,\n walkCursor,\n} from \"./idb-helpers.ts\";\nimport { prefixRange, scanFilter } from \"./idb-query.ts\";\nimport { openDb } from \"./idb-schema.ts\";\nimport {\n ADDRESSES,\n EVENTS,\n OUTBOX_BOUNDS,\n TAG_REFS,\n TOMBSTONES,\n WRITE_STORES,\n} from \"./idb-types.ts\";\nimport type {\n AddressRow,\n IDBCursorDirectionLike,\n IDBDatabaseLike,\n IDBFactoryLike,\n IDBTransactionLike,\n OutboxBoundRow,\n Tombstone,\n} from \"./idb-types.ts\";\nimport { decidePut, outboxBoundKey } from \"./put.ts\";\nimport type { EventStore, NegentropyItem, OutboxBound, PutResult } from \"./types.ts\";\n\n/** Options for {@link IndexedDbEventStore}. */\nexport type IndexedDbEventStoreOptions = {\n /** IndexedDB database name. */\n dbName?: string | undefined;\n};\n\n/**\n * Browser IndexedDB event store with the same replaceable / deletion semantics as\n * {@link MemoryEventStore}. Requires a DOM IndexedDB implementation.\n *\n * `open` loads every tombstone and address row into memory. Fine at 10^4 events; tens of MB at 10^5\n * addressables.\n */\nexport class IndexedDbEventStore implements EventStore {\n readonly #dbName: string;\n #db: IDBDatabaseLike | undefined;\n /** Shared so concurrent open()/first-use calls cannot open a second connection. */\n #opening: Promise<void> | undefined;\n /** Set when this connection closed to unblock a newer-version open; ops then fail fast. */\n #yielded = false;\n readonly #deletion = new DeletionState();\n #replaceable = new Map<string, string>();\n /** Serializes writes so abort restore cannot roll back a committed sibling tx. */\n readonly #writeQueue = new SerialQueue();\n\n constructor(opts: IndexedDbEventStoreOptions = {}) {\n this.#dbName = opts.dbName ?? \"@qntx/nostr\";\n }\n\n static isAvailable(): boolean {\n // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- reads an optional host global\n return (globalThis as { indexedDB?: IDBFactoryLike }).indexedDB !== undefined;\n }\n\n async open(): Promise<void> {\n if (this.#db) {\n return;\n }\n this.#opening ??= (async (): Promise<void> => {\n try {\n await this.#doOpen();\n } finally {\n this.#opening = undefined;\n }\n })();\n await this.#opening;\n }\n\n async #doOpen(): Promise<void> {\n if (!IndexedDbEventStore.isAvailable()) {\n throw new StorageError(\"IndexedDB is not available in this environment\");\n }\n const db = await openDb(this.#dbName);\n const closeOnUpgrade = db.onversionchange;\n db.onversionchange = (ev) => {\n closeOnUpgrade?.(ev);\n this.#yielded = true;\n };\n this.#yielded = false;\n this.#db = db;\n try {\n await this.#loadCaches(db);\n } catch (error) {\n // Do not keep a connection whose cache mirror failed to load.\n this.#db = undefined;\n try {\n db.close();\n } catch {\n // ignore\n }\n throw error;\n }\n }\n\n async #ensure(): Promise<IDBDatabaseLike> {\n if (this.#yielded) {\n throw new StorageError(\"IndexedDB connection closed by a newer version\");\n }\n await this.open();\n const db = this.#db;\n if (db === undefined) {\n throw new StorageError(\"IndexedDB is not available in this environment\");\n }\n return db;\n }\n\n /** Loads every tombstone and address row into RAM. Tens of MB at 10^5 addressables. */\n async #loadCaches(db: IDBDatabaseLike): Promise<void> {\n const tx = db.transaction([TOMBSTONES, ADDRESSES], \"readonly\");\n const done = txDone(tx);\n this.#deletion.clear();\n this.#replaceable.clear();\n const tombs = await reqOf<unknown[]>(tx.objectStore(TOMBSTONES).getAll());\n this.#deletion.absorb(tombstonesToPlan(tombs ?? []));\n await walkCursor(tx.objectStore(ADDRESSES), undefined, \"next\", (cursor) => {\n // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- address cursor values are the stored AddressRow rows\n const row = cursor.value as AddressRow;\n this.#replaceable.set(row.address, row.id);\n return false;\n });\n await done;\n }\n\n async put(event: Event): Promise<PutResult> {\n const [result] = await this.putMany([event]);\n if (result === undefined) {\n throw new StorageError(\"putMany returned no result\");\n }\n return result;\n }\n\n /** One readwrite transaction; abort rejects the whole batch with no partial persist. */\n async putMany(events: ReadonlyArray<Event>): Promise<PutResult[]> {\n if (events.length === 0) {\n return [];\n }\n return this.#writeQueue.run(async () => this.#putManyLocked(events));\n }\n\n async #putManyLocked(events: ReadonlyArray<Event>): Promise<PutResult[]> {\n return this.#withWriteTx(WRITE_STORES, async (tx) => this.#putAllInTx(tx, events));\n }\n\n /**\n * Run `fn` inside a readwrite transaction, snapshotting the in-memory deletion/replaceable\n * mirrors so a transaction abort or thrown error restores them to match the rolled-back rows.\n */\n async #withWriteTx<T>(\n stores: string | string[],\n fn: (tx: IDBTransactionLike) => T | Promise<T>,\n ): Promise<T> {\n let db: IDBDatabaseLike;\n try {\n db = await this.#ensure();\n } catch (error) {\n throw toStorageError(error);\n }\n const tx = db.transaction(stores, \"readwrite\");\n const txSettled = new Promise<void>((resolve, reject) => {\n tx.oncomplete = () => resolve();\n tx.onerror = () => reject(tx.error ?? new StorageError(\"IndexedDB transaction failed\"));\n tx.onabort = () => reject(tx.error ?? new StorageError(\"IndexedDB transaction aborted\"));\n });\n const txOutcome = (async (): Promise<unknown> => {\n try {\n await txSettled;\n return undefined;\n } catch (error: unknown) {\n return error;\n }\n })();\n const cache = this.#snapshotCaches();\n try {\n const result = await fn(tx);\n const txErr = await txOutcome;\n if (txErr !== undefined) {\n throw toStorageError(txErr);\n }\n return result;\n } catch (error) {\n this.#restoreCaches(cache);\n try {\n tx.abort();\n } catch {\n // already aborted or complete\n }\n await txOutcome;\n throw toStorageError(error);\n }\n }\n\n /** Sequential awaits so the tx stays alive between events. */\n async #putAllInTx(tx: IDBTransactionLike, batch: ReadonlyArray<Event>): Promise<PutResult[]> {\n const eventsStore = tx.objectStore(EVENTS);\n const addressesStore = tx.objectStore(ADDRESSES);\n const results: PutResult[] = [];\n for (const raw of batch) {\n if (!validateSignedEvent(raw)) {\n results.push(\"invalid\");\n continue;\n }\n const event = raw;\n const byId = new Map<string, Pick<Event, \"id\" | \"pubkey\" | \"kind\" | \"created_at\" | \"tags\">>();\n const existing = await reqOf<Event | undefined>(eventsStore.get(event.id));\n if (existing) {\n byId.set(existing.id, existing);\n }\n if (event.kind === Kind.EventDeletion) {\n for (const tag of event.tags) {\n if (tag[0] !== \"e\" || tag[1] === undefined) {\n continue;\n }\n const got = await reqOf<Event | undefined>(eventsStore.get(tag[1].toLowerCase()));\n if (got) {\n byId.set(got.id, got);\n }\n }\n }\n const addrRows = new Map<string, { id: string; created_at: number }>();\n const ownAddr = eventAddress(event);\n if (ownAddr !== undefined) {\n const row = await reqOf<AddressRow | undefined>(addressesStore.get(ownAddr));\n if (row) {\n addrRows.set(ownAddr, { id: row.id, created_at: row.created_at });\n }\n }\n if (event.kind === Kind.EventDeletion) {\n for (const tag of event.tags) {\n if (tag[0] !== \"a\" || tag[1] === undefined) {\n continue;\n }\n const coord = parseEventAddress(tag[1]);\n if (!coord) {\n continue;\n }\n const key = `${coord.kind}:${coord.pubkey}:${coord.identifier}`;\n if (addrRows.has(key)) {\n continue;\n }\n const row = await reqOf<AddressRow | undefined>(addressesStore.get(key));\n if (row) {\n addrRows.set(key, { id: row.id, created_at: row.created_at });\n }\n }\n }\n const d = decidePut(event, {\n deletion: this.#deletion,\n getById: (id) => byId.get(id),\n getReplaceable: (address) => addrRows.get(address),\n });\n // Await row deletes in this loop so the next events.get runs in the IDB\n // request continuation. `await` of a no-request promise auto-commits the tx.\n if (d.action === \"delete\") {\n const remove = new Set([...d.plan.removeIds, ...d.coordIds]);\n for (const id of remove) {\n await this.#deleteEventRows(tx, id);\n }\n } else if (d.action === \"insert\" && d.replaceId !== undefined) {\n await this.#deleteEventRows(tx, d.replaceId);\n }\n results.push(\n applyPutIndexedDb(\n tx,\n {\n deletion: this.#deletion,\n replaceable: this.#replaceable,\n },\n d,\n ),\n );\n }\n return results;\n }\n\n #snapshotCaches(): {\n replaceable: Map<string, string>;\n ids: Set<string>;\n pending: Map<string, string>;\n coordinates: Map<string, number>;\n } {\n return {\n replaceable: new Map(this.#replaceable),\n ids: new Set(this.#deletion.ids),\n pending: new Map(this.#deletion.pending),\n coordinates: new Map(this.#deletion.coordinates),\n };\n }\n\n #restoreCaches(snap: {\n replaceable: Map<string, string>;\n ids: Set<string>;\n pending: Map<string, string>;\n coordinates: Map<string, number>;\n }): void {\n this.#replaceable = snap.replaceable;\n this.#deletion.ids.clear();\n for (const id of snap.ids) {\n this.#deletion.ids.add(id);\n }\n this.#deletion.pending.clear();\n for (const [k, v] of snap.pending) {\n this.#deletion.pending.set(k, v);\n }\n this.#deletion.coordinates.clear();\n for (const [k, v] of snap.coordinates) {\n this.#deletion.coordinates.set(k, v);\n }\n }\n\n async get(id: string): Promise<Event | undefined> {\n const key = id.toLowerCase();\n if (this.#deletion.ids.has(key)) {\n return undefined;\n }\n const db = await this.#ensure();\n const tx = db.transaction(EVENTS, \"readonly\");\n const done = txDone(tx);\n const event = await reqOf<Event | undefined>(tx.objectStore(EVENTS).get(key));\n await done;\n return event;\n }\n\n async query(filters: ReadonlyArray<Filter>): Promise<Event[]> {\n const db = await this.#ensure();\n const tx = db.transaction([EVENTS, TAG_REFS], \"readonly\");\n const done = txDone(tx);\n const seen = new Set<string>();\n const events: Event[] = [];\n for (const filter of filters) {\n const matched = await this.#queryOne(tx, filter);\n for (const event of matched) {\n if (seen.has(event.id)) {\n continue;\n }\n seen.add(event.id);\n events.push(event);\n }\n }\n await done;\n sortEvents(events);\n return events;\n }\n\n async count(filters: ReadonlyArray<Filter>): Promise<number> {\n const db = await this.#ensure();\n const tx = db.transaction([EVENTS, TAG_REFS], \"readonly\");\n const done = txDone(tx);\n const seen = new Set<string>();\n for (const filter of filters) {\n const local = new Set<string>();\n await this.#scan(tx, filter, (event) => {\n local.add(event.id);\n return filter.limit !== undefined && local.size >= filter.limit;\n });\n for (const id of local) {\n seen.add(id);\n }\n }\n await done;\n return seen.size;\n }\n\n async negentropyItems(filter: Filter): Promise<NegentropyItem[]> {\n const db = await this.#ensure();\n const tx = db.transaction([EVENTS, TAG_REFS], \"readonly\");\n const done = txDone(tx);\n const items: NegentropyItem[] = [];\n await this.#scan(tx, filter, (event) => {\n items.push({ id: event.id, created_at: event.created_at });\n return filter.limit !== undefined && items.length >= filter.limit;\n });\n await done;\n items.sort(itemCompare);\n return items;\n }\n\n #acceptHit(filter: Filter, event: Event): boolean {\n if (this.#deletion.ids.has(event.id)) {\n return false;\n }\n if (this.#deletion.covers(event)) {\n return false;\n }\n return matchFilter(filter, event);\n }\n\n async #queryOne(tx: IDBTransactionLike, filter: Filter): Promise<Event[]> {\n const matched: Event[] = [];\n await this.#scan(tx, filter, (event) => {\n matched.push(event);\n return filter.limit !== undefined && matched.length >= filter.limit;\n });\n sortEvents(matched);\n return matched;\n }\n\n async #scan(\n tx: IDBTransactionLike,\n filter: Filter,\n take: (event: Event) => boolean,\n ): Promise<void> {\n return scanFilter(tx, filter, (event) => this.#acceptHit(filter, event), take);\n }\n\n async getOutboxBound(pubkey: string, kind: number): Promise<OutboxBound | undefined> {\n try {\n const db = await this.#ensure();\n const tx = db.transaction(OUTBOX_BOUNDS, \"readonly\");\n const done = txDone(tx);\n const row = await reqOf<OutboxBoundRow | undefined>(\n tx.objectStore(OUTBOX_BOUNDS).get(outboxBoundKey(pubkey, kind)),\n );\n await done;\n if (row && typeof row.oldest === \"number\" && typeof row.newest === \"number\") {\n return { oldest: row.oldest, newest: row.newest };\n }\n return await this.#deriveOutboxBound(pubkey, kind);\n } catch (error) {\n throw toStorageError(error);\n }\n }\n\n async setOutboxBound(pubkey: string, kind: number, bound: OutboxBound): Promise<void> {\n return this.#writeQueue.run(async () => this.#setOutboxBoundLocked(pubkey, kind, bound));\n }\n\n async #setOutboxBoundLocked(pubkey: string, kind: number, bound: OutboxBound): Promise<void> {\n await this.#withWriteTx(OUTBOX_BOUNDS, (tx) => {\n tx.objectStore(OUTBOX_BOUNDS).put({\n key: outboxBoundKey(pubkey, kind),\n oldest: bound.oldest,\n newest: bound.newest,\n } satisfies OutboxBoundRow);\n });\n }\n\n async remove(ids: ReadonlyArray<string>): Promise<number> {\n return this.#writeQueue.run(async () => this.#removeLocked(ids));\n }\n\n async #removeLocked(ids: ReadonlyArray<string>): Promise<number> {\n return this.#withWriteTx(WRITE_STORES, async (tx) => {\n const tombstones = tx.objectStore(TOMBSTONES);\n let n = 0;\n for (const raw of ids) {\n const id = raw.toLowerCase();\n if (await this.#deleteEventRows(tx, id)) {\n n += 1;\n }\n tombstones.put({ key: `id:${id}`, type: \"id\" } satisfies Tombstone);\n tombstones.delete(`pending:${id}`);\n this.#deletion.ids.add(id);\n this.#deletion.pending.delete(id);\n }\n return n;\n });\n }\n\n async clear(): Promise<void> {\n return this.#writeQueue.run(async () => this.#clearLocked());\n }\n\n async #clearLocked(): Promise<void> {\n const stores = [...WRITE_STORES, OUTBOX_BOUNDS];\n await this.#withWriteTx(stores, (tx) => {\n for (const name of stores) {\n tx.objectStore(name).clear();\n }\n this.#deletion.clear();\n this.#replaceable.clear();\n });\n }\n\n close(): void {\n this.#db?.close();\n this.#db = undefined;\n }\n\n async #deleteEventRows(tx: IDBTransactionLike, id: string): Promise<boolean> {\n const events = tx.objectStore(EVENTS);\n const event = await reqOf<Event | undefined>(events.get(id));\n if (!event) {\n return false;\n }\n const addr = eventAddress(event);\n let row: AddressRow | undefined;\n if (addr !== undefined) {\n row = await reqOf<AddressRow | undefined>(tx.objectStore(ADDRESSES).get(addr));\n if (this.#replaceable.get(addr) === event.id) {\n this.#replaceable.delete(addr);\n }\n }\n deleteStoredEvent(tx, event, row);\n return true;\n }\n\n async #deriveOutboxBound(pubkey: string, kind: number): Promise<OutboxBound | undefined> {\n const db = await this.#ensure();\n const pk = pubkey.toLowerCase();\n const tx = db.transaction(EVENTS, \"readonly\");\n const done = txDone(tx);\n const index = tx.objectStore(EVENTS).index(\"kind_pubkey_created_at\");\n const range = prefixRange([kind, pk]);\n const filter: Filter = { authors: [pk], kinds: [kind] };\n let oldest: number | undefined;\n let newest: number | undefined;\n const firstLive = async (\n direction: IDBCursorDirectionLike,\n assign: (createdAt: number) => void,\n ) =>\n walkCursor(index, range, direction, (cursor) => {\n // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- index cursor values are stored Event rows\n const event = cursor.value as Event;\n if (!this.#acceptHit(filter, event)) {\n return false;\n }\n assign(event.created_at);\n return true;\n });\n await Promise.all([\n firstLive(\"next\", (createdAt) => {\n oldest = createdAt;\n }),\n firstLive(\"prev\", (createdAt) => {\n newest = createdAt;\n }),\n ]);\n await done;\n if (oldest === undefined || newest === undefined) {\n return undefined;\n }\n return { oldest, newest };\n }\n}\n","// oxlint-disable eslint/no-await-in-loop -- SqlDriver statements are issued sequentially; drivers are not required to support overlapping statements on one connection\nimport { compareEventsDesc, itemCompare, sortEvents } from \"../core/event.ts\";\nimport type { Event } from \"../core/event.ts\";\nimport type { Filter } from \"../core/filter.ts\";\nimport { Kind } from \"../core/kind.ts\";\nimport { SerialQueue } from \"../core/serial.ts\";\nimport { eventAddress, formatEventAddress, parseEventAddress } from \"../core/tag.ts\";\nimport { DeletionState } from \"./deletion.ts\";\nimport type { DeletionPlan } from \"./deletion.ts\";\nimport { toStorageError } from \"./error.ts\";\nimport { decidePut } from \"./put.ts\";\nimport type { PutDecision, PutLookup } from \"./put.ts\";\nimport type { EventStore, NegentropyItem, OutboxBound, PutResult } from \"./types.ts\";\n\n/** Value bindable to a SQLite statement parameter. */\n// oxlint-disable-next-line typescript/no-restricted-types -- SQL NULL is a first-class bind value\nexport type SqlValue = string | number | null | Uint8Array;\n\n/**\n * Minimal async SQLite driver surface.\n *\n * `transaction` must serialize `fn` exclusively — no interleaved statements from other callers may\n * run while the callback transaction is active. It commits when `fn` resolves and rolls back when\n * `fn` rejects.\n *\n * The store always issues statements sequentially — a driver never sees a second `exec`/`run`/`all`\n * start before the previous one settles, inside or outside a transaction — so drivers need not\n * support overlapping statements on one connection.\n *\n * The store does not rely on per-connection PRAGMAs: tag rows are deleted explicitly, so `PRAGMA\n * foreign_keys` may be off on any connection (including transaction connections).\n *\n * `expo-sqlite` maps onto this interface without changes to query code. Note that\n * `withExclusiveTransactionAsync` resolves `void` and statements inside the exclusive transaction\n * must run on the `txn` connection passed to its callback, not on `db` — the result is captured\n * out-of-band:\n *\n * ```ts\n * const toDriver = (db: SQLiteDatabase): SqlDriver => ({\n * exec: (sql) => db.execAsync(sql),\n * run: async (sql, params = []) => ({\n * changes: (await db.runAsync(sql, [...params])).changes,\n * }),\n * all: (sql, params = []) => db.getAllAsync(sql, [...params]),\n * transaction: async (fn) => {\n * let result!: Awaited<ReturnType<typeof fn>>;\n * await db.withExclusiveTransactionAsync(async (txn) => {\n * result = await fn(toDriver(txn));\n * });\n * return result;\n * },\n * });\n * ```\n */\nexport type SqlDriver = {\n /** Execute SQL without parameters (DDL, PRAGMA, multi-statement batches). */\n exec: (sql: string) => Promise<void>;\n /** Run a statement; resolves with the number of changed rows. */\n run: (sql: string, params?: ReadonlyArray<SqlValue>) => Promise<{ changes: number }>;\n /** Run a query; resolves with all result rows. */\n all: <Row>(sql: string, params?: ReadonlyArray<SqlValue>) => Promise<Row[]>;\n /** Run `fn` inside an exclusive transaction; commit on resolve, roll back on reject. */\n transaction: <T>(fn: (tx: SqlDriver) => Promise<T>) => Promise<T>;\n};\n\n/** Bound values per `IN (...)` list; stays below every host's variable limit. */\nconst IN_CHUNK = 500;\n// Bump on any schema change: a database whose user_version does not match is\n// dropped and recreated, not migrated (the stores are caches of relay data).\nconst SCHEMA_VERSION = 2;\n\nconst SCHEMA_SQL = `\nCREATE TABLE IF NOT EXISTS events (\n id TEXT PRIMARY KEY,\n pubkey TEXT NOT NULL,\n kind INTEGER NOT NULL,\n created_at INTEGER NOT NULL,\n content TEXT NOT NULL,\n tags TEXT NOT NULL,\n sig TEXT NOT NULL,\n address TEXT\n);\nCREATE UNIQUE INDEX IF NOT EXISTS events_address\n ON events(address) WHERE address IS NOT NULL;\nCREATE INDEX IF NOT EXISTS events_kind_created ON events(kind, created_at DESC, id);\nCREATE INDEX IF NOT EXISTS events_pubkey_kind_created\n ON events(pubkey, kind, created_at DESC, id);\nCREATE INDEX IF NOT EXISTS events_pubkey_created ON events(pubkey, created_at DESC, id);\nCREATE INDEX IF NOT EXISTS events_created ON events(created_at DESC, id);\nCREATE TABLE IF NOT EXISTS tags (\n event_id TEXT NOT NULL REFERENCES events(id) ON DELETE CASCADE,\n name TEXT NOT NULL,\n value TEXT NOT NULL,\n created_at INTEGER NOT NULL\n);\nCREATE INDEX IF NOT EXISTS tags_name_value_created\n ON tags(name, value, created_at DESC);\nCREATE INDEX IF NOT EXISTS tags_event_id ON tags(event_id);\nCREATE TABLE IF NOT EXISTS tombstones (\n kind TEXT NOT NULL CHECK(kind IN ('id', 'pending', 'coord')),\n key TEXT NOT NULL,\n pubkey TEXT,\n until INTEGER,\n PRIMARY KEY(kind, key)\n);\nCREATE TABLE IF NOT EXISTS outbox_bounds (\n pubkey TEXT NOT NULL,\n kind INTEGER NOT NULL,\n oldest INTEGER NOT NULL,\n newest INTEGER NOT NULL,\n PRIMARY KEY(pubkey, kind)\n);\n`;\n\n// Dropping a table also drops its indexes. Child tables go before `events`\n// so the drop is safe under `PRAGMA foreign_keys`.\nconst DROP_SQL = `\nDROP TABLE IF EXISTS tags;\nDROP TABLE IF EXISTS tombstones;\nDROP TABLE IF EXISTS outbox_bounds;\nDROP TABLE IF EXISTS events;\n`;\n\ntype EventRow = {\n id: string;\n pubkey: string;\n kind: number;\n created_at: number;\n content: string;\n tags: string;\n sig: string;\n};\n\ntype TombstoneKind = \"id\" | \"pending\" | \"coord\";\n\ntype TombstoneRow = {\n kind: TombstoneKind;\n key: string;\n // oxlint-disable-next-line typescript/no-restricted-types -- nullable SQL column\n pubkey: string | null;\n // oxlint-disable-next-line typescript/no-restricted-types -- nullable SQL column\n until: number | null;\n};\n\nfunction rowToEvent(row: EventRow): Event {\n // Key order mirrors finalizeEvent's canonical construction so a round-trip\n // through the store JSON-equals the originally signed object.\n return {\n kind: row.kind,\n // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- the tags column is written by #insertEvent as JSON.stringify(event.tags)\n tags: JSON.parse(row.tags) as string[][],\n content: row.content,\n created_at: row.created_at,\n pubkey: row.pubkey,\n id: row.id,\n sig: row.sig,\n };\n}\n\nfunction chunkValues<T>(values: ReadonlyArray<T>): T[][] {\n const chunks: T[][] = [];\n for (let i = 0; i < values.length; i += IN_CHUNK) {\n chunks.push(values.slice(i, i + IN_CHUNK));\n }\n return chunks;\n}\n\nfunction inClause(column: string, count: number): string {\n return `${column} IN (${Array.from({ length: count }, () => \"?\").join(\",\")})`;\n}\n\n/** One query variant of a filter: WHERE terms and bound params. */\ntype FilterPlan = {\n wheres: string[];\n params: SqlValue[];\n};\n\n/**\n * Compile one NIP-01 filter into query variants. `IN` lists are chunked so a variant never binds\n * more than {@link IN_CHUNK} values per list; variants are the cartesian product of chunks across\n * constrained fields. Multi-letter `#` keys are not NIP-01 tag conditions and are skipped, exactly\n * like `matchFilter`. Returns `undefined` when an empty list field makes the filter match nothing.\n */\nfunction compileFilter(filter: Filter): FilterPlan[] | undefined {\n const variants: FilterPlan[][] = [];\n const pushIn = (values: ReadonlyArray<SqlValue>, clause: (n: number) => string) => {\n variants.push(\n chunkValues(values).map((chunk) => ({\n wheres: [clause(chunk.length)],\n params: [...chunk],\n })),\n );\n };\n\n if (filter.ids) {\n if (filter.ids.length === 0) {\n return undefined;\n }\n pushIn(\n filter.ids.map((id) => id.toLowerCase()),\n (n) => inClause(\"id\", n),\n );\n }\n if (filter.authors) {\n if (filter.authors.length === 0) {\n return undefined;\n }\n pushIn(\n filter.authors.map((pk) => pk.toLowerCase()),\n (n) => inClause(\"pubkey\", n),\n );\n }\n if (filter.kinds) {\n if (filter.kinds.length === 0) {\n return undefined;\n }\n pushIn(filter.kinds, (n) => inClause(\"kind\", n));\n }\n if (filter.since !== undefined) {\n variants.push([{ wheres: [\"created_at >= ?\"], params: [filter.since] }]);\n }\n if (filter.until !== undefined) {\n variants.push([{ wheres: [\"created_at <= ?\"], params: [filter.until] }]);\n }\n\n for (const key of Object.keys(filter)) {\n if (!key.startsWith(\"#\")) {\n continue;\n }\n const name = key.slice(1);\n if (name.length !== 1) {\n continue;\n }\n // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- startsWith(\"#\") above guarantees the template key\n const values = filter[key as `#${string}`];\n if (!values) {\n continue;\n }\n if (values.length === 0) {\n return undefined;\n }\n const normalized = name === \"e\" || name === \"p\" ? values.map((v) => v.toLowerCase()) : values;\n variants.push(\n chunkValues(normalized).map((chunk) => ({\n wheres: [\n `EXISTS (SELECT 1 FROM tags t WHERE t.event_id = events.id ` +\n `AND t.name = ? AND ${inClause(\"t.value\", chunk.length)})`,\n ],\n params: [name, ...chunk],\n })),\n );\n }\n\n const plans: FilterPlan[] = [{ wheres: [], params: [] }];\n for (const field of variants) {\n const next: FilterPlan[] = [];\n for (const plan of plans) {\n for (const variant of field) {\n next.push({\n wheres: [...plan.wheres, ...variant.wheres],\n params: [...plan.params, ...variant.params],\n });\n }\n }\n plans.length = 0;\n plans.push(...next);\n }\n return plans;\n}\n\n/**\n * SQLite-backed {@link EventStore} for React Native (`expo-sqlite`, `op-sqlite`) and desktop\n * runtimes. Same semantics as {@link MemoryEventStore} and {@link IndexedDbEventStore}: identical\n * `decidePut` insertion policy, NIP-09 tombstones, and filter handling, with the database — not\n * in-memory caches — as the source of truth.\n *\n * Construct via {@link SqliteEventStore.open}; writes are serialized and run inside an exclusive\n * driver transaction.\n */\nexport class SqliteEventStore implements EventStore {\n readonly #driver: SqlDriver;\n readonly #writeQueue = new SerialQueue();\n\n private constructor(driver: SqlDriver) {\n this.#driver = driver;\n }\n\n /** Create the schema on first use and return a ready store. */\n static async open(driver: SqlDriver): Promise<SqliteEventStore> {\n const store = new SqliteEventStore(driver);\n await store.#migrate();\n return store;\n }\n\n async #migrate(): Promise<void> {\n try {\n const rows = await this.#driver.all<{ user_version: number }>(\"PRAGMA user_version\");\n const version = rows[0]?.user_version ?? 0;\n if (version === SCHEMA_VERSION) {\n return;\n }\n await this.#driver.transaction(async (tx) => {\n if (version !== 0) {\n await tx.exec(DROP_SQL);\n }\n await tx.exec(SCHEMA_SQL);\n await tx.exec(`PRAGMA user_version = ${SCHEMA_VERSION}`);\n });\n } catch (error) {\n throw toStorageError(error);\n }\n }\n\n async put(event: Event): Promise<PutResult> {\n const results = await this.putMany([event]);\n const result = results.at(0);\n if (result === undefined) {\n throw toStorageError(new Error(\"putMany returned no result\"));\n }\n return result;\n }\n\n async putMany(events: ReadonlyArray<Event>): Promise<PutResult[]> {\n if (events.length === 0) {\n return [];\n }\n return this.#writeQueue.run(async () => {\n try {\n return await this.#driver.transaction(async (tx) => this.#putAllInTx(tx, events));\n } catch (error) {\n throw toStorageError(error);\n }\n });\n }\n\n async #putAllInTx(tx: SqlDriver, batch: ReadonlyArray<Event>): Promise<PutResult[]> {\n const results: PutResult[] = [];\n for (const event of batch) {\n const lookup = await this.#buildLookup(tx, event);\n const decision = decidePut(event, lookup);\n await this.#applyDecision(tx, decision);\n results.push(decision.result);\n }\n return results;\n }\n\n async #buildLookup(tx: SqlDriver, event: Event): Promise<PutLookup> {\n const byId = new Map<string, Event>();\n const idList = [event.id];\n if (event.kind === Kind.EventDeletion) {\n for (const tag of event.tags) {\n if (tag[0] === \"e\" && tag[1] !== undefined && tag[1] !== \"\") {\n idList.push(tag[1].toLowerCase());\n }\n }\n }\n for (const row of await this.#eventRowsByIds(tx, idList)) {\n byId.set(row.id, row);\n }\n\n const byAddress = new Map<string, { id: string; created_at: number }>();\n const addressList: string[] = [];\n const ownAddress = eventAddress(event);\n if (ownAddress !== undefined) {\n addressList.push(ownAddress);\n }\n if (event.kind === Kind.EventDeletion) {\n for (const tag of event.tags) {\n if (tag[0] !== \"a\" || tag[1] === undefined || tag[1] === \"\") {\n continue;\n }\n const coord = parseEventAddress(tag[1]);\n if (coord === undefined) {\n continue;\n }\n const key = formatEventAddress(coord.kind, coord.pubkey, coord.identifier);\n if (!addressList.includes(key)) {\n addressList.push(key);\n }\n }\n }\n const addressRows: Array<{ address: string; id: string; created_at: number }> = [];\n for (const chunk of chunkValues(addressList)) {\n addressRows.push(\n ...(await tx.all<{ address: string; id: string; created_at: number }>(\n `SELECT address, id, created_at FROM events\n WHERE ${inClause(\"address\", chunk.length)}`,\n chunk,\n )),\n );\n }\n for (const row of addressRows) {\n byAddress.set(row.address, { id: row.id, created_at: row.created_at });\n }\n\n const deletion = new DeletionState();\n const tombstoneRows = await tx.all<TombstoneRow>(\n ownAddress === undefined\n ? `SELECT kind, key, pubkey, until FROM tombstones\n WHERE kind IN ('id', 'pending') AND key = ?`\n : `SELECT kind, key, pubkey, until FROM tombstones\n WHERE (kind IN ('id', 'pending') AND key = ?)\n OR (kind = 'coord' AND key = ?)`,\n ownAddress === undefined ? [event.id] : [event.id, ownAddress],\n );\n for (const row of tombstoneRows) {\n if (row.kind === \"id\") {\n deletion.ids.add(row.key);\n } else if (row.kind === \"pending\" && row.pubkey !== null) {\n deletion.pending.set(row.key, row.pubkey);\n } else if (row.kind === \"coord\" && row.until !== null) {\n deletion.coordinates.set(row.key, row.until);\n }\n }\n\n return {\n deletion,\n getById: (id) => byId.get(id),\n getReplaceable: (address) => byAddress.get(address),\n };\n }\n\n async #eventRowsByIds(tx: SqlDriver, ids: ReadonlyArray<string>): Promise<Event[]> {\n const events: Event[] = [];\n for (const chunk of chunkValues(ids)) {\n const rows = await tx.all<EventRow>(\n `SELECT id, pubkey, kind, created_at, content, tags, sig FROM events\n WHERE ${inClause(\"id\", chunk.length)}`,\n chunk,\n );\n for (const row of rows) {\n events.push(rowToEvent(row));\n }\n }\n return events;\n }\n\n async #applyDecision(tx: SqlDriver, d: PutDecision): Promise<void> {\n switch (d.action) {\n case \"skip\":\n return;\n case \"tombstone\":\n await this.#putTombstone(tx, \"id\", d.event.id);\n await this.#deleteTombstone(tx, \"pending\", d.event.id);\n return;\n case \"delete\":\n await this.#persistPlan(tx, d.event.id, d.plan, d.coordIds);\n for (const id of [...d.plan.removeIds, ...d.coordIds]) {\n await this.#deleteEvent(tx, id);\n }\n await this.#insertEvent(tx, d.event);\n return;\n case \"insert\":\n if (d.replaceId !== undefined) {\n await this.#deleteEvent(tx, d.replaceId);\n }\n await this.#insertEvent(tx, d.event);\n }\n }\n\n /** Persist a deletion plan's tombstones, mirroring `DeletionState.absorb`. */\n async #persistPlan(\n tx: SqlDriver,\n deletionId: string,\n plan: DeletionPlan,\n coordIds: ReadonlyArray<string>,\n ): Promise<void> {\n await this.#deleteTombstone(tx, \"pending\", deletionId);\n for (const id of [...plan.removeIds, ...coordIds]) {\n await this.#putTombstone(tx, \"id\", id);\n await this.#deleteTombstone(tx, \"pending\", id);\n }\n for (const p of plan.pendingIds) {\n const covered = await tx.all<{ found: number }>(\n `SELECT 1 AS found FROM tombstones WHERE kind = 'id' AND key = ?`,\n [p.id],\n );\n if (covered.length === 0) {\n await tx.run(\n `INSERT OR REPLACE INTO tombstones (kind, key, pubkey) VALUES ('pending', ?, ?)`,\n [p.id, p.pubkey],\n );\n }\n }\n for (const c of plan.coordinates) {\n await tx.run(\n `INSERT INTO tombstones (kind, key, until) VALUES ('coord', ?, ?)\n ON CONFLICT(kind, key)\n DO UPDATE SET until = MAX(tombstones.until, excluded.until)`,\n [c.key, c.until],\n );\n }\n }\n\n async #putTombstone(tx: SqlDriver, kind: TombstoneKind, key: string): Promise<void> {\n await tx.run(`INSERT OR REPLACE INTO tombstones (kind, key) VALUES (?, ?)`, [kind, key]);\n }\n\n async #deleteTombstone(tx: SqlDriver, kind: TombstoneKind, key: string): Promise<void> {\n await tx.run(`DELETE FROM tombstones WHERE kind = ? AND key = ?`, [kind, key]);\n }\n\n async #insertEvent(tx: SqlDriver, event: Event): Promise<void> {\n await tx.run(\n `INSERT INTO events (id, pubkey, kind, created_at, content, tags, sig, address)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,\n [\n event.id,\n event.pubkey,\n event.kind,\n event.created_at,\n event.content,\n JSON.stringify(event.tags),\n event.sig,\n eventAddress(event) ?? null,\n ],\n );\n for (const tag of event.tags) {\n if (tag[0] === undefined || tag[1] === undefined || tag[0].length !== 1) {\n continue;\n }\n await tx.run(`INSERT INTO tags (event_id, name, value, created_at) VALUES (?, ?, ?, ?)`, [\n event.id,\n tag[0],\n tag[1],\n event.created_at,\n ]);\n }\n }\n\n async #deleteEvent(tx: SqlDriver, id: string): Promise<number> {\n // Explicit: a transaction may run on a connection where foreign_keys is off\n // (expo-sqlite's withExclusiveTransactionAsync), where ON DELETE CASCADE never fires.\n await tx.run(`DELETE FROM tags WHERE event_id = ?`, [id]);\n const result = await tx.run(`DELETE FROM events WHERE id = ?`, [id]);\n return result.changes;\n }\n\n async get(id: string): Promise<Event | undefined> {\n try {\n const rows = await this.#driver.all<EventRow>(\n `SELECT id, pubkey, kind, created_at, content, tags, sig FROM events\n WHERE id = ?`,\n [id.toLowerCase()],\n );\n const row = rows.at(0);\n return row === undefined ? undefined : rowToEvent(row);\n } catch (error) {\n throw toStorageError(error);\n }\n }\n\n async query(filters: ReadonlyArray<Filter>): Promise<Event[]> {\n try {\n const perFilter: Event[][] = [];\n for (const filter of filters) {\n perFilter.push(await this.#filterRows(filter, \"*\"));\n }\n const seen = new Set<string>();\n const events: Event[] = [];\n for (const rows of perFilter) {\n for (const event of rows) {\n if (seen.has(event.id)) {\n continue;\n }\n seen.add(event.id);\n events.push(event);\n }\n }\n return sortEvents(events);\n } catch (error) {\n throw toStorageError(error);\n }\n }\n\n async count(filters: ReadonlyArray<Filter>): Promise<number> {\n try {\n const perFilter: NegentropyItem[][] = [];\n for (const filter of filters) {\n perFilter.push(await this.#filterRows(filter, \"id, created_at\"));\n }\n const seen = new Set<string>();\n for (const rows of perFilter) {\n for (const row of rows) {\n seen.add(row.id);\n }\n }\n return seen.size;\n } catch (error) {\n throw toStorageError(error);\n }\n }\n\n async negentropyItems(filter: Filter): Promise<NegentropyItem[]> {\n try {\n const items = await this.#filterRows(filter, \"id, created_at\");\n return [...items].sort(itemCompare);\n } catch (error) {\n throw toStorageError(error);\n }\n }\n\n /**\n * Rows matching one filter, deduped, newest-first, with `limit` applied. `select` is a trusted\n * column list for the projection.\n */\n async #filterRows(filter: Filter, select: \"*\"): Promise<Event[]>;\n async #filterRows(filter: Filter, select: \"id, created_at\"): Promise<NegentropyItem[]>;\n async #filterRows(\n filter: Filter,\n select: \"*\" | \"id, created_at\",\n ): Promise<Event[] | NegentropyItem[]> {\n if (filter.limit === 0) {\n return [];\n }\n const plans = compileFilter(filter);\n if (plans === undefined) {\n return [];\n }\n\n if (select === \"*\") {\n const merged = await this.#planRows<EventRow>(plans, \"*\", filter.limit);\n return merged.map(rowToEvent);\n }\n return this.#planRows<NegentropyItem>(plans, \"id, created_at\", filter.limit);\n }\n\n /** Rows for the compiled plans of one filter, deduped newest-first, `limit` applied. */\n async #planRows<Row extends { id: string; created_at: number }>(\n plans: ReadonlyArray<FilterPlan>,\n select: string,\n limit: number | undefined,\n ): Promise<Row[]> {\n const tail = ` ORDER BY created_at DESC, id ASC${limit === undefined ? \"\" : \" LIMIT ?\"}`;\n const perPlan: Row[][] = [];\n for (const plan of plans) {\n const where = plan.wheres.length > 0 ? ` WHERE ${plan.wheres.join(\" AND \")}` : \"\";\n const params = limit === undefined ? plan.params : [...plan.params, limit];\n perPlan.push(\n await this.#driver.all<Row>(`SELECT ${select} FROM events${where}${tail}`, params),\n );\n }\n const sorted = perPlan.flat().sort(compareEventsDesc);\n const seen = new Set<string>();\n const merged: Row[] = [];\n for (const row of sorted) {\n if (seen.has(row.id)) {\n continue;\n }\n seen.add(row.id);\n merged.push(row);\n }\n return limit === undefined ? merged : merged.slice(0, limit);\n }\n\n async getOutboxBound(pubkey: string, kind: number): Promise<OutboxBound | undefined> {\n const pk = pubkey.toLowerCase();\n try {\n const rows = await this.#driver.all<{ oldest: number; newest: number }>(\n `SELECT oldest, newest FROM outbox_bounds\n WHERE pubkey = ? AND kind = ?`,\n [pk, kind],\n );\n const row = rows.at(0);\n if (row !== undefined) {\n return { oldest: row.oldest, newest: row.newest };\n }\n const derived = await this.#driver.all<{\n // oxlint-disable-next-line typescript/no-restricted-types -- MIN() of an empty set is SQL NULL\n oldest: number | null;\n // oxlint-disable-next-line typescript/no-restricted-types -- MAX() of an empty set is SQL NULL\n newest: number | null;\n }>(\n `SELECT MIN(created_at) AS oldest, MAX(created_at) AS newest\n FROM events WHERE pubkey = ? AND kind = ?`,\n [pk, kind],\n );\n const d = derived.at(0);\n if (d === undefined || d.oldest === null || d.newest === null) {\n return undefined;\n }\n return { oldest: d.oldest, newest: d.newest };\n } catch (error) {\n throw toStorageError(error);\n }\n }\n\n async setOutboxBound(pubkey: string, kind: number, bound: OutboxBound): Promise<void> {\n const pk = pubkey.toLowerCase();\n await this.#writeQueue.run(async () => {\n try {\n await this.#driver.transaction(async (tx) => {\n await tx.run(\n `INSERT OR REPLACE INTO outbox_bounds (pubkey, kind, oldest, newest)\n VALUES (?, ?, ?, ?)`,\n [pk, kind, bound.oldest, bound.newest],\n );\n });\n } catch (error) {\n throw toStorageError(error);\n }\n });\n }\n\n async remove(ids: ReadonlyArray<string>): Promise<number> {\n const lowered = ids.map((id) => id.toLowerCase());\n return this.#writeQueue.run(async () => {\n try {\n return await this.#driver.transaction(async (tx) => {\n let removed = 0;\n for (const id of lowered) {\n removed += await this.#deleteEvent(tx, id);\n await this.#putTombstone(tx, \"id\", id);\n await this.#deleteTombstone(tx, \"pending\", id);\n }\n return removed;\n });\n } catch (error) {\n throw toStorageError(error);\n }\n });\n }\n\n async clear(): Promise<void> {\n await this.#writeQueue.run(async () => {\n try {\n await this.#driver.transaction(async (tx) => {\n await tx.run(\"DELETE FROM tags\");\n await tx.run(\"DELETE FROM events\");\n await tx.run(\"DELETE FROM tombstones\");\n await tx.run(\"DELETE FROM outbox_bounds\");\n });\n } catch (error) {\n throw toStorageError(error);\n }\n });\n }\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"util-DfsyJ9nz.mjs","names":["bytesToHex","nobleBytesToHex","randomBytes","nobleRandomBytes","hexToBytes","nobleHexToBytes"],"sources":["../src/core/limits.ts","../src/core/error.ts","../src/core/util.ts"],"sourcesContent":["/** NIP-01 event id length in bytes. */\nexport const EVENT_ID_BYTES = 32;\n\n/** BIP-340 signature length in bytes. */\nexport const SIGNATURE_BYTES = 64;\n\n/** BIP-340 public key length in bytes (x-only). */\nexport const PUBLIC_KEY_BYTES = 32;\n\n/** BIP-340 secret key length in bytes. */\nexport const SECRET_KEY_BYTES = 32;\n\n/** NIP-01 subscription id max character length. */\nexport const SUBSCRIPTION_ID_MAX_CHARS = 64;\n","/** Base error for all @qntx/nostr failures. */\nexport class NostrError extends Error {\n override name = \"NostrError\";\n}\n\n/** `error.message` for Error instances, `String(error)` otherwise. */\nexport function errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\n/** The Error itself, or a NostrError carrying `String(value)` for non-Error throws. */\nexport function toError(value: unknown): Error {\n return value instanceof Error ? value : new NostrError(errorMessage(value));\n}\n\n/** Invalid hex encoding or length. */\nexport class HexError extends NostrError {\n override name = \"HexError\";\n}\n\n/** Invalid URL / relay URL. */\nexport class UrlError extends NostrError {\n override name = \"UrlError\";\n}\n\n/** Event shape / wire validation failure. */\nexport class EventValidationError extends NostrError {\n override name = \"EventValidationError\";\n}\n\n/** Cryptographic operation failure (keys, signatures). */\nexport class CryptoError extends NostrError {\n override name = \"CryptoError\";\n}\n\n/** Message parse / encode failure. */\nexport class MessageError extends NostrError {\n override name = \"MessageError\";\n}\n","import {\n bytesToHex as nobleBytesToHex,\n hexToBytes as nobleHexToBytes,\n randomBytes as nobleRandomBytes,\n} from \"@noble/hashes/utils.js\";\n\nimport { HexError, UrlError } from \"./error.ts\";\nimport { SECRET_KEY_BYTES } from \"./limits.ts\";\n\nexport const utf8Encoder: TextEncoder = new TextEncoder();\nexport const utf8Decoder: TextDecoder = new TextDecoder();\n\nconst HEX32_RE = /^[0-9a-f]{64}$/;\nconst HEX64_RE = /^[0-9a-f]{128}$/;\n\n/** Lowercase hex encode. */\nexport function bytesToHex(bytes: Uint8Array): string {\n return nobleBytesToHex(bytes);\n}\n\n/** Cryptographically secure random bytes (Web Crypto under the hood). */\nexport function randomBytes(length: number): Uint8Array {\n return nobleRandomBytes(length);\n}\n\n/** Decode lowercase or mixed-case hex to bytes. */\nexport function hexToBytes(hex: string): Uint8Array {\n try {\n return nobleHexToBytes(hex);\n } catch (error) {\n throw new HexError(`invalid hex string of length ${hex.length}`, {\n cause: error,\n });\n }\n}\n\n/** True when value is canonical NIP-01 lowercase hex of 32 bytes (64 chars). */\nexport function isHex32(value: string): boolean {\n return HEX32_RE.test(value);\n}\n\n/** True when value is canonical NIP-01 lowercase hex of 64 bytes (128 chars). */\nexport function isHex64(value: string): boolean {\n return HEX64_RE.test(value);\n}\n\n/** Caller input of any case: lowercases first, then requires canonical hex shape. */\nexport function assertHex32(value: string, label: string): string {\n const normalized = value.toLowerCase();\n if (!isHex32(normalized)) {\n throw new HexError(`invalid ${label}: expected 64-char hex`);\n }\n return normalized;\n}\n\nexport function assertByteLength(bytes: Uint8Array, expected: number, label: string): void {\n if (bytes.length !== expected) {\n throw new HexError(`invalid ${label} length: expected ${expected}, got ${bytes.length}`);\n }\n}\n\nexport function assertSecretKeyBytes(bytes: Uint8Array): void {\n assertByteLength(bytes, SECRET_KEY_BYTES, \"secret key\");\n}\n\n/** Current unix time in whole seconds (NIP-01 `created_at`). */\nexport function nowSeconds(): number {\n return Math.floor(Date.now() / 1000);\n}\n\n/** Structural guard: a plain record (not `null`, not an array). */\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * True when `value` contains a UTF-16 surrogate code unit without its other half (NIP-01 events\n * reject these; `JSON.parse` accepts `\\ud800`-style escapes, so validate after parsing).\n * Char-by-char because Hermes may lack `String.prototype.isWellFormed`.\n */\nexport function hasLoneSurrogate(value: string): boolean {\n // Code-unit scan: a high surrogate must be followed by a low surrogate; a\n // lone low surrogate fails on its own.\n for (let i = 0; i < value.length; i++) {\n // oxlint-disable-next-line unicorn/prefer-code-point -- surrogate halves need code units, not code points\n const code = value.charCodeAt(i);\n if (code < 0xd800 || code > 0xdfff) {\n continue;\n }\n if (code <= 0xdbff) {\n // oxlint-disable-next-line unicorn/prefer-code-point -- surrogate halves need code units, not code points\n const next = value.charCodeAt(i + 1);\n if (next >= 0xdc00 && next <= 0xdfff) {\n i += 1;\n continue;\n }\n }\n return true;\n }\n return false;\n}\n\n/**\n * Deep form of {@link hasLoneSurrogate}: true when any string in `value` — including record keys —\n * carries an unpaired surrogate. `JSON.parse` accepts `\\ud800`-style escapes that serde_json\n * rejects, so JSON codecs validate the parsed value with this before the shape checks to keep both\n * languages agreeing on the invalid-JSON error (N10).\n */\nexport function containsLoneSurrogate(value: unknown): boolean {\n if (typeof value === \"string\") {\n return hasLoneSurrogate(value);\n }\n if (Array.isArray(value)) {\n return value.some(containsLoneSurrogate);\n }\n if (isRecord(value)) {\n return Object.entries(value).some(\n ([key, entry]) => hasLoneSurrogate(key) || containsLoneSurrogate(entry),\n );\n }\n return false;\n}\n\n/** Mutable view of a readonly exported type, for local construction. */\nexport type Mutable<T> = { -readonly [K in keyof T]: T[K] };\n\n/**\n * Normalize a relay URL to a stable form: `http:`/`https:` are rewritten to `ws:`/`wss:` (a bare\n * host gets `wss://`), any other scheme throws UrlError. The result has a lowercased host, the\n * default port removed, duplicate path slashes collapsed, a sorted query, and no fragment. `URL`\n * serialization keeps a trailing `/` on the root path (`wss://a.example/`).\n */\nexport function normalizeURL(url: string): string {\n try {\n let input = url;\n if (!input.includes(\"://\")) {\n input = `wss://${input}`;\n }\n const p = new URL(input);\n if (p.protocol === \"http:\") {\n p.protocol = \"ws:\";\n } else if (p.protocol === \"https:\") {\n p.protocol = \"wss:\";\n }\n if (p.protocol !== \"ws:\" && p.protocol !== \"wss:\") {\n throw new UrlError(`unsupported relay URL scheme: ${p.protocol}`);\n }\n p.pathname = p.pathname.replaceAll(/\\/+/g, \"/\");\n if (p.pathname.endsWith(\"/\") && p.pathname.length > 1) {\n p.pathname = p.pathname.slice(0, -1);\n }\n if ((p.port === \"80\" && p.protocol === \"ws:\") || (p.port === \"443\" && p.protocol === \"wss:\")) {\n p.port = \"\";\n }\n p.searchParams.sort();\n p.hash = \"\";\n return p.toString();\n } catch (error) {\n if (error instanceof UrlError) {\n throw error;\n }\n throw new UrlError(`invalid URL: ${url}`, {\n cause: error,\n });\n }\n}\n\n/**\n * Normalize each entry like {@link normalizeURL}; empty or invalid entries are skipped and the\n * results are deduplicated in first-seen order.\n */\nexport function normalizeRelayUrls(urls: Iterable<string>): string[] {\n const seen = new Set<string>();\n for (const url of urls) {\n if (url === \"\") {\n continue;\n }\n try {\n seen.add(normalizeURL(url));\n } catch {\n // invalid relay urls are skipped\n }\n }\n return [...seen];\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"verifier-DEHUy4AD.mjs","names":[],"sources":["../src/core/verifier.ts"],"sourcesContent":["import { schnorr } from \"@noble/curves/secp256k1.js\";\nimport { sha256 } from \"@noble/hashes/sha2.js\";\n\nimport {\n isMarkedFailed,\n isMarkedVerified,\n markUnverified,\n markVerified,\n serializeValidatedEvent,\n validateSignedEvent,\n} from \"./event.ts\";\nimport type { Event } from \"./event.ts\";\nimport { hexToBytes, utf8Encoder } from \"./util.ts\";\n\n/**\n * Verification backend: return true iff `sha256(serializedUtf8)` equals `id` and `sig` is a valid\n * BIP-340 signature of `id` by the x-only `pubkey`. Inputs are raw bytes (`id`/`pubkey` 32, `sig`\n * 64). Exceptions propagate through the built verifier unmarked — a throwing backend is broken and\n * must surface.\n */\nexport type SerializedEventVerifier = (\n serializedUtf8: Uint8Array,\n id: Uint8Array,\n pubkey: Uint8Array,\n sig: Uint8Array,\n) => boolean;\n\n/** Whole-event verifier: return true iff the event's id and BIP-340 signature verify. */\nexport type EventVerifier = (event: Event) => boolean;\n\n/**\n * Build an event verifier with exactly the semantics of {@link verifyEvent}, delegating the hash and\n * signature check to `backend`. Verified/failed results share `verifyEvent`'s WeakSet caches, so\n * verifiers built this way and `verifyEvent` agree on (and reuse) each other's results.\n */\nexport function createEventVerifier(backend: SerializedEventVerifier): EventVerifier {\n return (event) => {\n if (isMarkedVerified(event)) {\n return true;\n }\n if (isMarkedFailed(event)) {\n return false;\n }\n if (!validateSignedEvent(event)) {\n markUnverified(event);\n return false;\n }\n const ok = backend(\n utf8Encoder.encode(serializeValidatedEvent(event)),\n hexToBytes(event.id),\n hexToBytes(event.pubkey),\n hexToBytes(event.sig),\n );\n if (ok) {\n markVerified(event);\n } else {\n markUnverified(event);\n }\n return ok;\n };\n}\n\nconst nobleSerializedVerify: SerializedEventVerifier = (serializedUtf8, id, pubkey, sig) => {\n try {\n const digest = sha256(serializedUtf8);\n if (!digest.every((byte, i) => byte === id[i])) {\n return false;\n }\n return schnorr.verify(sig, id, pubkey);\n } catch {\n return false;\n }\n};\n\n/** Verify event id and BIP-340 signature. Uses WeakSet cache (does not mutate the event). */\nexport const verifyEvent: EventVerifier = createEventVerifier(nobleSerializedVerify);\n"],"mappings":";;;;;;;;;;AAmCA,SAAgB,oBAAoB,SAAiD;CACnF,QAAQ,UAAU;EAChB,IAAI,iBAAiB,KAAK,GACxB,OAAO;EAET,IAAI,eAAe,KAAK,GACtB,OAAO;EAET,IAAI,CAAC,oBAAoB,KAAK,GAAG;GAC/B,eAAe,KAAK;GACpB,OAAO;EACT;EACA,MAAM,KAAK,QACT,YAAY,OAAO,wBAAwB,KAAK,CAAC,GACjD,WAAW,MAAM,EAAE,GACnB,WAAW,MAAM,MAAM,GACvB,WAAW,MAAM,GAAG,CACtB;EACA,IAAI,IACF,aAAa,KAAK;OAElB,eAAe,KAAK;EAEtB,OAAO;CACT;AACF;AAEA,MAAM,yBAAkD,gBAAgB,IAAI,QAAQ,QAAQ;CAC1F,IAAI;EAEF,IAAI,CADW,OAAO,cACZ,CAAC,CAAC,OAAO,MAAM,MAAM,SAAS,GAAG,EAAE,GAC3C,OAAO;EAET,OAAO,QAAQ,OAAO,KAAK,IAAI,MAAM;CACvC,QAAQ;EACN,OAAO;CACT;AACF;;AAGA,MAAa,cAA6B,oBAAoB,qBAAqB"}