@nostrify/nostrify 0.48.2 → 0.49.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.
@@ -1,5 +1,5 @@
1
1
 
2
- > @nostrify/nostrify@0.48.1 build /home/sid/repos/nostrify/packages/nostrify
2
+ > @nostrify/nostrify@0.48.3 build /home/alex/Projects/nostrify/packages/nostrify
3
3
  > npx tsc -p tsconfig.json && node ../../esbuild.config.js --package ./
4
4
 
5
5
  npm warn Unknown env config "verify-deps-before-run". This will stop working in the next major version of npm.
@@ -8,7 +8,7 @@ Building with esbuild...
8
8
 
9
9
  dist/NRelay1.js 8.9kb
10
10
  dist/NSchema.js 5.8kb
11
- dist/NPool.js 5.0kb
11
+ dist/NPool.js 5.4kb
12
12
  dist/test/TestRelayServer.js 4.4kb
13
13
  dist/NSet.js 4.1kb
14
14
  dist/NConnectSigner.js 3.9kb
@@ -28,6 +28,5 @@ Building with esbuild...
28
28
  dist/test/mod.js 701b
29
29
  ...and 11 more output files...
30
30
 
31
- ⚡ Done in 31ms
32
- Copying source files...
31
+ ⚡ Done in 21ms
33
32
  Done!
