undici 6.20.0 → 7.0.0-alpha.2

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 (137) hide show
  1. package/README.md +6 -10
  2. package/docs/docs/api/Agent.md +0 -3
  3. package/docs/docs/api/Client.md +1 -3
  4. package/docs/docs/api/Debug.md +1 -1
  5. package/docs/docs/api/Dispatcher.md +60 -8
  6. package/docs/docs/api/EnvHttpProxyAgent.md +0 -1
  7. package/docs/docs/api/Fetch.md +1 -0
  8. package/docs/docs/api/MockAgent.md +2 -0
  9. package/docs/docs/api/MockPool.md +2 -1
  10. package/docs/docs/api/Pool.md +0 -1
  11. package/docs/docs/api/RetryAgent.md +1 -1
  12. package/docs/docs/api/RetryHandler.md +1 -1
  13. package/docs/docs/api/WebSocket.md +45 -3
  14. package/index.js +6 -6
  15. package/lib/api/abort-signal.js +2 -0
  16. package/lib/api/api-connect.js +3 -1
  17. package/lib/api/api-pipeline.js +7 -6
  18. package/lib/api/api-request.js +32 -47
  19. package/lib/api/api-stream.js +39 -50
  20. package/lib/api/api-upgrade.js +5 -3
  21. package/lib/api/readable.js +261 -64
  22. package/lib/api/util.js +2 -0
  23. package/lib/core/constants.js +11 -9
  24. package/lib/core/diagnostics.js +122 -128
  25. package/lib/core/errors.js +4 -4
  26. package/lib/core/request.js +11 -9
  27. package/lib/core/symbols.js +2 -1
  28. package/lib/core/tree.js +9 -1
  29. package/lib/core/util.js +219 -48
  30. package/lib/dispatcher/agent.js +3 -17
  31. package/lib/dispatcher/balanced-pool.js +5 -8
  32. package/lib/dispatcher/client-h1.js +278 -54
  33. package/lib/dispatcher/client-h2.js +1 -1
  34. package/lib/dispatcher/client.js +23 -34
  35. package/lib/dispatcher/dispatcher-base.js +2 -34
  36. package/lib/dispatcher/dispatcher.js +3 -24
  37. package/lib/dispatcher/fixed-queue.js +91 -49
  38. package/lib/dispatcher/pool-stats.js +2 -0
  39. package/lib/dispatcher/pool.js +3 -6
  40. package/lib/dispatcher/proxy-agent.js +6 -7
  41. package/lib/handler/decorator-handler.js +24 -0
  42. package/lib/handler/redirect-handler.js +11 -2
  43. package/lib/handler/retry-handler.js +12 -3
  44. package/lib/interceptor/dns.js +346 -0
  45. package/lib/interceptor/dump.js +2 -2
  46. package/lib/interceptor/redirect.js +11 -14
  47. package/lib/interceptor/response-error.js +4 -1
  48. package/lib/llhttp/constants.d.ts +97 -0
  49. package/lib/llhttp/constants.js +412 -192
  50. package/lib/llhttp/constants.js.map +1 -0
  51. package/lib/llhttp/llhttp-wasm.js +11 -1
  52. package/lib/llhttp/llhttp_simd-wasm.js +11 -1
  53. package/lib/llhttp/utils.d.ts +2 -0
  54. package/lib/llhttp/utils.js +9 -9
  55. package/lib/llhttp/utils.js.map +1 -0
  56. package/lib/mock/mock-agent.js +5 -8
  57. package/lib/mock/mock-client.js +9 -4
  58. package/lib/mock/mock-errors.js +3 -1
  59. package/lib/mock/mock-interceptor.js +8 -6
  60. package/lib/mock/mock-pool.js +9 -4
  61. package/lib/mock/mock-symbols.js +3 -1
  62. package/lib/mock/mock-utils.js +29 -5
  63. package/lib/web/cache/cache.js +24 -21
  64. package/lib/web/cache/cachestorage.js +1 -1
  65. package/lib/web/cookies/index.js +17 -13
  66. package/lib/web/cookies/parse.js +2 -2
  67. package/lib/web/eventsource/eventsource-stream.js +9 -8
  68. package/lib/web/eventsource/eventsource.js +10 -6
  69. package/lib/web/fetch/body.js +42 -36
  70. package/lib/web/fetch/constants.js +35 -26
  71. package/lib/web/fetch/data-url.js +1 -1
  72. package/lib/web/fetch/formdata-parser.js +2 -2
  73. package/lib/web/fetch/formdata.js +65 -54
  74. package/lib/web/fetch/headers.js +117 -85
  75. package/lib/web/fetch/index.js +55 -62
  76. package/lib/web/fetch/request.js +135 -77
  77. package/lib/web/fetch/response.js +86 -56
  78. package/lib/web/fetch/util.js +90 -64
  79. package/lib/web/fetch/webidl.js +99 -64
  80. package/lib/web/websocket/connection.js +76 -147
  81. package/lib/web/websocket/constants.js +3 -4
  82. package/lib/web/websocket/events.js +4 -2
  83. package/lib/web/websocket/frame.js +45 -3
  84. package/lib/web/websocket/receiver.js +29 -33
  85. package/lib/web/websocket/sender.js +18 -13
  86. package/lib/web/websocket/stream/websocketerror.js +83 -0
  87. package/lib/web/websocket/stream/websocketstream.js +485 -0
  88. package/lib/web/websocket/util.js +128 -77
  89. package/lib/web/websocket/websocket.js +234 -135
  90. package/package.json +20 -33
  91. package/scripts/strip-comments.js +3 -1
  92. package/types/agent.d.ts +7 -7
  93. package/types/api.d.ts +24 -24
  94. package/types/balanced-pool.d.ts +11 -11
  95. package/types/client.d.ts +11 -12
  96. package/types/diagnostics-channel.d.ts +10 -10
  97. package/types/dispatcher.d.ts +96 -97
  98. package/types/env-http-proxy-agent.d.ts +2 -2
  99. package/types/errors.d.ts +53 -47
  100. package/types/fetch.d.ts +8 -8
  101. package/types/formdata.d.ts +7 -7
  102. package/types/global-dispatcher.d.ts +4 -4
  103. package/types/global-origin.d.ts +5 -5
  104. package/types/handlers.d.ts +4 -4
  105. package/types/header.d.ts +157 -1
  106. package/types/index.d.ts +42 -46
  107. package/types/interceptors.d.ts +22 -8
  108. package/types/mock-agent.d.ts +21 -18
  109. package/types/mock-client.d.ts +4 -4
  110. package/types/mock-errors.d.ts +3 -3
  111. package/types/mock-interceptor.d.ts +19 -19
  112. package/types/mock-pool.d.ts +4 -4
  113. package/types/patch.d.ts +0 -4
  114. package/types/pool-stats.d.ts +8 -8
  115. package/types/pool.d.ts +12 -12
  116. package/types/proxy-agent.d.ts +4 -4
  117. package/types/readable.d.ts +22 -14
  118. package/types/retry-agent.d.ts +1 -1
  119. package/types/retry-handler.d.ts +8 -8
  120. package/types/util.d.ts +3 -3
  121. package/types/utility.d.ts +7 -0
  122. package/types/webidl.d.ts +44 -6
  123. package/types/websocket.d.ts +34 -1
  124. package/docs/docs/api/DispatchInterceptor.md +0 -60
  125. package/lib/interceptor/redirect-interceptor.js +0 -21
  126. package/lib/mock/pluralizer.js +0 -29
  127. package/lib/web/cache/symbols.js +0 -5
  128. package/lib/web/fetch/file.js +0 -126
  129. package/lib/web/fetch/symbols.js +0 -9
  130. package/lib/web/fileapi/encoding.js +0 -290
  131. package/lib/web/fileapi/filereader.js +0 -344
  132. package/lib/web/fileapi/progressevent.js +0 -78
  133. package/lib/web/fileapi/symbols.js +0 -10
  134. package/lib/web/fileapi/util.js +0 -391
  135. package/lib/web/websocket/symbols.js +0 -12
  136. package/types/file.d.ts +0 -39
  137. package/types/filereader.d.ts +0 -54
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # undici
2
2
 
