@qntx/nostr 0.4.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/{client-C6EultPx.mjs → client-CadO5fAX.mjs} +33 -3
  2. package/dist/client-CadO5fAX.mjs.map +1 -0
  3. package/dist/client.d.mts +2 -2
  4. package/dist/client.mjs +1 -1
  5. package/dist/{index-BP4UmZKF.d.mts → index-CNl2q67H.d.mts} +20 -5
  6. package/dist/{index-skvsYz00.d.mts → index-lvXeER5t.d.mts} +3 -3
  7. package/dist/{index-D-TK_KJG.d.mts → index-q4MDce_r.d.mts} +2 -2
  8. package/dist/index.d.mts +6 -6
  9. package/dist/index.mjs +3 -3
  10. package/dist/instance-CIca-ynn.d.mts +15 -0
  11. package/dist/instance-DQE8BoWt.mjs +221 -0
  12. package/dist/instance-DQE8BoWt.mjs.map +1 -0
  13. package/dist/loaders.d.mts +1 -1
  14. package/dist/nostr_crypto_wasm_bg.wasm +0 -0
  15. package/dist/{pool-BGVx59yd.mjs → pool-As20OGxB.mjs} +95 -34
  16. package/dist/pool-As20OGxB.mjs.map +1 -0
  17. package/dist/{pool-B7p_reAm.d.mts → pool-DBIYMtwy.d.mts} +42 -4
  18. package/dist/{reactive-DFpqtIf_.d.mts → reactive-7aqeip4A.d.mts} +3 -1
  19. package/dist/{reactive-DDlThDSZ.mjs → reactive-BfhPp9a3.mjs} +38 -4
  20. package/dist/reactive-BfhPp9a3.mjs.map +1 -0
  21. package/dist/relay.d.mts +3 -3
  22. package/dist/relay.mjs +1 -1
  23. package/dist/store.d.mts +1 -1
  24. package/dist/store.mjs +1 -1
  25. package/dist/wasm.browser.d.mts +8 -0
  26. package/dist/wasm.browser.mjs +19 -0
  27. package/dist/wasm.browser.mjs.map +1 -0
  28. package/dist/wasm.d.mts +1 -11
  29. package/dist/wasm.mjs +6 -207
  30. package/dist/wasm.mjs.map +1 -1
  31. package/package.json +10 -3
  32. package/dist/client-C6EultPx.mjs.map +0 -1
  33. package/dist/pool-BGVx59yd.mjs.map +0 -1
  34. package/dist/reactive-DDlThDSZ.mjs.map +0 -1