@@ -0,0 +1,112 @@
1
+
2
+ > @nostrify/nostrify@0.48.3 test /home/alex/Projects/nostrify/packages/nostrify
3
+ > node --test "**/*.test.ts" NPool.test.ts
4
+
5
+ ✔ BunkerURI (3.103099ms)
6
+ ✔ BunkerURI.fromJSON (0.946417ms)
7
+ ✔ NBrowserSigner - without extension (2.589198ms)
8
+ ✔ NBrowserSigner - with extension polyfill (119.959927ms)
9
+ ✔ NBrowserSigner.nip44 - with extension polyfill (52.91706ms)
10
+ ✔ NBrowserSigner.nip04 - with extension polyfill (26.596641ms)
11
+ ✔ NBrowserSigner.getRelays - with extension polyfill (2.472527ms)
12
+ ✔ NBrowserSigner - missing nip44 support (0.590565ms)
13
+ ✔ NBrowserSigner - missing nip04 support (0.438438ms)
14
+ ✔ NBrowserSigner - feature detection (2.027338ms)
15
+ ✔ NCache (5.240283ms)
16
+ ✔ NConnectSigner.signEvent with nip04 encryption (176.945128ms)
17
+ ✔ NConnectSigner.signEvent with nip44 encryption (48.644554ms)
18
+ ✔ NIP05.lookup (62.16534ms)
19
+ ✔ NIP05.lookup with invalid values but valid profile pointer (7.565914ms)
20
+ ✔ NIP05.lookup with invalid document (8.293136ms)
21
+ ✔ NIP50.parseInput (3.720895ms)
22
+ ✔ NIP50.parseInput with negated token (0.516845ms)
23
+ ✔ NIP98.template (5.089398ms)
24
+ ✔ NIP98.template with payload (25.784117ms)
25
+ ✔ NIP98.verify (147.280886ms)
26
+ ✔ NIP98.verify fails with missing header (2.366208ms)
27
+ ✔ NIP98.verify fails with missing token (2.93968ms)
28
+ ✔ NIP98.verify fails with invalid token (1.902872ms)
29
+ ✔ NIP98.verify fails with invalid event (19.228631ms)
30
+ ✔ NIP98.verify fails with wrong event kind (43.698948ms)
31
+ ✔ NIP98.verify fails with wrong request URL (28.101031ms)
32
+ ✔ NIP98.verify fails with wrong request method (16.899954ms)
33
+ ✔ NIP98.verify fails with expired event (11.929701ms)
34
+ ✔ NIP98.verify fails with invalid payload (22.675508ms)
35
+ ✔ NIP98Client.fetch - basic GET request (148.088681ms)
36
+ ✔ NIP98Client.fetch - POST request with body (61.272434ms)
37
+ ✔ NIP98Client.fetch - with Request object input (20.816298ms)
38
+ ✔ NIP98Client.fetch - with URL object input (13.590767ms)
39
+ ✔ NIP98Client.fetch - uses default fetch when not provided (11.116957ms)
40
+ ✔ NIP98Client.fetch - preserves existing headers (10.24455ms)
41
+ ✔ NIP98Client.fetch - event can be verified with NIP98.verify (20.715648ms)
42
+ ✔ NIP98Client.fetch - handles different HTTP methods (42.038683ms)
43
+ ✔ NKinds (2.178663ms)
44
+ ✔ NPool.query (241.171699ms)
45
+ ✔ NPool.req (46.962439ms)
46
+ ✔ NPool.event (23.769734ms)
47
+ ✔ NPool.query with eoseTimeout (539.078185ms)
48
+ ✔ NPool.query with eoseTimeout disabled (513.370943ms)
49
+ ✔ NRelay1.query (211.675544ms)
50
+ ✔ NRelay1.query mismatched filter (23.228492ms)
51
+ ✔ NRelay1.req (39.130493ms)
52
+ ✔ NRelay1.event (10.531752ms)
53
+ ﹣ NRelay1 backoff (0.314695ms) # SKIP
54
+ ▶ NRelay1 idleTimeout
55
+ ✔ websocket opens (3.120731ms)
56
+ ✔ websocket closes after idleTimeout (150.272113ms)
57
+ ✔ websocket wakes up during activity (13.881526ms)
58
+ ✔ NRelay1 idleTimeout (171.04005ms)
59
+ ✔ NRelay1.count rejects when the server sends CLOSED (4.5074ms)
60
+ ✔ NRelay1 closes when it receives a binary message (3.958042ms)
61
+ ✔ n.id (4.218825ms)
62
+ ✔ n.bech32 (2.260858ms)
63
+ ✔ n.filter (10.493851ms)
64
+ ✔ n.event (4.264811ms)
65
+ ✔ n.metadata (12.16193ms)
66
+ ✔ NSecSigner (144.76192ms)
67
+ ✔ NSecSigner.nip44 (46.572963ms)
68
+ ✔ NSet (22.349102ms)
69
+ ✔ NSet.add (replaceable) (0.442686ms)
70
+ ✔ NSet.add (parameterized) (0.812003ms)
71
+ ✔ NSet.add (deletion) (0.396319ms)
72
+ ✔ Construct a RelayError from the reason message (2.506081ms)
73
+ ✔ Throw a new RelayError if the OK message is false (0.543135ms)
74
+ ✔ LNURL.fromString (6.121727ms)
75
+ ✔ LNURL.fromLightningAddress (1.103623ms)
76
+ ✔ LNURL.toString (1.355309ms)
77
+ ✔ LNURL.getDetails (51.878139ms)
78
+ ✔ LNURL.getInvoice (4.968249ms)
79
+ ✔ ErrorRelay (101.168282ms)
80
+ ✔ MockRelay (3.234627ms)
81
+ ✔ BlossomUploader.upload (582.794766ms)
82
+ ✖ NostrBuildUploader.upload (13.017756ms)
83
+ ✔ CircularSet (8.83538ms)
84
+ ✔ push, iterate, & close (103.505875ms)
85
+ ✔ close & reopen (0.598761ms)
86
+ ✔ aborts with signal (51.591307ms)
87
+ ✔ already aborted signal in constructor (0.68864ms)
88
+ ✔ push after abort (0.767599ms)
89
+ ✔ multiple messages in queue (0.448797ms)
90
+ ✔ N64 (7.090686ms)
91
+ ✔ N64.encodeEvent (0.411437ms)
92
+ ✔ N64.decodeEvent (1.08019ms)
93
+ ℹ tests 87
94
+ ℹ suites 0
95
+ ℹ pass 85
96
+ ℹ fail 1
97
+ ℹ cancelled 0
98
+ ℹ skipped 1
99
+ ℹ todo 0
100
+ ℹ duration_ms 2955.079793
101
+
102
+ ✖ failing tests:
103
+
104
+ test at uploaders/NostrBuildUploader.test.ts:12:7
105
+ ✖ NostrBuildUploader.upload (13.017756ms)
106
+ Error: nostr.build with default uploader requires a secret key to be configured
107
+ at TestContext.<anonymous> (file:///home/alex/Projects/nostrify/packages/nostrify/uploaders/NostrBuildUploader.test.ts:25:13)
108
+ at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
109
+ at async Test.run (node:internal/test_runner/test:1088:7)
110
+ at async startSubtestAfterBootstrap (node:internal/test_runner/harness:332:3)
111
+ at async file:///home/alex/Projects/nostrify/packages/nostrify/uploaders/NostrBuildUploader.test.ts:12:1
112
+  ELIFECYCLE  Test failed. See above for more details.
@@ -1,5 +1,5 @@
1
1
 
2
- > @nostrify/nostrify@0.48.1 typecheck /home/sid/repos/nostrify/packages/nostrify
2
+ > @nostrify/nostrify@0.48.3 typecheck /home/alex/Projects/nostrify/packages/nostrify
3
3
  > npx tsc --noEmit
4
4
 
5
5
  npm warn Unknown env config "verify-deps-before-run". This will stop working in the next major version of npm.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.49.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Add configurable EOSE timeout to NPool to prevent slow relays from degrading query performance
8
+
9
+ Added a new `eoseTimeout` option to `NPoolOpts` that starts a timer after the first relay sends EOSE. This prevents slow relays from blocking queries while still giving them reasonable time to respond.
10
+
11
+ Key features:
12
+
13
+ - Defaults to 1000ms timeout after first EOSE
14
+ - Set to 0 to disable and wait for all relays (previous behavior)
15
+ - Configurable via `eoseTimeout` in NPool constructor options
16
+
17
+ This significantly improves query performance when using relay pools with varying response times.
18
+
19
+ ## 0.48.3
20
+
21
+ ### Patch Changes
22
+
23
+ - Fix TypeScript import errors by removing source .ts files from dist directory
24
+ - Updated dependencies
25
+ - @nostrify/types@0.36.9
26
+
3
27
  ## 0.48.2
4
28
 
5
29
  ### Patch Changes
package/NPool.test.ts CHANGED
@@ -126,3 +126,103 @@ await test("NPool.event", async () => {
126
126
 
127
127
  clearTimeout(tid);
128
128
  });
129
+
130
+ await test("NPool.query with eoseTimeout", async () => {
131
+ const controller = new AbortController();
132
+ const tid = setTimeout(() => controller.abort(), 5000);
133
+
134
+ await using server1 = await TestRelayServer.create();
135
+
136
+ // Create a slow server that delays EOSE by 2 seconds
137
+ await using server2 = await TestRelayServer.create({
138
+ async handleMessage(socket, msg) {
139
+ if (msg[0] === 'REQ') {
140
+ const [_, subId, ...filters] = msg;
141
+ // Send events first
142
+ for (const event of event1s) {
143
+ socket.send(JSON.stringify(['EVENT', subId, event]));
144
+ }
145
+ // Delay EOSE by 2 seconds
146
+ await new Promise((resolve) => setTimeout(resolve, 2000));
147
+ socket.send(JSON.stringify(['EOSE', subId]));
148
+ }
149
+ },
150
+ });
151
+
152
+ for (const event of event1s) {
153
+ await server1.event(event);
154
+ }
155
+
156
+ const pool = new NPool({
157
+ open: (url) => new NRelay1(url),
158
+ reqRouter: (filters) =>
159
+ new Map([
160
+ [server1.url, filters],
161
+ [server2.url, filters],
162
+ ]),
163
+ eventRouter: () => [server1.url],
164
+ eoseTimeout: 500, // 500ms timeout after first EOSE
165
+ });
166
+
167
+ const startTime = Date.now();
168
+ const events = await pool.query([{ kinds: [1], limit: 15 }]);
169
+ const duration = Date.now() - startTime;
170
+
171
+ // Should return events from the fast relay
172
+ deepStrictEqual(events.length, 10);
173
+
174
+ // Should complete in less than 1 second (500ms fast relay + 500ms timeout)
175
+ // Not wait for the slow relay's 2 second delay
176
+ ok(duration < 1500, `Expected duration < 1500ms, got ${duration}ms`);
177
+
178
+ await pool.close();
179
+ clearTimeout(tid);
180
+ });
181
+
182
+ await test("NPool.query with eoseTimeout disabled", async () => {
183
+ const controller = new AbortController();
184
+ const tid = setTimeout(() => controller.abort(), 5000);
185
+
186
+ await using server1 = await TestRelayServer.create();
187
+
188
+ // Create a slow server that delays EOSE by 500ms
189
+ await using server2 = await TestRelayServer.create({
190
+ async handleMessage(socket, msg) {
191
+ if (msg[0] === 'REQ') {
192
+ const [_, subId, ...filters] = msg;
193
+ for (const event of event1s) {
194
+ socket.send(JSON.stringify(['EVENT', subId, event]));
195
+ }
196
+ await new Promise((resolve) => setTimeout(resolve, 500));
197
+ socket.send(JSON.stringify(['EOSE', subId]));
198
+ }
199
+ },
200
+ });
201
+
202
+ for (const event of event1s) {
203
+ await server1.event(event);
204
+ }
205
+
206
+ const pool = new NPool({
207
+ open: (url) => new NRelay1(url),
208
+ reqRouter: (filters) =>
209
+ new Map([
210
+ [server1.url, filters],
211
+ [server2.url, filters],
212
+ ]),
213
+ eventRouter: () => [server1.url],
214
+ eoseTimeout: 0, // Disable timeout
215
+ });
216
+
217
+ const startTime = Date.now();
218
+ const events = await pool.query([{ kinds: [1], limit: 15 }]);
219
+ const duration = Date.now() - startTime;
220
+
221
+ deepStrictEqual(events.length, 10);
222
+
223
+ // Should wait for both relays to finish (at least 500ms for the slow one)
224
+ ok(duration >= 400, `Expected duration >= 400ms, got ${duration}ms`);
225
+
226
+ await pool.close();
227
+ clearTimeout(tid);
228
+ });
package/NPool.ts CHANGED
@@ -23,6 +23,8 @@ export interface NPoolOpts<T extends NRelay> {
23
23
  | Promise<ReadonlyMap<string, NostrFilter[]>>;
24
24
  /** Determines the relays to use for publishing the given event. To support the Outbox model, it should analyze the `pubkey` field of the event. */
25
25
  eventRouter(event: NostrEvent): string[] | Promise<string[]>;
26
+ /** Maximum time in milliseconds to wait for remaining relays after the first EOSE is received in query(). Defaults to 1000ms. Set to 0 to disable timeout. */
27
+ eoseTimeout?: number;
26
28
  }
27
29
 
28
30
  /**
@@ -121,6 +123,9 @@ export class NPool<T extends NRelay = NRelay> implements NRelay {
121
123
  const closes = new Set<string>();
122
124
  const events = new CircularSet<string>(1000);
123
125
 
126
+ const eoseTimeout = this.opts.eoseTimeout ?? 1000;
127
+ let timeoutId: ReturnType<typeof setTimeout> | undefined;
128
+
124
129
  const relayPromises: Promise<void>[] = [];
125
130
 
126
131
  for (const [url, filters] of routes.entries()) {
@@ -130,6 +135,12 @@ export class NPool<T extends NRelay = NRelay> implements NRelay {
130
135
  for await (const msg of relay.req(filters, { signal })) {
131
136
  if (msg[0] === 'EOSE') {
132
137
  eoses.add(url);
138
+ // Start timeout after first EOSE if timeout is enabled
139
+ if (eoses.size === 1 && eoseTimeout > 0 && timeoutId === undefined) {
140
+ timeoutId = setTimeout(() => {
141
+ controller.abort();
142
+ }, eoseTimeout);
143
+ }
133
144
  if (eoses.size === routes.size) {
134
145
  machina.push(msg);
135
146
  }
@@ -160,6 +171,9 @@ export class NPool<T extends NRelay = NRelay> implements NRelay {
160
171
  yield msg;
161
172
  }
162
173
  } finally {
174
+ if (timeoutId !== undefined) {
175
+ clearTimeout(timeoutId);
176
+ }
163
177
  controller.abort();
164
178
  // Wait for all relay promises to complete to prevent hanging promises
165
179
  await Promise.allSettled(relayPromises);
package/dist/NPool.d.ts CHANGED
@@ -6,6 +6,8 @@ export interface NPoolOpts<T extends NRelay> {
6
6
  reqRouter(filters: NostrFilter[]): ReadonlyMap<string, NostrFilter[]> | Promise<ReadonlyMap<string, NostrFilter[]>>;
7
7
  /** Determines the relays to use for publishing the given event. To support the Outbox model, it should analyze the `pubkey` field of the event. */
8
8
  eventRouter(event: NostrEvent): string[] | Promise<string[]>;
9
+ /** Maximum time in milliseconds to wait for remaining relays after the first EOSE is received in query(). Defaults to 1000ms. Set to 0 to disable timeout. */
10
+ eoseTimeout?: number;
9
11
  }
