undici 7.0.0-alpha.1 → 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 (71) hide show
  1. package/README.md +2 -2
  2. package/docs/docs/api/Client.md +1 -1
  3. package/docs/docs/api/Debug.md +1 -1
  4. package/docs/docs/api/Dispatcher.md +53 -2
  5. package/docs/docs/api/MockAgent.md +2 -0
  6. package/docs/docs/api/MockPool.md +2 -1
  7. package/docs/docs/api/RetryAgent.md +1 -1
  8. package/docs/docs/api/RetryHandler.md +1 -1
  9. package/docs/docs/api/WebSocket.md +45 -3
  10. package/index.js +6 -2
  11. package/lib/api/abort-signal.js +2 -0
  12. package/lib/api/api-pipeline.js +4 -2
  13. package/lib/api/api-request.js +4 -2
  14. package/lib/api/api-stream.js +3 -1
  15. package/lib/api/api-upgrade.js +2 -2
  16. package/lib/api/readable.js +194 -41
  17. package/lib/api/util.js +2 -0
  18. package/lib/core/connect.js +49 -22
  19. package/lib/core/constants.js +11 -9
  20. package/lib/core/diagnostics.js +122 -128
  21. package/lib/core/request.js +4 -4
  22. package/lib/core/symbols.js +2 -0
  23. package/lib/core/tree.js +4 -2
  24. package/lib/core/util.js +220 -39
  25. package/lib/dispatcher/client-h1.js +299 -60
  26. package/lib/dispatcher/client-h2.js +1 -1
  27. package/lib/dispatcher/client.js +24 -7
  28. package/lib/dispatcher/fixed-queue.js +91 -49
  29. package/lib/dispatcher/pool-stats.js +2 -0
  30. package/lib/dispatcher/proxy-agent.js +3 -1
  31. package/lib/handler/redirect-handler.js +2 -2
  32. package/lib/handler/retry-handler.js +2 -2
  33. package/lib/interceptor/dns.js +346 -0
  34. package/lib/mock/mock-agent.js +5 -8
  35. package/lib/mock/mock-client.js +7 -2
  36. package/lib/mock/mock-errors.js +3 -1
  37. package/lib/mock/mock-interceptor.js +8 -6
  38. package/lib/mock/mock-pool.js +7 -2
  39. package/lib/mock/mock-symbols.js +2 -1
  40. package/lib/mock/mock-utils.js +33 -5
  41. package/lib/util/timers.js +50 -6
  42. package/lib/web/cache/cache.js +24 -21
  43. package/lib/web/cache/cachestorage.js +1 -1
  44. package/lib/web/cookies/index.js +6 -4
  45. package/lib/web/fetch/body.js +42 -34
  46. package/lib/web/fetch/constants.js +35 -26
  47. package/lib/web/fetch/formdata-parser.js +14 -3
  48. package/lib/web/fetch/formdata.js +40 -20
  49. package/lib/web/fetch/headers.js +116 -84
  50. package/lib/web/fetch/index.js +65 -59
  51. package/lib/web/fetch/request.js +130 -55
  52. package/lib/web/fetch/response.js +79 -36
  53. package/lib/web/fetch/util.js +104 -57
  54. package/lib/web/fetch/webidl.js +38 -14
  55. package/lib/web/websocket/connection.js +92 -15
  56. package/lib/web/websocket/constants.js +2 -3
  57. package/lib/web/websocket/events.js +4 -2
  58. package/lib/web/websocket/receiver.js +20 -26
  59. package/lib/web/websocket/stream/websocketerror.js +83 -0
  60. package/lib/web/websocket/stream/websocketstream.js +485 -0
  61. package/lib/web/websocket/util.js +115 -10
  62. package/lib/web/websocket/websocket.js +45 -170
  63. package/package.json +6 -6
  64. package/types/interceptors.d.ts +14 -0
  65. package/types/mock-agent.d.ts +3 -0
  66. package/types/readable.d.ts +10 -7
  67. package/types/webidl.d.ts +24 -4
  68. package/types/websocket.d.ts +33 -0
  69. package/lib/mock/pluralizer.js +0 -29
  70. package/lib/web/cache/symbols.js +0 -5
  71. package/lib/web/fetch/symbols.js +0 -8
package/README.md CHANGED
@@ -230,7 +230,7 @@ A body can be of the following types:
230
230
  - URLSearchParams
231
231
  - FormData
232
232
 
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)
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).
234
234
 
235
235
  ```js
236
236
  import { fetch } from 'undici'
@@ -261,7 +261,7 @@ await fetch('http://example.com', { method: 'POST', body })
261
261
 
262
262
  - `'half'`
263
263
 
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).
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).
265
265
 
266
266
  #### `response.body`
267
267
 
@@ -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.
@@ -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
 
@@ -478,7 +478,7 @@ The `RequestOptions.method` property should not be value `'CONNECT'`.
478
478
  #### Parameter: `ResponseData`
479
479
 
480
480
  * **statusCode** `number`
481
- * **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`.
482
482
  * **body** `stream.Readable` which also implements [the body mixin from the Fetch Standard](https://fetch.spec.whatwg.org/#body-mixin).
483
483
  * **trailers** `Record<string, string>` - This object starts out
484
484
  as empty and will be mutated to contain trailers after `body` has emitted `'end'`.
@@ -974,7 +974,7 @@ const client = new Client("http://example.com").compose(
974
974
  })