@@ -0,0 +1,221 @@
1
+ import { f as CryptoError, v as WasmVerifyPoisonedError } from "./util-LfQirgDk.mjs";
2
+ import { t as createEventVerifier } from "./verifier-t5WoWbtY.mjs";
3
+ //#region src/wasm/abi.ts
4
+ /** Imports 0.2.122 may request. Instantiation fails if anything else appears. */
5
+ const ALLOWED_WASM_IMPORTS = [{
6
+ module: "wbg",
7
+ name: "__wbindgen_throw"
8
+ }, {
9
+ module: "./nostr_crypto_wasm_bg.js",
10
+ name: "__wbindgen_throw"
11
+ }];
12
+ const utf8 = new TextDecoder();
13
+ function decodeUtf8(memory, ptr, len) {
14
+ return utf8.decode(new Uint8Array(memory.buffer, ptr, len));
15
+ }
16
+ function makeWbgImports(holder) {
17
+ const throwRuntime = (ptr, len) => {
18
+ const mem = holder.exports?.memory;
19
+ const msg = mem ? decodeUtf8(mem, ptr, len) : "wasm panic";
20
+ throw new WebAssembly.RuntimeError(msg);
21
+ };
22
+ const wbg = { __wbindgen_throw: throwRuntime };
23
+ return {
24
+ wbg,
25
+ "./nostr_crypto_wasm_bg.js": wbg
26
+ };
27
+ }
28
+ /** Fail if the module imports anything outside {@link ALLOWED_WASM_IMPORTS}. */
29
+ function assertAllowedWasmImports(mod) {
30
+ const allowed = new Set(ALLOWED_WASM_IMPORTS.map((item) => `${item.module}\0${item.name}`));
31
+ for (const imp of WebAssembly.Module.imports(mod)) {
32
+ if (imp.kind !== "function") throw new TypeError(`unexpected wasm import kind ${imp.kind}: ${imp.module}.${imp.name}`);
33
+ if (!allowed.has(`${imp.module}\0${imp.name}`)) throw new TypeError(`unexpected wasm import ${imp.module}.${imp.name}`);
34
+ }
35
+ }
36
+ function requireMemory(value) {
37
+ if (!(value instanceof WebAssembly.Memory)) throw new TypeError("wasm missing memory export");
38
+ return value;
39
+ }
40
+ function requireFn(value, name) {
41
+ if (typeof value !== "function") throw new TypeError(`wasm missing ${name} export`);
42
+ return value;
43
+ }
44
+ function asExports(raw) {
45
+ const exports = {
46
+ memory: requireMemory(raw["memory"]),
47
+ verify: requireFn(raw["verify"], "verify"),
48
+ verify_serialized: requireFn(raw["verify_serialized"], "verify_serialized"),
49
+ sign: requireFn(raw["sign"], "sign"),
50
+ public_key: requireFn(raw["public_key"], "public_key"),
51
+ __wbindgen_export: requireFn(raw["__wbindgen_export"], "__wbindgen_export"),
52
+ __wbindgen_export2: requireFn(raw["__wbindgen_export2"], "__wbindgen_export2"),
53
+ __wbindgen_add_to_stack_pointer: requireFn(raw["__wbindgen_add_to_stack_pointer"], "__wbindgen_add_to_stack_pointer")
54
+ };
55
+ const start = raw["__wbindgen_start"];
56
+ if (typeof start === "function") exports.__wbindgen_start = requireFn(start, "__wbindgen_start");
57
+ return exports;
58
+ }
59
+ function passBytes(exports, bytes) {
60
+ const len = bytes.length;
61
+ const ptr = exports.__wbindgen_export(len, 1) >>> 0;
62
+ new Uint8Array(exports.memory.buffer, ptr, len).set(bytes);
63
+ return {
64
+ ptr,
65
+ len
66
+ };
67
+ }
68
+ function wasmVerify(exports, id, pubkey, sig) {
69
+ const idP = passBytes(exports, id);
70
+ const pkP = passBytes(exports, pubkey);
71
+ const sigP = passBytes(exports, sig);
72
+ return exports.verify(idP.ptr, idP.len, pkP.ptr, pkP.len, sigP.ptr, sigP.len) !== 0;
73
+ }
74
+ function wasmVerifySerialized(exports, serialized, id, pubkey, sig) {
75
+ const serP = passBytes(exports, serialized);
76
+ const idP = passBytes(exports, id);
77
+ const pkP = passBytes(exports, pubkey);
78
+ const sigP = passBytes(exports, sig);
79
+ return exports.verify_serialized(serP.ptr, serP.len, idP.ptr, idP.len, pkP.ptr, pkP.len, sigP.ptr, sigP.len) !== 0;
80
+ }
81
+ function takeBytes(exports, ptr, len) {
82
+ const out = new Uint8Array(len);
83
+ if (len > 0) {
84
+ out.set(new Uint8Array(exports.memory.buffer, ptr, len));
85
+ exports.__wbindgen_export2(ptr, len, 1);
86
+ }
87
+ return out;
88
+ }
89
+ function callReturningBytes(exports, invoke) {
90
+ const retptr = exports.__wbindgen_add_to_stack_pointer(-16);
91
+ try {
92
+ invoke(retptr);
93
+ const view = new DataView(exports.memory.buffer);
94
+ return takeBytes(exports, view.getUint32(retptr, true), view.getUint32(retptr + 4, true));
95
+ } finally {
96
+ exports.__wbindgen_add_to_stack_pointer(16);
97
+ }
98
+ }
99
+ function wasmSign(exports, id, seckey, aux) {
100
+ const idP = passBytes(exports, id);
101
+ const skP = passBytes(exports, seckey);
102
+ const auxP = passBytes(exports, aux);
103
+ return callReturningBytes(exports, (retptr) => {
104
+ exports.sign(retptr, idP.ptr, idP.len, skP.ptr, skP.len, auxP.ptr, auxP.len);
105
+ });
106
+ }
107
+ function wasmPublicKey(exports, seckey) {
108
+ const skP = passBytes(exports, seckey);
109
+ return callReturningBytes(exports, (retptr) => {
110
+ exports.public_key(retptr, skP.ptr, skP.len);
111
+ });
112
+ }
113
+ async function instantiateCryptoWasm(bytes) {
114
+ const holder = {};
115
+ const { module, instance } = await WebAssembly.instantiate(bytes, makeWbgImports(holder));
116
+ assertAllowedWasmImports(module);
117
+ holder.exports = asExports(instance.exports);
118
+ holder.exports.__wbindgen_start?.();
119
+ return holder.exports;
120
+ }
121
+ //#endregion
122
+ //#region src/wasm/adapter.ts
123
+ /**
124
+ * Build the WASM event verifier on {@link createEventVerifier}. A `WebAssembly.RuntimeError` from
125
+ * the module means the instance aborted — it poisons every later call with the same
126
+ * `WasmVerifyPoisonedError`; any other backend error verifies `false` (the event is marked
127
+ * failed).
128
+ */
129
+ function createWasmEventVerifier(verifySerialized, poison) {
130
+ const backend = (serializedUtf8, id, pubkey, sig) => {
131
+ if (poison.error !== void 0) throw poison.error;
132
+ try {
133
+ return verifySerialized(serializedUtf8, id, pubkey, sig);
134
+ } catch (error) {
135
+ if (error instanceof WebAssembly.RuntimeError) {
136
+ poison.error = new WasmVerifyPoisonedError("wasm verify aborted the instance", { cause: error });
137
+ throw poison.error;
138
+ }
139
+ return false;
140
+ }
141
+ };
142
+ return createEventVerifier(backend);
143
+ }
144
+ //#endregion
145
+ //#region src/wasm/instance.ts
146
+ function isWasmBytes(value) {
147
+ return value instanceof ArrayBuffer || ArrayBuffer.isView(value);
148
+ }
149
+ async function fetchWasmUrl(url) {
150
+ const res = await fetch(url);
151
+ if (!res.ok) throw new CryptoError(`failed to fetch wasm: ${res.status} ${url.href}`);
152
+ return new Uint8Array(await res.arrayBuffer());
153
+ }
154
+ function requireByteLength(bytes, expected, label) {
155
+ if (bytes.length !== expected) throw new CryptoError(`invalid ${label} length: expected ${expected}, got ${bytes.length}`);
156
+ }
157
+ function wrapPoison(poison, fn) {
158
+ if (poison.error) throw poison.error;
159
+ try {
160
+ return fn();
161
+ } catch (error) {
162
+ if (error instanceof WebAssembly.RuntimeError) {
163
+ poison.error = new WasmVerifyPoisonedError("wasm verify aborted the instance", { cause: error });
164
+ throw poison.error;
165
+ }
166
+ throw error;
167
+ }
168
+ }
169
+ function bindExports(exports) {
170
+ const poison = {};
171
+ const rawSerialized = (serializedUtf8, id, pubkey, sig) => wasmVerifySerialized(exports, serializedUtf8, id, pubkey, sig);
172
+ return {
173
+ verify: (id, pubkey, sig) => wrapPoison(poison, () => wasmVerify(exports, id, pubkey, sig)),
174
+ verifySerialized: (serializedUtf8, id, pubkey, sig) => wrapPoison(poison, () => rawSerialized(serializedUtf8, id, pubkey, sig)),
175
+ verifyEvent: createWasmEventVerifier(rawSerialized, poison),
176
+ sign: (id, seckey, aux) => wrapPoison(poison, () => {
177
+ requireByteLength(id, 32, "id");
178
+ requireByteLength(seckey, 32, "secret key");
179
+ requireByteLength(aux, 32, "aux");
180
+ const sig = wasmSign(exports, id, seckey, aux);
181
+ if (sig.length !== 64) throw new CryptoError("wasm sign failed");
182
+ return sig;
183
+ }),
184
+ publicKey: (seckey) => wrapPoison(poison, () => {
185
+ requireByteLength(seckey, 32, "secret key");
186
+ const pk = wasmPublicKey(exports, seckey);
187
+ if (pk.length !== 32) throw new CryptoError("wasm publicKey failed");
188
+ return pk;
189
+ })
190
+ };
191
+ }
192
+ /** Builds a loader that interns a single instance per module slot. */
193
+ function createNostrWasmLoader(wasmBytes) {
194
+ let interned;
195
+ async function instantiate(opts) {
196
+ return bindExports(await instantiateCryptoWasm(await wasmBytes(opts)));
197
+ }
198
+ /** Instantiate once. Repeats reuse the same module. Failure throws; no noble fallback. */
199
+ async function loadNostrWasm(opts) {
200
+ if (interned) return interned;
201
+ const pending = instantiate(opts);
202
+ interned = pending;
203
+ try {
204
+ return await pending;
205
+ } catch (error) {
206
+ if (interned === pending) interned = void 0;
207
+ throw error;
208
+ }
209
+ }
210
+ function resetNostrWasm() {
211
+ interned = void 0;
212
+ }
213
+ return {
214
+ loadNostrWasm,
215
+ resetNostrWasm
216
+ };
217
+ }
218
+ //#endregion
219
+ export { fetchWasmUrl as n, isWasmBytes as r, createNostrWasmLoader as t };
220
+
221
+ //# sourceMappingURL=instance-DQE8BoWt.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instance-DQE8BoWt.mjs","names":[],"sources":["../src/wasm/abi.ts","../src/wasm/adapter.ts","../src/wasm/instance.ts"],"sourcesContent":["/** Wasm-bindgen 0.2.122 bundler-produced exports used by interned TS glue. */\nexport type CryptoWasmExports = {\n memory: WebAssembly.Memory;\n verify: (\n idPtr: number,\n idLen: number,\n pkPtr: number,\n pkLen: number,\n sigPtr: number,\n sigLen: number,\n ) => number;\n verify_serialized: (\n serPtr: number,\n serLen: number,\n idPtr: number,\n idLen: number,\n pkPtr: number,\n pkLen: number,\n sigPtr: number,\n sigLen: number,\n ) => number;\n sign: (\n retptr: number,\n idPtr: number,\n idLen: number,\n skPtr: number,\n skLen: number,\n auxPtr: number,\n auxLen: number,\n ) => void;\n public_key: (retptr: number, skPtr: number, skLen: number) => void;\n /** 0.2.122 name for `__wbindgen_malloc(size, align)`. */\n __wbindgen_export: (size: number, align: number) => number;\n /** 0.2.122 name for `__wbindgen_free(ptr, size, align)` after malloc claimed `__wbindgen_export`. */\n __wbindgen_export2: (ptr: number, size: number, align: number) => void;\n __wbindgen_add_to_stack_pointer: (delta: number) => number;\n __wbindgen_start?: () => void;\n};\n\n/** Imports 0.2.122 may request. Instantiation fails if anything else appears. */\nexport const ALLOWED_WASM_IMPORTS: ReadonlyArray<{ module: string; name: string }> = [\n { module: \"wbg\", name: \"__wbindgen_throw\" },\n { module: \"./nostr_crypto_wasm_bg.js\", name: \"__wbindgen_throw\" },\n];\n\nconst utf8 = new TextDecoder();\n\nfunction decodeUtf8(memory: WebAssembly.Memory, ptr: number, len: number): string {\n return utf8.decode(new Uint8Array(memory.buffer, ptr, len));\n}\n\nfunction makeWbgImports(holder: { exports?: CryptoWasmExports }): WebAssembly.Imports {\n const throwRuntime = (ptr: number, len: number): never => {\n const mem = holder.exports?.memory;\n const msg = mem ? decodeUtf8(mem, ptr, len) : \"wasm panic\";\n throw new WebAssembly.RuntimeError(msg);\n };\n const wbg = {\n __wbindgen_throw: throwRuntime,\n };\n return {\n wbg,\n \"./nostr_crypto_wasm_bg.js\": wbg,\n };\n}\n\n/** Fail if the module imports anything outside {@link ALLOWED_WASM_IMPORTS}. */\nexport function assertAllowedWasmImports(mod: WebAssembly.Module): void {\n const allowed = new Set(ALLOWED_WASM_IMPORTS.map((item) => `${item.module}\\0${item.name}`));\n for (const imp of WebAssembly.Module.imports(mod)) {\n if (imp.kind !== \"function\") {\n throw new TypeError(`unexpected wasm import kind ${imp.kind}: ${imp.module}.${imp.name}`);\n }\n if (!allowed.has(`${imp.module}\\0${imp.name}`)) {\n throw new TypeError(`unexpected wasm import ${imp.module}.${imp.name}`);\n }\n }\n}\n\nfunction requireMemory(value: WebAssembly.ExportValue | undefined): WebAssembly.Memory {\n if (!(value instanceof WebAssembly.Memory)) {\n throw new TypeError(\"wasm missing memory export\");\n }\n return value;\n}\n\n// oxlint-disable-next-line no-unnecessary-type-parameters -- T binds the declared ABI signature at each call site\nfunction requireFn<T extends (...args: number[]) => unknown>(\n value: WebAssembly.ExportValue | undefined,\n name: string,\n): T {\n if (typeof value !== \"function\") {\n throw new TypeError(`wasm missing ${name} export`);\n }\n // oxlint-disable-next-line no-unsafe-type-assertion -- wasm exports are opaque; each name is bound to the signature the ABI contract requires\n return value as T;\n}\n\nfunction asExports(raw: WebAssembly.Exports): CryptoWasmExports {\n const exports: CryptoWasmExports = {\n memory: requireMemory(raw[\"memory\"]),\n verify: requireFn(raw[\"verify\"], \"verify\"),\n verify_serialized: requireFn(raw[\"verify_serialized\"], \"verify_serialized\"),\n sign: requireFn(raw[\"sign\"], \"sign\"),\n public_key: requireFn(raw[\"public_key\"], \"public_key\"),\n __wbindgen_export: requireFn(raw[\"__wbindgen_export\"], \"__wbindgen_export\"),\n __wbindgen_export2: requireFn(raw[\"__wbindgen_export2\"], \"__wbindgen_export2\"),\n __wbindgen_add_to_stack_pointer: requireFn(\n raw[\"__wbindgen_add_to_stack_pointer\"],\n \"__wbindgen_add_to_stack_pointer\",\n ),\n };\n const start = raw[\"__wbindgen_start\"];\n if (typeof start === \"function\") {\n exports.__wbindgen_start = requireFn<() => void>(start, \"__wbindgen_start\");\n }\n return exports;\n}\n\nfunction passBytes(exports: CryptoWasmExports, bytes: Uint8Array): { ptr: number; len: number } {\n const len = bytes.length;\n const ptr = exports.__wbindgen_export(len, 1) >>> 0;\n new Uint8Array(exports.memory.buffer, ptr, len).set(bytes);\n return { ptr, len };\n}\n\nexport function wasmVerify(\n exports: CryptoWasmExports,\n id: Uint8Array,\n pubkey: Uint8Array,\n sig: Uint8Array,\n): boolean {\n const idP = passBytes(exports, id);\n const pkP = passBytes(exports, pubkey);\n const sigP = passBytes(exports, sig);\n return exports.verify(idP.ptr, idP.len, pkP.ptr, pkP.len, sigP.ptr, sigP.len) !== 0;\n}\n\nexport function wasmVerifySerialized(\n exports: CryptoWasmExports,\n serialized: Uint8Array,\n id: Uint8Array,\n pubkey: Uint8Array,\n sig: Uint8Array,\n): boolean {\n const serP = passBytes(exports, serialized);\n const idP = passBytes(exports, id);\n const pkP = passBytes(exports, pubkey);\n const sigP = passBytes(exports, sig);\n return (\n exports.verify_serialized(\n serP.ptr,\n serP.len,\n idP.ptr,\n idP.len,\n pkP.ptr,\n pkP.len,\n sigP.ptr,\n sigP.len,\n ) !== 0\n );\n}\n\nfunction takeBytes(exports: CryptoWasmExports, ptr: number, len: number): Uint8Array {\n const out = new Uint8Array(len);\n if (len > 0) {\n out.set(new Uint8Array(exports.memory.buffer, ptr, len));\n exports.__wbindgen_export2(ptr, len, 1);\n }\n return out;\n}\n\nfunction callReturningBytes(\n exports: CryptoWasmExports,\n invoke: (retptr: number) => void,\n): Uint8Array {\n const retptr = exports.__wbindgen_add_to_stack_pointer(-16);\n try {\n invoke(retptr);\n const view = new DataView(exports.memory.buffer);\n const ptr = view.getUint32(retptr, true);\n const len = view.getUint32(retptr + 4, true);\n return takeBytes(exports, ptr, len);\n } finally {\n exports.__wbindgen_add_to_stack_pointer(16);\n }\n}\n\nexport function wasmSign(\n exports: CryptoWasmExports,\n id: Uint8Array,\n seckey: Uint8Array,\n aux: Uint8Array,\n): Uint8Array {\n const idP = passBytes(exports, id);\n const skP = passBytes(exports, seckey);\n const auxP = passBytes(exports, aux);\n return callReturningBytes(exports, (retptr) => {\n exports.sign(retptr, idP.ptr, idP.len, skP.ptr, skP.len, auxP.ptr, auxP.len);\n });\n}\n\nexport function wasmPublicKey(exports: CryptoWasmExports, seckey: Uint8Array): Uint8Array {\n const skP = passBytes(exports, seckey);\n return callReturningBytes(exports, (retptr) => {\n exports.public_key(retptr, skP.ptr, skP.len);\n });\n}\n\nexport async function instantiateCryptoWasm(\n bytes: ArrayBuffer | ArrayBufferView,\n): Promise<CryptoWasmExports> {\n const holder: { exports?: CryptoWasmExports } = {};\n const { module, instance } = await WebAssembly.instantiate(bytes, makeWbgImports(holder));\n assertAllowedWasmImports(module);\n holder.exports = asExports(instance.exports);\n holder.exports.__wbindgen_start?.();\n return holder.exports;\n}\n","import { WasmVerifyPoisonedError } from \"../core/error.ts\";\nimport type { Event } from \"../core/event.ts\";\nimport { createEventVerifier } from \"../core/verifier.ts\";\nimport type { SerializedEventVerifier } from \"../core/verifier.ts\";\n\nexport { WasmVerifyPoisonedError } from \"../core/error.ts\";\n\n/**\n * Build the WASM event verifier on {@link createEventVerifier}. A `WebAssembly.RuntimeError` from\n * the module means the instance aborted — it poisons every later call with the same\n * `WasmVerifyPoisonedError`; any other backend error verifies `false` (the event is marked\n * failed).\n */\nexport function createWasmEventVerifier(\n verifySerialized: SerializedEventVerifier,\n poison: { error?: Error },\n): (event: Event) => boolean {\n const backend: SerializedEventVerifier = (serializedUtf8, id, pubkey, sig) => {\n if (poison.error !== undefined) {\n throw poison.error;\n }\n try {\n return verifySerialized(serializedUtf8, id, pubkey, sig);\n } catch (error) {\n if (error instanceof WebAssembly.RuntimeError) {\n poison.error = new WasmVerifyPoisonedError(\"wasm verify aborted the instance\", {\n cause: error,\n });\n throw poison.error;\n }\n return false;\n }\n };\n return createEventVerifier(backend);\n}\n","import { CryptoError } from \"../core/error.ts\";\nimport type { Event } from \"../core/event.ts\";\nimport type { SigningBackend } from \"../core/key.ts\";\nimport {\n instantiateCryptoWasm,\n wasmPublicKey,\n wasmSign,\n wasmVerify,\n wasmVerifySerialized,\n} from \"./abi.ts\";\nimport type { CryptoWasmExports } from \"./abi.ts\";\nimport { createWasmEventVerifier, WasmVerifyPoisonedError } from \"./adapter.ts\";\n\nexport type LoadNostrWasmOptions = {\n /** Bytes, or a URL whose bytes will be read by the platform loader. */\n module?: ArrayBuffer | ArrayBufferView | URL;\n};\n\nexport type NostrWasm = SigningBackend & {\n verify: (id: Uint8Array, pubkey: Uint8Array, sig: Uint8Array) => boolean;\n verifySerialized: (\n serializedUtf8: Uint8Array,\n id: Uint8Array,\n pubkey: Uint8Array,\n sig: Uint8Array,\n ) => boolean;\n verifyEvent: (event: Event) => boolean;\n};\n\nexport function isWasmBytes(value: unknown): value is ArrayBuffer | ArrayBufferView {\n return value instanceof ArrayBuffer || ArrayBuffer.isView(value);\n}\n\nexport async function fetchWasmUrl(url: URL): Promise<Uint8Array> {\n const res = await fetch(url);\n if (!res.ok) {\n throw new CryptoError(`failed to fetch wasm: ${res.status} ${url.href}`);\n }\n return new Uint8Array(await res.arrayBuffer());\n}\n\nfunction requireByteLength(bytes: Uint8Array, expected: number, label: string): void {\n if (bytes.length !== expected) {\n throw new CryptoError(`invalid ${label} length: expected ${expected}, got ${bytes.length}`);\n }\n}\n\nfunction wrapPoison<T>(poison: { error?: Error }, fn: () => T): T {\n if (poison.error) {\n throw poison.error;\n }\n try {\n return fn();\n } catch (error) {\n if (error instanceof WebAssembly.RuntimeError) {\n poison.error = new WasmVerifyPoisonedError(\"wasm verify aborted the instance\", {\n cause: error,\n });\n throw poison.error;\n }\n throw error;\n }\n}\n\nfunction bindExports(exports: CryptoWasmExports): NostrWasm {\n const poison: { error?: Error } = {};\n const rawSerialized = (\n serializedUtf8: Uint8Array,\n id: Uint8Array,\n pubkey: Uint8Array,\n sig: Uint8Array,\n ): boolean => wasmVerifySerialized(exports, serializedUtf8, id, pubkey, sig);\n return {\n verify: (id, pubkey, sig) => wrapPoison(poison, () => wasmVerify(exports, id, pubkey, sig)),\n verifySerialized: (serializedUtf8, id, pubkey, sig) =>\n wrapPoison(poison, () => rawSerialized(serializedUtf8, id, pubkey, sig)),\n verifyEvent: createWasmEventVerifier(rawSerialized, poison),\n sign: (id, seckey, aux) =>\n wrapPoison(poison, () => {\n requireByteLength(id, 32, \"id\");\n requireByteLength(seckey, 32, \"secret key\");\n requireByteLength(aux, 32, \"aux\");\n const sig = wasmSign(exports, id, seckey, aux);\n if (sig.length !== 64) {\n throw new CryptoError(\"wasm sign failed\");\n }\n return sig;\n }),\n publicKey: (seckey) =>\n wrapPoison(poison, () => {\n requireByteLength(seckey, 32, \"secret key\");\n const pk = wasmPublicKey(exports, seckey);\n if (pk.length !== 32) {\n throw new CryptoError(\"wasm publicKey failed\");\n }\n return pk;\n }),\n };\n}\n\n/** Builds a loader that interns a single instance per module slot. */\nexport function createNostrWasmLoader(\n wasmBytes: (opts?: LoadNostrWasmOptions) => Promise<ArrayBuffer | ArrayBufferView>,\n): {\n loadNostrWasm: (opts?: LoadNostrWasmOptions) => Promise<NostrWasm>;\n resetNostrWasm: () => void;\n} {\n let interned: Promise<NostrWasm> | undefined;\n\n async function instantiate(opts?: LoadNostrWasmOptions): Promise<NostrWasm> {\n const bytes = await wasmBytes(opts);\n const exports = await instantiateCryptoWasm(bytes);\n return bindExports(exports);\n }\n\n /** Instantiate once. Repeats reuse the same module. Failure throws; no noble fallback. */\n async function loadNostrWasm(opts?: LoadNostrWasmOptions): Promise<NostrWasm> {\n if (interned) {\n return interned;\n }\n const pending = instantiate(opts);\n interned = pending;\n try {\n return await pending;\n } catch (error) {\n if (interned === pending) {\n interned = undefined;\n }\n throw error;\n }\n }\n\n function resetNostrWasm(): void {\n interned = undefined;\n }\n\n return { loadNostrWasm, resetNostrWasm };\n}\n"],"mappings":"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"}
@@ -1,3 +1,3 @@
1
1
  import { r as OutboxBound } from "./types-2pCUprRU.mjs";
