undici 6.21.0 → 7.0.0-alpha.10

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 (156) hide show
  1. package/README.md +27 -46
  2. package/docs/docs/api/Agent.md +14 -17
  3. package/docs/docs/api/BalancedPool.md +16 -16
  4. package/docs/docs/api/CacheStore.md +131 -0
  5. package/docs/docs/api/Client.md +12 -14
  6. package/docs/docs/api/Debug.md +1 -1
  7. package/docs/docs/api/Dispatcher.md +98 -194
  8. package/docs/docs/api/EnvHttpProxyAgent.md +12 -13
  9. package/docs/docs/api/MockAgent.md +5 -3
  10. package/docs/docs/api/MockClient.md +5 -5
  11. package/docs/docs/api/MockPool.md +4 -3
  12. package/docs/docs/api/Pool.md +15 -16
  13. package/docs/docs/api/PoolStats.md +1 -1
  14. package/docs/docs/api/ProxyAgent.md +3 -3
  15. package/docs/docs/api/RedirectHandler.md +1 -1
  16. package/docs/docs/api/RetryAgent.md +1 -1
  17. package/docs/docs/api/RetryHandler.md +4 -4
  18. package/docs/docs/api/WebSocket.md +46 -4
  19. package/docs/docs/api/api-lifecycle.md +11 -11
  20. package/docs/docs/best-practices/mocking-request.md +2 -2
  21. package/docs/docs/best-practices/proxy.md +1 -1
  22. package/index.d.ts +1 -1
  23. package/index.js +23 -7
  24. package/lib/api/abort-signal.js +2 -0
  25. package/lib/api/api-connect.js +3 -1
  26. package/lib/api/api-pipeline.js +7 -6
  27. package/lib/api/api-request.js +33 -48
  28. package/lib/api/api-stream.js +39 -50
  29. package/lib/api/api-upgrade.js +5 -3
  30. package/lib/api/readable.js +235 -62
  31. package/lib/api/util.js +2 -0
  32. package/lib/cache/memory-cache-store.js +177 -0
  33. package/lib/cache/sqlite-cache-store.js +446 -0
  34. package/lib/core/constants.js +35 -10
  35. package/lib/core/diagnostics.js +122 -128
  36. package/lib/core/errors.js +6 -6
  37. package/lib/core/request.js +13 -11
  38. package/lib/core/symbols.js +2 -1
  39. package/lib/core/tree.js +9 -1
  40. package/lib/core/util.js +237 -49
  41. package/lib/dispatcher/agent.js +3 -17
  42. package/lib/dispatcher/balanced-pool.js +5 -8
  43. package/lib/dispatcher/client-h1.js +379 -134
  44. package/lib/dispatcher/client-h2.js +173 -107
  45. package/lib/dispatcher/client.js +19 -32
  46. package/lib/dispatcher/dispatcher-base.js +6 -35
  47. package/lib/dispatcher/dispatcher.js +7 -24
  48. package/lib/dispatcher/fixed-queue.js +91 -49
  49. package/lib/dispatcher/pool-stats.js +2 -0
  50. package/lib/dispatcher/pool.js +3 -6
  51. package/lib/dispatcher/proxy-agent.js +3 -6
  52. package/lib/handler/cache-handler.js +393 -0
  53. package/lib/handler/cache-revalidation-handler.js +124 -0
  54. package/lib/handler/decorator-handler.js +27 -0
  55. package/lib/handler/redirect-handler.js +54 -59
  56. package/lib/handler/retry-handler.js +77 -109
  57. package/lib/handler/unwrap-handler.js +96 -0
  58. package/lib/handler/wrap-handler.js +98 -0
  59. package/lib/interceptor/cache.js +350 -0
  60. package/lib/interceptor/dns.js +375 -0
  61. package/lib/interceptor/dump.js +2 -2
  62. package/lib/interceptor/redirect.js +11 -14
  63. package/lib/interceptor/response-error.js +18 -7
  64. package/lib/llhttp/constants.d.ts +97 -0
  65. package/lib/llhttp/constants.js +412 -192
  66. package/lib/llhttp/constants.js.map +1 -0
  67. package/lib/llhttp/llhttp-wasm.js +11 -1
  68. package/lib/llhttp/llhttp_simd-wasm.js +11 -1
  69. package/lib/llhttp/utils.d.ts +2 -0
  70. package/lib/llhttp/utils.js +9 -9
  71. package/lib/llhttp/utils.js.map +1 -0
  72. package/lib/mock/mock-agent.js +5 -8
  73. package/lib/mock/mock-client.js +9 -4
  74. package/lib/mock/mock-errors.js +3 -1
  75. package/lib/mock/mock-interceptor.js +8 -6
  76. package/lib/mock/mock-pool.js +9 -4
  77. package/lib/mock/mock-symbols.js +3 -1
  78. package/lib/mock/mock-utils.js +29 -5
  79. package/lib/util/cache.js +360 -0
  80. package/lib/web/cache/cache.js +24 -21
  81. package/lib/web/cache/cachestorage.js +1 -1
  82. package/lib/web/cookies/index.js +29 -14
  83. package/lib/web/cookies/parse.js +8 -3
  84. package/lib/web/eventsource/eventsource-stream.js +9 -8
  85. package/lib/web/eventsource/eventsource.js +10 -6
  86. package/lib/web/fetch/body.js +43 -41
  87. package/lib/web/fetch/constants.js +12 -5
  88. package/lib/web/fetch/data-url.js +3 -3
  89. package/lib/web/fetch/formdata-parser.js +72 -45
  90. package/lib/web/fetch/formdata.js +65 -54
  91. package/lib/web/fetch/headers.js +118 -86
  92. package/lib/web/fetch/index.js +58 -67
  93. package/lib/web/fetch/request.js +136 -77
  94. package/lib/web/fetch/response.js +87 -56
  95. package/lib/web/fetch/util.js +259 -109
  96. package/lib/web/fetch/webidl.js +113 -68
  97. package/lib/web/websocket/connection.js +76 -147
  98. package/lib/web/websocket/constants.js +70 -10
  99. package/lib/web/websocket/events.js +4 -2
  100. package/lib/web/websocket/frame.js +45 -3
  101. package/lib/web/websocket/receiver.js +29 -33
  102. package/lib/web/websocket/sender.js +18 -13
  103. package/lib/web/websocket/stream/websocketerror.js +83 -0
  104. package/lib/web/websocket/stream/websocketstream.js +485 -0
  105. package/lib/web/websocket/util.js +128 -77
  106. package/lib/web/websocket/websocket.js +234 -135
  107. package/package.json +24 -36
  108. package/scripts/strip-comments.js +3 -1
  109. package/types/agent.d.ts +7 -7
  110. package/types/api.d.ts +24 -24
  111. package/types/balanced-pool.d.ts +11 -11
  112. package/types/cache-interceptor.d.ts +172 -0
  113. package/types/client.d.ts +11 -12
  114. package/types/cookies.d.ts +2 -0
  115. package/types/diagnostics-channel.d.ts +10 -10
  116. package/types/dispatcher.d.ts +113 -90
  117. package/types/env-http-proxy-agent.d.ts +2 -2
  118. package/types/errors.d.ts +53 -47
  119. package/types/fetch.d.ts +17 -16
  120. package/types/formdata.d.ts +7 -7
  121. package/types/global-dispatcher.d.ts +4 -4
  122. package/types/global-origin.d.ts +5 -5
  123. package/types/handlers.d.ts +7 -7
  124. package/types/header.d.ts +157 -1
  125. package/types/index.d.ts +44 -46
  126. package/types/interceptors.d.ts +25 -8
  127. package/types/mock-agent.d.ts +21 -18
  128. package/types/mock-client.d.ts +4 -4
  129. package/types/mock-errors.d.ts +3 -3
  130. package/types/mock-interceptor.d.ts +19 -19
  131. package/types/mock-pool.d.ts +4 -4
  132. package/types/patch.d.ts +0 -4
  133. package/types/pool-stats.d.ts +8 -8
  134. package/types/pool.d.ts +12 -12
  135. package/types/proxy-agent.d.ts +4 -4
  136. package/types/readable.d.ts +18 -15
  137. package/types/retry-agent.d.ts +1 -1
  138. package/types/retry-handler.d.ts +10 -10
  139. package/types/util.d.ts +3 -3
  140. package/types/utility.d.ts +7 -0
  141. package/types/webidl.d.ts +44 -6
  142. package/types/websocket.d.ts +34 -1
  143. package/docs/docs/api/DispatchInterceptor.md +0 -60
  144. package/lib/interceptor/redirect-interceptor.js +0 -21
  145. package/lib/mock/pluralizer.js +0 -29
  146. package/lib/web/cache/symbols.js +0 -5
  147. package/lib/web/fetch/file.js +0 -126
  148. package/lib/web/fetch/symbols.js +0 -9
  149. package/lib/web/fileapi/encoding.js +0 -290
  150. package/lib/web/fileapi/filereader.js +0 -344
  151. package/lib/web/fileapi/progressevent.js +0 -78
  152. package/lib/web/fileapi/symbols.js +0 -10
  153. package/lib/web/fileapi/util.js +0 -391
  154. package/lib/web/websocket/symbols.js +0 -12
  155. package/types/file.d.ts +0 -39
  156. package/types/filereader.d.ts +0 -54