975
975
  );
976
976
 
977
- // or
977
+ // or
978
978
  client.dispatch(
979
979
  {
980
980
  path: "/",
@@ -985,6 +985,57 @@ client.dispatch(
985
985
  );
986
986
  ```
987
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
+
988
1039
  ##### `Response Error Interceptor`
989
1040
 
990
1041
  **Introduction**
@@ -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.
@@ -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
@@ -39,7 +39,8 @@ module.exports.RedirectHandler = RedirectHandler
39
39
  module.exports.interceptors = {
40
40
  redirect: require('./lib/interceptor/redirect'),
41
41
  retry: require('./lib/interceptor/retry'),
42
- dump: require('./lib/interceptor/dump')
42
+ dump: require('./lib/interceptor/dump'),
43
+ dns: require('./lib/interceptor/dns')
43
44
  }
44
45
 
45
46
  module.exports.buildConnector = buildConnector
@@ -124,7 +125,7 @@ module.exports.setGlobalOrigin = setGlobalOrigin
124
125
  module.exports.getGlobalOrigin = getGlobalOrigin
125
126
 
126
127
  const { CacheStorage } = require('./lib/web/cache/cachestorage')
127
- const { kConstruct } = require('./lib/web/cache/symbols')
128
+ const { kConstruct } = require('./lib/core/symbols')
128
129
 
129
130
  // Cache & CacheStorage are tightly coupled with fetch. Even if it may run
130
131
  // in an older version of Node, it doesn't have any use without fetch.
@@ -148,6 +149,9 @@ module.exports.CloseEvent = CloseEvent
148
149
  module.exports.ErrorEvent = ErrorEvent
149
150
  module.exports.MessageEvent = MessageEvent
150
151
 
152
+ module.exports.WebSocketStream = require('./lib/web/websocket/stream/websocketstream').WebSocketStream
153
+ module.exports.WebSocketError = require('./lib/web/websocket/stream/websocketerror').WebSocketError
154
+
151
155
  module.exports.request = makeDispatcher(api.request)
152
156
  module.exports.stream = makeDispatcher(api.stream)
153
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
 
@@ -15,6 +15,8 @@ const {
15
15
  const util = require('../core/util')
16
16
  const { addSignal, removeSignal } = require('./abort-signal')
17
17
 
18
+ function noop () {}
19
+
18
20
  const kResume = Symbol('resume')
19
21
 
20
22
  class PipelineRequest extends Readable {
@@ -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,
@@ -183,7 +185,7 @@ class PipelineHandler extends AsyncResource {
183
185
  context
184
186
  })
185
187
  } catch (err) {
186
- this.res.on('error', util.nop)
188
+ this.res.on('error', noop)
187
189
  throw err
188
190
  }
189
191
 
@@ -6,6 +6,8 @@ const { Readable } = require('./readable')
6
6
  const { InvalidArgumentError, RequestAbortedError } = require('../core/errors')
7
7
  const util = require('../core/util')
8
8
 
9
+ function noop () {}
10
+
9
11
  class RequestHandler extends AsyncResource {
10
12
  constructor (opts, callback) {
11
13
  if (!opts || typeof opts !== 'object') {
@@ -38,7 +40,7 @@ class RequestHandler extends AsyncResource {
38
40
  super('UNDICI_REQUEST')
39
41
  } catch (err) {
40
42
  if (util.isStream(body)) {
41
- util.destroy(body.on('error', util.nop), err)
43
+ util.destroy(body.on('error', noop), err)
42
44
  }
43
45
  throw err
44
46
  }
@@ -159,7 +161,7 @@ class RequestHandler extends AsyncResource {
159
161
  this.body = null
160
162
 
161
163
  if (util.isStream(body)) {
162
- body.on('error', util.nop)
164
+ body.on('error', noop)
163
165
  util.destroy(body, err)
164
166
  }
165
167
  }
@@ -7,6 +7,8 @@ const { InvalidArgumentError, InvalidReturnValueError } = require('../core/error
7
7
  const util = require('../core/util')
8
8
  const { addSignal, removeSignal } = require('./abort-signal')
9
9
 
10
+ function noop () {}
11
+
10
12
  class StreamHandler extends AsyncResource {
11
13
  constructor (opts, factory, callback) {
12
14
  if (!opts || typeof opts !== 'object') {
@@ -39,7 +41,7 @@ class StreamHandler extends AsyncResource {
39
41
  super('UNDICI_STREAM')
40
42
  } catch (err) {
41
43
  if (util.isStream(body)) {
42
- util.destroy(body.on('error', util.nop), err)
44
+ util.destroy(body.on('error', noop), err)
43
45
  }
44
46
  throw err
45
47
  }
@@ -50,9 +50,9 @@ class UpgradeHandler extends AsyncResource {
50
50
  }
51
51
 
52
52
  onUpgrade (statusCode, rawHeaders, socket) {
53
- const { callback, opaque, context } = this
53
+ assert(statusCode === 101)
54
54
 
55
- assert.strictEqual(statusCode, 101)
55
+ const { callback, opaque, context } = this
56
56
 
57
57
  removeSignal(this)
58
58