10
12
  /**
11
13
  * The `NPool` class is a `NRelay` implementation for connecting to multiple relays.
@@ -1 +1 @@
1
- {"version":3,"file":"NPool.d.ts","sourceRoot":"","sources":["../NPool.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,WAAW,EACX,gBAAgB,EAChB,cAAc,EACd,eAAe,EACf,MAAM,EACP,MAAM,iBAAiB,CAAC;AAOzB,MAAM,WAAW,SAAS,CAAC,CAAC,SAAS,MAAM;IACzC,sDAAsD;IACtD,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC,CAAC;IACrB,8JAA8J;IAC9J,SAAS,CACP,OAAO,EAAE,WAAW,EAAE,GAEpB,WAAW,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,GAClC,OAAO,CAAC,WAAW,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC;IAChD,mJAAmJ;IACnJ,WAAW,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;CAC9D;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,qBAAa,KAAK,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,CAAE,YAAW,MAAM;IAC7D,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,IAAI,CAAe;gBAEf,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC;IAI9B,wDAAwD;IACjD,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC;IAY5B,6GAA6G;IACtG,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC;IAQtC,IAAW,MAAM,IAAI,WAAW,CAAC,MAAM,EAAE,CAAC,CAAC,CAE1C;IAED;;;;;;;;;OASG;IACI,GAAG,CACR,OAAO,EAAE,WAAW,EAAE,EACtB,IAAI,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,GACjD,aAAa,CAAC,eAAe,GAAG,cAAc,GAAG,gBAAgB,CAAC;IAiErE;;;;OAIG;IACG,KAAK,CACT,KAAK,EAAE,UAAU,EACjB,IAAI,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,GACjD,OAAO,CAAC,IAAI,CAAC;IAahB;;;;;;;;;;;OAWG;IACG,KAAK,CACT,OAAO,EAAE,WAAW,EAAE,EACtB,IAAI,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,GACjD,OAAO,CAAC,UAAU,EAAE,CAAC;IA4BxB,wCAAwC;IAClC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAMtB,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7C"}
1
+ {"version":3,"file":"NPool.d.ts","sourceRoot":"","sources":["../NPool.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,WAAW,EACX,gBAAgB,EAChB,cAAc,EACd,eAAe,EACf,MAAM,EACP,MAAM,iBAAiB,CAAC;AAOzB,MAAM,WAAW,SAAS,CAAC,CAAC,SAAS,MAAM;IACzC,sDAAsD;IACtD,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC,CAAC;IACrB,8JAA8J;IAC9J,SAAS,CACP,OAAO,EAAE,WAAW,EAAE,GAEpB,WAAW,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,GAClC,OAAO,CAAC,WAAW,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC;IAChD,mJAAmJ;IACnJ,WAAW,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC7D,8JAA8J;IAC9J,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,qBAAa,KAAK,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,CAAE,YAAW,MAAM;IAC7D,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,IAAI,CAAe;gBAEf,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC;IAI9B,wDAAwD;IACjD,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC;IAY5B,6GAA6G;IACtG,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC;IAQtC,IAAW,MAAM,IAAI,WAAW,CAAC,MAAM,EAAE,CAAC,CAAC,CAE1C;IAED;;;;;;;;;OASG;IACI,GAAG,CACR,OAAO,EAAE,WAAW,EAAE,EACtB,IAAI,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,GACjD,aAAa,CAAC,eAAe,GAAG,cAAc,GAAG,gBAAgB,CAAC;IA6ErE;;;;OAIG;IACG,KAAK,CACT,KAAK,EAAE,UAAU,EACjB,IAAI,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,GACjD,OAAO,CAAC,IAAI,CAAC;IAahB;;;;;;;;;;;OAWG;IACG,KAAK,CACT,OAAO,EAAE,WAAW,EAAE,EACtB,IAAI,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,GACjD,OAAO,CAAC,UAAU,EAAE,CAAC;IA4BxB,wCAAwC;IAClC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAMtB,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7C"}
package/dist/NPool.js CHANGED
@@ -51,6 +51,8 @@ class NPool {
51
51
  const eoses = /* @__PURE__ */ new Set();
52
52
  const closes = /* @__PURE__ */ new Set();
53
53
  const events = new CircularSet(1e3);
54
+ const eoseTimeout = this.opts.eoseTimeout ?? 1e3;
55
+ let timeoutId;
54
56
  const relayPromises = [];
55
57
  for (const [url, filters2] of routes.entries()) {
56
58
  const relay = this.relay(url);
@@ -59,6 +61,11 @@ class NPool {
59
61
  for await (const msg of relay.req(filters2, { signal })) {
60
62
  if (msg[0] === "EOSE") {
61
63
  eoses.add(url);
64
+ if (eoses.size === 1 && eoseTimeout > 0 && timeoutId === void 0) {
65
+ timeoutId = setTimeout(() => {
66
+ controller.abort();
67
+ }, eoseTimeout);
68
+ }
62
69
  if (eoses.size === routes.size) {
63
70
  machina.push(msg);
64
71
  }
@@ -87,6 +94,9 @@ class NPool {
87
94
  yield msg;
88
95
  }
89
96
  } finally {
97
+ if (timeoutId !== void 0) {
98
+ clearTimeout(timeoutId);
99
+ }
90
100
  controller.abort();
91
101
  await Promise.allSettled(relayPromises);
92
102
  }