3
- [![Node CI](https://github.com/nodejs/undici/actions/workflows/nodejs.yml/badge.svg)](https://github.com/nodejs/undici/actions/workflows/nodejs.yml) [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](http://standardjs.com/) [![npm version](https://badge.fury.io/js/undici.svg)](https://badge.fury.io/js/undici) [![codecov](https://codecov.io/gh/nodejs/undici/branch/main/graph/badge.svg?token=yZL6LtXkOA)](https://codecov.io/gh/nodejs/undici)
3
+ [![Node CI](https://github.com/nodejs/undici/actions/workflows/nodejs.yml/badge.svg)](https://github.com/nodejs/undici/actions/workflows/nodejs.yml) [![neostandard javascript style](https://img.shields.io/badge/neo-standard-7fffff?style=flat\&labelColor=ff80ff)](https://github.com/neostandard/neostandard) [![npm version](https://badge.fury.io/js/undici.svg)](https://badge.fury.io/js/undici) [![codecov](https://codecov.io/gh/nodejs/undici/branch/main/graph/badge.svg?token=yZL6LtXkOA)](https://codecov.io/gh/nodejs/undici)
4
4
 
5
5
  An HTTP/1.1 client, written from scratch for Node.js.
6
6
 
@@ -84,6 +84,7 @@ The `body` mixins are the most common way to format the request/response body. M
84
84
 
85
85
  - [`.arrayBuffer()`](https://fetch.spec.whatwg.org/#dom-body-arraybuffer)
86
86
  - [`.blob()`](https://fetch.spec.whatwg.org/#dom-body-blob)
87
+ - [`.bytes()`](https://fetch.spec.whatwg.org/#dom-body-bytes)
87
88
  - [`.json()`](https://fetch.spec.whatwg.org/#dom-body-json)
88
89
  - [`.text()`](https://fetch.spec.whatwg.org/#dom-body-text)
89
90
 
@@ -126,7 +127,6 @@ Arguments:
126
127
  * **options** [`RequestOptions`](./docs/docs/api/Dispatcher.md#parameter-requestoptions)
127
128
  * **dispatcher** `Dispatcher` - Default: [getGlobalDispatcher](#undicigetglobaldispatcher)
128
129
  * **method** `String` - Default: `PUT` if `options.body`, otherwise `GET`
129
- * **maxRedirections** `Integer` - Default: `0`
130
130
 
131
131
  Returns a promise with the result of the `Dispatcher.request` method.
132
132
 
@@ -142,7 +142,6 @@ Arguments:
142
142
  * **options** [`StreamOptions`](./docs/docs/api/Dispatcher.md#parameter-streamoptions)
143
143
  * **dispatcher** `Dispatcher` - Default: [getGlobalDispatcher](#undicigetglobaldispatcher)
144
144
  * **method** `String` - Default: `PUT` if `options.body`, otherwise `GET`
145
- * **maxRedirections** `Integer` - Default: `0`
146
145
  * **factory** `Dispatcher.stream.factory`
147
146
 
148
147
  Returns a promise with the result of the `Dispatcher.stream` method.
@@ -159,7 +158,6 @@ Arguments:
159
158
  * **options** [`PipelineOptions`](./docs/docs/api/Dispatcher.md#parameter-pipelineoptions)
160
159
  * **dispatcher** `Dispatcher` - Default: [getGlobalDispatcher](#undicigetglobaldispatcher)
161
160
  * **method** `String` - Default: `PUT` if `options.body`, otherwise `GET`
162
- * **maxRedirections** `Integer` - Default: `0`
163
161
  * **handler** `Dispatcher.pipeline.handler`
164
162
 
165
163
  Returns: `stream.Duplex`
@@ -177,7 +175,6 @@ Arguments:
177
175
  * **url** `string | URL | UrlObject`
178
176
  * **options** [`ConnectOptions`](./docs/docs/api/Dispatcher.md#parameter-connectoptions)
179
177
  * **dispatcher** `Dispatcher` - Default: [getGlobalDispatcher](#undicigetglobaldispatcher)
180
- * **maxRedirections** `Integer` - Default: `0`
181
178
  * **callback** `(err: Error | null, data: ConnectData | null) => void` (optional)
182
179
 
183
180
  Returns a promise with the result of the `Dispatcher.connect` method.
@@ -233,7 +230,7 @@ A body can be of the following types:
233
230
  - URLSearchParams
234
231
  - FormData
235
232
 
236
- In this implementation of fetch, ```request.body``` now accepts ```Async Iterables```. It is not present in the [Fetch Standard.](https://fetch.spec.whatwg.org)
233
+ In this implementation of fetch, ```request.body``` now accepts ```Async Iterables```. It is not present in the [Fetch Standard](https://fetch.spec.whatwg.org).
237
234
 
238
235
  ```js
239
236
  import { fetch } from 'undici'
@@ -262,13 +259,13 @@ await fetch('http://example.com', { method: 'POST', body })
262
259
 
263
260
  #### `request.duplex`
264
261
 
265
- - half
262
+ - `'half'`
266
263
 
267
- In this implementation of fetch, `request.duplex` must be set if `request.body` is `ReadableStream` or `Async Iterables`, however, fetch requests are currently always full duplex. For more detail refer to the [Fetch Standard.](https://fetch.spec.whatwg.org/#dom-requestinit-duplex).
264
+ In this implementation of fetch, `request.duplex` must be set if `request.body` is `ReadableStream` or `Async Iterables`, however, even though the value must be set to `'half'`, it is actually a _full_ duplex. For more detail refer to the [Fetch Standard](https://fetch.spec.whatwg.org/#dom-requestinit-duplex).
268
265
 
269
266
  #### `response.body`
270
267
 
271
- Nodejs has two kinds of streams: [web streams](https://nodejs.org/dist/latest-v16.x/docs/api/webstreams.html), which follow the API of the WHATWG web standard found in browsers, and an older Node-specific [streams API](https://nodejs.org/api/stream.html). `response.body` returns a readable web stream. If you would prefer to work with a Node stream you can convert a web stream using `.fromWeb()`.
268
+ Nodejs has two kinds of streams: [web streams](https://nodejs.org/api/webstreams.html), which follow the API of the WHATWG web standard found in browsers, and an older Node-specific [streams API](https://nodejs.org/api/stream.html). `response.body` returns a readable web stream. If you would prefer to work with a Node stream you can convert a web stream using `.fromWeb()`.
272
269
 
273
270
  ```js
274
271
  import { fetch } from 'undici'
@@ -337,7 +334,6 @@ Arguments:
337
334
  * **url** `string | URL | UrlObject`
338
335
  * **options** [`UpgradeOptions`](./docs/docs/api/Dispatcher.md#parameter-upgradeoptions)
339
336
  * **dispatcher** `Dispatcher` - Default: [getGlobalDispatcher](#undicigetglobaldispatcher)
340
- * **maxRedirections** `Integer` - Default: `0`
341
337
  * **callback** `(error: Error | null, data: UpgradeData) => void` (optional)
342
338
 
343
339
  Returns a promise with the result of the `Dispatcher.upgrade` method.
@@ -19,8 +19,6 @@ Returns: `Agent`
19
19
  Extends: [`PoolOptions`](Pool.md#parameter-pooloptions)
20
20
 
21
21
  * **factory** `(origin: URL, opts: Object) => Dispatcher` - Default: `(origin, opts) => new Pool(origin, opts)`
22
- * **maxRedirections** `Integer` - Default: `0`. The number of HTTP redirection to follow unless otherwise specified in `DispatchOptions`.
23
- * **interceptors** `{ Agent: DispatchInterceptor[] }` - Default: `[RedirectInterceptor]` - A list of interceptors that are applied to the dispatch method. Additional logic can be applied (such as, but not limited to: 302 status code handling, authentication, cookies, compression and caching). Note that the behavior of interceptors is Experimental and might change at any given time.
24
22
 
25
23
  ## Instance Properties
26
24
 
@@ -51,7 +49,6 @@ Implements [`Dispatcher.dispatch(options, handler)`](Dispatcher.md#dispatcherdis
51
49
  Extends: [`DispatchOptions`](Dispatcher.md#parameter-dispatchoptions)
52
50
 
53
51
  * **origin** `string | URL`
54
- * **maxRedirections** `Integer`.
55
52
 
56
53
  Implements [`Dispatcher.destroy([error, callback])`](Dispatcher.md#dispatcherdestroyerror-callback-promise).
57
54
 
@@ -19,7 +19,7 @@ Returns: `Client`
19
19
 
20
20
  > ⚠️ Warning: The `H2` support is experimental.
21
21
 
22
- * **bodyTimeout** `number | null` (optional) - Default: `300e3` - The timeout after which a request will time out, in milliseconds. Monitors time between receiving body data. Use `0` to disable it entirely. Defaults to 300 seconds. Please note the `timeout` will be reset if you keep writing data to the scoket everytime.
22
+ * **bodyTimeout** `number | null` (optional) - Default: `300e3` - The timeout after which a request will time out, in milliseconds. Monitors time between receiving body data. Use `0` to disable it entirely. Defaults to 300 seconds. Please note the `timeout` will be reset if you keep writing data to the socket everytime.
23
23
  * **headersTimeout** `number | null` (optional) - Default: `300e3` - The amount of time, in milliseconds, the parser will wait to receive the complete HTTP headers while not sending the request. Defaults to 300 seconds.
24
24
  * **keepAliveMaxTimeout** `number | null` (optional) - Default: `600e3` - The maximum allowed `keepAliveTimeout`, in milliseconds, when overridden by *keep-alive* hints from the server. Defaults to 10 minutes.
25
25
  * **keepAliveTimeout** `number | null` (optional) - Default: `4e3` - The timeout, in milliseconds, after which a socket without active requests will time out. Monitors time between activity on a connected socket. This value may be overridden by *keep-alive* hints from the server. See [MDN: HTTP - Headers - Keep-Alive directives](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Keep-Alive#directives) for more details. Defaults to 4 seconds.
@@ -29,8 +29,6 @@ Returns: `Client`
29
29
  * **pipelining** `number | null` (optional) - Default: `1` - The amount of concurrent requests to be sent over the single TCP/TLS connection according to [RFC7230](https://tools.ietf.org/html/rfc7230#section-6.3.2). Carefully consider your workload and environment before enabling concurrent requests as pipelining may reduce performance if used incorrectly. Pipelining is sensitive to network stack settings as well as head of line blocking caused by e.g. long running requests. Set to `0` to disable keep-alive connections.
30
30
  * **connect** `ConnectOptions | Function | null` (optional) - Default: `null`.
31
31
  * **strictContentLength** `Boolean` (optional) - Default: `true` - Whether to treat request content length mismatches as errors. If true, an error is thrown when the request content-length header doesn't match the length of the request body.
32
- <!-- TODO: Remove once we drop its support -->
33
- * **interceptors** `{ Client: DispatchInterceptor[] }` - Default: `[RedirectInterceptor]` - A list of interceptors that are applied to the dispatch method. Additional logic can be applied (such as, but not limited to: 302 status code handling, authentication, cookies, compression and caching). Note that the behavior of interceptors is Experimental and might change at any given time. **Note: this is deprecated in favor of [Dispatcher#compose](./Dispatcher.md#dispatcher). Support will be droped in next major.**
34
32
  * **autoSelectFamily**: `boolean` (optional) - Default: depends on local Node version, on Node 18.13.0 and above is `false`. Enables a family autodetection algorithm that loosely implements section 5 of [RFC 8305](https://tools.ietf.org/html/rfc8305#section-5). See [here](https://nodejs.org/api/net.html#socketconnectoptions-connectlistener) for more details. This option is ignored if not supported by the current Node version.
35
33
  * **autoSelectFamilyAttemptTimeout**: `number` - Default: depends on local Node version, on Node 18.13.0 and above is `250`. The amount of time in milliseconds to wait for a connection attempt to finish before trying the next address when using the `autoSelectFamily` option. See [here](https://nodejs.org/api/net.html#socketconnectoptions-connectlistener) for more details.
36
34
  * **allowH2**: `boolean` - Default: `false`. Enables support for H2 if the server has assigned bigger priority to it through ALPN negotiation.
@@ -2,7 +2,7 @@
2
2
 
3
3
  Undici (and subsenquently `fetch` and `websocket`) exposes a debug statement that can be enabled by setting `NODE_DEBUG` within the environment.
4
4
 
5
- The flags availabile are:
5
+ The flags available are:
6
6
 
7
7
  ## `undici`
8
8
 
@@ -201,7 +201,6 @@ Returns: `Boolean` - `false` if dispatcher is busy and further dispatch calls wo
201
201
  * **upgrade** `string | null` (optional) - Default: `null` - Upgrade the request. Should be used to specify the kind of upgrade i.e. `'Websocket'`.
202
202
  * **bodyTimeout** `number | null` (optional) - The timeout after which a request will time out, in milliseconds. Monitors time between receiving body data. Use `0` to disable it entirely. Defaults to 300 seconds.
203
203
  * **headersTimeout** `number | null` (optional) - The amount of time, in milliseconds, the parser will wait to receive the complete HTTP headers while not sending the request. Defaults to 300 seconds.
204
- * **throwOnError** `boolean` (optional) - Default: `false` - Whether Undici should throw an error upon receiving a 4xx or 5xx response from the server.
205
204
  * **expectContinue** `boolean` (optional) - Default: `false` - For H2, it appends the expect: 100-continue header, and halts the request body until a 100-continue is received from the remote server
206
205
 
207
206
  #### Parameter: `DispatchHandler`
@@ -479,7 +478,7 @@ The `RequestOptions.method` property should not be value `'CONNECT'`.
479
478
  #### Parameter: `ResponseData`
480
479
 
481
480
  * **statusCode** `number`
482
- * **headers** `Record<string, string | string[]>` - Note that all header keys are lower-cased, e. g. `content-type`.
481
+ * **headers** `Record<string, string | string[]>` - Note that all header keys are lower-cased, e.g. `content-type`.
483
482
  * **body** `stream.Readable` which also implements [the body mixin from the Fetch Standard](https://fetch.spec.whatwg.org/#body-mixin).
484
483
  * **trailers** `Record<string, string>` - This object starts out
485
484
  as empty and will be mutated to contain trailers after `body` has emitted `'end'`.
@@ -488,11 +487,13 @@ The `RequestOptions.method` property should not be value `'CONNECT'`.
488
487
 
489
488
  `body` contains the following additional [body mixin](https://fetch.spec.whatwg.org/#body-mixin) methods and properties:
490
489
 
491
- - `text()`
492
- - `json()`
493
- - `arrayBuffer()`
494
- - `body`
495
- - `bodyUsed`
490
+ * [`.arrayBuffer()`](https://fetch.spec.whatwg.org/#dom-body-arraybuffer)
491
+ * [`.blob()`](https://fetch.spec.whatwg.org/#dom-body-blob)
492
+ * [`.bytes()`](https://fetch.spec.whatwg.org/#dom-body-bytes)
493
+ * [`.json()`](https://fetch.spec.whatwg.org/#dom-body-json)
494
+ * [`.text()`](https://fetch.spec.whatwg.org/#dom-body-text)
495
+ * `body`
496
+ * `bodyUsed`
496
497
 
497
498
  `body` can not be consumed twice. For example, calling `text()` after `json()` throws `TypeError`.
498
499
 
@@ -973,7 +974,7 @@ const client = new Client("http://example.com").compose(
973
974
  })
974
975
  );
975
976
 
976
- // or
977
+ // or
977
978
  client.dispatch(
978
979
  {
979
980
  path: "/",
@@ -984,6 +985,57 @@ client.dispatch(
984
985
  );
985
986
  ```
986
987
 
988
+ ##### `dns`
989
+
990
+ The `dns` interceptor enables you to cache DNS lookups for a given duration, per origin.
991
+
992
+ >It is well suited for scenarios where you want to cache DNS lookups to avoid the overhead of resolving the same domain multiple times
993
+
994
+ **Options**
995
+ - `maxTTL` - The maximum time-to-live (in milliseconds) of the DNS cache. It should be a positive integer. Default: `10000`.
996
+ - Set `0` to disable TTL.
997
+ - `maxItems` - The maximum number of items to cache. It should be a positive integer. Default: `Infinity`.
998
+ - `dualStack` - Whether to resolve both IPv4 and IPv6 addresses. Default: `true`.
999
+ - It will also attempt a happy-eyeballs-like approach to connect to the available addresses in case of a connection failure.
1000
+ - `affinity` - Whether to use IPv4 or IPv6 addresses. Default: `4`.
1001
+ - It can be either `'4` or `6`.
1002
+ - It will only take effect if `dualStack` is `false`.
1003
+ - `lookup: (hostname: string, options: LookupOptions, callback: (err: NodeJS.ErrnoException | null, addresses: DNSInterceptorRecord[]) => void) => void` - Custom lookup function. Default: `dns.lookup`.
1004
+ - For more info see [dns.lookup](https://nodejs.org/api/dns.html#dns_dns_lookup_hostname_options_callback).
1005
+ - `pick: (origin: URL, records: DNSInterceptorRecords, affinity: 4 | 6) => DNSInterceptorRecord` - Custom pick function. Default: `RoundRobin`.
1006
+ - The function should return a single record from the records array.
1007
+ - By default a simplified version of Round Robin is used.
1008
+ - The `records` property can be mutated to store the state of the balancing algorithm.
1009
+
1010
+ > The `Dispatcher#options` also gets extended with the options `dns.affinity`, `dns.dualStack`, `dns.lookup` and `dns.pick` which can be used to configure the interceptor at a request-per-request basis.
1011
+
1012
+
1013
+ **DNSInterceptorRecord**
1014
+ It represents a DNS record.
1015
+ - `family` - (`number`) The IP family of the address. It can be either `4` or `6`.
1016
+ - `address` - (`string`) The IP address.
1017
+
1018
+ **DNSInterceptorOriginRecords**
1019
+ It represents a map of DNS IP addresses records for a single origin.
1020
+ - `4.ips` - (`DNSInterceptorRecord[] | null`) The IPv4 addresses.
1021
+ - `6.ips` - (`DNSInterceptorRecord[] | null`) The IPv6 addresses.
1022
+
1023
+ **Example - Basic DNS Interceptor**
1024
+
1025
+ ```js
1026
+ const { Client, interceptors } = require("undici");
1027
+ const { dns } = interceptors;
1028
+
1029
+ const client = new Agent().compose([
1030
+ dns({ ...opts })
1031
+ ])
1032
+
1033
+ const response = await client.request({
1034
+ origin: `http://localhost:3030`,
1035
+ ...requestOpts
1036
+ })
1037
+ ```
1038
+
987
1039
  ##### `Response Error Interceptor`
988
1040
 
989
1041
  **Introduction**
@@ -133,7 +133,6 @@ Implements [`Dispatcher.dispatch(options, handler)`](Dispatcher.md#dispatcherdis
133
133
  Extends: [`DispatchOptions`](Dispatcher.md#parameter-dispatchoptions)
134
134
 
135
135
  * **origin** `string | URL`
136
- * **maxRedirections** `Integer`.
137
136
 
138
137
  Implements [`Dispatcher.destroy([error, callback])`](Dispatcher.md#dispatcherdestroyerror-callback-promise).
139
138
 
@@ -28,6 +28,7 @@ This API is implemented as per the standard, you can find documentation on [MDN]
28
28
 
29
29
  - [`.arrayBuffer()`](https://fetch.spec.whatwg.org/#dom-body-arraybuffer)
30
30
  - [`.blob()`](https://fetch.spec.whatwg.org/#dom-body-blob)
31
+ - [`.bytes()`](https://fetch.spec.whatwg.org/#dom-body-bytes)
31
32
  - [`.formData()`](https://fetch.spec.whatwg.org/#dom-body-formdata)
32
33
  - [`.json()`](https://fetch.spec.whatwg.org/#dom-body-json)
33
34
  - [`.text()`](https://fetch.spec.whatwg.org/#dom-body-text)
@@ -18,6 +18,8 @@ Extends: [`AgentOptions`](Agent.md#parameter-agentoptions)
18
18
 
19
19
  * **agent** `Agent` (optional) - Default: `new Agent([options])` - a custom agent encapsulated by the MockAgent.
20
20
 
21
+ * **ignoreTrailingSlash** `boolean` (optional) - Default: `false` - set the default value for `ignoreTrailingSlash` for interceptors.
22
+
21
23
  ### Example - Basic MockAgent instantiation
22
24
 
23
25
  This will instantiate the MockAgent. It will not do anything until registered as the agent to use with requests and mock interceptions are added.
@@ -58,6 +58,7 @@ Returns: `MockInterceptor` corresponding to the input options.
58
58
  * **body** `string | RegExp | (body: string) => boolean` - (optional) - a matcher for the HTTP request body.
59
59
  * **headers** `Record<string, string | RegExp | (body: string) => boolean`> - (optional) - a matcher for the HTTP request headers. To be intercepted, a request must match all defined headers. Extra headers not defined here may (or may not) be included in the request and do not affect the interception in any way.
60
60
  * **query** `Record<string, any> | null` - (optional) - a matcher for the HTTP request query string params. Only applies when a `string` was provided for `MockPoolInterceptOptions.path`.
61
+ * **ignoreTrailingSlash** `boolean` - (optional) - set to `true` if the matcher should also match by ignoring potential trailing slashes in `MockPoolInterceptOptions.path`.
61
62
 
62
63
  ### Return: `MockInterceptor`
63
64
 
@@ -81,7 +82,7 @@ By default, `reply` and `replyWithError` define the behaviour for the first matc
81
82
 
82
83
  ### Return: `MockScope`
83
84
 
84
- A `MockScope` is associated with a single `MockInterceptor`. With this, we can configure the default behaviour of a intercepted reply.
85
+ A `MockScope` is associated with a single `MockInterceptor`. With this, we can configure the default behaviour of an intercepted reply.
85
86
 
86
87
  * **delay** `(waitInMs: number) => MockScope` - delay the associated reply by a set amount in ms.
87
88
  * **persist** `() => MockScope` - any matching request will always reply with the defined response indefinitely.
@@ -19,7 +19,6 @@ Extends: [`ClientOptions`](Client.md#parameter-clientoptions)
19
19
 
20
20
  * **factory** `(origin: URL, opts: Object) => Dispatcher` - Default: `(origin, opts) => new Client(origin, opts)`
21
21
  * **connections** `number | null` (optional) - Default: `null` - The number of `Client` instances to create. When set to `null`, the `Pool` instance will create an unlimited amount of `Client` instances.
22
- * **interceptors** `{ Pool: DispatchInterceptor[] } }` - Default: `{ Pool: [] }` - A list of interceptors that are applied to the dispatch method. Additional logic can be applied (such as, but not limited to: 302 status code handling, authentication, cookies, compression and caching).
23
22
 
24
23
  ## Instance Properties
25
24
 
@@ -40,6 +40,6 @@ import { Agent, RetryAgent } from 'undici'
40
40
  const agent = new RetryAgent(new Agent())
41
41
 
42
42
  const res = await agent.request('http://example.com')
43
- console.log(res.statuCode)
43
+ console.log(res.statusCode)
44
44
  console.log(await res.body.text())
45
45
  ```
@@ -46,7 +46,7 @@ It represents the retry state for a given request.
46
46
  - **dispatch** `(options: Dispatch.DispatchOptions, handlers: Dispatch.DispatchHandlers) => Promise<Dispatch.DispatchResponse>` (required) - Dispatch function to be called after every retry.
47
47
  - **handler** Extends [`Dispatch.DispatchHandlers`](Dispatcher.md#dispatcherdispatchoptions-handler) (required) - Handler function to be called after the request is successful or the retries are exhausted.
48
48
 
49
- >__Note__: The `RetryHandler` does not retry over stateful bodies (e.g. streams, AsyncIterable) as those, once consumed, are left in an state that cannot be reutilized. For these situations the `RetryHandler` will identify
49
+ >__Note__: The `RetryHandler` does not retry over stateful bodies (e.g. streams, AsyncIterable) as those, once consumed, are left in a state that cannot be reutilized. For these situations the `RetryHandler` will identify
50
50
  >the body as stateful and will not retry the request rejecting with the error `UND_ERR_REQ_RETRY`.
51
51
 
52
52
  Examples:
@@ -1,7 +1,5 @@
1
1
  # Class: WebSocket
2
2
 
3
- > ⚠️ Warning: the WebSocket API is experimental.
4
-
5
3
  Extends: [`EventTarget`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget)
6
4
 
7
5
  The WebSocket object provides a way to manage a WebSocket connection to a server, allowing bidirectional communication. The API follows the [WebSocket spec](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) and [RFC 6455](https://datatracker.ietf.org/doc/html/rfc6455).
@@ -10,7 +8,7 @@ The WebSocket object provides a way to manage a WebSocket connection to a server
10
8
 
11
9
  Arguments:
12
10
 
13
- * **url** `URL | string` - The url's protocol *must* be `ws` or `wss`.
11
+ * **url** `URL | string`
14
12
  * **protocol** `string | string[] | WebSocketInit` (optional) - Subprotocol(s) to request the server use, or a [`Dispatcher`](./Dispatcher.md).
15
13
 
16
14
  ### Example:
@@ -36,6 +34,50 @@ import { WebSocket } from 'undici'
36
34
  const ws = new WebSocket('wss://echo.websocket.events', ['echo', 'chat'])
37
35
  ```
38
36
 
37
+ # Class: WebSocketStream
38
+
39
+ > ⚠️ Warning: the WebSocketStream API has not been finalized and is likely to change.
40
+
41
+ See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/WebSocketStream) for more information.
42
+
43
+ ## `new WebSocketStream(url[, protocol])`
44
+
45
+ Arguments:
46
+
47
+ * **url** `URL | string`
48
+ * **options** `WebSocketStreamOptions` (optional)
49
+
50
+ ### WebSocketStream Example
51
+
52
+ ```js
53
+ const stream = new WebSocketStream('https://echo.websocket.org/')
54
+ const { readable, writable } = await stream.opened
55
+
56
+ async function read () {
57
+ /** @type {ReadableStreamReader} */
58
+ const reader = readable.getReader()
59
+
60
+ while (true) {
61
+ const { done, value } = await reader.read()
62
+ if (done) break
63
+
64
+ // do something with value
65
+ }
66
+ }
67
+
68
+ async function write () {
69
+ /** @type {WritableStreamDefaultWriter} */
70
+ const writer = writable.getWriter()
71
+ writer.write('Hello, world!')
72
+ writer.releaseLock()
73
+ }
74
+
75
+ read()
76
+
77
+ setInterval(() => write(), 5000)
78
+
79
+ ```
80
+
39
81
  ## Read More
40
82
 
41
83
  - [MDN - WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
package/index.js CHANGED
@@ -21,7 +21,6 @@ const RetryHandler = require('./lib/handler/retry-handler')
21
21
  const { getGlobalDispatcher, setGlobalDispatcher } = require('./lib/global')
22
22
  const DecoratorHandler = require('./lib/handler/decorator-handler')
23
23
  const RedirectHandler = require('./lib/handler/redirect-handler')
24
- const createRedirectInterceptor = require('./lib/interceptor/redirect-interceptor')
25
24
 
26
25
  Object.assign(Dispatcher.prototype, api)
27
26
 
@@ -37,11 +36,11 @@ module.exports.RetryHandler = RetryHandler
37
36
 
38
37
  module.exports.DecoratorHandler = DecoratorHandler
39
38
  module.exports.RedirectHandler = RedirectHandler
40
- module.exports.createRedirectInterceptor = createRedirectInterceptor
41
39
  module.exports.interceptors = {
42
40
  redirect: require('./lib/interceptor/redirect'),
43
41
  retry: require('./lib/interceptor/retry'),
44
- dump: require('./lib/interceptor/dump')
42
+ dump: require('./lib/interceptor/dump'),
43
+ dns: require('./lib/interceptor/dns')
45
44
  }
46
45
 
47
46
  module.exports.buildConnector = buildConnector
@@ -119,8 +118,6 @@ module.exports.Headers = require('./lib/web/fetch/headers').Headers
119
118
  module.exports.Response = require('./lib/web/fetch/response').Response
120
119
  module.exports.Request = require('./lib/web/fetch/request').Request
121
120
  module.exports.FormData = require('./lib/web/fetch/formdata').FormData
122
- module.exports.File = globalThis.File ?? require('node:buffer').File
123
- module.exports.FileReader = require('./lib/web/fileapi/filereader').FileReader
124
121
 
125
122
  const { setGlobalOrigin, getGlobalOrigin } = require('./lib/web/fetch/global')
126
123
 
@@ -128,7 +125,7 @@ module.exports.setGlobalOrigin = setGlobalOrigin
128
125
  module.exports.getGlobalOrigin = getGlobalOrigin
129
126
 
130
127
  const { CacheStorage } = require('./lib/web/cache/cachestorage')
131
- const { kConstruct } = require('./lib/web/cache/symbols')
128
+ const { kConstruct } = require('./lib/core/symbols')
132
129
 
133
130
  // Cache & CacheStorage are tightly coupled with fetch. Even if it may run
134
131
  // in an older version of Node, it doesn't have any use without fetch.
@@ -152,6 +149,9 @@ module.exports.CloseEvent = CloseEvent
152
149
  module.exports.ErrorEvent = ErrorEvent
153
150
  module.exports.MessageEvent = MessageEvent
154
151
 
152
+ module.exports.WebSocketStream = require('./lib/web/websocket/stream/websocketstream').WebSocketStream
153
+ module.exports.WebSocketError = require('./lib/web/websocket/stream/websocketerror').WebSocketError
154
+
155
155
  module.exports.request = makeDispatcher(api.request)
156
156
  module.exports.stream = makeDispatcher(api.stream)
157
157
  module.exports.pipeline = makeDispatcher(api.pipeline)
@@ -1,3 +1,5 @@
1
+ 'use strict'
2
+
1
3
  const { addAbortListener } = require('../core/util')
2
4
  const { RequestAbortedError } = require('../core/errors')
3
5
 
@@ -95,7 +95,9 @@ function connect (opts, callback) {
95
95
 
96
96
  try {
97
97
  const connectHandler = new ConnectHandler(opts, callback)
98
- this.dispatch({ ...opts, method: 'CONNECT' }, connectHandler)
98
+ const connectOptions = { ...opts, method: 'CONNECT' }
99
+
100
+ this.dispatch(connectOptions, connectHandler)
99
101
  } catch (err) {
100
102
  if (typeof callback !== 'function') {
101
103
  throw err
@@ -5,15 +5,17 @@ const {
5
5
  Duplex,
6
6
  PassThrough
7
7
  } = require('node:stream')
8
+ const assert = require('node:assert')
9
+ const { AsyncResource } = require('node:async_hooks')
8
10
  const {
9
11
  InvalidArgumentError,
10
12
  InvalidReturnValueError,
11
13
  RequestAbortedError
12
14
  } = require('../core/errors')
13
15
  const util = require('../core/util')
14
- const { AsyncResource } = require('node:async_hooks')
15
16
  const { addSignal, removeSignal } = require('./abort-signal')
16
- const assert = require('node:assert')
17
+
18
+ function noop () {}
17
19
 
18
20
  const kResume = Symbol('resume')
19
21
 
@@ -92,7 +94,7 @@ class PipelineHandler extends AsyncResource {
92
94
  this.context = null
93
95
  this.onInfo = onInfo || null
94
96
 
95
- this.req = new PipelineRequest().on('error', util.nop)
97
+ this.req = new PipelineRequest().on('error', noop)
96
98
 
97
99
  this.ret = new Duplex({
98
100
  readableObjectMode: opts.objectMode,
@@ -145,7 +147,7 @@ class PipelineHandler extends AsyncResource {
145
147
  }
146
148
 
147
149
  onConnect (abort, context) {
148
- const { ret, res } = this
150
+ const { res } = this
149
151
 
150
152
  if (this.reason) {
151
153
  abort(this.reason)
@@ -153,7 +155,6 @@ class PipelineHandler extends AsyncResource {
153
155
  }
154
156
 
155
157
  assert(!res, 'pipeline cannot be retried')
156
- assert(!ret.destroyed)
157
158
 
158
159
  this.abort = abort
159
160
  this.context = context
@@ -184,7 +185,7 @@ class PipelineHandler extends AsyncResource {
184
185
  context
185
186
  })
186
187
  } catch (err) {
187
- this.res.on('error', util.nop)
188
+ this.res.on('error', noop)
188
189
  throw err
189
190
  }
190
191
 
@@ -1,11 +1,12 @@
1
1
  'use strict'
2
2
 
3
3
  const assert = require('node:assert')
4
+ const { AsyncResource } = require('node:async_hooks')
4
5
  const { Readable } = require('./readable')
5
6
  const { InvalidArgumentError, RequestAbortedError } = require('../core/errors')
6
7
  const util = require('../core/util')
7
- const { getResolveErrorBodyCallback } = require('./util')
8
- const { AsyncResource } = require('node:async_hooks')
8
+
9
+ function noop () {}
9
10
 
10
11
  class RequestHandler extends AsyncResource {
11
12
  constructor (opts, callback) {
@@ -13,7 +14,7 @@ class RequestHandler extends AsyncResource {
13
14
  throw new InvalidArgumentError('invalid opts')
14
15
  }
15
16
 
16
- const { signal, method, opaque, body, onInfo, responseHeaders, throwOnError, highWaterMark } = opts
17
+ const { signal, method, opaque, body, onInfo, responseHeaders, highWaterMark } = opts
17
18
 
18
19
  try {
19
20
  if (typeof callback !== 'function') {
@@ -39,7 +40,7 @@ class RequestHandler extends AsyncResource {
39
40
  super('UNDICI_REQUEST')
40
41
  } catch (err) {
41
42
  if (util.isStream(body)) {
42
- util.destroy(body.on('error', util.nop), err)
43
+ util.destroy(body.on('error', noop), err)
43
44
  }
44
45
  throw err
45
46
  }
@@ -54,38 +55,22 @@ class RequestHandler extends AsyncResource {
54
55
  this.trailers = {}
55
56
  this.context = null
56
57
  this.onInfo = onInfo || null
57
- this.throwOnError = throwOnError
58
58
  this.highWaterMark = highWaterMark
59
- this.signal = signal
60
59
  this.reason = null
61
60
  this.removeAbortListener = null
62
61
 
63
- if (util.isStream(body)) {
64
- body.on('error', (err) => {
65
- this.onError(err)
62
+ if (signal?.aborted) {
63
+ this.reason = signal.reason ?? new RequestAbortedError()
64
+ } else if (signal) {
65
+ this.removeAbortListener = util.addAbortListener(signal, () => {
66
+ this.reason = signal.reason ?? new RequestAbortedError()
67
+ if (this.res) {
68
+ util.destroy(this.res, this.reason)
69
+ } else if (this.abort) {
70
+ this.abort(this.reason)
71
+ }
66
72
  })
67
73
  }
68
-
69
- if (this.signal) {
70
- if (this.signal.aborted) {
71
- this.reason = this.signal.reason ?? new RequestAbortedError()
72
- } else {
73
- this.removeAbortListener = util.addAbortListener(this.signal, () => {
74
- this.reason = this.signal.reason ?? new RequestAbortedError()
75
- if (this.res) {
76
- util.destroy(this.res, this.reason)
77
- } else if (this.abort) {
78
- this.abort(this.reason)
79
- }
80
-
81
- if (this.removeAbortListener) {
82
- this.res?.off('close', this.removeAbortListener)
83
- this.removeAbortListener()
84
- this.removeAbortListener = null
85
- }
86
- })
87
- }
88
- }
89
74
  }
90
75
 
91
76
  onConnect (abort, context) {
@@ -127,25 +112,20 @@ class RequestHandler extends AsyncResource {
127
112
 
128
113
  if (this.removeAbortListener) {
129
114
  res.on('close', this.removeAbortListener)
115
+ this.removeAbortListener = null
130
116
  }
131
117
 
132
118
  this.callback = null
133
119
  this.res = res
134
120
  if (callback !== null) {
135
- if (this.throwOnError && statusCode >= 400) {
136
- this.runInAsyncScope(getResolveErrorBodyCallback, null,
137
- { callback, body: res, contentType, statusCode, statusMessage, headers }
138
- )
139
- } else {
140
- this.runInAsyncScope(callback, null, null, {
141
- statusCode,
142
- headers,
143
- trailers: this.trailers,
144
- opaque,
145
- body: res,
146
- context
147
- })
148
- }
121
+ this.runInAsyncScope(callback, null, null, {
122
+ statusCode,
123
+ headers,
124
+ trailers: this.trailers,
125
+ opaque,
126
+ body: res,
127
+ context
128
+ })
149
129
  }
150
130
  }
151
131
 
@@ -179,11 +159,14 @@ class RequestHandler extends AsyncResource {
179
159
 
180
160
  if (body) {
181
161
  this.body = null
182
- util.destroy(body, err)
162
+
163
+ if (util.isStream(body)) {
164
+ body.on('error', noop)
165
+ util.destroy(body, err)
166
+ }
183
167
  }
184
168
 
185
169
  if (this.removeAbortListener) {
186
- res?.off('close', this.removeAbortListener)
187
170
  this.removeAbortListener()
188
171
  this.removeAbortListener = null
189
172
  }
@@ -200,7 +183,9 @@ function request (opts, callback) {
200
183
  }
201
184
 
202
185
  try {
203
- this.dispatch(opts, new RequestHandler(opts, callback))
186
+ const handler = new RequestHandler(opts, callback)
187
+
188
+ this.dispatch(opts, handler)
204
189
  } catch (err) {
205
190
  if (typeof callback !== 'function') {
206
191
  throw err