@qntx/nostr 0.12.1 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{builder-BrmLOVj_.d.mts → builder-C72nJITG.d.mts} +2 -2
- package/dist/{builder-Cf2N12CG.mjs → builder-CGgM_QHT.mjs} +5 -5
- package/dist/{builder-Cf2N12CG.mjs.map → builder-CGgM_QHT.mjs.map} +1 -1
- package/dist/{client-B3_vToDX.mjs → client-COkEihj_.mjs} +87 -79
- package/dist/client-COkEihj_.mjs.map +1 -0
- package/dist/client.d.mts +1 -1
- package/dist/client.mjs +1 -1
- package/dist/core.d.mts +7 -7
- package/dist/core.mjs +9 -9
- package/dist/{emitter-DkCY6NkU.mjs → emitter-u4kJqQDk.mjs} +2 -2
- package/dist/{emitter-DkCY6NkU.mjs.map → emitter-u4kJqQDk.mjs.map} +1 -1
- package/dist/{error-BquXDmIN.d.mts → error-BPqAU5RB.d.mts} +2 -6
- package/dist/{error-BD_0QFKT.d.mts → error-BjSUsx6M.d.mts} +2 -2
- package/dist/{error-D3VNRVhL.mjs → error-Dyb89ahb.mjs} +2 -2
- package/dist/{error-D3VNRVhL.mjs.map → error-Dyb89ahb.mjs.map} +1 -1
- package/dist/{error-DH4guike.mjs → error-QyECUDIp.mjs} +2 -2
- package/dist/{error-DH4guike.mjs.map → error-QyECUDIp.mjs.map} +1 -1
- package/dist/{event-B3ua2_oL.mjs → event-DvM1f03b.mjs} +7 -3
- package/dist/event-DvM1f03b.mjs.map +1 -0
- package/dist/{filter-B16P33dz.mjs → filter-3jOh4uLN.mjs} +50 -20
- package/dist/filter-3jOh4uLN.mjs.map +1 -0
- package/dist/{filter-PG7r79rt.d.mts → filter-oYG2zjJ3.d.mts} +6 -2
- package/dist/{gossip-u0iBlZ1U.d.mts → gossip-BvCItMVZ.d.mts} +3 -3
- package/dist/{gossip-K_JIexQs.mjs → gossip-CRyhK6E6.mjs} +4 -4
- package/dist/{gossip-K_JIexQs.mjs.map → gossip-CRyhK6E6.mjs.map} +1 -1
- package/dist/gossip.d.mts +1 -1
- package/dist/gossip.mjs +1 -1
- package/dist/http-CuazNRIP.mjs.map +1 -1
- package/dist/{index-DD0Ywpay.d.mts → index-B2JlP2Jf.d.mts} +9 -9
- package/dist/{index-yq9xkONo.d.mts → index-DCobP2Jj.d.mts} +10 -10
- package/dist/{index-Hp26bgYG.d.mts → index-Dpj_MUxb.d.mts} +8 -5
- package/dist/{index-rKcmI6ex.d.mts → index-DzHmhHqD.d.mts} +3 -3
- package/dist/{index-SwMknboJ.d.mts → index-YID-zPDF.d.mts} +7 -7
- package/dist/{index-DxQO1eWA.d.mts → index-muydfuYD.d.mts} +21 -21
- package/dist/index.d.mts +19 -20
- package/dist/index.mjs +20 -20
- package/dist/{key-B218_SoL.mjs → key-D_oEutQ6.mjs} +5 -7
- package/dist/{key-B218_SoL.mjs.map → key-D_oEutQ6.mjs.map} +1 -1
- package/dist/{key-CwMVo14x.d.mts → key-DojW2QZl.d.mts} +3 -5
- package/dist/{kind-D8fENiiL.mjs → kind-I2Tvbe82.mjs} +6 -5
- package/dist/kind-I2Tvbe82.mjs.map +1 -0
- package/dist/{loaders-Xdbx-K61.mjs → loaders-BRh2riV9.mjs} +17 -16
- package/dist/loaders-BRh2riV9.mjs.map +1 -0
- package/dist/loaders.d.mts +2 -2
- package/dist/loaders.mjs +1 -1
- package/dist/{memory-VyPnOBU3.mjs → memory-D_GdEQYG.mjs} +2 -2
- package/dist/memory-D_GdEQYG.mjs.map +1 -0
- package/dist/memory-index-3GSrPIWj.mjs +671 -0
- package/dist/memory-index-3GSrPIWj.mjs.map +1 -0
- package/dist/{message-pYfVKxPn.mjs → message-BMfreCQc.mjs} +6 -6
- package/dist/message-BMfreCQc.mjs.map +1 -0
- package/dist/{nip17-BM4hJYBq.d.mts → nip17-BZvFQLcs.d.mts} +4 -4
- package/dist/{nip19-DpPN17bY.d.mts → nip19-D4RKn6GC.d.mts} +2 -2
- package/dist/{nip46-BHaxC9TZ.d.mts → nip46-i8L8_nIm.d.mts} +2 -2
- package/dist/{nip51-D_bu8Hph.d.mts → nip51-C_kTYcud.d.mts} +2 -2
- package/dist/{nip59-CGWfoItR.d.mts → nip59-DfJ6Moeu.d.mts} +2 -2
- package/dist/{nip65-DaWMg943.d.mts → nip65-CPNE0SV0.d.mts} +2 -2
- package/dist/{nip77-wHA_nEV5.d.mts → nip77-DhpoL7d8.d.mts} +2 -2
- package/dist/nips/blossom.d.mts +2 -2
- package/dist/nips/blossom.mjs +3 -3
- package/dist/nips/blossom.mjs.map +1 -1
- package/dist/nips/nip04.d.mts +9 -12
- package/dist/nips/nip04.mjs +38 -45
- package/dist/nips/nip04.mjs.map +1 -1
- package/dist/nips/nip05.d.mts +6 -3
- package/dist/nips/nip05.mjs +6 -3
- package/dist/nips/nip05.mjs.map +1 -1
- package/dist/nips/nip10.d.mts +2 -2
- package/dist/nips/nip10.mjs +4 -4
- package/dist/nips/nip11.d.mts +1 -1
- package/dist/nips/nip11.mjs +1 -1
- package/dist/nips/nip13.d.mts +1 -1
- package/dist/nips/nip13.mjs +1 -1
- package/dist/nips/nip13.mjs.map +1 -1
- package/dist/nips/nip17.d.mts +1 -1
- package/dist/nips/nip17.mjs +4 -4
- package/dist/nips/nip17.mjs.map +1 -1
- package/dist/nips/nip19.d.mts +1 -1
- package/dist/nips/nip19.mjs +2 -2
- package/dist/nips/nip19.mjs.map +1 -1
- package/dist/nips/nip21.d.mts +6 -3
- package/dist/nips/nip21.mjs +8 -5
- package/dist/nips/nip21.mjs.map +1 -1
- package/dist/nips/nip27.d.mts +1 -1
- package/dist/nips/nip27.mjs +15 -9
- package/dist/nips/nip27.mjs.map +1 -1
- package/dist/nips/nip42.mjs +2 -2
- package/dist/nips/nip44.mjs +1 -1
- package/dist/nips/nip46.d.mts +1 -1
- package/dist/nips/nip46.mjs +9 -7
- package/dist/nips/nip46.mjs.map +1 -1
- package/dist/nips/nip49.d.mts +1 -1
- package/dist/nips/nip49.mjs +1 -1
- package/dist/nips/nip51.d.mts +1 -1
- package/dist/nips/nip51.mjs +5 -5
- package/dist/nips/nip51.mjs.map +1 -1
- package/dist/nips/nip57.mjs +6 -5
- package/dist/nips/nip57.mjs.map +1 -1
- package/dist/nips/nip59.d.mts +1 -1
- package/dist/nips/nip59.mjs +5 -5
- package/dist/nips/nip59.mjs.map +1 -1
- package/dist/nips/nip65.d.mts +1 -1
- package/dist/nips/nip65.mjs +3 -3
- package/dist/nips/nip77.d.mts +1 -1
- package/dist/nips/nip77.mjs +1 -1
- package/dist/nips/nip77.mjs.map +1 -1
- package/dist/nips/nip98.d.mts +1 -1
- package/dist/nips/nip98.mjs +9 -6
- package/dist/nips/nip98.mjs.map +1 -1
- package/dist/{pool-DUuYJ0x4.d.mts → pool-MehgIIzm.d.mts} +20 -16
- package/dist/{pool-THuuOW4S.mjs → pool-lMT_mvzn.mjs} +25 -25
- package/dist/pool-lMT_mvzn.mjs.map +1 -0
- package/dist/{put-DRmV1wHD.mjs → put-BEM5j8xK.mjs} +5 -5
- package/dist/{put-DRmV1wHD.mjs.map → put-BEM5j8xK.mjs.map} +1 -1
- package/dist/{reactive-jyKrA9ej.d.mts → reactive-B7lPFgTS.d.mts} +3 -3
- package/dist/{reactive--pDpLt2H.mjs → reactive-DFHY-f00.mjs} +24 -9
- package/dist/reactive-DFHY-f00.mjs.map +1 -0
- package/dist/{relay-BGPqiGKF.mjs → relay-DIR77xQ2.mjs} +102 -89
- package/dist/relay-DIR77xQ2.mjs.map +1 -0
- package/dist/relay.d.mts +5 -5
- package/dist/relay.mjs +3 -3
- package/dist/{report-CHld4uWj.mjs → report-D2BM5dgn.mjs} +2 -2
- package/dist/{report-CHld4uWj.mjs.map → report-D2BM5dgn.mjs.map} +1 -1
- package/dist/{signer-CNTIeYH4.mjs → signer-DJcmC4nA.mjs} +17 -17
- package/dist/signer-DJcmC4nA.mjs.map +1 -0
- package/dist/signer.d.mts +1 -1
- package/dist/signer.mjs +2 -2
- package/dist/{storage-B5Wdt_Mn.mjs → storage-n2BE1bdY.mjs} +108 -179
- package/dist/storage-n2BE1bdY.mjs.map +1 -0
- package/dist/storage.d.mts +3 -3
- package/dist/storage.mjs +4 -4
- package/dist/store.d.mts +1 -1
- package/dist/store.mjs +1 -1
- package/dist/testing.d.mts +4 -4
- package/dist/testing.mjs +35 -10
- package/dist/testing.mjs.map +1 -1
- package/dist/{types-CqhnT-Mx.d.mts → types-DiVVyf1e.d.mts} +2 -2
- package/dist/{util-DDpPkiED.mjs → util-DfsyJ9nz.mjs} +30 -10
- package/dist/util-DfsyJ9nz.mjs.map +1 -0
- package/dist/{verifier-CjH1vnP2.mjs → verifier-DEHUy4AD.mjs} +3 -3
- package/dist/{verifier-CjH1vnP2.mjs.map → verifier-DEHUy4AD.mjs.map} +1 -1
- package/dist/{verifier-Cl24c9ph.d.mts → verifier-DhAbqVJF.d.mts} +2 -2
- package/dist/{websocket-CmeqvAkp.d.mts → websocket-DaCvJkF9.d.mts} +2 -5
- package/package.json +4 -3
- package/dist/client-B3_vToDX.mjs.map +0 -1
- package/dist/event-B3ua2_oL.mjs.map +0 -1
- package/dist/filter-B16P33dz.mjs.map +0 -1
- package/dist/kind-D8fENiiL.mjs.map +0 -1
- package/dist/loaders-Xdbx-K61.mjs.map +0 -1
- package/dist/memory-VyPnOBU3.mjs.map +0 -1
- package/dist/memory-index-Bhwe0ZdG.mjs +0 -374
- package/dist/memory-index-Bhwe0ZdG.mjs.map +0 -1
- package/dist/message-pYfVKxPn.mjs.map +0 -1
- package/dist/pool-THuuOW4S.mjs.map +0 -1
- package/dist/reactive--pDpLt2H.mjs.map +0 -1
- package/dist/relay-BGPqiGKF.mjs.map +0 -1
- package/dist/signer-CNTIeYH4.mjs.map +0 -1
- package/dist/storage-B5Wdt_Mn.mjs.map +0 -1
- package/dist/util-DDpPkiED.mjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"put-DRmV1wHD.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
|
+
{"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,6 +1,6 @@
|
|
|
1
1
|
import { t as Event } from "./event-j3BwI5dF.mjs";
|
|
2
|
-
import { t as Filter } from "./filter-
|
|
3
|
-
import { i as PutResult } from "./types-
|
|
2
|
+
import { t as Filter } from "./filter-oYG2zjJ3.mjs";
|
|
3
|
+
import { i as PutResult } from "./types-DiVVyf1e.mjs";
|
|
4
4
|
//#region src/store/reactive.d.ts
|
|
5
5
|
/** A reactive snapshot source compatible with `useSyncExternalStore`. */
|
|
6
6
|
type Watch<T> = {
|
|
@@ -59,4 +59,4 @@ declare class ReactiveEventStore {
|
|
|
59
59
|
}
|
|
60
60
|
//#endregion
|
|
61
61
|
export { Watch as i, ReactiveEventStoreEventMap as n, ReactiveEventStoreOptions as r, ReactiveEventStore as t };
|
|
62
|
-
//# sourceMappingURL=reactive-
|
|
62
|
+
//# sourceMappingURL=reactive-B7lPFgTS.d.mts.map
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as Kind } from "./kind-
|
|
3
|
-
import { S as parseEventAddress, _ as eventAddress, o as itemCompare, y as formatEventAddress } from "./event-
|
|
4
|
-
import {
|
|
5
|
-
import { t as invokeSafely } from "./report-
|
|
6
|
-
import { t as Emitter } from "./emitter-
|
|
1
|
+
import { f as normalizeURL } from "./util-DfsyJ9nz.mjs";
|
|
2
|
+
import { t as Kind } from "./kind-I2Tvbe82.mjs";
|
|
3
|
+
import { S as parseEventAddress, _ as eventAddress, o as itemCompare, y as formatEventAddress } from "./event-DvM1f03b.mjs";
|
|
4
|
+
import { r as filterFingerprint, s as matchFilters } from "./filter-3jOh4uLN.mjs";
|
|
5
|
+
import { n as reportError, t as invokeSafely } from "./report-D2BM5dgn.mjs";
|
|
6
|
+
import { t as Emitter } from "./emitter-u4kJqQDk.mjs";
|
|
7
7
|
import { i as trimOldest, n as removeFromSetMap, t as addToSetMap } from "./collections-DtwMc1NU.mjs";
|
|
8
|
-
import { t as MemoryIndex } from "./memory-index-
|
|
8
|
+
import { t as MemoryIndex } from "./memory-index-3GSrPIWj.mjs";
|
|
9
9
|
//#region src/store/reactive.ts
|
|
10
10
|
const SEEN_ON_PER_ID = 16;
|
|
11
|
+
/** Notify passes per microtask; overflow reschedules so a writing subscriber cannot livelock. */
|
|
12
|
+
const MAX_FLUSH_PASSES = 32;
|
|
11
13
|
var WatchImpl = class {
|
|
12
14
|
host;
|
|
13
15
|
kind;
|
|
@@ -296,7 +298,12 @@ var ReactiveEventStore = class {
|
|
|
296
298
|
this.#recency.add(id);
|
|
297
299
|
}
|
|
298
300
|
#recordSeen(id, relayUrl) {
|
|
299
|
-
|
|
301
|
+
let url;
|
|
302
|
+
try {
|
|
303
|
+
url = normalizeURL(relayUrl);
|
|
304
|
+
} catch {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
300
307
|
let urls = this.#seenOn.get(id);
|
|
301
308
|
if (!urls) {
|
|
302
309
|
urls = [];
|
|
@@ -358,10 +365,18 @@ var ReactiveEventStore = class {
|
|
|
358
365
|
if (this.#flushScheduled) return;
|
|
359
366
|
this.#flushScheduled = true;
|
|
360
367
|
queueMicrotask(() => {
|
|
368
|
+
let passes = 0;
|
|
361
369
|
while (this.#dirty.size > 0) {
|
|
362
370
|
const batch = [...this.#dirty];
|
|
363
371
|
this.#dirty.clear();
|
|
364
372
|
for (const watch of batch) watch.notify();
|
|
373
|
+
passes += 1;
|
|
374
|
+
if (passes >= MAX_FLUSH_PASSES && this.#dirty.size > 0) {
|
|
375
|
+
this.#flushScheduled = false;
|
|
376
|
+
reportError(/* @__PURE__ */ new Error("ReactiveEventStore flush overflow: a subscriber keeps writing"));
|
|
377
|
+
this.#scheduleFlush();
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
365
380
|
}
|
|
366
381
|
this.#flushScheduled = false;
|
|
367
382
|
});
|
|
@@ -392,4 +407,4 @@ var ReactiveEventStore = class {
|
|
|
392
407
|
//#endregion
|
|
393
408
|
export { ReactiveEventStore as t };
|
|
394
409
|
|
|
395
|
-
//# sourceMappingURL=reactive
|
|
410
|
+
//# sourceMappingURL=reactive-DFHY-f00.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
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,12 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as verifyEvent } from "./verifier-
|
|
3
|
-
import { n as canonicalizeFilters,
|
|
4
|
-
import { n as createSubscriptionId, r as encodeClientMessage, s as parseRelayMessage, t as assertSubscriptionId } from "./message-
|
|
5
|
-
import { t as NoSignerError } from "./error-
|
|
1
|
+
import { b as MessageError, f as normalizeURL, p as nowSeconds, w as toError, x as NostrError } from "./util-DfsyJ9nz.mjs";
|
|
2
|
+
import { n as verifyEvent } from "./verifier-DEHUy4AD.mjs";
|
|
3
|
+
import { n as canonicalizeFilters, r as filterFingerprint, s as matchFilters, t as canonicalizeFilter } from "./filter-3jOh4uLN.mjs";
|
|
4
|
+
import { n as createSubscriptionId, r as encodeClientMessage, s as parseRelayMessage, t as assertSubscriptionId } from "./message-BMfreCQc.mjs";
|
|
5
|
+
import { t as NoSignerError } from "./error-Dyb89ahb.mjs";
|
|
6
6
|
import { i as throwIfAborted, n as onAbort, r as raceSignal, t as abortReason } from "./abort-DBq2STPj.mjs";
|
|
7
|
-
import { t as invokeSafely } from "./report-
|
|
7
|
+
import { t as invokeSafely } from "./report-D2BM5dgn.mjs";
|
|
8
8
|
import { isAuthRequired, makeAuthEvent } from "./nips/nip42.mjs";
|
|
9
|
-
import { t as Emitter } from "./emitter-
|
|
9
|
+
import { t as Emitter } from "./emitter-u4kJqQDk.mjs";
|
|
10
10
|
import { Nip77Error, runNegSession } from "./nips/nip77.mjs";
|
|
11
11
|
//#region src/relay/error.ts
|
|
12
12
|
/** Base class for relay errors; carries the relay URL when known. */
|
|
@@ -49,20 +49,6 @@ var RelaySuspendedError = class extends RelayError {
|
|
|
49
49
|
}
|
|
50
50
|
};
|
|
51
51
|
//#endregion
|
|
52
|
-
//#region src/relay/websocket.ts
|
|
53
|
-
let impl;
|
|
54
|
-
try {
|
|
55
|
-
if (globalThis.WebSocket !== void 0) impl = globalThis.WebSocket;
|
|
56
|
-
} catch {}
|
|
57
|
-
/** Inject a WebSocket implementation (required on Node unless undici/global is present). */
|
|
58
|
-
function useWebSocketImplementation(websocketImplementation) {
|
|
59
|
-
impl = websocketImplementation;
|
|
60
|
-
}
|
|
61
|
-
function getWebSocketImplementation() {
|
|
62
|
-
if (!impl) throw new RelayConnectionError("No WebSocket implementation available; call useWebSocketImplementation() or run in a browser");
|
|
63
|
-
return impl;
|
|
64
|
-
}
|
|
65
|
-
//#endregion
|
|
66
52
|
//#region src/relay/auth.ts
|
|
67
53
|
var RelayAuth = class {
|
|
68
54
|
#challenge;
|
|
@@ -266,7 +252,7 @@ var CountTracker = class {
|
|
|
266
252
|
}
|
|
267
253
|
#finish(waiter) {
|
|
268
254
|
this.clearTimer(waiter);
|
|
269
|
-
this.#entries.delete(waiter.id);
|
|
255
|
+
if (this.#entries.get(waiter.id) === waiter) this.#entries.delete(waiter.id);
|
|
270
256
|
}
|
|
271
257
|
};
|
|
272
258
|
//#endregion
|
|
@@ -298,11 +284,11 @@ var Subscription = class {
|
|
|
298
284
|
this.filters = filters;
|
|
299
285
|
this.closeOnEose = opts.closeOnEose === true;
|
|
300
286
|
this.#handlers = {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
287
|
+
onEvent: opts.onEvent,
|
|
288
|
+
onEose: opts.onEose,
|
|
289
|
+
onClose: opts.onClose,
|
|
304
290
|
alreadyHaveEvent: opts.alreadyHaveEvent,
|
|
305
|
-
|
|
291
|
+
onEventSeen: opts.onEventSeen
|
|
306
292
|
};
|
|
307
293
|
if (opts.eoseTimeoutMs !== void 0) this.#eoseTimer = setTimeout(() => {
|
|
308
294
|
this.#eoseTimer = void 0;
|
|
@@ -327,21 +313,21 @@ var Subscription = class {
|
|
|
327
313
|
}
|
|
328
314
|
/**
|
|
329
315
|
* Local close: idempotent; clears the EOSE timer and abort listener, detaches via `onLocalClose`
|
|
330
|
-
* (sends CLOSE / leaves the live group), then fires `
|
|
316
|
+
* (sends CLOSE / leaves the live group), then fires `onClose`.
|
|
331
317
|
*/
|
|
332
318
|
close(reason = "closed by client") {
|
|
333
319
|
if (this.#closed) return;
|
|
334
320
|
this.#closed = true;
|
|
335
321
|
this.#teardown();
|
|
336
322
|
this.#onLocalClose(this);
|
|
337
|
-
invokeSafely(() => this.#handlers.
|
|
323
|
+
invokeSafely(() => this.#handlers.onClose?.(reason));
|
|
338
324
|
}
|
|
339
325
|
/** Remote/transport termination: like {@link close} but `onLocalClose` is not called. */
|
|
340
326
|
end(reason) {
|
|
341
327
|
if (this.#closed) return;
|
|
342
328
|
this.#closed = true;
|
|
343
329
|
this.#teardown();
|
|
344
|
-
invokeSafely(() => this.#handlers.
|
|
330
|
+
invokeSafely(() => this.#handlers.onClose?.(reason));
|
|
345
331
|
}
|
|
346
332
|
#teardown() {
|
|
347
333
|
if (this.#eoseTimer !== void 0) {
|
|
@@ -351,7 +337,7 @@ var Subscription = class {
|
|
|
351
337
|
this.#abortDispose?.();
|
|
352
338
|
this.#abortDispose = void 0;
|
|
353
339
|
}
|
|
354
|
-
/** Mark EOSE: fires `
|
|
340
|
+
/** Mark EOSE: fires `onEose`, then closes when `closeOnEose`. No-op when closed or EOSE'd. */
|
|
355
341
|
markEose() {
|
|
356
342
|
if (this.#closed || this.#eosed) return;
|
|
357
343
|
this.#eosed = true;
|
|
@@ -359,7 +345,7 @@ var Subscription = class {
|
|
|
359
345
|
clearTimeout(this.#eoseTimer);
|
|
360
346
|
this.#eoseTimer = void 0;
|
|
361
347
|
}
|
|
362
|
-
invokeSafely(() => this.#handlers.
|
|
348
|
+
invokeSafely(() => this.#handlers.onEose?.());
|
|
363
349
|
if (this.closeOnEose) this.close("eose");
|
|
364
350
|
}
|
|
365
351
|
/** Clear `eosed` for a replayed REQ (AUTH retry / reconnect). */
|
|
@@ -375,9 +361,9 @@ var Subscription = class {
|
|
|
375
361
|
resetAuthRetry() {
|
|
376
362
|
this.#authRetried = false;
|
|
377
363
|
}
|
|
378
|
-
/** `
|
|
364
|
+
/** `onEventSeen` for every EVENT id, before watermark/alreadyHaveEvent/verify. */
|
|
379
365
|
notifyReceived(id) {
|
|
380
|
-
invokeSafely(() => this.#handlers.
|
|
366
|
+
invokeSafely(() => this.#handlers.onEventSeen?.(id));
|
|
381
367
|
}
|
|
382
368
|
alreadyHas(id) {
|
|
383
369
|
let have = false;
|
|
@@ -389,10 +375,10 @@ var Subscription = class {
|
|
|
389
375
|
isAtWatermark(id) {
|
|
390
376
|
return this.#idsAtWatermark.has(id);
|
|
391
377
|
}
|
|
392
|
-
/** Verified event: advance the watermark, then fire `
|
|
378
|
+
/** Verified event: advance the watermark, then fire `onEvent`. */
|
|
393
379
|
deliver(event) {
|
|
394
380
|
this.noteVerified(event);
|
|
395
|
-
invokeSafely(() => this.#handlers.
|
|
381
|
+
invokeSafely(() => this.#handlers.onEvent?.(event));
|
|
396
382
|
}
|
|
397
383
|
/**
|
|
398
384
|
* Advance the reconnect watermark after a verified EVENT. A far-future `created_at` (buggy or
|
|
@@ -429,16 +415,17 @@ function subscriptionToAsyncIterable(start, opts) {
|
|
|
429
415
|
let head = 0;
|
|
430
416
|
let done = false;
|
|
431
417
|
let error;
|
|
432
|
-
let
|
|
418
|
+
let waiters = [];
|
|
433
419
|
let closer;
|
|
434
420
|
let localClose = false;
|
|
435
421
|
let disposeAbort;
|
|
436
422
|
const notify = () => {
|
|
437
|
-
|
|
438
|
-
|
|
423
|
+
const pending = waiters;
|
|
424
|
+
waiters = [];
|
|
425
|
+
for (const wake of pending) wake();
|
|
439
426
|
};
|
|
440
427
|
const waitForWake = async () => new Promise((resolve) => {
|
|
441
|
-
|
|
428
|
+
waiters.push(resolve);
|
|
442
429
|
});
|
|
443
430
|
const closeLocal = (reason) => {
|
|
444
431
|
localClose = true;
|
|
@@ -448,14 +435,11 @@ function subscriptionToAsyncIterable(start, opts) {
|
|
|
448
435
|
notify();
|
|
449
436
|
};
|
|
450
437
|
closer = start({
|
|
451
|
-
|
|
438
|
+
onEvent(event) {
|
|
452
439
|
queue.push(event);
|
|
453
440
|
notify();
|
|
454
441
|
},
|
|
455
|
-
|
|
456
|
-
if (opts?.includeEose === false) closeLocal("eose");
|
|
457
|
-
},
|
|
458
|
-
onclose(reason) {
|
|
442
|
+
onClose(reason) {
|
|
459
443
|
if (opts?.signal?.aborted === true) localClose = true;
|
|
460
444
|
if (!localClose && reason) error = new RelayClosedError(reason);
|
|
461
445
|
done = true;
|
|
@@ -524,6 +508,9 @@ async function fetchFilters(subscribe, filters, opts) {
|
|
|
524
508
|
let end = { type: "timeout" };
|
|
525
509
|
await new Promise((resolve, reject) => {
|
|
526
510
|
let settled = false;
|
|
511
|
+
let timer;
|
|
512
|
+
let sub;
|
|
513
|
+
let pendingCloseReason = "fetch complete";
|
|
527
514
|
const done = (err) => {
|
|
528
515
|
if (settled) return;
|
|
529
516
|
settled = true;
|
|
@@ -532,43 +519,54 @@ async function fetchFilters(subscribe, filters, opts) {
|
|
|
532
519
|
let reason = "aborted";
|
|
533
520
|
if (err instanceof Error) reason = err.message;
|
|
534
521
|
else if (err === void 0) reason = "fetch complete";
|
|
535
|
-
|
|
522
|
+
pendingCloseReason = reason;
|
|
523
|
+
sub?.close(reason);
|
|
536
524
|
if (err === void 0) resolve();
|
|
537
525
|
else reject(err);
|
|
538
526
|
};
|
|
539
527
|
const disposeAbort = onAbort(opts.signal, () => {
|
|
540
528
|
if (opts.signal !== void 0) done(abortReason(opts.signal));
|
|
541
529
|
});
|
|
542
|
-
|
|
530
|
+
try {
|
|
531
|
+
sub = subscribe(filters, {
|
|
532
|
+
id: opts.id,
|
|
533
|
+
signal: opts.signal,
|
|
534
|
+
closeOnEose: true,
|
|
535
|
+
onEvent(event) {
|
|
536
|
+
if (seen.has(event.id)) return;
|
|
537
|
+
seen.add(event.id);
|
|
538
|
+
events.push(event);
|
|
539
|
+
},
|
|
540
|
+
onEose() {
|
|
541
|
+
end = { type: "eose" };
|
|
542
|
+
done();
|
|
543
|
+
},
|
|
544
|
+
onClose(reason) {
|
|
545
|
+
if (settled) return;
|
|
546
|
+
if (opts.signal?.aborted === true) {
|
|
547
|
+
done(abortReason(opts.signal));
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
end = {
|
|
551
|
+
type: "closed",
|
|
552
|
+
reason
|
|
553
|
+
};
|
|
554
|
+
done();
|
|
555
|
+
}
|
|
556
|
+
});
|
|
557
|
+
} catch (error) {
|
|
558
|
+
disposeAbort();
|
|
559
|
+
reject(error);
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
if (settled) {
|
|
563
|
+
sub.close(pendingCloseReason);
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
timer = setTimeout(() => {
|
|
543
567
|
end = { type: "timeout" };
|
|
544
568
|
done();
|
|
545
569
|
}, opts.timeoutMs);
|
|
546
|
-
const sub = subscribe(filters, {
|
|
547
|
-
id: opts.id,
|
|
548
|
-
signal: opts.signal,
|
|
549
|
-
closeOnEose: true,
|
|
550
|
-
onevent(event) {
|
|
551
|
-
if (seen.has(event.id)) return;
|
|
552
|
-
seen.add(event.id);
|
|
553
|
-
events.push(event);
|
|
554
|
-
},
|
|
555
|
-
oneose() {
|
|
556
|
-
end = { type: "eose" };
|
|
557
|
-
done();
|
|
558
|
-
},
|
|
559
|
-
onclose(reason) {
|
|
560
|
-
if (settled) return;
|
|
561
|
-
if (opts.signal?.aborted === true) {
|
|
562
|
-
done(abortReason(opts.signal));
|
|
563
|
-
return;
|
|
564
|
-
}
|
|
565
|
-
end = {
|
|
566
|
-
type: "closed",
|
|
567
|
-
reason
|
|
568
|
-
};
|
|
569
|
-
done();
|
|
570
|
-
}
|
|
571
|
-
});
|
|
572
570
|
});
|
|
573
571
|
return {
|
|
574
572
|
events,
|
|
@@ -1086,25 +1084,32 @@ var SubscriptionRegistry = class {
|
|
|
1086
1084
|
return true;
|
|
1087
1085
|
}
|
|
1088
1086
|
closeAll(reason) {
|
|
1089
|
-
const
|
|
1087
|
+
const subs = [...this.#subs.values()];
|
|
1088
|
+
const groups = /* @__PURE__ */ new Set();
|
|
1089
|
+
for (const sub of subs) {
|
|
1090
|
+
const group = this.#liveBySubId.get(sub.id);
|
|
1091
|
+
if (group !== void 0) groups.add(group);
|
|
1092
|
+
}
|
|
1090
1093
|
for (const group of groups) invokeSafely(() => {
|
|
1091
1094
|
this.#endLiveGroup(group, {
|
|
1092
1095
|
sendClose: false,
|
|
1093
1096
|
reason
|
|
1094
1097
|
});
|
|
1095
1098
|
});
|
|
1096
|
-
for (const sub of
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1099
|
+
for (const sub of subs) {
|
|
1100
|
+
if (this.#subs.get(sub.id) === sub) this.#subs.delete(sub.id);
|
|
1101
|
+
sub.end(reason);
|
|
1102
|
+
}
|
|
1100
1103
|
}
|
|
1101
1104
|
#openExclusive(filters, opts) {
|
|
1102
1105
|
const sub = new Subscription(filters, opts, (closed) => {
|
|
1106
|
+
if (this.#subs.get(closed.id) !== closed) return;
|
|
1103
1107
|
this.#subs.delete(closed.id);
|
|
1104
1108
|
try {
|
|
1105
1109
|
if (this.#isOpen()) this.#send(["CLOSE", closed.id]);
|
|
1106
1110
|
} catch {}
|
|
1107
1111
|
});
|
|
1112
|
+
if (sub.closed) return sub;
|
|
1108
1113
|
this.#subs.set(sub.id, sub);
|
|
1109
1114
|
if (this.#isOpen()) this.#send([
|
|
1110
1115
|
"REQ",
|
|
@@ -1167,8 +1172,8 @@ var SubscriptionRegistry = class {
|
|
|
1167
1172
|
}
|
|
1168
1173
|
#forgetLiveGroup(group, reason) {
|
|
1169
1174
|
if (this.#liveByFp.get(group.fp) === group) this.#liveByFp.delete(group.fp);
|
|
1170
|
-
this.#liveBySubId.delete(group.sub.id);
|
|
1171
|
-
this.#subs.delete(group.sub.id);
|
|
1175
|
+
if (this.#liveBySubId.get(group.sub.id) === group) this.#liveBySubId.delete(group.sub.id);
|
|
1176
|
+
if (this.#subs.get(group.sub.id) === group.sub) this.#subs.delete(group.sub.id);
|
|
1172
1177
|
group.sub.end(reason);
|
|
1173
1178
|
}
|
|
1174
1179
|
#detachLive(group, handle) {
|
|
@@ -1194,7 +1199,10 @@ var SubscriptionRegistry = class {
|
|
|
1194
1199
|
#deliverLiveEvent(group, event) {
|
|
1195
1200
|
const { sub } = group;
|
|
1196
1201
|
const attachments = [...group.attachments];
|
|
1197
|
-
for (const att of attachments)
|
|
1202
|
+
for (const att of attachments) {
|
|
1203
|
+
if (att.closed) continue;
|
|
1204
|
+
att.notifyReceived(event.id);
|
|
1205
|
+
}
|
|
1198
1206
|
if (sub.isAtWatermark(event.id)) return;
|
|
1199
1207
|
const recipients = [];
|
|
1200
1208
|
for (const att of attachments) {
|
|
@@ -1280,7 +1288,9 @@ var Relay = class Relay {
|
|
|
1280
1288
|
#events = new Emitter();
|
|
1281
1289
|
constructor(url, opts = {}) {
|
|
1282
1290
|
this.url = normalizeURL(url);
|
|
1283
|
-
|
|
1291
|
+
const wsImpl = opts.websocketImplementation ?? globalThis.WebSocket;
|
|
1292
|
+
if (wsImpl === void 0) throw new RelayConnectionError("no WebSocket implementation; pass websocketImplementation");
|
|
1293
|
+
this.#WS = wsImpl;
|
|
1284
1294
|
this.#verify = opts.verifyEvent ?? verifyEvent;
|
|
1285
1295
|
this.#publishTimeoutMs = opts.publishTimeoutMs ?? 4400;
|
|
1286
1296
|
this.#connectTimeoutMs = opts.connectTimeoutMs ?? 5e3;
|
|
@@ -1694,9 +1704,14 @@ var Relay = class Relay {
|
|
|
1694
1704
|
subscribe(filters, opts = {}) {
|
|
1695
1705
|
if (filters.length === 0) throw new MessageError("REQ requires at least one filter");
|
|
1696
1706
|
if (!this.#isOpen() && !this.#enableReconnect) throw new RelayClosedError("not connected", this.url);
|
|
1707
|
+
if (opts.id !== void 0) this.#assertWireIdFree(assertSubscriptionId(opts.id));
|
|
1697
1708
|
const canonical = canonicalizeFilters(filters);
|
|
1698
1709
|
return this.#subscriptions.open(canonical, opts);
|
|
1699
1710
|
}
|
|
1711
|
+
/** Caller-chosen ids must not shadow an in-flight REQ or COUNT on this relay. */
|
|
1712
|
+
#assertWireIdFree(id) {
|
|
1713
|
+
if (this.#subscriptions.get(id) !== void 0 || this.#countRequests.get(id) !== void 0) throw new MessageError(`duplicate subscription id: ${id}`);
|
|
1714
|
+
}
|
|
1700
1715
|
#acceptEvent(event) {
|
|
1701
1716
|
if (this.#verifyDead) return false;
|
|
1702
1717
|
try {
|
|
@@ -1704,12 +1719,9 @@ var Relay = class Relay {
|
|
|
1704
1719
|
if (!ok) this.#events.emit("invalidevent", void 0);
|
|
1705
1720
|
return ok;
|
|
1706
1721
|
} catch (error) {
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
return false;
|
|
1711
|
-
}
|
|
1712
|
-
throw error;
|
|
1722
|
+
this.#verifyDead = true;
|
|
1723
|
+
this.#events.emit("verifiererror", toError(error));
|
|
1724
|
+
return false;
|
|
1713
1725
|
}
|
|
1714
1726
|
}
|
|
1715
1727
|
/** AsyncIterable of events for filters until the subscription is closed. */
|
|
@@ -1758,6 +1770,7 @@ var Relay = class Relay {
|
|
|
1758
1770
|
throwIfAborted(opts?.signal);
|
|
1759
1771
|
const canonical = canonicalizeFilters(filters);
|
|
1760
1772
|
const id = opts?.id === void 0 ? this.nextSubId("count") : createSubscriptionId(opts.id);
|
|
1773
|
+
if (opts?.id !== void 0) this.#assertWireIdFree(id);
|
|
1761
1774
|
const timeoutMs = opts?.timeoutMs ?? this.#publishTimeoutMs;
|
|
1762
1775
|
return this.#countRequests.track(id, {
|
|
1763
1776
|
timeoutMs,
|
|
@@ -1814,6 +1827,6 @@ var Relay = class Relay {
|
|
|
1814
1827
|
}
|
|
1815
1828
|
};
|
|
1816
1829
|
//#endregion
|
|
1817
|
-
export {
|
|
1830
|
+
export { RelayClosedError as a, RelayPublishError as c, subscriptionToAsyncIterable as i, RelaySuspendedError as l, Relay as n, RelayConnectionError as o, RelayStatus as r, RelayError as s, DEFAULT_REQUEST_TIMEOUT_MS as t, RelayTimeoutError as u };
|
|
1818
1831
|
|
|
1819
|
-
//# sourceMappingURL=relay-
|
|
1832
|
+
//# sourceMappingURL=relay-DIR77xQ2.mjs.map
|