2
- import { _ as ReplaceableLoader, a as OutboxFeed, c as groupAuthorsByOutboxRelay, d as bareNostrUser, f as ListLoaders, g as ReplaceableLoadResult, h as LoadStyle, i as OutboxError, l as NostrUser, m as MutedEntity, n as Loaders, o as OutboxFeedOptions, p as ListResult, r as createLoaders, s as createOutboxFeed, t as CreateLoadersOptions, u as ProfileLoader, v as EventLoader, y as EventRef } from "./index-skvsYz00.mjs";
2
+ import { _ as ReplaceableLoader, a as OutboxFeed, c as groupAuthorsByOutboxRelay, d as bareNostrUser, f as ListLoaders, g as ReplaceableLoadResult, h as LoadStyle, i as OutboxError, l as NostrUser, m as MutedEntity, n as Loaders, o as OutboxFeedOptions, p as ListResult, r as createLoaders, s as createOutboxFeed, t as CreateLoadersOptions, u as ProfileLoader, v as EventLoader, y as EventRef } from "./index-lvXeER5t.mjs";
3
3
  export { CreateLoadersOptions, type EventLoader, type EventRef, type ListLoaders, type ListResult, type LoadStyle, Loaders, type MutedEntity, type NostrUser, type OutboxBound, OutboxError, OutboxFeed, type OutboxFeedOptions, type ProfileLoader, type ReplaceableLoadResult, type ReplaceableLoader, bareNostrUser, createLoaders, createOutboxFeed, groupAuthorsByOutboxRelay };
