@qntx/nostr 0.8.0 → 0.9.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/README.md +1 -1
- package/dist/{builder-aNU2gGFP.mjs → builder-CVRmUeNm.mjs} +4 -4
- package/dist/{builder-aNU2gGFP.mjs.map → builder-CVRmUeNm.mjs.map} +1 -1
- package/dist/{builder-CTwkB2zM.d.mts → builder-GBddxj8i.d.mts} +3 -3
- package/dist/{client-Cj2lUaVh.mjs → client-CeyDLoyO.mjs} +50 -28
- package/dist/client-CeyDLoyO.mjs.map +1 -0
- package/dist/client.d.mts +1 -1
- package/dist/client.mjs +1 -1
- package/dist/core.d.mts +8 -8
- package/dist/core.mjs +8 -8
- package/dist/{event-CHf47dPM.mjs → event-C2eLiPpV.mjs} +15 -5
- package/dist/event-C2eLiPpV.mjs.map +1 -0
- package/dist/{event-DJeb_DIL.d.mts → event-j3BwI5dF.d.mts} +2 -4
- package/dist/{filter-jI_9htF7.d.mts → filter-CfEgQBXt.d.mts} +3 -5
- package/dist/{filter-BQl3GlHt.mjs → filter-TrF2LEoy.mjs} +3 -36
- package/dist/filter-TrF2LEoy.mjs.map +1 -0
- package/dist/{gossip-DFxIBlsr.mjs → gossip-5ezg0Quo.mjs} +3 -3
- package/dist/gossip-5ezg0Quo.mjs.map +1 -0
- package/dist/{gossip-OOeeFzG5.d.mts → gossip-BWb9HmfK.d.mts} +8 -5
- package/dist/gossip.d.mts +1 -1
- package/dist/gossip.mjs +1 -1
- package/dist/http-CuazNRIP.mjs.map +1 -1
- package/dist/{http-C4HwIu4B.d.mts → http-kwWG-V1X.d.mts} +9 -3
- package/dist/{index-C_w2WfVz.d.mts → index-C0mFpelb.d.mts} +6 -6
- package/dist/{index-Cl_1XdYP.d.mts → index-C1m5cHpd.d.mts} +22 -14
- package/dist/{index-BxCBX-8R.d.mts → index-CoX7SnO7.d.mts} +7 -4
- package/dist/{index-CPVEyzNX.d.mts → index-D0M3kjY2.d.mts} +9 -9
- package/dist/{index-DU9tRFTZ.d.mts → index-Hp26bgYG.d.mts} +11 -4
- package/dist/{index-DyKwQnbt.d.mts → index-JD1O7m2v.d.mts} +4 -4
- package/dist/index.d.mts +19 -19
- package/dist/index.mjs +17 -17
- package/dist/{instance-C62w3cEt.mjs → instance-CI_IaZpf.mjs} +2 -2
- package/dist/{instance-C62w3cEt.mjs.map → instance-CI_IaZpf.mjs.map} +1 -1
- package/dist/{instance-DjsL8h84.d.mts → instance-X_WkCnpR.d.mts} +3 -3
- package/dist/{key-D_5OgGT7.d.mts → key-CyKN1RTi.d.mts} +2 -2
- package/dist/{key-DnVpchl6.mjs → key-DY_HEIpY.mjs} +5 -4
- package/dist/key-DY_HEIpY.mjs.map +1 -0
- package/dist/{kind-Cxwhhkx8.mjs → kind-D8fENiiL.mjs} +11 -5
- package/dist/kind-D8fENiiL.mjs.map +1 -0
- package/dist/{loaders-BmPkQfzI.mjs → loaders-DifimjJ7.mjs} +9 -8
- package/dist/{loaders-BmPkQfzI.mjs.map → loaders-DifimjJ7.mjs.map} +1 -1
- package/dist/loaders.d.mts +2 -2
- package/dist/loaders.mjs +1 -1
- package/dist/{memory-CdJ07kub.mjs → memory-DENGQ_f6.mjs} +2 -2
- package/dist/{memory-CdJ07kub.mjs.map → memory-DENGQ_f6.mjs.map} +1 -1
- package/dist/{memory-index-Cbc83vOZ.mjs → memory-index-D_lvlYAy.mjs} +4 -4
- package/dist/{memory-index-Cbc83vOZ.mjs.map → memory-index-D_lvlYAy.mjs.map} +1 -1
- package/dist/{message-tclYCVNc.d.mts → message-CcUz22qh.d.mts} +3 -3
- package/dist/{message-oNr7t0SJ.mjs → message-Rd5Z_17h.mjs} +5 -4
- package/dist/{message-oNr7t0SJ.mjs.map → message-Rd5Z_17h.mjs.map} +1 -1
- package/dist/{nip17-B5WgW4C4.d.mts → nip17-B7MI82Iq.d.mts} +4 -4
- package/dist/{nip51-CR0Q8eym.d.mts → nip51-O_vhOiK2.d.mts} +3 -3
- package/dist/{nip59-C-RWFQIv.d.mts → nip59-CdDoJptA.d.mts} +2 -2
- package/dist/{nip65-D1fbmhOg.d.mts → nip65-BL79HI8N.d.mts} +3 -3
- package/dist/{nip77-DzUwUxHq.d.mts → nip77-wHA_nEV5.d.mts} +2 -2
- package/dist/nips/blossom.d.mts +3 -3
- package/dist/nips/blossom.mjs +2 -2
- package/dist/nips/nip05.d.mts +1 -1
- package/dist/nips/nip10.d.mts +2 -2
- package/dist/nips/nip10.mjs +3 -3
- package/dist/nips/nip11.d.mts +1 -1
- package/dist/nips/nip13.d.mts +1 -1
- package/dist/nips/nip17.d.mts +1 -1
- package/dist/nips/nip17.mjs +3 -3
- package/dist/nips/nip27.d.mts +1 -1
- package/dist/nips/nip42.d.mts +1 -1
- package/dist/nips/nip42.mjs +1 -1
- package/dist/nips/nip51.d.mts +1 -1
- package/dist/nips/nip51.mjs +3 -3
- package/dist/nips/nip57.d.mts +1 -1
- package/dist/nips/nip57.mjs +3 -3
- package/dist/nips/nip59.d.mts +1 -1
- package/dist/nips/nip59.mjs +4 -4
- package/dist/nips/nip65.d.mts +1 -1
- package/dist/nips/nip65.mjs +2 -2
- package/dist/nips/nip77.d.mts +1 -1
- package/dist/nips/nip77.mjs +8 -5
- package/dist/nips/nip77.mjs.map +1 -1
- package/dist/nips/nip98.d.mts +1 -1
- package/dist/nips/nip98.mjs +3 -3
- package/dist/{pool-CqQvUnsq.d.mts → pool-BDI_u03Q.d.mts} +8 -7
- package/dist/{pool-vlw74C4t.mjs → pool-jok2-iBJ.mjs} +28 -21
- package/dist/pool-jok2-iBJ.mjs.map +1 -0
- package/dist/{put-w13ci3sC.mjs → put-JsiCKMyZ.mjs} +4 -4
- package/dist/put-JsiCKMyZ.mjs.map +1 -0
- package/dist/{reactive-Tz0otDIP.mjs → reactive-6Wn9qf_d.mjs} +5 -5
- package/dist/{reactive-Tz0otDIP.mjs.map → reactive-6Wn9qf_d.mjs.map} +1 -1
- package/dist/{reactive-gGqUrzW8.d.mts → reactive-X4p6nABy.d.mts} +4 -4
- package/dist/{relay-quCs7MHD.mjs → relay-__QtO33h.mjs} +18 -10
- package/dist/relay-__QtO33h.mjs.map +1 -0
- package/dist/relay.d.mts +3 -3
- package/dist/relay.mjs +2 -2
- package/dist/{signer-poTGe6Er.mjs → signer-D7PtVvQn.mjs} +14 -7
- package/dist/signer-D7PtVvQn.mjs.map +1 -0
- package/dist/signer.d.mts +2 -2
- package/dist/signer.mjs +1 -1
- package/dist/{storage-B5IXTVuS.mjs → storage-oihjE0_x.mjs} +10 -10
- package/dist/{storage-B5IXTVuS.mjs.map → storage-oihjE0_x.mjs.map} +1 -1
- package/dist/storage.d.mts +2 -2
- package/dist/storage.mjs +3 -3
- package/dist/store.d.mts +1 -1
- package/dist/store.mjs +1 -1
- package/dist/testing.d.mts +2 -2
- package/dist/testing.mjs +7 -7
- package/dist/{types-DNmiscfd.d.mts → types-BJ4wZ7JJ.d.mts} +2 -2
- package/dist/{types-BacwnDtU.d.mts → types-Ccox7-5v.d.mts} +3 -3
- package/dist/{verifier-CgTFpgsC.d.mts → verifier-BjL2M0ka.d.mts} +2 -2
- package/dist/{verifier-B51Wkt5H.mjs → verifier-uC_jd-Nw.mjs} +3 -3
- package/dist/verifier-uC_jd-Nw.mjs.map +1 -0
- package/dist/wasm.browser.d.mts +1 -1
- package/dist/wasm.browser.mjs +1 -1
- package/dist/wasm.d.mts +1 -1
- package/dist/wasm.mjs +1 -1
- package/package.json +5 -5
- package/dist/client-Cj2lUaVh.mjs.map +0 -1
- package/dist/event-CHf47dPM.mjs.map +0 -1
- package/dist/filter-BQl3GlHt.mjs.map +0 -1
- package/dist/gossip-DFxIBlsr.mjs.map +0 -1
- package/dist/key-DnVpchl6.mjs.map +0 -1
- package/dist/kind-Cxwhhkx8.mjs.map +0 -1
- package/dist/pool-vlw74C4t.mjs.map +0 -1
- package/dist/put-w13ci3sC.mjs.map +0 -1
- package/dist/relay-quCs7MHD.mjs.map +0 -1
- package/dist/signer-poTGe6Er.mjs.map +0 -1
- package/dist/verifier-B51Wkt5H.mjs.map +0 -1
package/dist/nips/nip77.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nip77.mjs","names":[],"sources":["../../src/nips/nip77.ts"],"sourcesContent":["/**\n * NIP-77: Negentropy Syncing. Transport-free V1 algorithm. Does not import relay or client.\n *\n * @see https://github.com/nostr-protocol/nips/blob/master/77.md\n */\nimport { sha256 } from \"@noble/hashes/sha2.js\";\n\nimport { NostrError } from \"../core/error.ts\";\nimport type { Event } from \"../core/event.ts\";\nimport { assertHex32, bytesToHex, hexToBytes } from \"../core/util.ts\";\n\nexport const PROTOCOL_VERSION = 0x61;\nexport const DEFAULT_FRAME_SIZE_LIMIT = 60_000;\nexport const MAX_NEG_ROUNDS = 1024;\n\nconst ID_SIZE = 32;\nconst FINGERPRINT_SIZE = 16;\nconst INFINITY = Number.MAX_VALUE;\nconst BUCKETS = 16;\nconst ID_LIST_THRESHOLD = BUCKETS * 2;\n\nconst Mode = {\n Skip: 0,\n Fingerprint: 1,\n IdList: 2,\n} as const;\n\nexport class Nip77Error extends NostrError {\n override name = \"Nip77Error\";\n}\n\nexport type NegItem = {\n readonly timestamp: number;\n readonly id: Uint8Array;\n};\n\nexport type ReconcileOutcome = {\n /** Event ids the local side holds that the peer does not. */\n readonly have: ReadonlyArray<string>;\n /** Event ids the peer holds that the local side does not. */\n readonly need: ReadonlyArray<string>;\n /** Next NEG-MSG hex, or undefined when the session converged. */\n readonly nextMessage: string | undefined;\n};\n\n/**\n * Byte buffer that is either an append target (`extend`/`unwrap`, `#pos` stays 0) or a read cursor\n * (`read`/`readN` advance `#pos`). `length` is bytes remaining ahead of `#pos`.\n */\nclass EncodedBuf {\n #raw: Uint8Array;\n #pos = 0;\n #end = 0;\n\n constructor(buffer?: Uint8Array | number) {\n if (typeof buffer === \"number\") {\n this.#raw = new Uint8Array(buffer);\n } else if (buffer) {\n this.#raw = new Uint8Array(buffer);\n this.#end = buffer.length;\n } else {\n this.#raw = new Uint8Array(512);\n }\n }\n\n get length(): number {\n return this.#end - this.#pos;\n }\n\n /** Unread (or written) bytes — never a live view for appends after reads. */\n unwrap(): Uint8Array {\n return this.#raw.subarray(this.#pos, this.#end);\n }\n\n get capacity(): number {\n return this.#raw.byteLength;\n }\n\n extend(buf: Uint8Array | EncodedBuf): void {\n const bytes = buf instanceof EncodedBuf ? buf.unwrap() : buf;\n const targetSize = bytes.length + this.#end;\n if (this.capacity < targetSize) {\n const old = this.#raw;\n const next = new Uint8Array(Math.max(this.capacity * 2, targetSize));\n next.set(old);\n this.#raw = next;\n }\n this.#raw.set(bytes, this.#end);\n this.#end += bytes.length;\n }\n\n read(): number {\n if (this.length === 0) {\n throw new Nip77Error(\"parse ends prematurely\");\n }\n const first = this.#raw[this.#pos];\n if (first === undefined) {\n throw new Nip77Error(\"parse ends prematurely\");\n }\n this.#pos += 1;\n return first;\n }\n\n readN(n: number): Uint8Array {\n if (this.length < n) {\n throw new Nip77Error(\"parse ends prematurely\");\n }\n const head = this.#raw.subarray(this.#pos, this.#pos + n);\n this.#pos += n;\n return head;\n }\n}\n\nfunction decodeVarInt(buf: EncodedBuf): number {\n let res = 0;\n for (;;) {\n const byte = buf.read();\n res = res * 128 + (byte & 0x7f);\n if (res > Number.MAX_SAFE_INTEGER) {\n throw new Nip77Error(\"varint exceeds max safe integer\");\n }\n if ((byte & 0x80) === 0) {\n break;\n }\n }\n return res;\n}\n\nfunction encodeVarInt(n: number): EncodedBuf {\n if (!Number.isSafeInteger(n) || n < 0) {\n throw new Nip77Error(\"varint must be a non-negative safe integer\");\n }\n if (n === 0) {\n return new EncodedBuf(new Uint8Array([0]));\n }\n const digits: number[] = [];\n let value = n;\n while (value !== 0) {\n digits.push(value % 128);\n value = Math.floor(value / 128);\n }\n digits.reverse();\n const encoded = digits.map((digit, i) => (i === digits.length - 1 ? digit : digit | 0x80));\n return new EncodedBuf(new Uint8Array(encoded));\n}\n\nfunction getBytes(buf: EncodedBuf, n: number): Uint8Array {\n return buf.readN(n);\n}\n\nclass Accumulator {\n readonly #buf: Uint8Array;\n\n constructor() {\n this.#buf = new Uint8Array(ID_SIZE);\n }\n\n add(other: Uint8Array): void {\n const view = new DataView(this.#buf.buffer, this.#buf.byteOffset, this.#buf.byteLength);\n const otherView = new DataView(other.buffer, other.byteOffset, other.byteLength);\n let carry = 0;\n for (let i = 0; i < 8; i++) {\n const offset = i * 4;\n const next = view.getUint32(offset, true) + carry + otherView.getUint32(offset, true);\n carry = next > 0xffffffff ? 1 : 0;\n view.setUint32(offset, next >>> 0, true);\n }\n }\n\n fingerprint(n: number): Uint8Array {\n const input = new EncodedBuf();\n input.extend(this.#buf);\n input.extend(encodeVarInt(n));\n return sha256(input.unwrap()).subarray(0, FINGERPRINT_SIZE);\n }\n}\n\nfunction compareBytes(a: Uint8Array, b: Uint8Array): number {\n const n = Math.min(a.byteLength, b.byteLength);\n for (let i = 0; i < n; i++) {\n const x = a[i];\n const y = b[i];\n if (x === undefined || y === undefined) {\n break;\n }\n if (x < y) {\n return -1;\n }\n if (x > y) {\n return 1;\n }\n }\n if (a.byteLength > b.byteLength) {\n return 1;\n }\n if (a.byteLength < b.byteLength) {\n return -1;\n }\n return 0;\n}\n\nfunction compareNegItems(a: NegItem, b: NegItem): number {\n if (a.timestamp !== b.timestamp) {\n return a.timestamp - b.timestamp;\n }\n return compareBytes(a.id, b.id);\n}\n\nexport class NegentropyStorageVector {\n readonly #items: NegItem[] = [];\n #sealed = false;\n\n insert(timestamp: number, id: string): void {\n if (this.#sealed) {\n throw new Nip77Error(\"already sealed\");\n }\n if (timestamp === INFINITY) {\n throw new Nip77Error(\"timestamp is reserved infinity\");\n }\n const idb = hexToBytes(assertHex32(id, \"event id\"));\n this.#items.push({ timestamp, id: idb });\n }\n\n seal(): void {\n if (this.#sealed) {\n throw new Nip77Error(\"already sealed\");\n }\n this.#sealed = true;\n this.#items.sort(compareNegItems);\n for (let i = 1; i < this.#items.length; i++) {\n const prev = this.#items[i - 1];\n const item = this.#items[i];\n if (prev !== undefined && item !== undefined && compareNegItems(prev, item) === 0) {\n throw new Nip77Error(\"duplicate item inserted\");\n }\n }\n }\n\n size(): number {\n this.#checkSealed();\n return this.#items.length;\n }\n\n iterate(begin: number, end: number, cb: (item: NegItem, i: number) => boolean): void {\n this.#checkSealed();\n this.#checkBounds(begin, end);\n for (let i = begin; i < end; i++) {\n const item = this.#items[i];\n if (item === undefined || !cb(item, i)) {\n break;\n }\n }\n }\n\n findLowerBound(begin: number, end: number, bound: NegItem): number {\n this.#checkSealed();\n this.#checkBounds(begin, end);\n let first = begin;\n let count = end - begin;\n while (count > 0) {\n const step = Math.floor(count / 2);\n const it = first + step;\n const item = this.#items[it];\n if (item === undefined) {\n break;\n }\n if (compareNegItems(item, bound) < 0) {\n first = it + 1;\n count -= step + 1;\n } else {\n count = step;\n }\n }\n return first;\n }\n\n fingerprint(begin: number, end: number): Uint8Array {\n const acc = new Accumulator();\n this.iterate(begin, end, (item) => {\n acc.add(item.id);\n return true;\n });\n return acc.fingerprint(end - begin);\n }\n\n #checkSealed(): void {\n if (!this.#sealed) {\n throw new Nip77Error(\"not sealed\");\n }\n }\n\n #checkBounds(begin: number, end: number): void {\n if (begin > end || end > this.#items.length) {\n throw new Nip77Error(\"bad range\");\n }\n }\n}\n\nexport function storageFromItems(\n items: ReadonlyArray<{ id: string; created_at: number }>,\n): NegentropyStorageVector {\n const storage = new NegentropyStorageVector();\n const seen = new Set<string>();\n for (const item of items) {\n const id = item.id.toLowerCase();\n if (seen.has(id)) {\n continue;\n }\n seen.add(id);\n storage.insert(item.created_at, id);\n }\n storage.seal();\n return storage;\n}\n\nexport function storageFromEvents(\n events: ReadonlyArray<Pick<Event, \"id\" | \"created_at\">>,\n): NegentropyStorageVector {\n return storageFromItems(events);\n}\n\ntype Bound = { timestamp: number; id: Uint8Array };\n\nexport class Negentropy {\n readonly #storage: NegentropyStorageVector;\n readonly #frameSizeLimit: number;\n #initiator = false;\n #lastTimestampIn = 0;\n #lastTimestampOut = 0;\n\n constructor(storage: NegentropyStorageVector, frameSizeLimit: number = DEFAULT_FRAME_SIZE_LIMIT) {\n if (frameSizeLimit !== 0 && frameSizeLimit < 4096) {\n throw new Nip77Error(\"frameSizeLimit too small\");\n }\n this.#storage = storage;\n this.#frameSizeLimit = frameSizeLimit;\n }\n\n initiate(): string {\n this.#initiator = true;\n const output = new EncodedBuf();\n output.extend(new Uint8Array([PROTOCOL_VERSION]));\n this.#splitRange(0, this.#storage.size(), this.#bound(INFINITY), output);\n return bytesToHex(output.unwrap());\n }\n\n reconcile(queryMsg: string): ReconcileOutcome {\n const have: string[] = [];\n const need: string[] = [];\n const query = new EncodedBuf(hexToBytes(queryMsg));\n\n this.#lastTimestampIn = 0;\n this.#lastTimestampOut = 0;\n\n const fullOutput = new EncodedBuf();\n fullOutput.extend(new Uint8Array([PROTOCOL_VERSION]));\n\n const protocolVersion = query.read();\n if (protocolVersion < 0x60 || protocolVersion > 0x6f) {\n throw new Nip77Error(\"invalid negentropy protocol version byte\");\n }\n if (protocolVersion !== PROTOCOL_VERSION) {\n // NIP-77: reply with the highest supported version so the peer can downgrade.\n return { have, need, nextMessage: bytesToHex(fullOutput.unwrap()) };\n }\n\n const storageSize = this.#storage.size();\n let prevBound = this.#bound(0);\n let prevIndex = 0;\n let skip = false;\n\n while (query.length > 0) {\n const o = new EncodedBuf();\n\n const currBound = this.#decodeBound(query);\n const mode = decodeVarInt(query);\n const lower = prevIndex;\n const upper = this.#storage.findLowerBound(prevIndex, storageSize, currBound);\n\n if (mode === Mode.Skip) {\n skip = true;\n } else if (mode === Mode.Fingerprint) {\n const theirFingerprint = getBytes(query, FINGERPRINT_SIZE);\n const ourFingerprint = this.#storage.fingerprint(lower, upper);\n if (compareBytes(theirFingerprint, ourFingerprint) === 0) {\n skip = true;\n } else {\n if (skip) {\n skip = false;\n this.#encodeSkipInto(o, prevBound);\n }\n this.#splitRange(lower, upper, currBound, o);\n }\n } else if (mode === Mode.IdList) {\n const numIds = decodeVarInt(query);\n const theirElems = new Map<string, Uint8Array>();\n for (let i = 0; i < numIds; i++) {\n const e = getBytes(query, ID_SIZE);\n theirElems.set(bytesToHex(e), e);\n }\n\n if (this.#initiator) {\n skip = true;\n this.#storage.iterate(lower, upper, (item) => {\n const id = bytesToHex(item.id);\n if (theirElems.has(id)) {\n theirElems.delete(id);\n } else {\n have.push(id);\n }\n return true;\n });\n for (const id of theirElems.keys()) {\n need.push(id);\n }\n } else {\n if (skip) {\n skip = false;\n this.#encodeSkipInto(o, prevBound);\n }\n o.extend(this.#encodeBound(currBound));\n o.extend(encodeVarInt(Mode.IdList));\n const ourIds: Uint8Array[] = [];\n this.#storage.iterate(lower, upper, (item) => {\n ourIds.push(item.id);\n return true;\n });\n o.extend(encodeVarInt(ourIds.length));\n for (const id of ourIds) {\n o.extend(id);\n }\n }\n } else {\n throw new Nip77Error(\"unexpected mode\");\n }\n\n if (this.#exceededFrameSizeLimit(fullOutput.length + o.length)) {\n const remainingFingerprint = this.#storage.fingerprint(upper, storageSize);\n fullOutput.extend(this.#encodeBound(this.#bound(INFINITY)));\n fullOutput.extend(encodeVarInt(Mode.Fingerprint));\n fullOutput.extend(remainingFingerprint);\n break;\n }\n fullOutput.extend(o);\n prevIndex = upper;\n prevBound = currBound;\n }\n\n return {\n have,\n need,\n nextMessage: fullOutput.length === 1 ? undefined : bytesToHex(fullOutput.unwrap()),\n };\n }\n\n #bound(timestamp: number, id?: Uint8Array): Bound {\n return { timestamp, id: id ?? new Uint8Array(0) };\n }\n\n #splitRange(lower: number, upper: number, upperBound: Bound, o: EncodedBuf): void {\n const numElems = upper - lower;\n if (numElems < ID_LIST_THRESHOLD) {\n o.extend(this.#encodeBound(upperBound));\n o.extend(encodeVarInt(Mode.IdList));\n o.extend(encodeVarInt(numElems));\n this.#storage.iterate(lower, upper, (item) => {\n o.extend(item.id);\n return true;\n });\n return;\n }\n\n const itemsPerBucket = Math.floor(numElems / BUCKETS);\n const bucketsWithExtra = numElems % BUCKETS;\n let curr = lower;\n for (let i = 0; i < BUCKETS; i++) {\n const bucketSize = itemsPerBucket + (i < bucketsWithExtra ? 1 : 0);\n const ourFingerprint = this.#storage.fingerprint(curr, curr + bucketSize);\n curr += bucketSize;\n let nextBound: Bound;\n if (curr === upper) {\n nextBound = upperBound;\n } else {\n const at = curr - 1;\n let prevItem: NegItem | undefined;\n let currItem: NegItem | undefined;\n this.#storage.iterate(at, at + 2, (item, index) => {\n if (index === at) {\n prevItem = item;\n } else {\n currItem = item;\n }\n return true;\n });\n if (prevItem === undefined || currItem === undefined) {\n throw new Nip77Error(\"missing bound items\");\n }\n nextBound = this.#minimalBound(prevItem, currItem);\n }\n o.extend(this.#encodeBound(nextBound));\n o.extend(encodeVarInt(Mode.Fingerprint));\n o.extend(ourFingerprint);\n }\n }\n\n #exceededFrameSizeLimit(n: number): boolean {\n if (this.#frameSizeLimit === 0) {\n return false;\n }\n return n > this.#frameSizeLimit - 200;\n }\n\n #decodeTimestampIn(encoded: EncodedBuf): number {\n let timestamp = decodeVarInt(encoded);\n timestamp = timestamp === 0 ? INFINITY : timestamp - 1;\n if (this.#lastTimestampIn === INFINITY || timestamp === INFINITY) {\n this.#lastTimestampIn = INFINITY;\n return INFINITY;\n }\n timestamp += this.#lastTimestampIn;\n this.#lastTimestampIn = timestamp;\n return timestamp;\n }\n\n #decodeBound(encoded: EncodedBuf): Bound {\n const timestamp = this.#decodeTimestampIn(encoded);\n const len = decodeVarInt(encoded);\n if (len > ID_SIZE) {\n throw new Nip77Error(\"bound key too long\");\n }\n return { timestamp, id: getBytes(encoded, len) };\n }\n\n #encodeTimestampOut(timestamp: number): EncodedBuf {\n if (timestamp === INFINITY) {\n this.#lastTimestampOut = INFINITY;\n return encodeVarInt(0);\n }\n const delta = timestamp - this.#lastTimestampOut;\n this.#lastTimestampOut = timestamp;\n return encodeVarInt(delta + 1);\n }\n\n #encodeSkipInto(o: EncodedBuf, bound: Bound): void {\n o.extend(this.#encodeBound(bound));\n o.extend(encodeVarInt(Mode.Skip));\n }\n\n #encodeBound(key: Bound): EncodedBuf {\n const output = new EncodedBuf();\n output.extend(this.#encodeTimestampOut(key.timestamp));\n output.extend(encodeVarInt(key.id.length));\n output.extend(key.id);\n return output;\n }\n\n #minimalBound(prev: NegItem, curr: NegItem): Bound {\n if (curr.timestamp !== prev.timestamp) {\n return this.#bound(curr.timestamp);\n }\n let shared = 0;\n for (let i = 0; i < ID_SIZE; i++) {\n if (curr.id[i] !== prev.id[i]) {\n break;\n }\n shared += 1;\n }\n return this.#bound(curr.timestamp, curr.id.subarray(0, shared + 1));\n }\n}\n\n/** Drive initiate/reconcile until nextMessage is null. Transport-free. */\nexport async function runNegSession(opts: {\n storage: NegentropyStorageVector;\n frameSizeLimit?: number;\n openingSend: (hex: string) => void;\n msgSend: (hex: string) => void;\n next: () => Promise<string>;\n}): Promise<{ have: string[]; need: string[] }> {\n const neg = new Negentropy(opts.storage, opts.frameSizeLimit);\n const have = new Set<string>();\n const need = new Set<string>();\n opts.openingSend(neg.initiate());\n for (let round = 0; round < MAX_NEG_ROUNDS; round++) {\n // oxlint-disable-next-line no-await-in-loop -- protocol rounds are sequential by definition\n const out = neg.reconcile(await opts.next());\n for (const id of out.have) {\n have.add(id);\n }\n for (const id of out.need) {\n need.add(id);\n }\n if (out.nextMessage === undefined) {\n return { have: [...have], need: [...need] };\n }\n opts.msgSend(out.nextMessage);\n }\n throw new Nip77Error(\"negentropy exceeded max rounds\");\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"nip77.mjs","names":[],"sources":["../../src/nips/nip77.ts"],"sourcesContent":["/**\n * NIP-77: Negentropy Syncing. Transport-free V1 algorithm. Does not import relay or client.\n *\n * @see https://github.com/nostr-protocol/nips/blob/master/77.md\n */\nimport { sha256 } from \"@noble/hashes/sha2.js\";\n\nimport { NostrError } from \"../core/error.ts\";\nimport type { Event } from \"../core/event.ts\";\nimport { assertHex32, bytesToHex, hexToBytes } from \"../core/util.ts\";\n\nexport const PROTOCOL_VERSION = 0x61;\nexport const DEFAULT_FRAME_SIZE_LIMIT = 60_000;\nexport const MAX_NEG_ROUNDS = 1024;\n\nconst ID_SIZE = 32;\nconst FINGERPRINT_SIZE = 16;\nconst INFINITY = Number.MAX_VALUE;\nconst BUCKETS = 16;\nconst ID_LIST_THRESHOLD = BUCKETS * 2;\n\nconst Mode = {\n Skip: 0,\n Fingerprint: 1,\n IdList: 2,\n} as const;\n\nexport class Nip77Error extends NostrError {\n override name = \"Nip77Error\";\n}\n\nexport type NegItem = {\n readonly timestamp: number;\n readonly id: Uint8Array;\n};\n\nexport type ReconcileOutcome = {\n /** Event ids the local side holds that the peer does not. */\n readonly have: ReadonlyArray<string>;\n /** Event ids the peer holds that the local side does not. */\n readonly need: ReadonlyArray<string>;\n /** Next NEG-MSG hex, or undefined when the session converged. */\n readonly nextMessage: string | undefined;\n};\n\n/**\n * Byte buffer that is either an append target (`extend`/`unwrap`, `#pos` stays 0) or a read cursor\n * (`read`/`readN` advance `#pos`). `length` is bytes remaining ahead of `#pos`.\n */\nclass EncodedBuf {\n #raw: Uint8Array;\n #pos = 0;\n #end = 0;\n\n constructor(buffer?: Uint8Array | number) {\n if (typeof buffer === \"number\") {\n this.#raw = new Uint8Array(buffer);\n } else if (buffer) {\n this.#raw = new Uint8Array(buffer);\n this.#end = buffer.length;\n } else {\n this.#raw = new Uint8Array(512);\n }\n }\n\n get length(): number {\n return this.#end - this.#pos;\n }\n\n /** Unread (or written) bytes — never a live view for appends after reads. */\n unwrap(): Uint8Array {\n return this.#raw.subarray(this.#pos, this.#end);\n }\n\n get capacity(): number {\n return this.#raw.byteLength;\n }\n\n extend(buf: Uint8Array | EncodedBuf): void {\n const bytes = buf instanceof EncodedBuf ? buf.unwrap() : buf;\n const targetSize = bytes.length + this.#end;\n if (this.capacity < targetSize) {\n const old = this.#raw;\n const next = new Uint8Array(Math.max(this.capacity * 2, targetSize));\n next.set(old);\n this.#raw = next;\n }\n this.#raw.set(bytes, this.#end);\n this.#end += bytes.length;\n }\n\n read(): number {\n if (this.length === 0) {\n throw new Nip77Error(\"parse ends prematurely\");\n }\n const first = this.#raw[this.#pos];\n if (first === undefined) {\n throw new Nip77Error(\"parse ends prematurely\");\n }\n this.#pos += 1;\n return first;\n }\n\n readN(n: number): Uint8Array {\n if (this.length < n) {\n throw new Nip77Error(\"parse ends prematurely\");\n }\n const head = this.#raw.subarray(this.#pos, this.#pos + n);\n this.#pos += n;\n return head;\n }\n}\n\nfunction decodeVarInt(buf: EncodedBuf): number {\n let res = 0;\n for (;;) {\n const byte = buf.read();\n res = res * 128 + (byte & 0x7f);\n if (res > Number.MAX_SAFE_INTEGER) {\n throw new Nip77Error(\"varint exceeds max safe integer\");\n }\n if ((byte & 0x80) === 0) {\n break;\n }\n }\n return res;\n}\n\nfunction encodeVarInt(n: number): EncodedBuf {\n if (!Number.isSafeInteger(n) || n < 0) {\n throw new Nip77Error(\"varint must be a non-negative safe integer\");\n }\n if (n === 0) {\n return new EncodedBuf(new Uint8Array([0]));\n }\n const digits: number[] = [];\n let value = n;\n while (value !== 0) {\n digits.push(value % 128);\n value = Math.floor(value / 128);\n }\n digits.reverse();\n const encoded = digits.map((digit, i) => (i === digits.length - 1 ? digit : digit | 0x80));\n return new EncodedBuf(new Uint8Array(encoded));\n}\n\nfunction getBytes(buf: EncodedBuf, n: number): Uint8Array {\n return buf.readN(n);\n}\n\nclass Accumulator {\n readonly #buf: Uint8Array;\n\n constructor() {\n this.#buf = new Uint8Array(ID_SIZE);\n }\n\n add(other: Uint8Array): void {\n const view = new DataView(this.#buf.buffer, this.#buf.byteOffset, this.#buf.byteLength);\n const otherView = new DataView(other.buffer, other.byteOffset, other.byteLength);\n let carry = 0;\n for (let i = 0; i < 8; i++) {\n const offset = i * 4;\n const next = view.getUint32(offset, true) + carry + otherView.getUint32(offset, true);\n carry = next > 0xffffffff ? 1 : 0;\n view.setUint32(offset, next >>> 0, true);\n }\n }\n\n fingerprint(n: number): Uint8Array {\n const input = new EncodedBuf();\n input.extend(this.#buf);\n input.extend(encodeVarInt(n));\n return sha256(input.unwrap()).subarray(0, FINGERPRINT_SIZE);\n }\n}\n\nfunction compareBytes(a: Uint8Array, b: Uint8Array): number {\n const n = Math.min(a.byteLength, b.byteLength);\n for (let i = 0; i < n; i++) {\n const x = a[i];\n const y = b[i];\n if (x === undefined || y === undefined) {\n break;\n }\n if (x < y) {\n return -1;\n }\n if (x > y) {\n return 1;\n }\n }\n if (a.byteLength > b.byteLength) {\n return 1;\n }\n if (a.byteLength < b.byteLength) {\n return -1;\n }\n return 0;\n}\n\nfunction compareNegItems(a: NegItem, b: NegItem): number {\n if (a.timestamp !== b.timestamp) {\n return a.timestamp - b.timestamp;\n }\n return compareBytes(a.id, b.id);\n}\n\nexport class NegentropyStorageVector {\n readonly #items: NegItem[] = [];\n #sealed = false;\n\n insert(timestamp: number, id: string): void {\n if (this.#sealed) {\n throw new Nip77Error(\"already sealed\");\n }\n if (timestamp === INFINITY) {\n throw new Nip77Error(\"timestamp is reserved infinity\");\n }\n const idb = hexToBytes(assertHex32(id, \"event id\"));\n this.#items.push({ timestamp, id: idb });\n }\n\n seal(): void {\n if (this.#sealed) {\n throw new Nip77Error(\"already sealed\");\n }\n this.#sealed = true;\n this.#items.sort(compareNegItems);\n for (let i = 1; i < this.#items.length; i++) {\n const prev = this.#items[i - 1];\n const item = this.#items[i];\n if (prev !== undefined && item !== undefined && compareNegItems(prev, item) === 0) {\n throw new Nip77Error(\"duplicate item inserted\");\n }\n }\n }\n\n size(): number {\n this.#checkSealed();\n return this.#items.length;\n }\n\n iterate(begin: number, end: number, cb: (item: NegItem, i: number) => boolean): void {\n this.#checkSealed();\n this.#checkBounds(begin, end);\n for (let i = begin; i < end; i++) {\n const item = this.#items[i];\n if (item === undefined || !cb(item, i)) {\n break;\n }\n }\n }\n\n findLowerBound(begin: number, end: number, bound: NegItem): number {\n this.#checkSealed();\n this.#checkBounds(begin, end);\n let first = begin;\n let count = end - begin;\n while (count > 0) {\n const step = Math.floor(count / 2);\n const it = first + step;\n const item = this.#items[it];\n if (item === undefined) {\n break;\n }\n if (compareNegItems(item, bound) < 0) {\n first = it + 1;\n count -= step + 1;\n } else {\n count = step;\n }\n }\n return first;\n }\n\n fingerprint(begin: number, end: number): Uint8Array {\n const acc = new Accumulator();\n this.iterate(begin, end, (item) => {\n acc.add(item.id);\n return true;\n });\n return acc.fingerprint(end - begin);\n }\n\n #checkSealed(): void {\n if (!this.#sealed) {\n throw new Nip77Error(\"not sealed\");\n }\n }\n\n #checkBounds(begin: number, end: number): void {\n if (begin > end || end > this.#items.length) {\n throw new Nip77Error(\"bad range\");\n }\n }\n}\n\nexport function storageFromItems(\n items: ReadonlyArray<{ id: string; created_at: number }>,\n): NegentropyStorageVector {\n const storage = new NegentropyStorageVector();\n const seen = new Set<string>();\n for (const item of items) {\n const id = item.id.toLowerCase();\n if (seen.has(id)) {\n continue;\n }\n seen.add(id);\n storage.insert(item.created_at, id);\n }\n storage.seal();\n return storage;\n}\n\nexport function storageFromEvents(\n events: ReadonlyArray<Pick<Event, \"id\" | \"created_at\">>,\n): NegentropyStorageVector {\n return storageFromItems(events);\n}\n\ntype Bound = { timestamp: number; id: Uint8Array };\n\nexport class Negentropy {\n readonly #storage: NegentropyStorageVector;\n readonly #frameSizeLimit: number;\n #initiator = false;\n #lastTimestampIn = 0;\n #lastTimestampOut = 0;\n\n constructor(storage: NegentropyStorageVector, frameSizeLimit: number = DEFAULT_FRAME_SIZE_LIMIT) {\n if (frameSizeLimit !== 0 && frameSizeLimit < 4096) {\n throw new Nip77Error(\"frameSizeLimit too small\");\n }\n this.#storage = storage;\n this.#frameSizeLimit = frameSizeLimit;\n }\n\n initiate(): string {\n this.#initiator = true;\n const output = new EncodedBuf();\n output.extend(new Uint8Array([PROTOCOL_VERSION]));\n this.#splitRange(0, this.#storage.size(), this.#bound(INFINITY), output);\n return bytesToHex(output.unwrap());\n }\n\n reconcile(queryMsg: string): ReconcileOutcome {\n const have: string[] = [];\n const need: string[] = [];\n const query = new EncodedBuf(hexToBytes(queryMsg));\n\n this.#lastTimestampIn = 0;\n this.#lastTimestampOut = 0;\n\n const fullOutput = new EncodedBuf();\n fullOutput.extend(new Uint8Array([PROTOCOL_VERSION]));\n\n const protocolVersion = query.read();\n if (protocolVersion < 0x60 || protocolVersion > 0x6f) {\n throw new Nip77Error(\"invalid negentropy protocol version byte\");\n }\n if (protocolVersion !== PROTOCOL_VERSION) {\n // The initiator is the client: nobody downgrades on its behalf, so a mismatched\n // version is a hard failure. The responder answers with its highest version so the\n // peer can downgrade (NIP-77).\n if (this.#initiator) {\n throw new Nip77Error(\n `unsupported negentropy protocol version ${protocolVersion - 0x60} (peer), we speak ${PROTOCOL_VERSION - 0x60}`,\n );\n }\n return { have, need, nextMessage: bytesToHex(fullOutput.unwrap()) };\n }\n\n const storageSize = this.#storage.size();\n let prevBound = this.#bound(0);\n let prevIndex = 0;\n let skip = false;\n\n while (query.length > 0) {\n const o = new EncodedBuf();\n\n const currBound = this.#decodeBound(query);\n const mode = decodeVarInt(query);\n const lower = prevIndex;\n const upper = this.#storage.findLowerBound(prevIndex, storageSize, currBound);\n\n if (mode === Mode.Skip) {\n skip = true;\n } else if (mode === Mode.Fingerprint) {\n const theirFingerprint = getBytes(query, FINGERPRINT_SIZE);\n const ourFingerprint = this.#storage.fingerprint(lower, upper);\n if (compareBytes(theirFingerprint, ourFingerprint) === 0) {\n skip = true;\n } else {\n if (skip) {\n skip = false;\n this.#encodeSkipInto(o, prevBound);\n }\n this.#splitRange(lower, upper, currBound, o);\n }\n } else if (mode === Mode.IdList) {\n const numIds = decodeVarInt(query);\n const theirElems = new Map<string, Uint8Array>();\n for (let i = 0; i < numIds; i++) {\n const e = getBytes(query, ID_SIZE);\n theirElems.set(bytesToHex(e), e);\n }\n\n if (this.#initiator) {\n skip = true;\n this.#storage.iterate(lower, upper, (item) => {\n const id = bytesToHex(item.id);\n if (theirElems.has(id)) {\n theirElems.delete(id);\n } else {\n have.push(id);\n }\n return true;\n });\n for (const id of theirElems.keys()) {\n need.push(id);\n }\n } else {\n if (skip) {\n skip = false;\n this.#encodeSkipInto(o, prevBound);\n }\n o.extend(this.#encodeBound(currBound));\n o.extend(encodeVarInt(Mode.IdList));\n const ourIds: Uint8Array[] = [];\n this.#storage.iterate(lower, upper, (item) => {\n ourIds.push(item.id);\n return true;\n });\n o.extend(encodeVarInt(ourIds.length));\n for (const id of ourIds) {\n o.extend(id);\n }\n }\n } else {\n throw new Nip77Error(\"unexpected mode\");\n }\n\n if (this.#exceededFrameSizeLimit(fullOutput.length + o.length)) {\n const remainingFingerprint = this.#storage.fingerprint(upper, storageSize);\n fullOutput.extend(this.#encodeBound(this.#bound(INFINITY)));\n fullOutput.extend(encodeVarInt(Mode.Fingerprint));\n fullOutput.extend(remainingFingerprint);\n break;\n }\n fullOutput.extend(o);\n prevIndex = upper;\n prevBound = currBound;\n }\n\n return {\n have,\n need,\n nextMessage: fullOutput.length === 1 ? undefined : bytesToHex(fullOutput.unwrap()),\n };\n }\n\n #bound(timestamp: number, id?: Uint8Array): Bound {\n return { timestamp, id: id ?? new Uint8Array(0) };\n }\n\n #splitRange(lower: number, upper: number, upperBound: Bound, o: EncodedBuf): void {\n const numElems = upper - lower;\n if (numElems < ID_LIST_THRESHOLD) {\n o.extend(this.#encodeBound(upperBound));\n o.extend(encodeVarInt(Mode.IdList));\n o.extend(encodeVarInt(numElems));\n this.#storage.iterate(lower, upper, (item) => {\n o.extend(item.id);\n return true;\n });\n return;\n }\n\n const itemsPerBucket = Math.floor(numElems / BUCKETS);\n const bucketsWithExtra = numElems % BUCKETS;\n let curr = lower;\n for (let i = 0; i < BUCKETS; i++) {\n const bucketSize = itemsPerBucket + (i < bucketsWithExtra ? 1 : 0);\n const ourFingerprint = this.#storage.fingerprint(curr, curr + bucketSize);\n curr += bucketSize;\n let nextBound: Bound;\n if (curr === upper) {\n nextBound = upperBound;\n } else {\n const at = curr - 1;\n let prevItem: NegItem | undefined;\n let currItem: NegItem | undefined;\n this.#storage.iterate(at, at + 2, (item, index) => {\n if (index === at) {\n prevItem = item;\n } else {\n currItem = item;\n }\n return true;\n });\n if (prevItem === undefined || currItem === undefined) {\n throw new Nip77Error(\"missing bound items\");\n }\n nextBound = this.#minimalBound(prevItem, currItem);\n }\n o.extend(this.#encodeBound(nextBound));\n o.extend(encodeVarInt(Mode.Fingerprint));\n o.extend(ourFingerprint);\n }\n }\n\n #exceededFrameSizeLimit(n: number): boolean {\n if (this.#frameSizeLimit === 0) {\n return false;\n }\n return n > this.#frameSizeLimit - 200;\n }\n\n #decodeTimestampIn(encoded: EncodedBuf): number {\n let timestamp = decodeVarInt(encoded);\n timestamp = timestamp === 0 ? INFINITY : timestamp - 1;\n if (this.#lastTimestampIn === INFINITY || timestamp === INFINITY) {\n this.#lastTimestampIn = INFINITY;\n return INFINITY;\n }\n timestamp += this.#lastTimestampIn;\n this.#lastTimestampIn = timestamp;\n return timestamp;\n }\n\n #decodeBound(encoded: EncodedBuf): Bound {\n const timestamp = this.#decodeTimestampIn(encoded);\n const len = decodeVarInt(encoded);\n if (len > ID_SIZE) {\n throw new Nip77Error(\"bound key too long\");\n }\n return { timestamp, id: getBytes(encoded, len) };\n }\n\n #encodeTimestampOut(timestamp: number): EncodedBuf {\n if (timestamp === INFINITY) {\n this.#lastTimestampOut = INFINITY;\n return encodeVarInt(0);\n }\n const delta = timestamp - this.#lastTimestampOut;\n this.#lastTimestampOut = timestamp;\n return encodeVarInt(delta + 1);\n }\n\n #encodeSkipInto(o: EncodedBuf, bound: Bound): void {\n o.extend(this.#encodeBound(bound));\n o.extend(encodeVarInt(Mode.Skip));\n }\n\n #encodeBound(key: Bound): EncodedBuf {\n const output = new EncodedBuf();\n output.extend(this.#encodeTimestampOut(key.timestamp));\n output.extend(encodeVarInt(key.id.length));\n output.extend(key.id);\n return output;\n }\n\n #minimalBound(prev: NegItem, curr: NegItem): Bound {\n if (curr.timestamp !== prev.timestamp) {\n return this.#bound(curr.timestamp);\n }\n let shared = 0;\n for (let i = 0; i < ID_SIZE; i++) {\n if (curr.id[i] !== prev.id[i]) {\n break;\n }\n shared += 1;\n }\n return this.#bound(curr.timestamp, curr.id.subarray(0, shared + 1));\n }\n}\n\n/** Drive initiate/reconcile until nextMessage is null. Transport-free. */\nexport async function runNegSession(opts: {\n storage: NegentropyStorageVector;\n frameSizeLimit?: number;\n openingSend: (hex: string) => void;\n msgSend: (hex: string) => void;\n next: () => Promise<string>;\n}): Promise<{ have: string[]; need: string[] }> {\n const neg = new Negentropy(opts.storage, opts.frameSizeLimit);\n const have = new Set<string>();\n const need = new Set<string>();\n opts.openingSend(neg.initiate());\n for (let round = 0; round < MAX_NEG_ROUNDS; round++) {\n // oxlint-disable-next-line no-await-in-loop -- protocol rounds are sequential by definition\n const out = neg.reconcile(await opts.next());\n for (const id of out.have) {\n have.add(id);\n }\n for (const id of out.need) {\n need.add(id);\n }\n if (out.nextMessage === undefined) {\n return { have: [...have], need: [...need] };\n }\n opts.msgSend(out.nextMessage);\n }\n throw new Nip77Error(\"negentropy exceeded max rounds\");\n}\n"],"mappings":"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"}
|
package/dist/nips/nip98.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as EventTemplate, t as Event } from "../event-
|
|
1
|
+
import { n as EventTemplate, t as Event } from "../event-j3BwI5dF.mjs";
|
|
2
2
|
import { a as NostrError } from "../error-BquXDmIN.mjs";
|
|
3
3
|
//#region src/nips/nip98.d.ts
|
|
4
4
|
export declare class Nip98Error extends NostrError {
|
package/dist/nips/nip98.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { d as nowSeconds, f as utf8Decoder, i as bytesToHex, p as utf8Encoder, v as NostrError } from "../util-DMF9AIO0.mjs";
|
|
2
|
-
import { t as Kind } from "../kind-
|
|
3
|
-
import {
|
|
4
|
-
import { n as verifyEvent } from "../verifier-
|
|
2
|
+
import { t as Kind } from "../kind-D8fENiiL.mjs";
|
|
3
|
+
import { h as validateSignedEvent, v as firstTagValue } from "../event-C2eLiPpV.mjs";
|
|
4
|
+
import { n as verifyEvent } from "../verifier-uC_jd-Nw.mjs";
|
|
5
5
|
import { sha256 } from "@noble/hashes/sha2.js";
|
|
6
6
|
import { base64 } from "@scure/base";
|
|
7
7
|
//#region src/nips/nip98.ts
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { n as EventTemplate, t as Event } from "./event-
|
|
2
|
-
import { t as Filter } from "./filter-
|
|
3
|
-
import { n as CountResult } from "./message-
|
|
4
|
-
import { t as EventVerifier } from "./verifier-
|
|
5
|
-
import { a as NegentropyStorageVector } from "./nip77-
|
|
1
|
+
import { n as EventTemplate, t as Event } from "./event-j3BwI5dF.mjs";
|
|
2
|
+
import { t as Filter } from "./filter-CfEgQBXt.mjs";
|
|
3
|
+
import { n as CountResult } from "./message-CcUz22qh.mjs";
|
|
4
|
+
import { t as EventVerifier } from "./verifier-BjL2M0ka.mjs";
|
|
5
|
+
import { a as NegentropyStorageVector } from "./nip77-wHA_nEV5.mjs";
|
|
6
6
|
import { t as WebSocketConstructor } from "./websocket-CmeqvAkp.mjs";
|
|
7
7
|
//#region src/relay/subscription.d.ts
|
|
8
8
|
type SubscriptionHandlers = {
|
|
@@ -124,7 +124,8 @@ type RelayEventMap = {
|
|
|
124
124
|
reconnect: undefined;
|
|
125
125
|
/**
|
|
126
126
|
* Fired when a delivered EVENT fails id/signature verification, just before the event is dropped.
|
|
127
|
-
* A poisoned verifier dropping events does not fire it.
|
|
127
|
+
* A poisoned verifier dropping events does not fire it. EVENTs that do not match the
|
|
128
|
+
* subscription's filters are dropped silently and are not counted here.
|
|
128
129
|
*/
|
|
129
130
|
invalidevent: undefined;
|
|
130
131
|
};
|
|
@@ -392,4 +393,4 @@ declare class Pool {
|
|
|
392
393
|
}
|
|
393
394
|
//#endregion
|
|
394
395
|
export { Closer as _, PoolFetchResult as a, SubscriptionHandlers as b, PoolSubscribeOptions as c, RelayOptions as d, RelayStatus as f, RelayFetchResult as g, RelayFetchEnd as h, PoolEventMap as i, Relay as l, PublishResult as m, Pool as n, PoolOptions as o, RelayStatusName as p, PoolCountResult as r, PoolPublishResult as s, InvalidEventPolicy as t, RelayEventMap as u, RelaySubscription as v, subscriptionToAsyncIterable as x, SubscribeOptions as y };
|
|
395
|
-
//# sourceMappingURL=pool-
|
|
396
|
+
//# sourceMappingURL=pool-BDI_u03Q.d.mts.map
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { S as toError, _ as MessageError, u as normalizeURL } from "./util-DMF9AIO0.mjs";
|
|
2
|
-
import { n as canonicalizeFilters } from "./filter-
|
|
3
|
-
import { t as assertSubscriptionId } from "./message-
|
|
4
|
-
import { t as invokeSafely } from "./report-CHld4uWj.mjs";
|
|
5
|
-
import { c as RelayConnectionError, d as RelaySuspendedError, n as Relay, r as RelayStatus, s as RelayClosedError, u as RelayPublishError } from "./relay-quCs7MHD.mjs";
|
|
2
|
+
import { n as canonicalizeFilters } from "./filter-TrF2LEoy.mjs";
|
|
3
|
+
import { t as assertSubscriptionId } from "./message-Rd5Z_17h.mjs";
|
|
6
4
|
import { i as throwIfAborted, n as onAbort, t as abortReason } from "./abort-DBq2STPj.mjs";
|
|
5
|
+
import { t as invokeSafely } from "./report-CHld4uWj.mjs";
|
|
6
|
+
import { c as RelayConnectionError, d as RelaySuspendedError, n as Relay, r as RelayStatus, u as RelayPublishError } from "./relay-__QtO33h.mjs";
|
|
7
7
|
import { t as Emitter } from "./emitter-DkCY6NkU.mjs";
|
|
8
8
|
//#region src/relay/url.ts
|
|
9
9
|
/**
|
|
@@ -59,6 +59,8 @@ function fanIn(pool, jobs, opts = {}) {
|
|
|
59
59
|
const eoseAttempted = /* @__PURE__ */ new Set();
|
|
60
60
|
let pendingEose = 0;
|
|
61
61
|
let pending = 0;
|
|
62
|
+
/** Close reason of the last attached subscription that ended; drives the aggregate reason. */
|
|
63
|
+
let lastReason;
|
|
62
64
|
const fireEose = () => {
|
|
63
65
|
if (closed || eoseFired) return;
|
|
64
66
|
eoseFired = true;
|
|
@@ -101,13 +103,22 @@ function fanIn(pool, jobs, opts = {}) {
|
|
|
101
103
|
return { close: closeAll };
|
|
102
104
|
}
|
|
103
105
|
const disposeAbort = onAbort(opts.signal, () => closeAll("aborted"));
|
|
106
|
+
/**
|
|
107
|
+
* Single completion rule for both endings: `pending` reaches zero when every URL entry either
|
|
108
|
+
* failed to attach or its subscription ended — at that point no attached closer can still be
|
|
109
|
+
* live. The reason is the last subscription's close reason, or "all relays failed" when no relay
|
|
110
|
+
* ever attached.
|
|
111
|
+
*/
|
|
112
|
+
const finish = () => {
|
|
113
|
+
if (pending <= 0 && !closed) {
|
|
114
|
+
settleClose();
|
|
115
|
+
invokeSafely(() => opts.onclose?.(lastReason ?? "all relays failed"));
|
|
116
|
+
}
|
|
117
|
+
};
|
|
104
118
|
const failUrl = (jobIndex, key) => {
|
|
105
119
|
settleEose(jobIndex, key, false);
|
|
106
120
|
pending -= 1;
|
|
107
|
-
|
|
108
|
-
settleClose();
|
|
109
|
-
invokeSafely(() => opts.onclose?.("all relays failed"));
|
|
110
|
-
}
|
|
121
|
+
finish();
|
|
111
122
|
};
|
|
112
123
|
const attach = (relay, job, jobIndex) => {
|
|
113
124
|
if (closed) return;
|
|
@@ -130,11 +141,9 @@ function fanIn(pool, jobs, opts = {}) {
|
|
|
130
141
|
oneose: () => settleEose(jobIndex, relay.url, true),
|
|
131
142
|
onclose: (reason) => {
|
|
132
143
|
settleEose(jobIndex, relay.url, false);
|
|
144
|
+
lastReason = reason;
|
|
133
145
|
pending -= 1;
|
|
134
|
-
|
|
135
|
-
settleClose();
|
|
136
|
-
invokeSafely(() => opts.onclose?.(reason));
|
|
137
|
-
}
|
|
146
|
+
finish();
|
|
138
147
|
}
|
|
139
148
|
});
|
|
140
149
|
closers.push(sub);
|
|
@@ -149,12 +158,8 @@ function fanIn(pool, jobs, opts = {}) {
|
|
|
149
158
|
const tryAttach = (relay) => {
|
|
150
159
|
try {
|
|
151
160
|
attach(relay, job, jobIndex);
|
|
152
|
-
} catch
|
|
153
|
-
|
|
154
|
-
failUrl(jobIndex, key);
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
throw error;
|
|
161
|
+
} catch {
|
|
162
|
+
failUrl(jobIndex, key);
|
|
158
163
|
}
|
|
159
164
|
};
|
|
160
165
|
(async () => {
|
|
@@ -383,8 +388,10 @@ var Pool = class {
|
|
|
383
388
|
authSigner: signFn
|
|
384
389
|
});
|
|
385
390
|
created.on("close", () => {
|
|
386
|
-
this.#relays.
|
|
387
|
-
|
|
391
|
+
if (this.#relays.get(norm) === created) {
|
|
392
|
+
this.#relays.delete(norm);
|
|
393
|
+
this.#lastActivity.delete(norm);
|
|
394
|
+
}
|
|
388
395
|
});
|
|
389
396
|
created.on("invalidevent", () => {
|
|
390
397
|
this.#noteInvalidEvent(norm);
|
|
@@ -603,4 +610,4 @@ var Pool = class {
|
|
|
603
610
|
//#endregion
|
|
604
611
|
export { isInsecureRelayUrl as i, fanIn as n, fetchRouted as r, Pool as t };
|
|
605
612
|
|
|
606
|
-
//# sourceMappingURL=pool-
|
|
613
|
+
//# sourceMappingURL=pool-jok2-iBJ.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pool-jok2-iBJ.mjs","names":[],"sources":["../src/relay/url.ts","../src/relay/fan-in.ts","../src/relay/pool.ts"],"sourcesContent":["import { normalizeURL } from \"../core/util.ts\";\n\n/**\n * {@link normalizeURL}, or the raw string when it is not a valid relay URL (ensureRelay then fails\n * it like a dead relay).\n */\nexport function relayUrlKey(url: string): string {\n try {\n return normalizeURL(url);\n } catch {\n return url;\n }\n}\n\n/** Unique relay keys in first-seen order. */\nexport function uniqueRelayUrls(urls: Iterable<string>): string[] {\n const seen = new Set<string>();\n const unique: string[] = [];\n for (const url of urls) {\n const key = relayUrlKey(url);\n if (seen.has(key)) {\n continue;\n }\n seen.add(key);\n unique.push(key);\n }\n return unique;\n}\n\n/**\n * True for `ws:` / `http:` URLs that are not `.onion`. Local-network `ws://` is still insecure;\n * allow those via a trusted set.\n */\nexport function isInsecureRelayUrl(url: string): boolean {\n try {\n const parsed = new URL(url);\n if (parsed.hostname.endsWith(\".onion\")) {\n return false;\n }\n return parsed.protocol === \"ws:\" || parsed.protocol === \"http:\";\n } catch {\n return false;\n }\n}\n","import { abortReason, onAbort, throwIfAborted } from \"../core/abort.ts\";\nimport type { Event } from \"../core/event.ts\";\nimport type { Filter } from \"../core/filter.ts\";\nimport { invokeSafely } from \"../core/report.ts\";\nimport type { Pool } from \"./pool.ts\";\nimport type { Relay } from \"./relay.ts\";\nimport type { Closer } from \"./subscription.ts\";\nimport { uniqueRelayUrls } from \"./url.ts\";\n\nexport type RoutedJob = {\n urls: ReadonlyArray<string>;\n filters: ReadonlyArray<Filter>;\n id?: string | undefined;\n};\n\nexport type FanInOptions = {\n /** First receipt only (deduped across jobs). `relayUrl` is the normalized relay URL. */\n onevent?: ((event: Event, relayUrl: string) => void) | undefined;\n oneose?: (() => void) | undefined;\n onclose?: ((reason: string) => void) | undefined;\n signal?: AbortSignal | undefined;\n /** Armed at fan-in only. Never forwarded to relay.subscribe. */\n eoseTimeoutMs?: number | undefined;\n alreadyHaveEvent?: ((id: string) => boolean) | undefined;\n /** Every receipt from every relay, including skipped duplicates. */\n receivedEvent?: ((id: string, relayUrl: string) => void) | undefined;\n closeOnEose?: boolean | undefined;\n /** Optional. ensureRelay uses Pool.#opts.connectTimeoutMs when omitted. */\n connectTimeoutMs?: number | undefined;\n};\n\n/**\n * Aggregate EOSE/close across routed jobs. One `seen` set. `pending` counts URL list entries\n * (duplicates included). `pendingEose` counts unique `jobIndex:url` keys.\n */\nexport function fanIn(pool: Pool, jobs: ReadonlyArray<RoutedJob>, opts: FanInOptions = {}): Closer {\n const seen = new Set<string>();\n const closers: Closer[] = [];\n let closed = false;\n let eoseFired = false;\n let sawEose = false;\n let eoseTimer: ReturnType<typeof setTimeout> | undefined;\n const eoseDone = new Set<string>();\n const eoseAttempted = new Set<string>();\n let pendingEose = 0;\n let pending = 0;\n /** Close reason of the last attached subscription that ended; drives the aggregate reason. */\n let lastReason: string | undefined;\n\n const fireEose = () => {\n if (closed || eoseFired) {\n return;\n }\n eoseFired = true;\n if (eoseTimer !== undefined) {\n clearTimeout(eoseTimer);\n eoseTimer = undefined;\n }\n invokeSafely(() => opts.oneose?.());\n };\n\n /**\n * Remove one relay from the pending-EOSE set. `eosed` credits a real EOSE; a CLOSED or a connect\n * failure only drops the wait — the aggregate `oneose` fires once every remaining relay ended AND\n * at least one actually EOSE'd.\n */\n const settleEose = (jobIndex: number, url: string, eosed: boolean) => {\n const key = `${jobIndex}:${url}`;\n if (eoseDone.has(key)) {\n return;\n }\n eoseDone.add(key);\n if (eosed) {\n sawEose = true;\n }\n pendingEose -= 1;\n if (pendingEose === 0 && sawEose) {\n fireEose();\n }\n };\n\n const settleClose = () => {\n closed = true;\n disposeAbort();\n if (eoseTimer !== undefined) {\n clearTimeout(eoseTimer);\n eoseTimer = undefined;\n }\n };\n\n const closeAll = (reason?: string) => {\n if (closed) {\n return;\n }\n settleClose();\n for (const c of closers) {\n c.close(reason);\n }\n invokeSafely(() => opts.onclose?.(reason ?? \"closed by client\"));\n };\n\n if (opts.signal?.aborted === true) {\n closed = true;\n invokeSafely(() => opts.onclose?.(\"aborted\"));\n return { close: closeAll };\n }\n\n const disposeAbort = onAbort(opts.signal, () => closeAll(\"aborted\"));\n\n /**\n * Single completion rule for both endings: `pending` reaches zero when every URL entry either\n * failed to attach or its subscription ended — at that point no attached closer can still be\n * live. The reason is the last subscription's close reason, or \"all relays failed\" when no relay\n * ever attached.\n */\n const finish = (): void => {\n if (pending <= 0 && !closed) {\n settleClose();\n invokeSafely(() => opts.onclose?.(lastReason ?? \"all relays failed\"));\n }\n };\n\n const failUrl = (jobIndex: number, key: string): void => {\n settleEose(jobIndex, key, false);\n pending -= 1;\n finish();\n };\n\n const attach = (relay: Relay, job: RoutedJob, jobIndex: number): void => {\n if (closed) {\n return;\n }\n const received = opts.receivedEvent;\n const sub = relay.subscribe([...job.filters], {\n id: jobs.length === 1 ? job.id : undefined,\n closeOnEose: opts.closeOnEose,\n alreadyHaveEvent: (id) => {\n let have = seen.has(id);\n invokeSafely(() => {\n have = have || Boolean(opts.alreadyHaveEvent?.(id));\n });\n return have;\n },\n receivedEvent:\n received === undefined ? undefined : (id) => invokeSafely(() => received(id, relay.url)),\n onevent: (event) => {\n seen.add(event.id);\n invokeSafely(() => opts.onevent?.(event, relay.url));\n },\n oneose: () => settleEose(jobIndex, relay.url, true),\n onclose: (reason) => {\n settleEose(jobIndex, relay.url, false);\n lastReason = reason;\n pending -= 1;\n finish();\n },\n });\n closers.push(sub);\n };\n\n for (const [jobIndex, job] of jobs.entries()) {\n // Equivalent spellings of one relay URL attach exactly once per job.\n for (const key of uniqueRelayUrls(job.urls)) {\n pending += 1;\n const eoseKey = `${jobIndex}:${key}`;\n if (!eoseAttempted.has(eoseKey)) {\n eoseAttempted.add(eoseKey);\n pendingEose += 1;\n }\n\n const tryAttach = (relay: Relay): void => {\n try {\n attach(relay, job, jobIndex);\n } catch {\n // Any attach failure retires this URL: RelayClosedError is the expected\n // dropped-between-connect-and-subscribe race; anything else must not escape the\n // `void` wrapper as an unhandled rejection, so it fails the URL as well.\n failUrl(jobIndex, key);\n }\n };\n\n void (async (): Promise<void> => {\n let relay: Relay;\n try {\n relay = await pool.ensureRelay(key, {\n signal: opts.signal,\n timeoutMs: opts.connectTimeoutMs,\n });\n } catch {\n // ensureRelay rejected only — not tryAttach throws (those must not look like connect failure).\n const existing = pool.getRelay(key);\n if (existing !== undefined) {\n tryAttach(existing);\n return;\n }\n failUrl(jobIndex, key);\n return;\n }\n tryAttach(relay);\n })();\n }\n }\n\n if (opts.eoseTimeoutMs !== undefined && pending > 0) {\n eoseTimer = setTimeout(() => {\n eoseTimer = undefined;\n fireEose();\n }, opts.eoseTimeoutMs);\n }\n\n if (pending === 0) {\n queueMicrotask(() => {\n if (closed) {\n return;\n }\n settleClose();\n invokeSafely(() => opts.onclose?.(\"no relays\"));\n });\n }\n\n return { close: closeAll };\n}\n\nexport async function fetchRouted(\n pool: Pool,\n jobs: ReadonlyArray<RoutedJob>,\n opts: {\n timeoutMs?: number | undefined;\n signal?: AbortSignal | undefined;\n connectTimeoutMs?: number | undefined;\n /** Every event of every relay batch, including cross-relay duplicates. */\n onevent?: ((event: Event, relayUrl: string) => void) | undefined;\n } = {},\n): Promise<Event[]> {\n throwIfAborted(opts.signal);\n const byId = new Map<string, Event>();\n await Promise.all(\n jobs.flatMap((job) => {\n return uniqueRelayUrls(job.urls).map(async (url) => {\n let batch: ReadonlyArray<Event>;\n let relayUrl: string;\n try {\n const relay = await pool.ensureRelay(url, {\n signal: opts.signal,\n timeoutMs: opts.connectTimeoutMs,\n });\n relayUrl = relay.url;\n const result = await relay.fetch([...job.filters], {\n timeoutMs: opts.timeoutMs,\n signal: opts.signal,\n });\n batch = result.events;\n } catch {\n // An abort rejects the whole call; per-relay failures are skipped.\n if (opts.signal?.aborted === true) {\n throw abortReason(opts.signal);\n }\n return;\n }\n // The whole batch lands before callbacks so a throwing onevent cannot\n // drop events; listener errors are reported, never propagated.\n for (const event of batch) {\n if (!byId.has(event.id)) {\n byId.set(event.id, event);\n }\n }\n for (const event of batch) {\n invokeSafely(() => opts.onevent?.(event, relayUrl));\n }\n });\n }),\n );\n return [...byId.values()];\n}\n","import { abortReason, throwIfAborted } from \"../core/abort.ts\";\nimport { Emitter } from \"../core/emitter.ts\";\nimport { MessageError, toError } from \"../core/error.ts\";\nimport type { Event, EventTemplate } from \"../core/event.ts\";\nimport { canonicalizeFilters } from \"../core/filter.ts\";\nimport type { Filter } from \"../core/filter.ts\";\nimport { assertSubscriptionId } from \"../core/message.ts\";\nimport type { CountResult } from \"../core/message.ts\";\nimport { normalizeURL } from \"../core/util.ts\";\nimport { RelayConnectionError, RelayPublishError, RelaySuspendedError } from \"./error.ts\";\nimport { fanIn, fetchRouted } from \"./fan-in.ts\";\nimport { Relay, RelayStatus } from \"./relay.ts\";\nimport type { RelayFetchEnd, RelayOptions, SubscribeOptions } from \"./relay.ts\";\nimport type { Closer } from \"./subscription.ts\";\nimport { isInsecureRelayUrl, uniqueRelayUrls } from \"./url.ts\";\n\n/** Per-relay tolerance for events that fail id/signature verification. */\nexport type InvalidEventPolicy = {\n /** Invalid events tolerated within `windowMs`; exceeding it suspends the relay. */\n limit: number;\n windowMs: number;\n /** How long a suspended relay stays closed before it may reconnect. */\n cooldownMs: number;\n};\n\n/** Pool-wide options applied to every managed relay. */\nexport type PoolOptions = Omit<RelayOptions, \"authSigner\"> & {\n /**\n * When set, automatically answer NIP-42 AUTH challenges for relays that send them. Return\n * undefined to skip a given relay URL.\n */\n automaticallyAuth?:\n | ((relayURL: string) => ((event: EventTemplate) => Promise<Event>) | undefined)\n | undefined;\n /** When false (default), ensureRelay rejects isInsecureRelayUrl unless trusted. */\n allowInsecure?: boolean | undefined;\n trustedInsecureUrls?: ReadonlyArray<string> | undefined;\n /** Close unused relays. Unset/0 = disabled. */\n idleTimeoutMs?: number | undefined;\n idleCleanupIntervalMs?: number | undefined;\n /**\n * Soft cap on connected non-pinned relays. When `ensureRelay` would create a new non-pinned relay\n * at the cap, the least-recently-used idle one is closed first (idle = no subscriptions and no\n * in-flight requests). If none is idle the connect proceeds anyway — the cap never fails a\n * request.\n */\n maxRelays?: number | undefined;\n /** Normalized URLs never closed by idle cleanup or `maxRelays` eviction. */\n pinnedUrls?: ReadonlyArray<string> | undefined;\n /**\n * When set, a relay whose `limit`-th-plus-one EVENT fails id/signature verification inside a\n * `windowMs` sliding window is disconnected and suspended for `cooldownMs`. During suspension\n * `ensureRelay` rejects with {@link RelaySuspendedError}; once it lifts, live subscriptions resume\n * through the normal reconnect path. Unset = events are dropped without counting, as before.\n */\n invalidEventPolicy?: InvalidEventPolicy | undefined;\n};\n\n/** Typed event payloads for {@link Pool.on}. */\nexport type PoolEventMap = {\n /** Normalized URLs closed by an idle-cleanup pass. */\n idle: ReadonlyArray<string>;\n /**\n * A relay suspended for exceeding `invalidEventPolicy`: `url` is the normalized relay URL and\n * `until` the epoch-ms time the suspension lifts.\n */\n suspend: { readonly url: string; readonly until: number };\n};\n\n/** Per-relay publish outcome: the relay's OK verdict or a transport-level failure. */\nexport type PoolPublishResult =\n | { readonly url: string; readonly status: \"ok\"; readonly message: string }\n | { readonly url: string; readonly status: \"rejected\"; readonly message: string }\n | { readonly url: string; readonly status: \"failed\"; readonly error: Error };\n\n/** Multi-relay subscribe options: callbacks also receive the normalized relay URL. */\nexport type PoolSubscribeOptions = Omit<SubscribeOptions, \"onevent\" | \"receivedEvent\"> & {\n onevent?: ((event: Event, relayUrl: string) => void) | undefined;\n receivedEvent?: ((id: string, relayUrl: string) => void) | undefined;\n};\n\n/** Per-relay one-shot fetch outcome: collected events plus how its REQ ended. */\nexport type PoolFetchResult = {\n readonly url: string;\n readonly events: ReadonlyArray<Event>;\n readonly end: RelayFetchEnd | { readonly type: \"failed\"; readonly error: Error };\n};\n\n/** Per-relay NIP-45 COUNT result (or a transport-level failure). */\nexport type PoolCountResult =\n | ({ readonly url: string; readonly status: \"ok\" } & CountResult)\n | { readonly url: string; readonly status: \"failed\"; readonly error: Error };\n\nconst countOk = (url: string, payload: CountResult): PoolCountResult => ({\n url,\n status: \"ok\",\n ...payload,\n});\n\nfunction isIdle(relay: Relay): boolean {\n return (\n relay.status !== RelayStatus.Connecting &&\n relay.subscriptionCount === 0 &&\n relay.inFlightCount === 0\n );\n}\n\n/** Multi-relay coordinator: connection reuse, cross-relay event dedup, fan-out publish. */\nexport class Pool {\n readonly #relays = new Map<string, Relay>();\n readonly #relayOptions: Omit<RelayOptions, \"authSigner\">;\n readonly #automaticallyAuth: PoolOptions[\"automaticallyAuth\"];\n readonly #maxRelays: number | undefined;\n readonly #invalidEventPolicy: InvalidEventPolicy | undefined;\n readonly #events = new Emitter<PoolEventMap>();\n readonly #lastActivity = new Map<string, number>();\n readonly #idleTimeoutMs: number;\n #idleTimer: ReturnType<typeof setInterval> | undefined;\n #allowInsecure: boolean;\n #trustedInsecure: Set<string>;\n #pinned: Set<string>;\n /** Per-relay timestamps of verification failures inside the sliding window. */\n readonly #invalidEvents = new Map<string, number[]>();\n /** Normalized URL → epoch ms when its suspension lifts. */\n readonly #suspendedUntil = new Map<string, number>();\n readonly #resumeTimers = new Map<string, ReturnType<typeof setTimeout>>();\n\n constructor(opts: PoolOptions = {}) {\n const {\n automaticallyAuth,\n allowInsecure,\n trustedInsecureUrls,\n idleTimeoutMs,\n idleCleanupIntervalMs,\n maxRelays,\n pinnedUrls,\n invalidEventPolicy,\n ...relayOptions\n } = opts;\n this.#relayOptions = relayOptions;\n this.#automaticallyAuth = automaticallyAuth;\n this.#maxRelays = maxRelays;\n this.#invalidEventPolicy = invalidEventPolicy;\n this.#allowInsecure = allowInsecure ?? false;\n this.#trustedInsecure = new Set((trustedInsecureUrls ?? []).map(normalizeURL));\n this.#pinned = new Set((pinnedUrls ?? []).map(normalizeURL));\n this.#idleTimeoutMs = idleTimeoutMs ?? 0;\n if (this.#idleTimeoutMs > 0) {\n this.#idleTimer = setInterval(() => this.cleanIdleRelays(), idleCleanupIntervalMs ?? 30_000);\n }\n }\n\n /**\n * Listen for a pool event. Multiple listeners per type are allowed and fire in registration\n * order; a throwing listener is reported and does not break the dispatch. Returns an unsubscribe\n * function.\n */\n on<K extends keyof PoolEventMap>(\n type: K,\n listener: (payload: PoolEventMap[K]) => void,\n ): () => void {\n return this.#events.on(type, listener);\n }\n\n setAllowInsecure(allow: boolean): void {\n this.#allowInsecure = allow;\n }\n\n setTrustedInsecureUrls(urls: ReadonlyArray<string>): void {\n this.#trustedInsecure = new Set(urls.map(normalizeURL));\n }\n\n setPinnedUrls(urls: ReadonlyArray<string>): void {\n this.#pinned = new Set(urls.map(normalizeURL));\n }\n\n /**\n * Drop cached AUTH rejections and re-fire pending challenges on every pooled relay, so a later\n * `setSigner` takes effect without reconnect.\n */\n resetAuth(): void {\n for (const relay of this.#relays.values()) {\n relay.resetAuth();\n }\n }\n\n cleanIdleRelays(): void {\n if (this.#idleTimeoutMs <= 0) {\n return;\n }\n const now = Date.now();\n const idle: string[] = [];\n for (const [url, relay] of this.#relays) {\n if (this.#pinned.has(url) || !isIdle(relay)) {\n continue;\n }\n const last = this.#lastActivity.get(url) ?? 0;\n if (!relay.connected || now - last >= this.#idleTimeoutMs) {\n idle.push(url);\n }\n }\n for (const url of this.#lastActivity.keys()) {\n if (!this.#relays.has(url)) {\n this.#lastActivity.delete(url);\n }\n }\n if (idle.length === 0) {\n return;\n }\n this.closeRelays(idle);\n this.#events.emit(\"idle\", idle);\n }\n\n #touch(url: string): void {\n this.#lastActivity.set(url, Date.now());\n }\n\n /**\n * Count one verification failure for `norm`; on the `limit + 1`-th inside `windowMs` suspend the\n * relay: drop its connection (subscriptions kept), record `until`, and emit `suspend`.\n */\n #noteInvalidEvent(norm: string): void {\n const policy = this.#invalidEventPolicy;\n if (policy === undefined) {\n return;\n }\n const now = Date.now();\n const window = (this.#invalidEvents.get(norm) ?? []).filter((at) => now - at < policy.windowMs);\n window.push(now);\n if (window.length <= policy.limit) {\n this.#invalidEvents.set(norm, window);\n return;\n }\n this.#invalidEvents.delete(norm);\n if (this.#suspendedUntil.has(norm)) {\n return;\n }\n const until = now + policy.cooldownMs;\n this.#suspendedUntil.set(norm, until);\n this.#relays.get(norm)?.disconnect();\n const resume = setTimeout(() => {\n this.#resumeTimers.delete(norm);\n this.#suspendedUntil.delete(norm);\n const relay = this.#relays.get(norm);\n if (relay === undefined || relay.connected || relay.subscriptionCount === 0) {\n return;\n }\n void (async (): Promise<void> => {\n try {\n await relay.connect();\n } catch {\n // With enableReconnect the relay's own backoff retries; otherwise the\n // next ensureRelay attempt reconnects on demand.\n }\n })();\n }, policy.cooldownMs);\n this.#resumeTimers.set(norm, resume);\n this.#events.emit(\"suspend\", { url: norm, until });\n }\n\n #rejectInsecure(url: string, norm: string): void {\n if (this.#allowInsecure) {\n return;\n }\n if (!isInsecureRelayUrl(url)) {\n return;\n }\n if (this.#trustedInsecure.has(norm)) {\n return;\n }\n throw new RelayConnectionError(\"insecure relay connection blocked\", norm);\n }\n\n #stopIdleCleanup(): void {\n if (this.#idleTimer === undefined) {\n return;\n }\n clearInterval(this.#idleTimer);\n this.#idleTimer = undefined;\n }\n\n /**\n * Soft cap: closing the least-recently-used idle non-pinned relay when a new non-pinned relay\n * would push the count past `maxRelays`. Busy relays are never evicted; with none idle the cap is\n * exceeded rather than failing.\n */\n #enforceMaxRelays(incoming: string): void {\n const cap = this.#maxRelays;\n if (cap === undefined || this.#pinned.has(incoming)) {\n return;\n }\n let count = 0;\n let oldestUrl: string | undefined;\n let oldestAt = Number.POSITIVE_INFINITY;\n for (const [url, relay] of this.#relays) {\n if (this.#pinned.has(url)) {\n continue;\n }\n count += 1;\n if (!isIdle(relay)) {\n continue;\n }\n const at = this.#lastActivity.get(url) ?? 0;\n if (at < oldestAt) {\n oldestAt = at;\n oldestUrl = url;\n }\n }\n if (count >= cap && oldestUrl !== undefined) {\n this.closeRelays([oldestUrl]);\n }\n }\n\n async ensureRelay(\n url: string,\n opts?: { signal?: AbortSignal | undefined; timeoutMs?: number | undefined },\n ): Promise<Relay> {\n const norm = normalizeURL(url);\n this.#rejectInsecure(url, norm);\n const suspendedUntil = this.#suspendedUntil.get(norm);\n if (suspendedUntil !== undefined) {\n if (Date.now() < suspendedUntil) {\n throw new RelaySuspendedError(norm, suspendedUntil);\n }\n this.#suspendedUntil.delete(norm);\n }\n let relay = this.#relays.get(norm);\n if (!relay) {\n this.#enforceMaxRelays(norm);\n const signFn = this.#automaticallyAuth?.(norm) ?? undefined;\n const created = new Relay(norm, { ...this.#relayOptions, authSigner: signFn });\n // Only drop from the pool on terminal close (reconnect keeps the entry). Guard on\n // identity: a stale relay's delayed close must not delete a newer relay's entry.\n created.on(\"close\", () => {\n if (this.#relays.get(norm) === created) {\n this.#relays.delete(norm);\n this.#lastActivity.delete(norm);\n }\n });\n created.on(\"invalidevent\", () => {\n this.#noteInvalidEvent(norm);\n });\n if (signFn) {\n created.on(\"auth\", () => {\n void (async (): Promise<void> => {\n try {\n await created.auth(signFn);\n } catch {\n // auth failure surfaces on subsequent CLOSED/OK; avoid unhandled rejection\n }\n })();\n });\n }\n this.#relays.set(norm, created);\n relay = created;\n }\n this.#touch(norm);\n if (!relay.connected) {\n try {\n await relay.connect({\n signal: opts?.signal,\n timeoutMs: opts?.timeoutMs ?? this.#relayOptions.connectTimeoutMs,\n });\n } catch (error) {\n // Keep the entry when reconnect is enabled so open subscriptions can recover.\n if (this.#relayOptions.enableReconnect !== true) {\n this.#relays.delete(norm);\n this.#lastActivity.delete(norm);\n }\n throw error;\n }\n }\n return relay;\n }\n\n /** Close every pooled relay and clear all pool state. */\n close(): void {\n this.#stopIdleCleanup();\n for (const timer of this.#resumeTimers.values()) {\n clearTimeout(timer);\n }\n this.#resumeTimers.clear();\n this.#suspendedUntil.clear();\n this.#invalidEvents.clear();\n for (const relay of this.#relays.values()) {\n relay.close();\n }\n this.#relays.clear();\n this.#lastActivity.clear();\n }\n\n /** Close and forget the listed relays; other pooled relays are untouched. */\n closeRelays(urls: ReadonlyArray<string>): void {\n for (const url of urls) {\n const norm = normalizeURL(url);\n const timer = this.#resumeTimers.get(norm);\n if (timer !== undefined) {\n clearTimeout(timer);\n this.#resumeTimers.delete(norm);\n }\n this.#relays.get(norm)?.close();\n this.#relays.delete(norm);\n this.#lastActivity.delete(norm);\n }\n }\n\n /**\n * Subscribe across relays. Deduplicates by event id. Returns a closer; callbacks receive every\n * new event once.\n */\n subscribe(\n relays: ReadonlyArray<string>,\n filters: ReadonlyArray<Filter>,\n opts: PoolSubscribeOptions = {},\n ): Closer {\n if (filters.length === 0) {\n throw new MessageError(\"REQ requires at least one filter\");\n }\n if (opts.id !== undefined) {\n assertSubscriptionId(opts.id);\n }\n const canonical = canonicalizeFilters(filters);\n return fanIn(this, [{ urls: relays, filters: canonical, id: opts.id }], {\n onevent: opts.onevent,\n oneose: opts.oneose,\n onclose: opts.onclose,\n signal: opts.signal,\n eoseTimeoutMs: opts.eoseTimeoutMs,\n alreadyHaveEvent: opts.alreadyHaveEvent,\n receivedEvent: opts.receivedEvent,\n closeOnEose: opts.closeOnEose,\n connectTimeoutMs: this.#relayOptions.connectTimeoutMs,\n });\n }\n\n /**\n * One-shot fetch per relay: each entry reports its own events and end reason (`eose`, `closed`,\n * `timeout`), or `failed` when the connect or REQ itself errored. An abort rejects the whole\n * call. Events are not deduped across relays — use {@link fetch} for that.\n */\n async fetchEach(\n relays: ReadonlyArray<string>,\n filters: ReadonlyArray<Filter>,\n opts?: { timeoutMs?: number | undefined; signal?: AbortSignal | undefined },\n ): Promise<PoolFetchResult[]> {\n if (filters.length === 0) {\n throw new MessageError(\"REQ requires at least one filter\");\n }\n throwIfAborted(opts?.signal);\n const canonical = canonicalizeFilters(filters);\n return Promise.all(\n uniqueRelayUrls(relays).map(async (url): Promise<PoolFetchResult> => {\n try {\n const relay = await this.ensureRelay(url, {\n signal: opts?.signal,\n timeoutMs: this.#relayOptions.connectTimeoutMs,\n });\n this.#touch(relay.url);\n const result = await relay.fetch([...canonical], {\n timeoutMs: opts?.timeoutMs,\n signal: opts?.signal,\n });\n return { url, events: result.events, end: result.end };\n } catch (error) {\n // An abort rejects the whole call; per-relay failures are reported.\n if (opts?.signal?.aborted === true) {\n throw abortReason(opts.signal);\n }\n return {\n url,\n events: [],\n end: { type: \"failed\", error: toError(error) },\n };\n }\n }),\n );\n }\n\n /** Fetch events until each connected relay EOSE or timeout; dedupe by id. */\n async fetch(\n relays: ReadonlyArray<string>,\n filters: ReadonlyArray<Filter>,\n opts?: {\n timeoutMs?: number | undefined;\n signal?: AbortSignal | undefined;\n /** Every event of every relay batch, including cross-relay duplicates. */\n onevent?: ((event: Event, relayUrl: string) => void) | undefined;\n },\n ): Promise<Event[]> {\n if (filters.length === 0) {\n throw new MessageError(\"REQ requires at least one filter\");\n }\n return fetchRouted(this, [{ urls: relays, filters: canonicalizeFilters(filters) }], {\n timeoutMs: opts?.timeoutMs,\n signal: opts?.signal,\n connectTimeoutMs: this.#relayOptions.connectTimeoutMs,\n onevent: opts?.onevent,\n });\n }\n\n /** Publish to all listed relays; returns per-relay outcomes. */\n async publish(\n relays: ReadonlyArray<string>,\n event: Event,\n opts?: { timeoutMs?: number | undefined },\n ): Promise<PoolPublishResult[]> {\n return Promise.all(\n uniqueRelayUrls(relays).map(async (url): Promise<PoolPublishResult> => {\n try {\n const relay = await this.ensureRelay(url, {\n timeoutMs: this.#relayOptions.connectTimeoutMs,\n });\n this.#touch(relay.url);\n const result = await relay.publish(event, { timeoutMs: opts?.timeoutMs });\n return result.ok\n ? { url, status: \"ok\", message: result.message }\n : { url, status: \"rejected\", message: result.message };\n } catch (error) {\n return { url, status: \"failed\", error: toError(error) };\n }\n }),\n );\n }\n\n /** First successful publish (Promise.any semantics). */\n async publishAny(\n relays: ReadonlyArray<string>,\n event: Event,\n opts?: { timeoutMs?: number | undefined },\n ): Promise<Extract<PoolPublishResult, { status: \"ok\" }>> {\n return Promise.any(\n uniqueRelayUrls(relays).map(async (url) => {\n const relay = await this.ensureRelay(url, {\n timeoutMs: this.#relayOptions.connectTimeoutMs,\n });\n this.#touch(relay.url);\n const result = await relay.publish(event, { timeoutMs: opts?.timeoutMs });\n if (!result.ok) {\n throw new RelayPublishError(result.message || \"rejected\", relay.url);\n }\n return { url, status: \"ok\" as const, message: result.message };\n }),\n );\n }\n\n /**\n * NIP-45 COUNT across relays. Per-relay outcomes; failures do not throw. Counts are not summed —\n * each relay reports independently (may overlap).\n */\n async count(\n relays: ReadonlyArray<string>,\n filters: ReadonlyArray<Filter>,\n opts?: { timeoutMs?: number | undefined; signal?: AbortSignal | undefined },\n ): Promise<PoolCountResult[]> {\n throwIfAborted(opts?.signal);\n const canonical = canonicalizeFilters(filters);\n return Promise.all(\n uniqueRelayUrls(relays).map(async (url): Promise<PoolCountResult> => {\n try {\n const relay = await this.ensureRelay(url, {\n signal: opts?.signal,\n timeoutMs: this.#relayOptions.connectTimeoutMs,\n });\n this.#touch(relay.url);\n const payload: CountResult = await relay.count(canonical, {\n timeoutMs: opts?.timeoutMs,\n signal: opts?.signal,\n });\n return countOk(url, payload);\n } catch (error) {\n // An abort rejects the whole call; per-relay failures are reported.\n if (opts?.signal?.aborted === true) {\n throw abortReason(opts.signal);\n }\n return { url, status: \"failed\", error: toError(error) };\n }\n }),\n );\n }\n\n listRelays(): string[] {\n return [...this.#relays.keys()];\n }\n\n /** Lookup a pooled relay, including disconnected reconnecting entries. */\n getRelay(url: string): Relay | undefined {\n try {\n return this.#relays.get(normalizeURL(url));\n } catch {\n return this.#relays.get(url);\n }\n }\n\n /** Currently connected URLs. Unlike listRelays(), excludes reconnecting/disconnected entries. */\n connectedUrls(): string[] {\n const urls: string[] = [];\n for (const [url, relay] of this.#relays) {\n if (relay.connected) {\n urls.push(url);\n }\n }\n return urls;\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { o as isHex32 } from "./util-DMF9AIO0.mjs";
|
|
2
|
-
import { i as isEphemeralKind, t as Kind } from "./kind-
|
|
3
|
-
import {
|
|
2
|
+
import { i as isEphemeralKind, t as Kind } from "./kind-D8fENiiL.mjs";
|
|
3
|
+
import { S as parseEventAddress, _ as eventAddress, a as isReplaceableWinner, h as validateSignedEvent, i as isMarkedVerified } from "./event-C2eLiPpV.mjs";
|
|
4
4
|
//#region src/storage/deletion.ts
|
|
5
5
|
/**
|
|
6
6
|
* NIP-09 application plan for a kind 5 event.
|
|
@@ -100,7 +100,7 @@ function outboxBoundKey(pubkey, kind) {
|
|
|
100
100
|
return `${pubkey.toLowerCase()}:${kind}`;
|
|
101
101
|
}
|
|
102
102
|
function decidePut(raw, lookup) {
|
|
103
|
-
if (!validateSignedEvent(raw)) return {
|
|
103
|
+
if (!isMarkedVerified(raw) && !validateSignedEvent(raw)) return {
|
|
104
104
|
action: "skip",
|
|
105
105
|
result: "invalid",
|
|
106
106
|
event: raw
|
|
@@ -184,4 +184,4 @@ function applyPutMemory(s, d) {
|
|
|
184
184
|
//#endregion
|
|
185
185
|
export { planDeletion as a, DeletionState as i, decidePut as n, outboxBoundKey as r, applyPutMemory as t };
|
|
186
186
|
|
|
187
|
-
//# sourceMappingURL=put-
|
|
187
|
+
//# sourceMappingURL=put-JsiCKMyZ.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"put-JsiCKMyZ.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,11 +1,11 @@
|
|
|
1
1
|
import { u as normalizeURL } from "./util-DMF9AIO0.mjs";
|
|
2
|
-
import { t as Kind } from "./kind-
|
|
3
|
-
import {
|
|
4
|
-
import { o as matchFilters, r as filterFingerprint } from "./filter-
|
|
2
|
+
import { t as Kind } from "./kind-D8fENiiL.mjs";
|
|
3
|
+
import { S as parseEventAddress, _ as eventAddress, o as itemCompare, y as formatEventAddress } from "./event-C2eLiPpV.mjs";
|
|
4
|
+
import { o as matchFilters, r as filterFingerprint } from "./filter-TrF2LEoy.mjs";
|
|
5
5
|
import { t as invokeSafely } from "./report-CHld4uWj.mjs";
|
|
6
6
|
import { t as Emitter } from "./emitter-DkCY6NkU.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-D_lvlYAy.mjs";
|
|
9
9
|
//#region src/store/reactive.ts
|
|
10
10
|
const SEEN_ON_PER_ID = 16;
|
|
11
11
|
var WatchImpl = class {
|
|
@@ -392,4 +392,4 @@ var ReactiveEventStore = class {
|
|
|
392
392
|
//#endregion
|
|
393
393
|
export { ReactiveEventStore as t };
|
|
394
394
|
|
|
395
|
-
//# sourceMappingURL=reactive-
|
|
395
|
+
//# sourceMappingURL=reactive-6Wn9qf_d.mjs.map
|