undici 8.4.0 → 8.4.1

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.
@@ -0,0 +1,278 @@
1
+ # Getting Started
2
+
3
+ ## Installation
4
+
5
+ ```bash
6
+ npm install undici
7
+ ```
8
+
9
+ ## Fetch
10
+
11
+ The quickest way to get started is with `fetch`, which follows the
12
+ [Fetch Standard](https://fetch.spec.whatwg.org/) and works the same way as
13
+ the browser API:
14
+
15
+ ```js
16
+ import { fetch } from 'undici'
17
+
18
+ const res = await fetch('https://example.com')
19
+ const data = await res.json()
20
+ console.log(data)
21
+ ```
22
+
23
+ ### Using the Request object
24
+
25
+ undici also exports a `Request` class that follows the Fetch Standard:
26
+
27
+ ```js
28
+ import { fetch, Request } from 'undici'
29
+
30
+ const req = new Request('https://example.com', {
31
+ method: 'POST',
32
+ headers: { 'content-type': 'application/json' },
33
+ body: JSON.stringify({ hello: 'world' })
34
+ })
35
+ const res = await fetch(req)
36
+ console.log(res.status)
37
+ ```
38
+
39
+ ### Streaming the response
40
+
41
+ `res.body` is a web `ReadableStream`. Use `pipeline` from
42
+ `node:stream/promises` to stream it to a file:
43
+
44
+ ```js
45
+ import { fetch } from 'undici'
46
+ import { pipeline } from 'node:stream/promises'
47
+ import { createWriteStream } from 'node:fs'
48
+
49
+ const res = await fetch('https://example.com/large-file.zip')
50
+ await pipeline(res.body, createWriteStream('./file.zip'))
51
+ ```
52
+
53
+ > Always consume or cancel the response body. In Node.js, garbage collection
54
+ > is not aggressive enough to release connections promptly, so leaving a body
55
+ > unread can cause connection leaks and stalled requests. See
56
+ > [Specification Compliance - Garbage Collection](/docs/#garbage-collection)
57
+ > for details.
58
+
59
+ For more on `fetch`, see [API Reference: Fetch](/docs/docs/api/Fetch.md).
60
+
61
+ ## Dispatchers: Connection reuse and pooling
62
+
63
+ By default, `fetch`, `request`, `stream`, and `pipeline` create a new connection
64
+ for each call. For applications that make many requests to the same origin,
65
+ this is wasteful. undici provides **dispatchers** that manage connections
66
+ internally.
67
+
68
+ ### `Agent` — for requests to multiple origins
69
+
70
+ `Agent` is the most general-purpose dispatcher. It pools connections per-origin
71
+ and is the recommended default for most applications. Use it with
72
+ `setGlobalDispatcher` to affect all undici calls globally:
73
+
74
+ ```js
75
+ import { Agent, setGlobalDispatcher, fetch } from 'undici'
76
+
77
+ const agent = new Agent({
78
+ keepAliveTimeout: 30_000,
79
+ keepAliveMaxTimeout: 600_000
80
+ })
81
+ setGlobalDispatcher(agent)
82
+
83
+ // All subsequent fetch/request/stream/pipeline calls reuse connections
84
+ const res = await fetch('https://api.example.com/data')
85
+ ```
86
+
87
+ You can also pass a dispatcher per-request:
88
+
89
+ ```js
90
+ await fetch('https://api.example.com/data', { dispatcher: agent })
91
+ ```
92
+
93
+ ### `Pool` — for requests to a single origin
94
+
95
+ `Pool` manages a fixed set of connections to one origin. It gives you explicit
96
+ control over concurrency:
97
+
98
+ ```js
99
+ import { Pool, request } from 'undici'
100
+
101
+ const pool = new Pool('https://api.example.com', { connections: 10 })
102
+
103
+ const { body } = await request('https://api.example.com/data', {
104
+ dispatcher: pool
105
+ })
106
+ const data = await body.json()
107
+
108
+ pool.close()
109
+ ```
110
+
111
+ ### `Client` — for a single connection
112
+
113
+ `Client` maps to a single TCP connection. It supports pipelining (sending
114
+ multiple requests before responses arrive):
115
+
116
+ ```js
117
+ import { Client } from 'undici'
118
+
119
+ const client = new Client('https://api.example.com', {
120
+ pipelining: 5
121
+ })
122
+
123
+ const { body } = await client.request({ path: '/', method: 'GET' })
124
+ await body.dump()
125
+
126
+ client.close()
127
+ ```
128
+
129
+ For more on dispatcher options and lifecycle, see:
130
+ - [API Reference: Agent](/docs/docs/api/Agent.md)
131
+ - [API Reference: Pool](/docs/docs/api/Pool.md)
132
+ - [API Reference: Client](/docs/docs/api/Client.md)
133
+
134
+ ## Timeouts
135
+
136
+ undici applies timeouts at two levels:
137
+
138
+ - **`headersTimeout`** — time to wait for response headers (default: 300s).
139
+ - **`bodyTimeout`** — time between consecutive body chunks (default: 300s).
140
+
141
+ Set these on the dispatcher or per-request:
142
+
143
+ ```js
144
+ import { Agent, setGlobalDispatcher } from 'undici'
145
+
146
+ const agent = new Agent({
147
+ headersTimeout: 5_000,
148
+ bodyTimeout: 30_000
149
+ })
150
+
151
+ setGlobalDispatcher(agent)
152
+ ```
153
+
154
+ Timeout errors are thrown as `HeadersTimeoutError` and `BodyTimeoutError`.
155
+ See [API Reference: Errors](/docs/docs/api/Errors.md) for the full list.
156
+
157
+ ## Error handling
158
+
159
+ undici exposes structured errors via `error.code`:
160
+
161
+ ```js
162
+ import { request, errors } from 'undici'
163
+
164
+ try {
165
+ const { body } = await request('https://example.com')
166
+ await body.json()
167
+ } catch (err) {
168
+ switch (err.code) {
169
+ case 'UND_ERR_CONNECT_TIMEOUT':
170
+ console.error('Connection timed out')
171
+ break
172
+ case 'UND_ERR_HEADERS_TIMEOUT':
173
+ console.error('Headers timed out')
174
+ break
175
+ case 'UND_ERR_BODY_TIMEOUT':
176
+ console.error('Body timed out')
177
+ break
178
+ case 'UND_ERR_ABORTED':
179
+ console.error('Request was aborted')
180
+ break
181
+ default:
182
+ console.error(err)
183
+ }
184
+ }
185
+ ```
186
+
187
+ ### Aborting requests
188
+
189
+ ```js
190
+ import { request } from 'undici'
191
+
192
+ const ac = new AbortController()
193
+
194
+ setTimeout(() => ac.abort(), 1000)
195
+
196
+ try {
197
+ const { body } = await request('https://example.com', {
198
+ signal: ac.signal
199
+ })
200
+ await body.dump()
201
+ } catch (err) {
202
+ console.error(err.code) // UND_ERR_ABORTED
203
+ }
204
+ ```
205
+
206
+ ## Common patterns
207
+
208
+ ### Proxies
209
+
210
+ Use `ProxyAgent` for HTTP(S) proxies, or `EnvHttpProxyAgent` to pick up
211
+ proxy settings from environment variables:
212
+
213
+ ```js
214
+ import { ProxyAgent, setGlobalDispatcher } from 'undici'
215
+
216
+ const proxy = new ProxyAgent('http://proxy.internal:8080')
217
+ setGlobalDispatcher(proxy)
218
+ ```
219
+
220
+ See [Best Practices: Proxy](/docs/docs/best-practices/proxy.md) and
221
+ [API Reference: ProxyAgent](/docs/docs/api/ProxyAgent.md).
222
+
223
+ ### Mocking in tests
224
+
225
+ ```js
226
+ import { MockAgent, setGlobalDispatcher, request } from 'undici'
227
+
228
+ const mockAgent = new MockAgent()
229
+ setGlobalDispatcher(mockAgent)
230
+
231
+ const mockPool = mockAgent.get('https://api.example.com')
232
+ mockPool.intercept({ path: '/users' }).reply(200, [{ id: 1 }])
233
+
234
+ const { body } = await request('https://api.example.com/users')
235
+ console.log(await body.json())
236
+ ```
237
+
238
+ See [Best Practices: Mocking Request](/docs/docs/best-practices/mocking-request.md)
239
+ and [API Reference: MockAgent](/docs/docs/api/MockAgent.md).
240
+
241
+ ### Testing with undici
242
+
243
+ For test suites, set short keep-alive timeouts to avoid slow teardowns:
244
+
245
+ ```js
246
+ import { Agent, setGlobalDispatcher } from 'undici'
247
+
248
+ const agent = new Agent({
249
+ keepAliveTimeout: 10,
250
+ keepAliveMaxTimeout: 10
251
+ })
252
+ setGlobalDispatcher(agent)
253
+ ```
254
+
255
+ See [Best Practices: Writing Tests](/docs/docs/best-practices/writing-tests.md).
256
+
257
+ ### Customizing the global fetch
258
+
259
+ You can override Node.js's built-in globals with `install()`:
260
+
261
+ ```js
262
+ import { install } from 'undici'
263
+
264
+ install()
265
+
266
+ // Global fetch, Headers, Response, Request, and FormData
267
+ // now come from undici, not the Node.js bundle
268
+ const res = await fetch('https://example.com')
269
+ ```
270
+
271
+ See [API Reference: Global Installation](/docs/docs/api/GlobalInstallation.md).
272
+
273
+ ## Further reading
274
+
275
+ - [Undici vs. Built-in Fetch](/docs/docs/best-practices/undici-vs-builtin-fetch.md) —
276
+ when to install undici vs using Node.js built-in fetch
277
+ - [API Reference](/docs/docs/api/Dispatcher.md) — full dispatcher API documentation
278
+ - [Examples](/docs/examples/) — runnable code examples
@@ -34,7 +34,7 @@ Implements [Client.closed](/docs/docs/api/Client.md#clientclosed)
34
34
 
35
35
  Implements [Client.destroyed](/docs/docs/api/Client.md#clientdestroyed)
36
36
 
37
- ### `Pool.stats`
37
+ ### `BalancedPool.stats`
38
38
 
39
39
  Returns [`PoolStats`](/docs/docs/api/PoolStats.md) instance for this pool.
40
40
 
@@ -10,7 +10,7 @@
10
10
  * **path** `string` (optional)
11
11
  * **secure** `boolean` (optional)
12
12
  * **httpOnly** `boolean` (optional)
13
- * **sameSite** `'String'|'Lax'|'None'` (optional)
13
+ * **sameSite** `'Strict'|'Lax'|'None'` (optional)
14
14
  * **unparsed** `string[]` (optional) Left over attributes that weren't parsed.
15
15
 
16
16
  ## `deleteCookie(headers, name[, attributes])`
@@ -211,6 +211,7 @@ Returns: `Boolean` - `false` if dispatcher is busy and further dispatch calls wo
211
211
  * **onResponseData** `(controller: DispatchController, chunk: Buffer) => void` - Invoked when response payload data is received. Not required for `upgrade` requests.
212
212
  * **onResponseEnd** `(controller: DispatchController, trailers: Record<string, string | string[]>) => void` - Invoked when response payload and trailers have been received and the request has completed. Not required for `upgrade` requests.
213
213
  * **onResponseError** `(controller: DispatchController, error: Error) => void` - Invoked when an error has occurred. May not throw.
214
+ * **onBodySent** `(chunk: Buffer) => void` (optional) - Invoked when a chunk of the request body is sent.
214
215
 
215
216
  #### Migration from legacy handler API
216
217
 
@@ -688,7 +689,7 @@ return null
688
689
 
689
690
  A faster version of `Dispatcher.request`. This method expects the second argument `factory` to return a [`stream.Writable`](https://nodejs.org/api/stream.html#stream_class_stream_writable) stream which the response will be written to. This improves performance by avoiding creating an intermediate [`stream.Readable`](https://nodejs.org/api/stream.html#stream_readable_streams) stream when the user expects to directly pipe the response body to a [`stream.Writable`](https://nodejs.org/api/stream.html#stream_class_stream_writable) stream.
690
691
 
691
- As demonstrated in [Example 1 - Basic GET stream request](/docs/docs/api/Dispatcher.md#example-1-basic-get-stream-request), it is recommended to use the `option.opaque` property to avoid creating a closure for the `factory` method. This pattern works well with Node.js Web Frameworks such as [Fastify](https://fastify.io). See [Example 2 - Stream to Fastify Response](/docs/docs/api/Dispatch.md#example-2-stream-to-fastify-response) for more details.
692
+ As demonstrated in [Example 1 - Basic GET stream request](/docs/docs/api/Dispatcher.md#example-1-basic-get-stream-request), it is recommended to use the `option.opaque` property to avoid creating a closure for the `factory` method. This pattern works well with Node.js Web Frameworks such as [Fastify](https://fastify.io). See [Example 2 - Stream to Fastify Response](/docs/docs/api/Dispatcher.md#example-2-stream-to-fastify-response) for more details.
692
693
 
693
694
  Arguments:
694
695
 
@@ -1016,7 +1017,7 @@ The `retry` interceptor allows you to customize the way your dispatcher handles
1016
1017
 
1017
1018
  It accepts the same arguments as the [`RetryHandler` constructor](/docs/docs/api/RetryHandler.md).
1018
1019
 
1019
- **Example - Basic Redirect Interceptor**
1020
+ **Example - Basic Retry Interceptor**
1020
1021
 
1021
1022
  ```js
1022
1023
  const { Client, interceptors } = require("undici");
@@ -1112,7 +1113,7 @@ It represents a storage object for resolved DNS records.
1112
1113
  **Example - Basic DNS Interceptor**
1113
1114
 
1114
1115
  ```js
1115
- const { Client, interceptors } = require("undici");
1116
+ const { Agent, interceptors } = require("undici");
1116
1117
  const { dns } = interceptors;
1117
1118
 
1118
1119
  const client = new Agent().compose([
@@ -1128,7 +1129,7 @@ const response = await client.request({
1128
1129
  **Example - DNS Interceptor and LRU cache as a storage**
1129
1130
 
1130
1131
  ```js
1131
- const { Client, interceptors } = require("undici");
1132
+ const { Agent, interceptors } = require("undici");
1132
1133
  const QuickLRU = require("quick-lru");
1133
1134
  const { dns } = interceptors;
1134
1135
 
@@ -52,11 +52,11 @@ import { setGlobalDispatcher, fetch, EnvHttpProxyAgent } from 'undici'
52
52
  const envHttpProxyAgent = new EnvHttpProxyAgent()
53
53
  setGlobalDispatcher(envHttpProxyAgent)
54
54
 
55
- const { status, json } = await fetch('http://localhost:3000/foo')
55
+ const response = await fetch('http://localhost:3000/foo')
56
56
 
57
- console.log('response received', status) // response received 200
57
+ console.log('response received', response.status) // response received 200
58
58
 
59
- const data = await json() // data { foo: "bar" }
59
+ const data = await response.json() // data { foo: "bar" }
60
60
  ```
61
61
 
62
62
  #### Example - Basic Proxy Request with global agent dispatcher
@@ -102,14 +102,11 @@ import { EnvHttpProxyAgent, fetch } from 'undici'
102
102
 
103
103
  const envHttpProxyAgent = new EnvHttpProxyAgent()
104
104
 
105
- const {
106
- status,
107
- json
108
- } = await fetch('http://localhost:3000/foo', { dispatcher: envHttpProxyAgent })
105
+ const response = await fetch('http://localhost:3000/foo', { dispatcher: envHttpProxyAgent })
109
106
 
110
- console.log('response received', status) // response received 200
107
+ console.log('response received', response.status) // response received 200
111
108
 
112
- const data = await json() // data { foo: "bar" }
109
+ const data = await response.json() // data { foo: "bar" }
113
110
  ```
114
111
 
115
112
  ## Instance Methods
@@ -32,7 +32,7 @@ import { errors } from 'undici'
32
32
  | `ResponseError` | `UND_ERR_RESPONSE` | response returned an error status code; carries `statusCode`, `headers` and `body`. |
33
33
  | `MaxOriginsReachedError` | `UND_ERR_MAX_ORIGINS_REACHED` | the maximum number of allowed origins has been reached. |
34
34
  | `BalancedPoolMissingUpstreamError` | `UND_ERR_BPL_MISSING_UPSTREAM` | no upstream has been added to the `BalancedPool`. |
35
- | `Socks5ProxyError` | `UND_ERR_SOCKS5*` | an error occurred during SOCKS5 proxy negotiation. |
35
+ | `Socks5ProxyError` | `UND_ERR_SOCKS5` | an error occurred during SOCKS5 proxy negotiation. |
36
36
  | `HTTPParserError` | `HPE_*` | an error occurred while parsing the HTTP response (extends `Error`, not `UndiciError`). |
37
37
 
38
38
  Be aware of the possible difference between the global dispatcher version and the actual undici version you might be using. We recommend to avoid the check `instanceof errors.UndiciError` and seek for the `error.code === '<error_code>'` instead to avoid inconsistencies.
@@ -15,7 +15,7 @@ same implementation. Use the built-in global `FormData` with the built-in
15
15
  global `fetch()`, and use `undici`'s `FormData` with `undici.fetch()`.
16
16
 
17
17
  If you want the installed `undici` package to provide the globals, call
18
- [`install()`](/docs/api/GlobalInstallation.md) so `fetch`, `Headers`,
18
+ [`install()`](/docs/docs/api/GlobalInstallation.md) so `fetch`, `Headers`,
19
19
  `Response`, `Request`, and `FormData` are installed together as a matching set.
20
20
 
21
21
  ## Response
@@ -26,7 +26,7 @@ This API is implemented as per the standard, you can find documentation on [MDN]
26
26
 
27
27
  This API is implemented as per the standard, you can find documentation on [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Request)
28
28
 
29
- ## Header
29
+ ## Headers
30
30
 
31
31
  This API is implemented as per the standard, you can find documentation on [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Headers)
32
32
 
@@ -17,7 +17,7 @@ const server = createServer((req, res) => {
17
17
  })
18
18
 
19
19
  server.listen()
20
- once(server, 'listening').then(() => {
20
+ once(server, 'listening').then(async () => {
21
21
  const client = new H2CClient(`http://localhost:${server.address().port}/`)
22
22
 
23
23
  const response = await client.request({ path: '/', method: 'GET' })
@@ -580,7 +580,7 @@ mockAgent.getCallHistory()?.firstCall()
580
580
  ```js
581
581
  const mockAgent = new MockAgent()
582
582
 
583
- mockAgent.clearAllCallHistory()
583
+ mockAgent.clearCallHistory()
584
584
  ```
585
585
 
586
586
  #### Example - call history instance class method
@@ -165,7 +165,7 @@ Parameters :
165
165
 
166
166
  - criteria : the first parameter. a function, regexp or object.
167
167
  - function : filter MockCallHistoryLog when the function returns false
168
- - regexp : filter MockCallHistoryLog when the regexp does not match on MockCallHistoryLog.toString() ([see](./MockCallHistoryLog.md#to-string))
168
+ - regexp : filter MockCallHistoryLog when the regexp does not match on MockCallHistoryLog.toString() ([see](/docs/docs/api/MockCallHistoryLog.md#to-string))
169
169
  - object : an object with MockCallHistoryLog properties as keys to apply multiple filters. each values are a [filter parameter](/docs/docs/api/MockCallHistory.md#filter-parameter)
170
170
  - options : the second parameter. an object.
171
171
  - options.operator : `'AND'` or `'OR'` (default `'OR'`). Used only if criteria is an object. see below
@@ -36,7 +36,7 @@ Implements [Client.destroyed](/docs/docs/api/Client.md#clientdestroyed)
36
36
 
37
37
  ### `Pool.stats`
38
38
 
39
- Returns [`PoolStats`](PoolStats.md) instance for this pool.
39
+ Returns [`PoolStats`](/docs/docs/api/PoolStats.md) instance for this pool.
40
40
 
41
41
  ## Instance Methods
42
42
 
@@ -12,7 +12,7 @@ Arguments:
12
12
  * **dispatcher** `undici.Dispatcher` (required) - the dispatcher to wrap
13
13
  * **options** `RetryHandlerOptions` (optional) - the options
14
14
 
15
- Returns: `ProxyAgent`
15
+ Returns: `RetryAgent`
16
16
 
17
17
  ### Parameter: `RetryHandlerOptions`
18
18
 
@@ -23,9 +23,9 @@ Returns: `ProxyAgent`
23
23
  - **minTimeout** `number` (optional) - Minimum number of milliseconds to wait before retrying. Default: `500` (half a second)
24
24
  - **timeoutFactor** `number` (optional) - Factor to multiply the timeout by for each retry attempt. Default: `2`
25
25
  - **retryAfter** `boolean` (optional) - It enables automatic retry after the `Retry-After` header is received. Default: `true`
26
- - **methods** `string[]` (optional) - Array of HTTP methods to retry. Default: `['GET', 'PUT', 'HEAD', 'OPTIONS', 'DELETE']`
26
+ - **methods** `string[]` (optional) - Array of HTTP methods to retry. Default: `['GET', 'HEAD', 'OPTIONS', 'PUT', 'DELETE', 'TRACE']`
27
27
  - **statusCodes** `number[]` (optional) - Array of HTTP status codes to retry. Default: `[429, 500, 502, 503, 504]`
28
- - **errorCodes** `string[]` (optional) - Array of Error codes to retry. Default: `['ECONNRESET', 'ECONNREFUSED', 'ENOTFOUND', 'ENETDOWN','ENETUNREACH', 'EHOSTDOWN', 'UND_ERR_SOCKET']`
28
+ - **errorCodes** `string[]` (optional) - Array of Error codes to retry. Default: `['ECONNRESET', 'ECONNREFUSED', 'ENOTFOUND', 'ENETDOWN', 'ENETUNREACH', 'EHOSTDOWN', 'EHOSTUNREACH', 'EPIPE', 'UND_ERR_SOCKET']`
29
29
 
30
30
  **`RetryContext`**
31
31
 
@@ -4,12 +4,12 @@ Extends: `undici.DispatcherHandlers`
4
4
 
5
5
  A handler class that implements the retry logic for a request.
6
6
 
7
- ## `new RetryHandler(dispatchOptions, retryHandlers, [retryOptions])`
7
+ ## `new RetryHandler(opts, { dispatch, handler })`
8
8
 
9
9
  Arguments:
10
10
 
11
- - **options** `Dispatch.DispatchOptions & RetryOptions` (required) - It is an intersection of `Dispatcher.DispatchOptions` and `RetryOptions`.
12
- - **retryHandlers** `RetryHandlers` (required) - Object containing the `dispatch` to be used on every retry, and `handler` for handling the `dispatch` lifecycle.
11
+ - **opts** `Dispatch.DispatchOptions & { retryOptions?: RetryOptions }` (required) - An intersection of `Dispatcher.DispatchOptions` and an optional `RetryOptions` object.
12
+ - **{ dispatch, handler }** `RetryHandlers` (required) - Object containing the `dispatch` to be used on every retry, and `handler` for handling the `dispatch` lifecycle.
13
13
 
14
14
  Returns: `retryHandler`
15
15
 
@@ -20,15 +20,15 @@ Extends: [`Dispatch.DispatchOptions`](/docs/docs/api/Dispatcher.md#parameter-dis
20
20
  #### `RetryOptions`
21
21
 
22
22
  - **throwOnError** `boolean` (optional) - Disable to prevent throwing error on last retry attept, useful if you need the body on errors from server or if you have custom error handler.
23
- - **retry** `(err: Error, context: RetryContext, callback: (err?: Error | null) => void) => number | null` (optional) - Function to be called after every retry. It should pass error if no more retries should be performed.
23
+ - **retry** `(err: Error, context: RetryContext, callback: (err?: Error | null) => void) => void` (optional) - Function to be called after every retry. It should pass error if no more retries should be performed.
24
24
  - **maxRetries** `number` (optional) - Maximum number of retries. Default: `5`
25
25
  - **maxTimeout** `number` (optional) - Maximum number of milliseconds to wait before retrying. Default: `30000` (30 seconds)
26
26
  - **minTimeout** `number` (optional) - Minimum number of milliseconds to wait before retrying. Default: `500` (half a second)
27
27
  - **timeoutFactor** `number` (optional) - Factor to multiply the timeout by for each retry attempt. Default: `2`
28
28
  - **retryAfter** `boolean` (optional) - It enables automatic retry after the `Retry-After` header is received. Default: `true`
29
- - **methods** `string[]` (optional) - Array of HTTP methods to retry. Default: `['GET', 'PUT', 'HEAD', 'OPTIONS', 'DELETE']`
29
+ - **methods** `string[]` (optional) - Array of HTTP methods to retry. Default: `['GET', 'HEAD', 'OPTIONS', 'PUT', 'DELETE', 'TRACE']`
30
30
  - **statusCodes** `number[]` (optional) - Array of HTTP status codes to retry. Default: `[429, 500, 502, 503, 504]`
31
- - **errorCodes** `string[]` (optional) - Array of Error codes to retry. Default: `['ECONNRESET', 'ECONNREFUSED', 'ENOTFOUND', 'ENETDOWN','ENETUNREACH', 'EHOSTDOWN', 'UND_ERR_SOCKET']`
31
+ - **errorCodes** `string[]` (optional) - Array of Error codes to retry. Default: `['ECONNRESET', 'ECONNREFUSED', 'ENOTFOUND', 'ENETDOWN', 'ENETUNREACH', 'EHOSTDOWN', 'EHOSTUNREACH', 'EPIPE', 'UND_ERR_SOCKET']`
32
32
 
33
33
  **`RetryContext`**
34
34
 
@@ -66,7 +66,7 @@ Implements [Client.destroyed](/docs/docs/api/Client.md#clientdestroyed)
66
66
 
67
67
  ### `RoundRobinPool.stats`
68
68
 
69
- Returns [`PoolStats`](PoolStats.md) instance for this pool.
69
+ Returns [`PoolStats`](/docs/docs/api/PoolStats.md) instance for this pool.
70
70
 
71
71
  ## Instance Methods
72
72
 
@@ -634,6 +634,6 @@ SnapshotAgent provides similar functionality to nock but is specifically designe
634
634
 
635
635
  ## See Also
636
636
 
637
- - [MockAgent](./MockAgent.md) - Manual mocking for more control
638
- - [MockCallHistory](./MockCallHistory.md) - Inspecting request history
639
- - [Testing Best Practices](../best-practices/writing-tests.md) - General testing guidance
637
+ - [MockAgent](/docs/docs/api/MockAgent.md) - Manual mocking for more control
638
+ - [MockCallHistory](/docs/docs/api/MockCallHistory.md) - Inspecting request history
639
+ - [Testing Best Practices](/docs/docs/best-practices/writing-tests.md) - General testing guidance
@@ -58,9 +58,9 @@ stateDiagram-v2
58
58
 
59
59
  ### idle
60
60
 
61
- The **idle** state is the initial state of a `Client` instance. While an `origin` is required for instantiating a `Client` instance, the underlying socket connection will not be established until a request is queued using [`Client.dispatch()`](/docs/docs/api/Client.md#clientdispatchoptions-handlers). By calling `Client.dispatch()` directly or using one of the multiple implementations ([`Client.connect()`](Client.md#clientconnectoptions-callback), [`Client.pipeline()`](Client.md#clientpipelineoptions-handler), [`Client.request()`](Client.md#clientrequestoptions-callback), [`Client.stream()`](Client.md#clientstreamoptions-factory-callback), and [`Client.upgrade()`](/docs/docs/api/Client.md#clientupgradeoptions-callback)), the `Client` instance will transition from **idle** to [**pending**](/docs/docs/api/Client.md#pending) and then most likely directly to [**processing**](/docs/docs/api/Client.md#processing).
61
+ The **idle** state is the initial state of a `Client` instance. While an `origin` is required for instantiating a `Client` instance, the underlying socket connection will not be established until a request is queued using [`Client.dispatch()`](/docs/docs/api/Client.md#clientdispatchoptions-handlers). By calling `Client.dispatch()` directly or using one of the multiple implementations ([`Client.connect()`](/docs/docs/api/Client.md#clientconnectoptions-callback), [`Client.pipeline()`](/docs/docs/api/Client.md#clientpipelineoptions-handler), [`Client.request()`](/docs/docs/api/Client.md#clientrequestoptions-callback), [`Client.stream()`](/docs/docs/api/Client.md#clientstreamoptions-factory-callback), and [`Client.upgrade()`](/docs/docs/api/Client.md#clientupgradeoptions-callback)), the `Client` instance will transition from **idle** to [**pending**](/docs/docs/api/Client.md#pending) and then most likely directly to [**processing**](/docs/docs/api/Client.md#processing).
62
62
 
63
- Calling [`Client.close()`](/docs/docs/api/Client.md#clientclosecallback) or [`Client.destroy()`](Client.md#clientdestroyerror-callback) transitions directly to the [**destroyed**](/docs/docs/api/Client.md#destroyed) state since the `Client` instance will have no queued requests in this state.
63
+ Calling [`Client.close()`](/docs/docs/api/Client.md#clientclosecallback) or [`Client.destroy()`](/docs/docs/api/Client.md#clientdestroyerror-callback) transitions directly to the [**destroyed**](/docs/docs/api/Client.md#destroyed) state since the `Client` instance will have no queued requests in this state.
64
64
 
65
65
  ### pending
66
66
 
@@ -72,11 +72,11 @@ Calling [`Client.destroy()`](/docs/docs/api/Client.md#clientdestroyerror-callbac
72
72
 
73
73
  ### processing
74
74
 
75
- The **processing** state is a state machine within itself. It initializes to the [**processing.running**](/docs/docs/api/Client.md#running) state. The [`Client.dispatch()`](/docs/docs/api/Client.md#clientdispatchoptions-handlers), [`Client.close()`](Client.md#clientclosecallback), and [`Client.destroy()`](Client.md#clientdestroyerror-callback) can be called at any time while the `Client` is in this state. `Client.dispatch()` will add more requests to the queue while existing requests continue to be processed. `Client.close()` will transition to the [**processing.closing**](/docs/docs/api/Client.md#closing) state. And `Client.destroy()` will transition to [**destroyed**](/docs/docs/api/Client.md#destroyed).
75
+ The **processing** state is a state machine within itself. It initializes to the [**processing.running**](/docs/docs/api/Client.md#running) state. The [`Client.dispatch()`](/docs/docs/api/Client.md#clientdispatchoptions-handlers), [`Client.close()`](/docs/docs/api/Client.md#clientclosecallback), and [`Client.destroy()`](/docs/docs/api/Client.md#clientdestroyerror-callback) can be called at any time while the `Client` is in this state. `Client.dispatch()` will add more requests to the queue while existing requests continue to be processed. `Client.close()` will transition to the [**processing.closing**](/docs/docs/api/Client.md#closing) state. And `Client.destroy()` will transition to [**destroyed**](/docs/docs/api/Client.md#destroyed).
76
76
 
77
77
  #### running
78
78
 
79
- In the **processing.running** sub-state, queued requests are being processed in a FIFO order. If a request body requires draining, the *needDrain* event transitions to the [**processing.busy**](/docs/docs/api/Client.md#busy) sub-state. The *close* event transitions the Client to the [**process.closing**](/docs/docs/api/Client.md#closing) sub-state. If all queued requests are processed and neither [`Client.close()`](/docs/docs/api/Client.md#clientclosecallback) nor [`Client.destroy()`](Client.md#clientdestroyerror-callback) are called, then the [**processing**](/docs/docs/api/Client.md#processing) machine will trigger a *keepalive* event transitioning the `Client` back to the [**pending**](/docs/docs/api/Client.md#pending) state. During this time, the `Client` is waiting for the socket connection to timeout, and once it does, it triggers the *timeout* event and transitions to the [**idle**](/docs/docs/api/Client.md#idle) state.
79
+ In the **processing.running** sub-state, queued requests are being processed in a FIFO order. If a request body requires draining, the *needDrain* event transitions to the [**processing.busy**](/docs/docs/api/Client.md#busy) sub-state. The *close* event transitions the Client to the [**process.closing**](/docs/docs/api/Client.md#closing) sub-state. If all queued requests are processed and neither [`Client.close()`](/docs/docs/api/Client.md#clientclosecallback) nor [`Client.destroy()`](/docs/docs/api/Client.md#clientdestroyerror-callback) are called, then the [**processing**](/docs/docs/api/Client.md#processing) machine will trigger a *keepalive* event transitioning the `Client` back to the [**pending**](/docs/docs/api/Client.md#pending) state. During this time, the `Client` is waiting for the socket connection to timeout, and once it does, it triggers the *timeout* event and transitions to the [**idle**](/docs/docs/api/Client.md#idle) state.
80
80
 
81
81
  #### busy
82
82
 
@@ -371,7 +371,6 @@ class Parser {
371
371
  finish () {
372
372
  assert(currentParser === null)
373
373
  assert(this.ptr != null)
374
- assert(!this.paused)
375
374
 
376
375
  const { llhttp } = this
377
376
 
@@ -556,6 +556,19 @@ function onUpgradeStreamClose () {
556
556
  }
557
557
 
558
558
  function onRequestStreamClose () {
559
+ const state = this[kRequestStreamState]
560
+
561
+ if (state) {
562
+ // Release the stream first so request references are cleared,
563
+ // then complete the response with trailers if available.
564
+ releaseRequestStream(this)
565
+
566
+ if (state.pendingEnd && !state.request.aborted && !state.request.completed) {
567
+ state.request.onResponseEnd(state.trailers || {})
568
+ state.finalizeRequest()
569
+ }
570
+ }
571
+
559
572
  this.off('data', onData)
560
573
  this.off('error', noop)
561
574
  closeStreamSession(this)
@@ -1081,14 +1094,14 @@ function onEnd () {
1081
1094
 
1082
1095
  stream.off('end', onEnd)
1083
1096
 
1084
- releaseRequestStream(stream)
1085
- // If we received a response, this is a normal completion
1097
+ // If we received a response, this is a normal completion.
1098
+ // Defer actual completion to onRequestStreamClose so that
1099
+ // onTrailers (which may fire after 'end' on Windows) can
1100
+ // store trailers first.
1086
1101
  if (state.responseReceived) {
1087
1102
  if (!request.aborted && !request.completed) {
1088
- request.onResponseEnd({})
1103
+ state.pendingEnd = true
1089
1104
  }
1090
-
1091
- state.finalizeRequest()
1092
1105
  } else {
1093
1106
  // Stream ended without receiving a response - this is an error
1094
1107
  // (e.g., server destroyed the stream before sending headers)
@@ -1101,8 +1114,6 @@ function onError (err) {
1101
1114
  const state = stream[kRequestStreamState]
1102
1115
 
1103
1116
  stream.off('error', onError)
1104
-
1105
- releaseRequestStream(stream)
1106
1117
  state.abort(err)
1107
1118
  }
1108
1119
 
@@ -1111,8 +1122,6 @@ function onFrameError (type, code) {
1111
1122
  const state = stream[kRequestStreamState]
1112
1123
 
1113
1124
  stream.off('frameError', onFrameError)
1114
-
1115
- releaseRequestStream(stream)
1116
1125
  state.abort(new InformationalError(`HTTP/2: "frameError" received - type ${type}, code ${code}`))
1117
1126
  }
1118
1127
 
@@ -1124,7 +1133,8 @@ function onTimeout () {
1124
1133
  const stream = this
1125
1134
  const state = stream[kRequestStreamState]
1126
1135
 
1127
- releaseRequestStream(stream)
1136
+ // Remove self so timeout doesn't fire again after we handle it
1137
+ stream.off('timeout', onTimeout)
1128
1138
 
1129
1139
  const err = state.responseReceived
1130
1140
  ? new BodyTimeoutError(`HTTP/2: "stream timeout after ${state.bodyTimeout}"`)
@@ -1138,14 +1148,14 @@ function onTrailers (trailers) {
1138
1148
  const { request } = state
1139
1149
 
1140
1150
  stream.off('trailers', onTrailers)
1151
+ stream.off('data', onData)
1141
1152
 
1142
1153
  if (request.aborted || request.completed) {
1143
1154
  return
1144
1155
  }
1145
1156
 
1146
- releaseRequestStream(stream)
1147
- request.onResponseEnd(trailers)
1148
- state.finalizeRequest()
1157
+ // Store trailers for onRequestStreamClose to use when completing
1158
+ state.trailers = trailers
1149
1159
  }
1150
1160
 
1151
1161
  function writeBodyH2 () {
@@ -568,9 +568,15 @@ function handleConnectError (client, err, { host, hostname, protocol, port }) {
568
568
  }
569
569
 
570
570
  if (err.code === 'ERR_TLS_CERT_ALTNAME_INVALID') {
571
- assert(client[kRunning] === 0)
571
+ const running = client[kQueue].splice(client[kRunningIdx], client[kRunning])
572
+ client[kPendingIdx] = client[kRunningIdx]
573
+
574
+ for (let i = 0; i < running.length; i++) {
575
+ util.errorRequest(client, running[i], err)
576
+ }
577
+
572
578
  while (client[kPending] > 0 && client[kQueue][client[kPendingIdx]].servername === client[kServerName]) {
573
- const request = client[kQueue][client[kPendingIdx]++]
579
+ const request = client[kQueue].splice(client[kPendingIdx], 1)[0]
574
580
  util.errorRequest(client, request, err)
575
581
  }
576
582
  } else {
@@ -535,6 +535,10 @@ module.exports = interceptorOpts => {
535
535
 
536
536
  return dispatch => {
537
537
  return function dnsInterceptor (origDispatchOpts, handler) {
538
+ if (origDispatchOpts.origin == null) {
539
+ return dispatch(origDispatchOpts, handler)
540
+ }
541
+
538
542
  const origin =
539
543
  origDispatchOpts.origin.constructor === URL
540
544
  ? origDispatchOpts.origin
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "undici",
3
- "version": "8.4.0",
3
+ "version": "8.4.1",
4
4
  "description": "An HTTP/1.1 client, written from scratch for Node.js",
5
5
  "homepage": "https://undici.nodejs.org",
6
6
  "bugs": {