Binary file
@@ -441,11 +441,14 @@ function openExclusive(ctx, filters, opts) {
441
441
  function subscribeLive(ctx, filters, opts) {
442
442
  const fp = filterFingerprint(filters);
443
443
  let group = ctx.liveByFp.get(fp);
444
+ if (group?.sub.eosed === true) group = void 0;
444
445
  let created = false;
445
446
  if (!group) {
446
447
  created = true;
448
+ const slot = { group: void 0 };
447
449
  const wire = new Subscription(filters, { id: opts.id }, () => {
448
- endLiveGroup(ctx, fp, {
450
+ const g = slot.group;
451
+ if (g) endLiveGroup(ctx, g, {
449
452
  sendClose: true,
450
453
  reason: "closed by client"
451
454
  });
@@ -455,18 +458,20 @@ function subscribeLive(ctx, filters, opts) {
455
458
  sub: wire,
456
459
  attachments: /* @__PURE__ */ new Set()
457
460
  };
461
+ slot.group = group;
458
462
  ctx.liveByFp.set(fp, group);
459
463
  ctx.liveBySubId.set(wire.id, group);
460
464
  ctx.subs.set(wire.id, wire);
461
465
  }
462
466
  const slot = { handle: void 0 };
467
+ const attached = group;
463
468
  const handle = new Subscription(filters, {
464
469
  ...opts,
465
470
  id: group.sub.id
466
471
  }, () => {
467
472
  const current = slot.handle;
468
473
  if (!current) return;
469
- detachLive(ctx, fp, current);
474
+ detachLive(ctx, attached, current);
470
475
  });
471
476
  slot.handle = handle;
472
477
  if (handle.closed) {
@@ -483,32 +488,23 @@ function subscribeLive(ctx, filters, opts) {
483
488
  else if (ctx.enableReconnect()) ctx.scheduleReconnect();
484
489
  }
485
490
  if (opts.eoseTimeoutMs !== void 0) ctx.armEoseTimeout(handle, opts.eoseTimeoutMs);
486
- if (group.sub.eosed) queueMicrotask(() => {
487
- if (handle.closed || handle.eosed) return;
488
- handle.eosed = true;
489
- invokeSafely(() => handle.handlers.oneose?.());
490
- });
491
491
  return handle;
492
492
  }
493
493
  function forgetLiveGroup(ctx, group) {
494
- ctx.liveByFp.delete(group.fp);
494
+ if (ctx.liveByFp.get(group.fp) === group) ctx.liveByFp.delete(group.fp);
495
495
  ctx.liveBySubId.delete(group.sub.id);
496
496
  ctx.subs.delete(group.sub.id);
497
497
  group.sub.closed = true;
498
498
  }
499
- function detachLive(ctx, fp, handle) {
500
- const group = ctx.liveByFp.get(fp);
501
- if (!group) return;
499
+ function detachLive(ctx, group, handle) {
502
500
  if (!group.attachments.delete(handle)) return;
503
501
  if (group.attachments.size > 0) return;
504
- endLiveGroup(ctx, fp, {
502
+ endLiveGroup(ctx, group, {
505
503
  sendClose: true,
506
504
  reason: "closed by client"
507
505
  });
508
506
  }
509
- function endLiveGroup(ctx, fp, opts) {
510
- const group = ctx.liveByFp.get(fp);
511
- if (!group) return;
507
+ function endLiveGroup(ctx, group, opts) {
512
508
  const { id } = group.sub;
513
509
  const remaining = [...group.attachments];
514
510
  group.attachments.clear();
@@ -555,9 +551,9 @@ function deliverLiveEose(group) {
555
551
  }
556
552
  }
557
553
  function closeAllSubscriptions(ctx, reason) {
558
- const fps = [...ctx.liveByFp.keys()];
559
- for (const fp of fps) invokeSafely(() => {
560
- endLiveGroup(ctx, fp, {
554
+ const groups = [...ctx.liveBySubId.values()];
555
+ for (const group of groups) invokeSafely(() => {
556
+ endLiveGroup(ctx, group, {
561
557
  sendClose: false,
562
558
  reason
563
559
  });
@@ -575,7 +571,7 @@ function closeAllSubscriptions(ctx, reason) {
575
571
  function dropSubscription(ctx, sub, reason) {
576
572
  const group = ctx.liveBySubId.get(sub.id);
577
573
  if (group) {
578
- endLiveGroup(ctx, group.fp, {
574
+ endLiveGroup(ctx, group, {
579
575
  sendClose: false,
580
576
  reason
581
577
  });
@@ -654,6 +650,7 @@ async function fetchFilters(subscribe, filters, opts) {
654
650
  throwIfAborted(opts.signal);
655
651
  const events = [];
656
652
  const seen = /* @__PURE__ */ new Set();
653
+ let end = { type: "timeout" };
657
654
  await new Promise((resolve, reject) => {
658
655
  let settled = false;
659
656
  const done = (err) => {
@@ -667,7 +664,10 @@ async function fetchFilters(subscribe, filters, opts) {
667
664
  if (err === void 0) resolve();
668
665
  else reject(err);
669
666
  };
670
- const timer = setTimeout(() => done(), opts.timeoutMs);
667
+ const timer = setTimeout(() => {
668
+ end = { type: "timeout" };
669
+ done();
670
+ }, opts.timeoutMs);
671
671
  const sub = subscribe(filters, {
672
672
  id: opts.id,
673
673
  signal: opts.signal,
@@ -678,15 +678,28 @@ async function fetchFilters(subscribe, filters, opts) {
678
678
  events.push(event);
679
679
  },
680
680
  oneose() {
681
+ end = { type: "eose" };
681
682
  done();
682
683
  },
683
- onclose() {
684
- if (!settled) done(opts.signal?.aborted === true ? abortReason(opts.signal) : void 0);
684
+ onclose(reason) {
685
+ if (settled) return;
686
+ if (opts.signal?.aborted === true) {
687
+ done(abortReason(opts.signal));
688
+ return;
689
+ }
690
+ end = {
691
+ type: "closed",
692
+ reason
693
+ };
694
+ done();
685
695
  }
686
696
  });
687
697
  opts.signal?.addEventListener("abort", () => done(opts.signal ? abortReason(opts.signal) : void 0), { once: true });
688
698
  });
689
- return events;
699
+ return {
700
+ events,
701
+ end
702
+ };
690
703
  }
691
704
  function armEoseTimeout(sub, eoseTimeoutMs) {
692
705
  const timer = setTimeout(() => {
@@ -1264,7 +1277,11 @@ var Relay = class Relay {
1264
1277
  stream(filters, opts) {
1265
1278
  return streamFilters((f, o) => this.subscribe(f, o), filters, opts);
1266
1279
  }
1267
- /** One-shot query: collect events until EOSE or timeout, then close. */
1280
+ /**
1281
+ * One-shot query: collect events until the REQ ends, then close. `end` reports how it ended —
1282
+ * relay `EOSE`, relay `CLOSED` (with its reason), or the local timeout; events received before a
1283
+ * CLOSED or the deadline are still returned. Abort rejects; a failed (re)connect rejects.
1284
+ */
1268
1285
  async fetch(filters, opts) {
1269
1286
  if (filters.length === 0) throw new MessageError("REQ requires at least one filter");
1270
1287
  const canonical = canonicalizeFilters(filters);
@@ -1272,8 +1289,7 @@ var Relay = class Relay {
1272
1289
  return fetchFilters((f, o) => this.subscribe(f, o), canonical, {
1273
1290
  timeoutMs: opts?.timeoutMs ?? 4400,
1274
1291
  signal: opts?.signal,
1275
- id: opts?.id,
1276
- url: this.url
1292
+ id: opts?.id
1277
1293
  });
1278
1294
  }
1279
1295
  /** Publish an event and wait for OK. */
@@ -1596,6 +1612,7 @@ function fanIn(pool, jobs, opts = {}) {
1596
1612
  const closers = [];
1597
1613
  let closed = false;
1598
1614
  let eoseFired = false;
1615
+ let sawEose = false;
1599
1616
  let eoseTimer;
1600
1617
  const eoseDone = /* @__PURE__ */ new Set();
1601
1618
  const eoseAttempted = /* @__PURE__ */ new Set();
@@ -1610,12 +1627,18 @@ function fanIn(pool, jobs, opts = {}) {
1610
1627
  }
1611
1628
  invokeSafely(() => opts.oneose?.());
1612
1629
  };
1613
- const markEose = (jobIndex, url) => {
1630
+ /**
1631
+ * Remove one relay from the pending-EOSE set. `eosed` credits a real EOSE; a CLOSED or a connect
1632
+ * failure only drops the wait — the aggregate `oneose` fires once every remaining relay ended AND
1633
+ * at least one actually EOSE'd.
1634
+ */
1635
+ const settleEose = (jobIndex, url, eosed) => {
1614
1636
  const key = `${jobIndex}:${url}`;
1615
1637
  if (eoseDone.has(key)) return;
1616
1638
  eoseDone.add(key);
1639
+ if (eosed) sawEose = true;
1617
1640
  pendingEose -= 1;
1618
- if (pendingEose === 0) fireEose();
1641
+ if (pendingEose === 0 && sawEose) fireEose();
1619
1642
  };
1620
1643
  const settleClose = () => {
1621
1644
  closed = true;
@@ -1637,7 +1660,7 @@ function fanIn(pool, jobs, opts = {}) {
1637
1660
  }
1638
1661
  opts.signal?.addEventListener("abort", () => closeAll("aborted"), { once: true });
1639
1662
  const failUrl = (jobIndex, key) => {
1640
- markEose(jobIndex, key);
1663
+ settleEose(jobIndex, key, false);
1641
1664
  pending -= 1;
1642
1665
  if (pending <= 0 && closers.length === 0 && !closed) {
1643
1666
  settleClose();
@@ -1662,9 +1685,9 @@ function fanIn(pool, jobs, opts = {}) {
1662
1685
  seen.add(event.id);
1663
1686
  invokeSafely(() => opts.onevent?.(event, relay.url));
1664
1687
  },
1665
- oneose: () => markEose(jobIndex, relay.url),
1688
+ oneose: () => settleEose(jobIndex, relay.url, true),
1666
1689
  onclose: (reason) => {
1667
- markEose(jobIndex, relay.url);
1690
+ settleEose(jobIndex, relay.url, false);
1668
1691
  pending -= 1;
1669
1692
  if (pending <= 0 && !closed) {
1670
1693
  settleClose();
@@ -1756,10 +1779,10 @@ async function fetchRouted(pool, jobs, opts = {}) {
1756
1779
  timeoutMs: opts.connectTimeoutMs
1757
1780
  });
1758
1781
  relayUrl = relay.url;
1759
- batch = await relay.fetch([...job.filters], {
1782
+ batch = (await relay.fetch([...job.filters], {
1760
1783
  timeoutMs: opts.timeoutMs,
1761
1784
  signal: opts.signal
1762
- });
1785
+ })).events;
1763
1786
  } catch {
1764
1787
  if (opts.signal?.aborted === true) throw abortReason(opts.signal);
1765
1788
  return;
@@ -2002,6 +2025,44 @@ var Pool = class {
2002
2025
  connectTimeoutMs: this.#opts.connectTimeoutMs
2003
2026
  });
2004
2027
  }
2028
+ /**
2029
+ * One-shot fetch per relay: each entry reports its own events and end reason (`eose`, `closed`,
2030
+ * `timeout`), or `failed` when the connect or REQ itself errored. An abort rejects the whole
2031
+ * call. Events are not deduped across relays — use {@link fetch} for that.
2032
+ */
2033
+ async fetchEach(relays, filters, opts) {
2034
+ if (filters.length === 0) throw new MessageError("REQ requires at least one filter");
2035
+ throwIfAborted(opts?.signal);
2036
+ const canonical = canonicalizeFilters(filters);
2037
+ return Promise.all(relays.map(async (url) => {
2038
+ try {
2039
+ const relay = await this.ensureRelay(url, {
2040
+ signal: opts?.signal,
2041
+ timeoutMs: this.#opts.connectTimeoutMs
2042
+ });
2043
+ this.#touch(relay.url);
2044
+ const result = await relay.fetch([...canonical], {
2045
+ timeoutMs: opts?.timeoutMs,
2046
+ signal: opts?.signal
2047
+ });
2048
+ return {
2049
+ url: relay.url,
2050
+ events: result.events,
2051
+ end: result.end
2052
+ };
2053
+ } catch (error) {
2054
+ if (opts?.signal?.aborted === true) throw abortReason(opts.signal);
2055
+ return {
2056
+ url,
2057
+ events: [],
2058
+ end: {
2059
+ type: "failed",
2060
+ reason: error instanceof Error ? error.message : String(error)
2061
+ }
2062
+ };
2063
+ }
2064
+ }));
2065
+ }
2005
2066
  /** Fetch events until each connected relay EOSE or timeout; dedupe by id. */
2006
2067
  async fetch(relays, filters, opts) {
2007
2068
  if (filters.length === 0) throw new MessageError("REQ requires at least one filter");
@@ -2102,4 +2163,4 @@ var Pool = class {
2102
2163
  //#endregion
2103
2164
  export { RelayStatus as a, isInsecureRelayUrl as c, RelayClosedError as d, RelayConnectionError as f, RelayTimeoutError as g, RelaySuspendedError as h, Relay as i, getWebSocketImplementation as l, RelayPublishError as m, fanIn as n, Subscription as o, RelayError as p, fetchRouted as r, subscriptionToAsyncIterable as s, Pool as t, useWebSocketImplementation as u };
2104
2165
 
2105
- //# sourceMappingURL=pool-BGVx59yd.mjs.map
2166
+ //# sourceMappingURL=pool-As20OGxB.mjs.map