@@ -197,23 +197,20 @@ Returns: `Boolean` - `false` if dispatcher is busy and further dispatch calls wo
197
197
  * **headers** `UndiciHeaders | string[]` (optional) - Default: `null`.
198
198
  * **query** `Record<string, any> | null` (optional) - Default: `null` - Query string params to be embedded in the request URL. Note that both keys and values of query are encoded using `encodeURIComponent`. If for some reason you need to send them unencoded, embed query params into path directly instead.
199
199
  * **idempotent** `boolean` (optional) - Default: `true` if `method` is `'HEAD'` or `'GET'` - Whether the requests can be safely retried or not. If `false` the request won't be sent until all preceding requests in the pipeline has completed.
200
- * **blocking** `boolean` (optional) - Default: `false` - Whether the response is expected to take a long time and would end up blocking the pipeline. When this is set to `true` further pipelining will be avoided on the same connection until headers have been received.
200
+ * **blocking** `boolean` (optional) - Default: `method !== 'HEAD'` - Whether the response is expected to take a long time and would end up blocking the pipeline. When this is set to `true` further pipelining will be avoided on the same connection until headers have been received.
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`
208
207
 
209
- * **onConnect** `(abort: () => void, context: object) => void` - Invoked before request is dispatched on socket. May be invoked multiple times when a request is retried when the request at the head of the pipeline fails.
210
- * **onError** `(error: Error) => void` - Invoked when an error has occurred. May not throw.
211
- * **onUpgrade** `(statusCode: number, headers: Buffer[], socket: Duplex) => void` (optional) - Invoked when request is upgraded. Required if `DispatchOptions.upgrade` is defined or `DispatchOptions.method === 'CONNECT'`.
212
- * **onResponseStarted** `() => void` (optional) - Invoked when response is received, before headers have been read.
213
- * **onHeaders** `(statusCode: number, headers: Buffer[], resume: () => void, statusText: string) => boolean` - Invoked when statusCode and headers have been received. May be invoked multiple times due to 1xx informational headers. Not required for `upgrade` requests.
214
- * **onData** `(chunk: Buffer) => boolean` - Invoked when response payload data is received. Not required for `upgrade` requests.
215
- * **onComplete** `(trailers: Buffer[]) => void` - Invoked when response payload and trailers have been received and the request has completed. Not required for `upgrade` requests.
216
- * **onBodySent** `(chunk: string | Buffer | Uint8Array) => void` - Invoked when a body chunk is sent to the server. Not required. For a stream or iterable body this will be invoked for every chunk. For other body types, it will be invoked once after the body is sent.
208
+ * **onRequestStart** `(controller: DispatchController, context: object) => void` - Invoked before request is dispatched on socket. May be invoked multiple times when a request is retried when the request at the head of the pipeline fails.
209
+ * **onRequestUpgrade** `(controller: DispatchController, statusCode: number, headers: Record<string, string | string[]>, socket: Duplex) => void` (optional) - Invoked when request is upgraded. Required if `DispatchOptions.upgrade` is defined or `DispatchOptions.method === 'CONNECT'`.
210
+ * **onResponseStart** `(controller: DispatchController, statusCode: number, headers: Record<string, string | string []>, statusMessage?: string) => void` - Invoked when statusCode and headers have been received. May be invoked multiple times due to 1xx informational headers. Not required for `upgrade` requests.
211
+ * **onResponseData** `(controller: DispatchController, chunk: Buffer) => void` - Invoked when response payload data is received. Not required for `upgrade` requests.
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
+ * **onResponseError** `(error: Error) => void` - Invoked when an error has occurred. May not throw.
217
214
 
218
215
  #### Example 1 - Dispatch GET request
219
216
 
@@ -378,7 +375,7 @@ Returns: `stream.Duplex`
378
375
 
379
376
  #### Parameter: PipelineOptions
380
377
 
381
- Extends: [`RequestOptions`](#parameter-requestoptions)
378
+ Extends: [`RequestOptions`](/docs/docs/api/Dispatcher.md#parameter-requestoptions)
382
379
 
383
380
  * **objectMode** `boolean` (optional) - Default: `false` - Set to `true` if the `handler` will return an object stream.
384
381
 
@@ -468,7 +465,7 @@ Returns: `void | Promise<ResponseData>` - Only returns a `Promise` if no `callba
468
465
 
469
466
  #### Parameter: `RequestOptions`
470
467
 
471
- Extends: [`DispatchOptions`](#parameter-dispatchoptions)
468
+ Extends: [`DispatchOptions`](/docs/docs/api/Dispatcher.md#parameter-dispatchoptions)
472
469
 
473
470
  * **opaque** `unknown` (optional) - Default: `null` - Used for passing through context to `ResponseData`.
474
471
  * **signal** `AbortSignal | events.EventEmitter | null` (optional) - Default: `null`.
@@ -479,7 +476,7 @@ The `RequestOptions.method` property should not be value `'CONNECT'`.
479
476
  #### Parameter: `ResponseData`
480
477
 
481
478
  * **statusCode** `number`
482
- * **headers** `Record<string, string | string[]>` - Note that all header keys are lower-cased, e. g. `content-type`.
479
+ * **headers** `Record<string, string | string[]>` - Note that all header keys are lower-cased, e.g. `content-type`.
483
480
  * **body** `stream.Readable` which also implements [the body mixin from the Fetch Standard](https://fetch.spec.whatwg.org/#body-mixin).
484
481
  * **trailers** `Record<string, string>` - This object starts out
485
482
  as empty and will be mutated to contain trailers after `body` has emitted `'end'`.
@@ -528,6 +525,7 @@ try {
528
525
  console.log('headers', headers)
529
526
  body.setEncoding('utf8')
530
527
  body.on('data', console.log)
528
+ body.on('error', console.error)
531
529
  body.on('end', () => {
532
530
  console.log('trailers', trailers)
533
531
  })
@@ -631,11 +629,30 @@ try {
631
629
  }
632
630
  ```
633
631
 
632
+ #### Example 3 - Conditionally reading the body
633
+
634
+ Remember to fully consume the body even in the case when it is not read.
635
+
636
+ ```js
637
+ const { body, statusCode } = await client.request({
638
+ path: '/',
639
+ method: 'GET'
640
+ })
641
+
642
+ if (statusCode === 200) {
643
+ return await body.arrayBuffer()
644
+ }
645
+
646
+ await body.dump()
647
+
648
+ return null
649
+ ```
650
+
634
651
  ### `Dispatcher.stream(options, factory[, callback])`
635
652
 
636
653
  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.
637
654
 
638
- As demonstrated in [Example 1 - Basic GET stream request](#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](#example-2---stream-to-fastify-response) for more details.
655
+ 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.
639
656
 
640
657
  Arguments:
641
658
 
@@ -917,7 +934,7 @@ await client.request({ path: '/', method: 'GET' })
917
934
 
918
935
  The `redirect` interceptor allows you to customize the way your dispatcher handles redirects.
919
936
 
920
- It accepts the same arguments as the [`RedirectHandler` constructor](./RedirectHandler.md).
937
+ It accepts the same arguments as the [`RedirectHandler` constructor](/docs/docs/api/RedirectHandler.md).
921
938
 
922
939
  **Example - Basic Redirect Interceptor**
923
940
 
@@ -935,7 +952,7 @@ client.request({ path: "/" })
935
952
 
936
953
  The `retry` interceptor allows you to customize the way your dispatcher handles retries.
937
954
 
938
- It accepts the same arguments as the [`RetryHandler` constructor](./RetryHandler.md).
955
+ It accepts the same arguments as the [`RetryHandler` constructor](/docs/docs/api/RetryHandler.md).
939
956
 
940
957
  **Example - Basic Redirect Interceptor**
941
958
 
@@ -975,7 +992,7 @@ const client = new Client("http://example.com").compose(
975
992
  })
976
993
  );
977
994
 
978
- // or
995
+ // or
979
996
  client.dispatch(
980
997
  {
981
998
  path: "/",
@@ -986,202 +1003,89 @@ client.dispatch(
986
1003
  );
987
1004
  ```
988
1005
 
989
- ##### `Response Error Interceptor`
990
-
991
- **Introduction**
992
-
993
- The Response Error Interceptor is designed to handle HTTP response errors efficiently. It intercepts responses and throws detailed errors for responses with status codes indicating failure (4xx, 5xx). This interceptor enhances error handling by providing structured error information, including response headers, data, and status codes.
1006
+ ##### `dns`
994
1007
 
995
- **ResponseError Class**
1008
+ The `dns` interceptor enables you to cache DNS lookups for a given duration, per origin.
996
1009
 
997
- The `ResponseError` class extends the `UndiciError` class and encapsulates detailed error information. It captures the response status code, headers, and data, providing a structured way to handle errors.
1010
+ >It is well suited for scenarios where you want to cache DNS lookups to avoid the overhead of resolving the same domain multiple times
998
1011
 
999
- **Definition**
1000
-
1001
- ```js
1002
- class ResponseError extends UndiciError {
1003
- constructor (message, code, { headers, data }) {
1004
- super(message);
1005
- this.name = 'ResponseError';
1006
- this.message = message || 'Response error';
1007
- this.code = 'UND_ERR_RESPONSE';
1008
- this.statusCode = code;
1009
- this.data = data;
1010
- this.headers = headers;
1011
- }
1012
- }
1013
- ```
1014
-
1015
- **Interceptor Handler**
1016
-
1017
- The interceptor's handler class extends `DecoratorHandler` and overrides methods to capture response details and handle errors based on the response status code.
1018
-
1019
- **Methods**
1020
-
1021
- - **onConnect**: Initializes response properties.
1022
- - **onHeaders**: Captures headers and status code. Decodes body if content type is `application/json` or `text/plain`.
1023
- - **onData**: Appends chunks to the body if status code indicates an error.
1024
- - **onComplete**: Finalizes error handling, constructs a `ResponseError`, and invokes the `onError` method.
1025
- - **onError**: Propagates errors to the handler.
1026
-
1027
- **Definition**
1012
+ **Options**
1013
+ - `maxTTL` - The maximum time-to-live (in milliseconds) of the DNS cache. It should be a positive integer. Default: `10000`.
1014
+ - Set `0` to disable TTL.
1015
+ - `maxItems` - The maximum number of items to cache. It should be a positive integer. Default: `Infinity`.
1016
+ - `dualStack` - Whether to resolve both IPv4 and IPv6 addresses. Default: `true`.
1017
+ - It will also attempt a happy-eyeballs-like approach to connect to the available addresses in case of a connection failure.
1018
+ - `affinity` - Whether to use IPv4 or IPv6 addresses. Default: `4`.
1019
+ - It can be either `'4` or `6`.
1020
+ - It will only take effect if `dualStack` is `false`.
1021
+ - `lookup: (hostname: string, options: LookupOptions, callback: (err: NodeJS.ErrnoException | null, addresses: DNSInterceptorRecord[]) => void) => void` - Custom lookup function. Default: `dns.lookup`.
1022
+ - For more info see [dns.lookup](https://nodejs.org/api/dns.html#dns_dns_lookup_hostname_options_callback).
1023
+ - `pick: (origin: URL, records: DNSInterceptorRecords, affinity: 4 | 6) => DNSInterceptorRecord` - Custom pick function. Default: `RoundRobin`.
1024
+ - The function should return a single record from the records array.
1025
+ - By default a simplified version of Round Robin is used.
1026
+ - The `records` property can be mutated to store the state of the balancing algorithm.
1027
+
1028
+ > 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.
1029
+
1030
+
1031
+ **DNSInterceptorRecord**
1032
+ It represents a DNS record.
1033
+ - `family` - (`number`) The IP family of the address. It can be either `4` or `6`.
1034
+ - `address` - (`string`) The IP address.
1035
+
1036
+ **DNSInterceptorOriginRecords**
1037
+ It represents a map of DNS IP addresses records for a single origin.
1038
+ - `4.ips` - (`DNSInterceptorRecord[] | null`) The IPv4 addresses.
1039
+ - `6.ips` - (`DNSInterceptorRecord[] | null`) The IPv6 addresses.
1040
+
1041
+ **Example - Basic DNS Interceptor**
1028
1042
 
1029
1043
  ```js
1030
- class Handler extends DecoratorHandler {
1031
- // Private properties
1032
- #handler;
1033
- #statusCode;
1034
- #contentType;
1035
- #decoder;
1036
- #headers;
1037
- #body;
1038
-
1039
- constructor (opts, { handler }) {
1040
- super(handler);
1041
- this.#handler = handler;
1042
- }
1043
-
1044
- onConnect (abort) {
1045
- this.#statusCode = 0;
1046
- this.#contentType = null;
1047
- this.#decoder = null;
1048
- this.#headers = null;
1049
- this.#body = '';
1050
- return this.#handler.onConnect(abort);
1051
- }
1052
-
1053
- onHeaders (statusCode, rawHeaders, resume, statusMessage, headers = parseHeaders(rawHeaders)) {
1054
- this.#statusCode = statusCode;
1055
- this.#headers = headers;
1056
- this.#contentType = headers['content-type'];
1057
-
1058
- if (this.#statusCode < 400) {
1059
- return this.#handler.onHeaders(statusCode, rawHeaders, resume, statusMessage, headers);
1060
- }
1061
-
1062
- if (this.#contentType === 'application/json' || this.#contentType === 'text/plain') {
1063
- this.#decoder = new TextDecoder('utf-8');
1064
- }
1065
- }
1066
-
1067
- onData (chunk) {
1068
- if (this.#statusCode < 400) {
1069
- return this.#handler.onData(chunk);
1070
- }
1071
- this.#body += this.#decoder?.decode(chunk, { stream: true }) ?? '';
1072
- }
1073
-
1074
- onComplete (rawTrailers) {
1075
- if (this.#statusCode >= 400) {
1076
- this.#body += this.#decoder?.decode(undefined, { stream: false }) ?? '';
1077
- if (this.#contentType === 'application/json') {
1078
- try {
1079
- this.#body = JSON.parse(this.#body);
1080
- } catch {
1081
- // Do nothing...
1082
- }
1083
- }
1084
-
1085
- let err;
1086
- const stackTraceLimit = Error.stackTraceLimit;
1087
- Error.stackTraceLimit = 0;
1088
- try {
1089
- err = new ResponseError('Response Error', this.#statusCode, this.#headers, this.#body);
1090
- } finally {
1091
- Error.stackTraceLimit = stackTraceLimit;
1092
- }
1093
-
1094
- this.#handler.onError(err);
1095
- } else {
1096
- this.#handler.onComplete(rawTrailers);
1097
- }
1098
- }
1044
+ const { Client, interceptors } = require("undici");
1045
+ const { dns } = interceptors;
1099
1046
 
1100
- onError (err) {
1101
- this.#handler.onError(err);
1102
- }
1103
- }
1047
+ const client = new Agent().compose([
1048
+ dns({ ...opts })
1049
+ ])
1104
1050
 
1105
- module.exports = (dispatch) => (opts, handler) => opts.throwOnError
1106
- ? dispatch(opts, new Handler(opts, { handler }))
1107
- : dispatch(opts, handler);
1051
+ const response = await client.request({
1052
+ origin: `http://localhost:3030`,
1053
+ ...requestOpts
1054
+ })
1108
1055
  ```
1109
1056
 
1110
- **Tests**
1111
-
1112
- Unit tests ensure the interceptor functions correctly, handling both error and non-error responses appropriately.
1057
+ ##### `responseError`
1113
1058
 
1114
- **Example Tests**
1059
+ The `responseError` interceptor throws an error for responses with status code errors (>= 400).
1115
1060
 
1116
- - **No Error if `throwOnError` is False**:
1061
+ **Example**
1117
1062
 
1118
1063
  ```js
1119
- test('should not error if request is not meant to throw error', async (t) => {
1120
- const opts = { throwOnError: false };
1121
- const handler = { onError: () => {}, onData: () => {}, onComplete: () => {} };
1122
- const interceptor = createResponseErrorInterceptor((opts, handler) => handler.onComplete());
1123
- assert.doesNotThrow(() => interceptor(opts, handler));
1124
- });
1125
- ```
1126
-
1127
- - **Error if Status Code is in Specified Error Codes**:
1128
-
1129
- ```js
1130
- test('should error if request status code is in the specified error codes', async (t) => {
1131
- const opts = { throwOnError: true, statusCodes: [500] };
1132
- const response = { statusCode: 500 };
1133
- let capturedError;
1134
- const handler = {
1135
- onError: (err) => { capturedError = err; },
1136
- onData: () => {},
1137
- onComplete: () => {}
1138
- };
1139
-
1140
- const interceptor = createResponseErrorInterceptor((opts, handler) => {
1141
- if (opts.throwOnError && opts.statusCodes.includes(response.statusCode)) {
1142
- handler.onError(new Error('Response Error'));
1143
- } else {
1144
- handler.onComplete();
1145
- }
1146
- });
1147
-
1148
- interceptor({ ...opts, response }, handler);
1064
+ const { Client, interceptors } = require("undici");
1065
+ const { responseError } = interceptors;
1149
1066
 
1150
- await new Promise(resolve => setImmediate(resolve));
1067
+ const client = new Client("http://example.com").compose(
1068
+ responseError()
1069
+ );
1151
1070
 
1152
- assert(capturedError, 'Expected error to be captured but it was not.');
1153
- assert.strictEqual(capturedError.message, 'Response Error');
1154
- assert.strictEqual(response.statusCode, 500);
1071
+ // Will throw a ResponseError for status codes >= 400
1072
+ await client.request({
1073
+ method: "GET",
1074
+ path: "/"
1155
1075
  });
1156
1076
  ```
1157
1077
 
1158
- - **No Error if Status Code is Not in Specified Error Codes**:
1159
-
1160
- ```js
1161
- test('should not error if request status code is not in the specified error codes', async (t) => {
1162
- const opts = { throwOnError: true, statusCodes: [500] };
1163
- const response = { statusCode: 404 };
1164
- const handler = {
1165
- onError: () => {},
1166
- onData: () => {},
1167
- onComplete: () => {}
1168
- };
1169
-
1170
- const interceptor = createResponseErrorInterceptor((opts, handler) => {
1171
- if (opts.throwOnError && opts.statusCodes.includes(response.statusCode)) {
1172
- handler.onError(new Error('Response Error'));
1173
- } else {
1174
- handler.onComplete();
1175
- }
1176
- });
1078
+ ##### `Cache Interceptor`
1177
1079
 
1178
- assert.doesNotThrow(() => interceptor({ ...opts, response }, handler));
1179
- });
1180
- ```
1080
+ The `cache` interceptor implements client-side response caching as described in
1081
+ [RFC9111](https://www.rfc-editor.org/rfc/rfc9111.html).
1181
1082
 
1182
- **Conclusion**
1083
+ **Options**
1183
1084
 
1184
- The Response Error Interceptor provides a robust mechanism for handling HTTP response errors by capturing detailed error information and propagating it through a structured `ResponseError` class. This enhancement improves error handling and debugging capabilities in applications using the interceptor.
1085
+ - `store` - The [`CacheStore`](/docs/docs/api/CacheStore.md) to store and retrieve responses from. Default is [`MemoryCacheStore`](/docs/docs/api/CacheStore.md#memorycachestore).
1086
+ - `methods` - The [**safe** HTTP methods](https://www.rfc-editor.org/rfc/rfc9110#section-9.2.1) to cache the response of.
1087
+ - `cacheByDefault` - The default expiration time to cache responses by if they don't have an explicit expiration. If this isn't present, responses without explicit expiration will not be cached. Default `undefined`.
1088
+ - `type` - The type of cache for Undici to act as. Can be `shared` or `private`. Default `shared`.
1185
1089
 
1186
1090
  ## Instance Events
1187
1091
 
@@ -1229,13 +1133,13 @@ Emitted when dispatcher is no longer busy.
1229
1133
 
1230
1134
  * `Record<string, string | string[] | undefined> | string[] | Iterable<[string, string | string[] | undefined]> | null`
1231
1135
 
1232
- Header arguments such as `options.headers` in [`Client.dispatch`](Client.md#clientdispatchoptions-handlers) can be specified in three forms:
1136
+ Header arguments such as `options.headers` in [`Client.dispatch`](/docs/docs/api/Client.md#clientdispatchoptions-handlers) can be specified in three forms:
1233
1137
  * As an object specified by the `Record<string, string | string[] | undefined>` (`IncomingHttpHeaders`) type.
1234
1138
  * As an array of strings. An array representation of a header list must have an even length, or an `InvalidArgumentError` will be thrown.
1235
1139
  * As an iterable that can encompass `Headers`, `Map`, or a custom iterator returning key-value pairs.
1236
1140
  Keys are lowercase and values are not modified.
1237
1141
 
1238
- Response headers will derive a `host` from the `url` of the [Client](Client.md#class-client) instance if no `host` header was previously specified.
1142
+ Response headers will derive a `host` from the `url` of the [Client](/docs/docs/api/Client.md#class-client) instance if no `host` header was previously specified.
1239
1143
 
1240
1144
  ### Example 1 - Object
1241
1145
 
@@ -20,7 +20,7 @@ Returns: `EnvHttpProxyAgent`
20
20
 
21
21
  ### Parameter: `EnvHttpProxyAgentOptions`
22
22
 
23
- Extends: [`AgentOptions`](Agent.md#parameter-agentoptions)
23
+ Extends: [`AgentOptions`](/docs/docs/api/Agent.md#parameter-agentoptions)
24
24
 
25
25
  * **httpProxy** `string` (optional) - When set, it will override the `HTTP_PROXY` environment variable.
26
26
  * **httpsProxy** `string` (optional) - When set, it will override the `HTTPS_PROXY` environment variable.
@@ -118,45 +118,44 @@ const data = await json() // data { foo: "bar" }
118
118
 
119
119
  ### `EnvHttpProxyAgent.close([callback])`
120
120
 
121
- Implements [`Dispatcher.close([callback])`](Dispatcher.md#dispatcherclosecallback-promise).
121
+ Implements [`Dispatcher.close([callback])`](/docs/docs/api/Dispatcher.md#dispatcherclosecallback-promise).
122
122
 
123
123
  ### `EnvHttpProxyAgent.destroy([error, callback])`
124
124
 
125
- Implements [`Dispatcher.destroy([error, callback])`](Dispatcher.md#dispatcherdestroyerror-callback-promise).
125
+ Implements [`Dispatcher.destroy([error, callback])`](/docs/docs/api/Dispatcher.md#dispatcherdestroyerror-callback-promise).
126
126
 
127
127
  ### `EnvHttpProxyAgent.dispatch(options, handler: AgentDispatchOptions)`
128
128
 
129
- Implements [`Dispatcher.dispatch(options, handler)`](Dispatcher.md#dispatcherdispatchoptions-handler).
129
+ Implements [`Dispatcher.dispatch(options, handler)`](/docs/docs/api/Dispatcher.md#dispatcherdispatchoptions-handler).
130
130
 
131
131
  #### Parameter: `AgentDispatchOptions`
132
132
 
133
- Extends: [`DispatchOptions`](Dispatcher.md#parameter-dispatchoptions)
133
+ Extends: [`DispatchOptions`](/docs/docs/api/Dispatcher.md#parameter-dispatchoptions)
134
134
 
135
135
  * **origin** `string | URL`
136
- * **maxRedirections** `Integer`.
137
136
 
138
- Implements [`Dispatcher.destroy([error, callback])`](Dispatcher.md#dispatcherdestroyerror-callback-promise).
137
+ Implements [`Dispatcher.destroy([error, callback])`](/docs/docs/api/Dispatcher.md#dispatcherdestroyerror-callback-promise).
139
138
 
140
139
  ### `EnvHttpProxyAgent.connect(options[, callback])`
141
140
 
142
- See [`Dispatcher.connect(options[, callback])`](Dispatcher.md#dispatcherconnectoptions-callback).
141
+ See [`Dispatcher.connect(options[, callback])`](/docs/docs/api/Dispatcher.md#dispatcherconnectoptions-callback).
143
142
 
144
143
  ### `EnvHttpProxyAgent.dispatch(options, handler)`
145
144
 
146
- Implements [`Dispatcher.dispatch(options, handler)`](Dispatcher.md#dispatcherdispatchoptions-handler).
145
+ Implements [`Dispatcher.dispatch(options, handler)`](/docs/docs/api/Dispatcher.md#dispatcherdispatchoptions-handler).
147
146
 
148
147
  ### `EnvHttpProxyAgent.pipeline(options, handler)`
149
148
 
150
- See [`Dispatcher.pipeline(options, handler)`](Dispatcher.md#dispatcherpipelineoptions-handler).
149
+ See [`Dispatcher.pipeline(options, handler)`](/docs/docs/api/Dispatcher.md#dispatcherpipelineoptions-handler).
151
150
 
152
151
  ### `EnvHttpProxyAgent.request(options[, callback])`
153
152
 
154
- See [`Dispatcher.request(options [, callback])`](Dispatcher.md#dispatcherrequestoptions-callback).
153
+ See [`Dispatcher.request(options [, callback])`](/docs/docs/api/Dispatcher.md#dispatcherrequestoptions-callback).
155
154
 
156
155
  ### `EnvHttpProxyAgent.stream(options, factory[, callback])`
157
156
 
158
- See [`Dispatcher.stream(options, factory[, callback])`](Dispatcher.md#dispatcherstreamoptions-factory-callback).
157
+ See [`Dispatcher.stream(options, factory[, callback])`](/docs/docs/api/Dispatcher.md#dispatcherstreamoptions-factory-callback).
159
158
 
160
159
  ### `EnvHttpProxyAgent.upgrade(options[, callback])`
161
160
 
162
- See [`Dispatcher.upgrade(options[, callback])`](Dispatcher.md#dispatcherupgradeoptions-callback).
161
+ See [`Dispatcher.upgrade(options[, callback])`](/docs/docs/api/Dispatcher.md#dispatcherupgradeoptions-callback).
@@ -14,10 +14,12 @@ Returns: `MockAgent`
14
14
 
15
15
  ### Parameter: `MockAgentOptions`
16
16
 
17
- Extends: [`AgentOptions`](Agent.md#parameter-agentoptions)
17
+ Extends: [`AgentOptions`](/docs/docs/api/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.
@@ -299,11 +301,11 @@ await mockAgent.close()
299
301
 
300
302
  ### `MockAgent.dispatch(options, handlers)`
301
303
 
302
- Implements [`Agent.dispatch(options, handlers)`](Agent.md#parameter-agentdispatchoptions).
304
+ Implements [`Agent.dispatch(options, handlers)`](/docs/docs/api/Agent.md#parameter-agentdispatchoptions).
303
305
 
304
306
  ### `MockAgent.request(options[, callback])`
305
307
 
306
- See [`Dispatcher.request(options [, callback])`](Dispatcher.md#dispatcherrequestoptions-callback).
308
+ See [`Dispatcher.request(options [, callback])`](/docs/docs/api/Dispatcher.md#dispatcherrequestoptions-callback).
307
309
 
308
310
  #### Example - MockAgent request
309
311
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  Extends: `undici.Client`
4
4
 
5
- A mock client class that implements the same api as [MockPool](MockPool.md).
5
+ A mock client class that implements the same api as [MockPool](/docs/docs/api/MockPool.md).
6
6
 
7
7
  ## `new MockClient(origin, [options])`
8
8
 
@@ -36,19 +36,19 @@ const mockClient = mockAgent.get('http://localhost:3000')
36
36
 
37
37
  ### `MockClient.intercept(options)`
38
38
 
39
- Implements: [`MockPool.intercept(options)`](MockPool.md#mockpoolinterceptoptions)
39
+ Implements: [`MockPool.intercept(options)`](/docs/docs/api/MockPool.md#mockpoolinterceptoptions)
40
40
 
41
41
  ### `MockClient.close()`
42
42
 
43
- Implements: [`MockPool.close()`](MockPool.md#mockpoolclose)
43
+ Implements: [`MockPool.close()`](/docs/docs/api/MockPool.md#mockpoolclose)
44
44
 
45
45
  ### `MockClient.dispatch(options, handlers)`
46
46
 
47
- Implements [`Dispatcher.dispatch(options, handlers)`](Dispatcher.md#dispatcherdispatchoptions-handler).
47
+ Implements [`Dispatcher.dispatch(options, handlers)`](/docs/docs/api/Dispatcher.md#dispatcherdispatchoptions-handler).
48
48
 
49
49
  ### `MockClient.request(options[, callback])`
50
50
 
51
- See [`Dispatcher.request(options [, callback])`](Dispatcher.md#dispatcherrequestoptions-callback).
51
+ See [`Dispatcher.request(options [, callback])`](/docs/docs/api/Dispatcher.md#dispatcherrequestoptions-callback).
52
52
 
53
53
  #### Example - MockClient request
54
54
 
@@ -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.
@@ -511,11 +512,11 @@ await mockPool.close()
511
512
 
512
513
  ### `MockPool.dispatch(options, handlers)`
513
514
 
514
- Implements [`Dispatcher.dispatch(options, handlers)`](Dispatcher.md#dispatcherdispatchoptions-handler).
515
+ Implements [`Dispatcher.dispatch(options, handlers)`](/docs/docs/api/Dispatcher.md#dispatcherdispatchoptions-handler).
515
516
 
516
517
  ### `MockPool.request(options[, callback])`
517
518
 
518
- See [`Dispatcher.request(options [, callback])`](Dispatcher.md#dispatcherrequestoptions-callback).
519
+ See [`Dispatcher.request(options [, callback])`](/docs/docs/api/Dispatcher.md#dispatcherrequestoptions-callback).
519
520
 
520
521
  #### Example - MockPool request
521
522