undici 7.0.0 → 7.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -281,17 +281,23 @@ stalls or deadlocks when running out of connections.
281
281
 
282
282
  ```js
283
283
  // Do
284
- const headers = await fetch(url)
285
- .then(async res => {
286
- for await (const chunk of res.body) {
287
- // force consumption of body
288
- }
289
- return res.headers
290
- })
284
+ const { body, headers } = await fetch(url);
285
+ for await (const chunk of body) {
286
+ // force consumption of body
287
+ }
291
288
 
292
289
  // Do not
293
- const headers = await fetch(url)
294
- .then(res => res.headers)
290
+ const { headers } = await fetch(url);
291
+ ```
292
+
293
+ The same applies for `request` too:
294
+ ```js
295
+ // Do
296
+ const { body, headers } = await request(url);
297
+ await res.body.dump(); // force consumption of body
298
+
299
+ // Do not
300
+ const { headers } = await request(url);
295
301
  ```
296
302
 
297
303
  However, if you want to get only headers, it might be better to use `HEAD` request method. Usage of this method will obviate the need for consumption or cancelling of the response body. See [MDN - HTTP - HTTP request methods - HEAD](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/HEAD) for more details.
@@ -434,6 +440,8 @@ and `undici.Agent`) which will enable the family autoselection algorithm when es
434
440
  * [__Matthew Aitken__](https://github.com/KhafraDev), <https://www.npmjs.com/~khaf>
435
441
  * [__Robert Nagy__](https://github.com/ronag), <https://www.npmjs.com/~ronag>
436
442
  * [__Szymon Marczak__](https://github.com/szmarczak), <https://www.npmjs.com/~szmarczak>
443
+
444
+ ## Past Collaborators
437
445
  * [__Tomas Della Vedova__](https://github.com/delvedor), <https://www.npmjs.com/~delvedor>
438
446
 
439
447
  ### Releasers
@@ -443,6 +451,16 @@ and `undici.Agent`) which will enable the family autoselection algorithm when es
443
451
  * [__Robert Nagy__](https://github.com/ronag), <https://www.npmjs.com/~ronag>
444
452
  * [__Matthew Aitken__](https://github.com/KhafraDev), <https://www.npmjs.com/~khaf>
445
453
 
454
+ ## Long Term Support
455
+
456
+ Undici aligns with the Node.js LTS schedule. The following table shows the supported versions:
457
+
458
+ | Version | Node.js | End of Life |
459
+ |---------|-------------|-------------|
460
+ | 5.x | v18.x | 2024-04-30 |
461
+ | 6.x | v20.x v22.x | 2026-04-30 |
462
+ | 7.x | v24.x | 2027-04-30 |
463
+
446
464
  ## License
447
465
 
448
466
  MIT
@@ -17,8 +17,6 @@ Returns: `Client`
17
17
 
18
18
  ### Parameter: `ClientOptions`
19
19
 
20
- > ⚠️ Warning: The `H2` support is experimental.
21
-
22
20
  * **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
21
  * **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
22
  * **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.
@@ -34,6 +32,17 @@ Returns: `Client`
34
32
  * **allowH2**: `boolean` - Default: `false`. Enables support for H2 if the server has assigned bigger priority to it through ALPN negotiation.
35
33
  * **maxConcurrentStreams**: `number` - Default: `100`. Dictates the maximum number of concurrent streams for a single H2 session. It can be overridden by a SETTINGS remote frame.
36
34
 
35
+ > **Notes about HTTP/2**
36
+ > - It only works under TLS connections. h2c is not supported.
37
+ > - The server must support HTTP/2 and choose it as the protocol during the ALPN negotiation.
38
+ > - The server must not have a bigger priority for HTTP/1.1 than HTTP/2.
39
+ > - Pseudo headers are automatically attached to the request. If you try to set them, they will be overwritten.
40
+ > - The `:path` header is automatically set to the request path.
41
+ > - The `:method` header is automatically set to the request method.
42
+ > - The `:scheme` header is automatically set to the request scheme.
43
+ > - The `:authority` header is automatically set to the request `host[:port]`.
44
+ > - `PUSH` frames are yet not supported.
45
+
37
46
  #### Parameter: `ConnectOptions`
38
47
 
39
48
  Every Tls option, see [here](https://nodejs.org/api/tls.html#tls_tls_connect_options_callback).
@@ -15,6 +15,7 @@ Returns: `ProxyAgent`
15
15
  ### Parameter: `ProxyAgentOptions`
16
16
 
17
17
  Extends: [`AgentOptions`](/docs/docs/api/Agent.md#parameter-agentoptions)
18
+ > It ommits `AgentOptions#connect`.
18
19
 
19
20
  * **uri** `string | URL` (required) - The URI of the proxy server. This can be provided as a string, as an instance of the URL class, or as an object with a `uri` property of type string.
20
21
  If the `uri` is provided as a string or `uri` is an object with an `uri` property of type string, then it will be parsed into a `URL` object according to the [WHATWG URL Specification](https://url.spec.whatwg.org).
@@ -22,8 +23,8 @@ For detailed information on the parsing process and potential validation errors,
22
23
  * **token** `string` (optional) - It can be passed by a string of token for authentication.
23
24
  * **auth** `string` (**deprecated**) - Use token.
24
25
  * **clientFactory** `(origin: URL, opts: Object) => Dispatcher` (optional) - Default: `(origin, opts) => new Pool(origin, opts)`
25
- * **requestTls** `BuildOptions` (optional) - Options object passed when creating the underlying socket via the connector builder for the request. See [TLS](https://nodejs.org/api/tls.html#tlsconnectoptions-callback).
26
- * **proxyTls** `BuildOptions` (optional) - Options object passed when creating the underlying socket via the connector builder for the proxy server. See [TLS](https://nodejs.org/api/tls.html#tlsconnectoptions-callback).
26
+ * **requestTls** `BuildOptions` (optional) - Options object passed when creating the underlying socket via the connector builder for the request. It extends from [`Client#ConnectOptions`](/docs/docs/api/Client.md#parameter-connectoptions).
27
+ * **proxyTls** `BuildOptions` (optional) - Options object passed when creating the underlying socket via the connector builder for the proxy server. It extends from [`Client#ConnectOptions`](/docs/docs/api/Client.md#parameter-connectoptions).
27
28
 
28
29
  Examples:
29
30
 
@@ -35,6 +36,13 @@ const proxyAgent = new ProxyAgent('my.proxy.server')
35
36
  const proxyAgent = new ProxyAgent(new URL('my.proxy.server'))
36
37
  // or
37
38
  const proxyAgent = new ProxyAgent({ uri: 'my.proxy.server' })
39
+ // or
40
+ const proxyAgent = new ProxyAgent({
41
+ uri: new URL('my.proxy.server'),
42
+ proxyTls: {
43
+ signal: AbortSignal.timeout(1000)
44
+ }
45
+ })
38
46
  ```
39
47
 
40
48
  #### Example - Basic ProxyAgent instantiation
@@ -128,3 +136,91 @@ Implements [`Agent.dispatch(options, handlers)`](/docs/docs/api/Agent.md#paramet
128
136
  ### `ProxyAgent.request(options[, callback])`
129
137
 
130
138
  See [`Dispatcher.request(options [, callback])`](/docs/docs/api/Dispatcher.md#dispatcherrequestoptions-callback).
139
+
140
+
141
+ #### Example - ProxyAgent with Fetch
142
+
143
+ This example demonstrates how to use `fetch` with a proxy via `ProxyAgent`. It is particularly useful for scenarios requiring proxy tunneling.
144
+
145
+ ```javascript
146
+ import { ProxyAgent, fetch } from 'undici';
147
+
148
+ // Define the ProxyAgent
149
+ const proxyAgent = new ProxyAgent('http://localhost:8000');
150
+
151
+ // Make a GET request through the proxy
152
+ const response = await fetch('http://localhost:3000/foo', {
153
+ dispatcher: proxyAgent,
154
+ method: 'GET',
155
+ });
156
+
157
+ console.log('Response status:', response.status);
158
+ console.log('Response data:', await response.text());
159
+ ```
160
+
161
+ ---
162
+
163
+ #### Example - ProxyAgent with a Custom Proxy Server
164
+
165
+ This example shows how to create a custom proxy server and use it with `ProxyAgent`.
166
+
167
+ ```javascript
168
+ import * as http from 'node:http';
169
+ import { createProxy } from 'proxy';
170
+ import { ProxyAgent, fetch } from 'undici';
171
+
172
+ // Create a proxy server
173
+ const proxyServer = createProxy(http.createServer());
174
+ proxyServer.listen(8000, () => {
175
+ console.log('Proxy server running on port 8000');
176
+ });
177
+
178
+ // Define and use the ProxyAgent
179
+ const proxyAgent = new ProxyAgent('http://localhost:8000');
180
+
181
+ const response = await fetch('http://example.com', {
182
+ dispatcher: proxyAgent,
183
+ method: 'GET',
184
+ });
185
+
186
+ console.log('Response status:', response.status);
187
+ console.log('Response data:', await response.text());
188
+ ```
189
+
190
+ ---
191
+
192
+ #### Example - ProxyAgent with HTTPS Tunneling
193
+
194
+ This example demonstrates how to perform HTTPS tunneling using a proxy.
195
+
196
+ ```javascript
197
+ import { ProxyAgent, fetch } from 'undici';
198
+
199
+ // Define a ProxyAgent for HTTPS proxy
200
+ const proxyAgent = new ProxyAgent('https://secure.proxy.server');
201
+
202
+ // Make a request to an HTTPS endpoint via the proxy
203
+ const response = await fetch('https://secure.endpoint.com/api/data', {
204
+ dispatcher: proxyAgent,
205
+ method: 'GET',
206
+ });
207
+
208
+ console.log('Response status:', response.status);
209
+ console.log('Response data:', await response.json());
210
+ ```
211
+
212
+ #### Example - ProxyAgent as a Global Dispatcher
213
+
214
+ `ProxyAgent` can be configured as a global dispatcher, making it available for all requests without explicitly passing it. This simplifies code and is useful when a single proxy configuration applies to all requests.
215
+
216
+ ```javascript
217
+ import { ProxyAgent, setGlobalDispatcher, fetch } from 'undici';
218
+
219
+ // Define and configure the ProxyAgent
220
+ const proxyAgent = new ProxyAgent('http://localhost:8000');
221
+ setGlobalDispatcher(proxyAgent);
222
+
223
+ // Make requests without specifying the dispatcher
224
+ const response = await fetch('http://example.com');
225
+ console.log('Response status:', response.status);
226
+ console.log('Response data:', await response.text());
package/index.js CHANGED
@@ -49,14 +49,8 @@ module.exports.cacheStores = {
49
49
  MemoryCacheStore: require('./lib/cache/memory-cache-store')
50
50
  }
51
51
 
52
- try {
53
- const SqliteCacheStore = require('./lib/cache/sqlite-cache-store')
54
- module.exports.cacheStores.SqliteCacheStore = SqliteCacheStore
55
- } catch (err) {
56
- if (err.code !== 'ERR_UNKNOWN_BUILTIN_MODULE') {
57
- throw err
58
- }
59
- }
52
+ const SqliteCacheStore = require('./lib/cache/sqlite-cache-store')
53
+ module.exports.cacheStores.SqliteCacheStore = SqliteCacheStore
60
54
 
61
55
  module.exports.buildConnector = buildConnector
62
56
  module.exports.errors = errors
@@ -1,9 +1,10 @@
1
1
  'use strict'
2
2
 
3
- const { DatabaseSync } = require('node:sqlite')
4
3
  const { Writable } = require('stream')
5
4
  const { assertCacheKey, assertCacheValue } = require('../util/cache.js')
6
5
 
6
+ let DatabaseSync
7
+
7
8
  const VERSION = 3
8
9
 
9
10
  // 2gb
@@ -101,6 +102,9 @@ module.exports = class SqliteCacheStore {
101
102
  }
102
103
  }
103
104
 
105
+ if (!DatabaseSync) {
106
+ DatabaseSync = require('node:sqlite').DatabaseSync
107
+ }
104
108
  this.#db = new DatabaseSync(opts?.location ?? ':memory:')
105
109
 
106
110
  this.#db.exec(`
@@ -30,14 +30,12 @@ const {
30
30
  kClosed,
31
31
  kBodyTimeout
32
32
  } = require('../core/symbols.js')
33
+ const { channels } = require('../core/diagnostics.js')
33
34
 
34
35
  const kOpenStreams = Symbol('open streams')
35
36
 
36
37
  let extractBody
37
38
 
38
- // Experimental
39
- let h2ExperimentalWarned = false
40
-
41
39
  /** @type {import('http2')} */
42
40
  let http2
43
41
  try {
@@ -82,13 +80,6 @@ function parseH2Headers (headers) {
82
80
  async function connectH2 (client, socket) {
83
81
  client[kSocket] = socket
84
82
 
85
- if (!h2ExperimentalWarned) {
86
- h2ExperimentalWarned = true
87
- process.emitWarning('H2 support is experimental, expect them to change at any time.', {
88
- code: 'UNDICI-H2'
89
- })
90
- }
91
-
92
83
  const session = http2.connect(client[kUrl], {
93
84
  createConnection: () => socket,
94
85
  peerMaxConcurrentStreams: client[kMaxConcurrentStreams],
@@ -458,6 +449,14 @@ function writeH2 (client, request) {
458
449
 
459
450
  session.ref()
460
451
 
452
+ if (channels.sendHeaders.hasSubscribers) {
453
+ let header = ''
454
+ for (const key in headers) {
455
+ header += `${key}: ${headers[key]}\r\n`
456
+ }
457
+ channels.sendHeaders.publish({ request, headers: header, socket: session[kSocket] })
458
+ }
459
+
461
460
  // TODO(metcoder95): add support for sending trailers
462
461
  const shouldEndStream = method === 'GET' || method === 'HEAD' || body === null
463
462
  if (expectContinue) {
@@ -1,6 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const assert = require('node:assert')
4
+ const WrapHandler = require('./wrap-handler')
4
5
 
5
6
  /**
6
7
  * @deprecated
@@ -9,63 +10,58 @@ module.exports = class DecoratorHandler {
9
10
  #handler
10
11
  #onCompleteCalled = false
11
12
  #onErrorCalled = false
13
+ #onResponseStartCalled = false
12
14
 
13
15
  constructor (handler) {
14
16
  if (typeof handler !== 'object' || handler === null) {
15
17
  throw new TypeError('handler must be an object')
16
18
  }
17
- this.#handler = handler
19
+ this.#handler = WrapHandler.wrap(handler)
18
20
  }
19
21
 
20
- onConnect (...args) {
21
- return this.#handler.onConnect?.(...args)
22
+ onRequestStart (...args) {
23
+ this.#handler.onRequestStart?.(...args)
22
24
  }
23
25
 
24
- onError (...args) {
25
- this.#onErrorCalled = true
26
- return this.#handler.onError?.(...args)
27
- }
28
-
29
- onUpgrade (...args) {
26
+ onRequestUpgrade (...args) {
30
27
  assert(!this.#onCompleteCalled)
31
28
  assert(!this.#onErrorCalled)
32
29
 
33
- return this.#handler.onUpgrade?.(...args)
30
+ return this.#handler.onRequestUpgrade?.(...args)
34
31
  }
35
32
 
36
- onResponseStarted (...args) {
33
+ onResponseStart (...args) {
37
34
  assert(!this.#onCompleteCalled)
38
35
  assert(!this.#onErrorCalled)
36
+ assert(!this.#onResponseStartCalled)
39
37
 
40
- return this.#handler.onResponseStarted?.(...args)
41
- }
42
-
43
- onHeaders (...args) {
44
- assert(!this.#onCompleteCalled)
45
- assert(!this.#onErrorCalled)
38
+ this.#onResponseStartCalled = true
46
39
 
47
- return this.#handler.onHeaders?.(...args)
40
+ return this.#handler.onResponseStart?.(...args)
48
41
  }
49
42
 
50
- onData (...args) {
43
+ onResponseData (...args) {
51
44
  assert(!this.#onCompleteCalled)
52
45
  assert(!this.#onErrorCalled)
53
46
 
54
- return this.#handler.onData?.(...args)
47
+ return this.#handler.onResponseData?.(...args)
55
48
  }
56
49
 
57
- onComplete (...args) {
50
+ onResponseEnd (...args) {
58
51
  assert(!this.#onCompleteCalled)
59
52
  assert(!this.#onErrorCalled)
60
53
 
61
54
  this.#onCompleteCalled = true
62
- return this.#handler.onComplete?.(...args)
55
+ return this.#handler.onResponseEnd?.(...args)
63
56
  }
64
57
 
65
- onBodySent (...args) {
66
- assert(!this.#onCompleteCalled)
67
- assert(!this.#onErrorCalled)
68
-
69
- return this.#handler.onBodySent?.(...args)
58
+ onResponseError (...args) {
59
+ this.#onErrorCalled = true
60
+ return this.#handler.onResponseError?.(...args)
70
61
  }
62
+
63
+ /**
64
+ * @deprecated
65
+ */
66
+ onBodySent () {}
71
67
  }
@@ -53,8 +53,7 @@ module.exports = class WrapHandler {
53
53
  onRequestUpgrade (controller, statusCode, headers, socket) {
54
54
  const rawHeaders = []
55
55
  for (const [key, val] of Object.entries(headers)) {
56
- // TODO (fix): What if val is Array
57
- rawHeaders.push(Buffer.from(key), Buffer.from(val))
56
+ rawHeaders.push(Buffer.from(key), Array.isArray(val) ? val.map(v => Buffer.from(v)) : Buffer.from(val))
58
57
  }
59
58
 
60
59
  this.#handler.onUpgrade?.(statusCode, rawHeaders, socket)
@@ -63,8 +62,7 @@ module.exports = class WrapHandler {
63
62
  onResponseStart (controller, statusCode, headers, statusMessage) {
64
63
  const rawHeaders = []
65
64
  for (const [key, val] of Object.entries(headers)) {
66
- // TODO (fix): What if val is Array
67
- rawHeaders.push(Buffer.from(key), Buffer.from(val))
65
+ rawHeaders.push(Buffer.from(key), Array.isArray(val) ? val.map(v => Buffer.from(v)) : Buffer.from(val))
68
66
  }
69
67
 
70
68
  if (this.#handler.onHeaders?.(statusCode, rawHeaders, () => controller.resume(), statusMessage) === false) {
@@ -81,8 +79,7 @@ module.exports = class WrapHandler {
81
79
  onResponseEnd (controller, trailers) {
82
80
  const rawTrailers = []
83
81
  for (const [key, val] of Object.entries(trailers)) {
84
- // TODO (fix): What if val is Array
85
- rawTrailers.push(Buffer.from(key), Buffer.from(val))
82
+ rawTrailers.push(Buffer.from(key), Array.isArray(val) ? val.map(v => Buffer.from(v)) : Buffer.from(val))
86
83
  }
87
84
 
88
85
  this.#handler.onComplete?.(rawTrailers)
@@ -225,8 +225,11 @@ function handleResult (
225
225
 
226
226
  let headers = {
227
227
  ...opts.headers,
228
- 'if-modified-since': new Date(result.cachedAt).toUTCString(),
229
- 'if-none-match': result.etag
228
+ 'if-modified-since': new Date(result.cachedAt).toUTCString()
229
+ }
230
+
231
+ if (result.etag) {
232
+ headers['if-none-match'] = result.etag
230
233
  }
231
234
 
232
235
  if (result.vary) {
@@ -222,19 +222,18 @@ class DNSDispatchHandler extends DecoratorHandler {
222
222
  #state = null
223
223
  #opts = null
224
224
  #dispatch = null
225
- #handler = null
226
225
  #origin = null
226
+ #controller = null
227
227
 
228
228
  constructor (state, { origin, handler, dispatch }, opts) {
229
229
  super(handler)
230
230
  this.#origin = origin
231
- this.#handler = handler
232
231
  this.#opts = { ...opts }
233
232
  this.#state = state
234
233
  this.#dispatch = dispatch
235
234
  }
236
235
 
237
- onError (err) {
236
+ onResponseError (controller, err) {
238
237
  switch (err.code) {
239
238
  case 'ETIMEDOUT':
240
239
  case 'ECONNREFUSED': {
@@ -242,7 +241,8 @@ class DNSDispatchHandler extends DecoratorHandler {
242
241
  // We delete the record and retry
243
242
  this.#state.runLookup(this.#origin, this.#opts, (err, newOrigin) => {
244
243
  if (err) {
245
- return this.#handler.onError(err)
244
+ super.onResponseError(controller, err)
245
+ return
246
246
  }
247
247
 
248
248
  const dispatchOpts = {
@@ -253,18 +253,18 @@ class DNSDispatchHandler extends DecoratorHandler {
253
253
  this.#dispatch(dispatchOpts, this)
254
254
  })
255
255
 
256
- // if dual-stack disabled, we error out
257
256
  return
258
257
  }
259
258
 
260
- this.#handler.onError(err)
261
- return
259
+ // if dual-stack disabled, we error out
260
+ super.onResponseError(controller, err)
261
+ break
262
262
  }
263
263
  case 'ENOTFOUND':
264
264
  this.#state.deleteRecord(this.#origin)
265
265
  // eslint-disable-next-line no-fallthrough
266
266
  default:
267
- this.#handler.onError(err)
267
+ super.onResponseError(controller, err)
268
268
  break
269
269
  }
270
270
  }
@@ -358,7 +358,7 @@ module.exports = interceptorOpts => {
358
358
  servername: origin.hostname, // For SNI on TLS
359
359
  origin: newOrigin,
360
360
  headers: {
361
- host: origin.hostname,
361
+ host: origin.host,
362
362
  ...origDispatchOpts.headers
363
363
  }
364
364
  }
@@ -1,19 +1,17 @@
1
1
  'use strict'
2
2
 
3
- const util = require('../core/util')
4
3
  const { InvalidArgumentError, RequestAbortedError } = require('../core/errors')
5
4
  const DecoratorHandler = require('../handler/decorator-handler')
6
5
 
7
6
  class DumpHandler extends DecoratorHandler {
8
7
  #maxSize = 1024 * 1024
9
- #abort = null
10
8
  #dumped = false
11
- #aborted = false
12
9
  #size = 0
13
- #reason = null
14
- #handler = null
10
+ #controller = null
11
+ aborted = false
12
+ reason = false
15
13
 
16
- constructor ({ maxSize }, handler) {
14
+ constructor ({ maxSize, signal }, handler) {
17
15
  if (maxSize != null && (!Number.isFinite(maxSize) || maxSize < 1)) {
18
16
  throw new InvalidArgumentError('maxSize must be a number greater than 0')
19
17
  }
@@ -21,23 +19,22 @@ class DumpHandler extends DecoratorHandler {
21
19
  super(handler)
22
20
 
23
21
  this.#maxSize = maxSize ?? this.#maxSize
24
- this.#handler = handler
22
+ // this.#handler = handler
25
23
  }
26
24
 
27
- onConnect (abort) {
28
- this.#abort = abort
29
-
30
- this.#handler.onConnect(this.#customAbort.bind(this))
25
+ #abort (reason) {
26
+ this.aborted = true
27
+ this.reason = reason
31
28
  }
32
29
 
33
- #customAbort (reason) {
34
- this.#aborted = true
35
- this.#reason = reason
30
+ onRequestStart (controller, context) {
31
+ controller.abort = this.#abort.bind(this)
32
+ this.#controller = controller
33
+
34
+ return super.onRequestStart(controller, context)
36
35
  }
37
36
 
38
- // TODO: will require adjustment after new hooks are out
39
- onHeaders (statusCode, rawHeaders, resume, statusMessage) {
40
- const headers = util.parseHeaders(rawHeaders)
37
+ onResponseStart (controller, statusCode, headers, statusMessage) {
41
38
  const contentLength = headers['content-length']
42
39
 
43
40
  if (contentLength != null && contentLength > this.#maxSize) {
@@ -48,55 +45,50 @@ class DumpHandler extends DecoratorHandler {
48
45
  )
49
46
  }
50
47
 
51
- if (this.#aborted) {
48
+ if (this.aborted === true) {
52
49
  return true
53
50
  }
54
51
 
55
- return this.#handler.onHeaders(
56
- statusCode,
57
- rawHeaders,
58
- resume,
59
- statusMessage
60
- )
52
+ return super.onResponseStart(controller, statusCode, headers, statusMessage)
61
53
  }
62
54
 
63
- onError (err) {
55
+ onResponseError (controller, err) {
64
56
  if (this.#dumped) {
65
57
  return
66
58
  }
67
59
 
68
- err = this.#reason ?? err
60
+ err = this.#controller.reason ?? err
69
61
 
70
- this.#handler.onError(err)
62
+ super.onResponseError(controller, err)
71
63
  }
72
64
 
73
- onData (chunk) {
65
+ onResponseData (controller, chunk) {
74
66
  this.#size = this.#size + chunk.length
75
67
 
76
68
  if (this.#size >= this.#maxSize) {
77
69
  this.#dumped = true
78
70
 
79
- if (this.#aborted) {
80
- this.#handler.onError(this.#reason)
71
+ if (this.aborted === true) {
72
+ super.onResponseError(controller, this.reason)
81
73
  } else {
82
- this.#handler.onComplete([])
74
+ super.onResponseEnd(controller, {})
83
75
  }
84
76
  }
85
77
 
86
78
  return true
87
79
  }
88
80
 
89
- onComplete (trailers) {
81
+ onResponseEnd (controller, trailers) {
90
82
  if (this.#dumped) {
91
83
  return
92
84
  }
93
85
 
94
- if (this.#aborted) {
95
- this.#handler.onError(this.reason)
86
+ if (this.#controller.aborted === true) {
87
+ super.onResponseError(controller, this.reason)
96
88
  return
97
89
  }
98
90
 
99
- this.#handler.onComplete(trailers)
91
+ super.onResponseEnd(controller, trailers)
100
92
  }
101
93
  }
102
94
 
@@ -107,13 +99,9 @@ function createDumpInterceptor (
107
99
  ) {
108
100
  return dispatch => {
109
101
  return function Intercept (opts, handler) {
110
- const { dumpMaxSize = defaultMaxSize } =
111
- opts
102
+ const { dumpMaxSize = defaultMaxSize } = opts
112
103
 
113
- const dumpHandler = new DumpHandler(
114
- { maxSize: dumpMaxSize },
115
- handler
116
- )
104
+ const dumpHandler = new DumpHandler({ maxSize: dumpMaxSize, signal: opts.signal }, handler)
117
105
 
118
106
  return dispatch(opts, dumpHandler)
119
107
  }
@@ -1,43 +1,41 @@
1
1
  'use strict'
2
2
 
3
- const { parseHeaders } = require('../core/util')
3
+ // const { parseHeaders } = require('../core/util')
4
4
  const DecoratorHandler = require('../handler/decorator-handler')
5
5
  const { ResponseError } = require('../core/errors')
6
6
 
7
7
  class ResponseErrorHandler extends DecoratorHandler {
8
- #handler
9
8
  #statusCode
10
9
  #contentType
11
10
  #decoder
12
11
  #headers
13
12
  #body
14
13
 
15
- constructor (opts, { handler }) {
14
+ constructor (_opts, { handler }) {
16
15
  super(handler)
17
- this.#handler = handler
18
16
  }
19
17
 
20
- onConnect (abort) {
18
+ #checkContentType (contentType) {
19
+ return (this.#contentType ?? '').indexOf(contentType) === 0
20
+ }
21
+
22
+ onRequestStart (controller, context) {
21
23
  this.#statusCode = 0
22
24
  this.#contentType = null
23
25
  this.#decoder = null
24
26
  this.#headers = null
25
27
  this.#body = ''
26
28
 
27
- return this.#handler.onConnect(abort)
28
- }
29
-
30
- #checkContentType (contentType) {
31
- return this.#contentType.indexOf(contentType) === 0
29
+ return super.onRequestStart(controller, context)
32
30
  }
33
31
 
34
- onHeaders (statusCode, rawHeaders, resume, statusMessage, headers = parseHeaders(rawHeaders)) {
32
+ onResponseStart (controller, statusCode, headers, statusMessage) {
35
33
  this.#statusCode = statusCode
36
34
  this.#headers = headers
37
35
  this.#contentType = headers['content-type']
38
36
 
39
37
  if (this.#statusCode < 400) {
40
- return this.#handler.onHeaders(statusCode, rawHeaders, resume, statusMessage, headers)
38
+ return super.onResponseStart(controller, statusCode, headers, statusMessage)
41
39
  }
42
40
 
43
41
  if (this.#checkContentType('application/json') || this.#checkContentType('text/plain')) {
@@ -45,15 +43,15 @@ class ResponseErrorHandler extends DecoratorHandler {
45
43
  }
46
44
  }
47
45
 
48
- onData (chunk) {
46
+ onResponseData (controller, chunk) {
49
47
  if (this.#statusCode < 400) {
50
- return this.#handler.onData(chunk)
48
+ return super.onResponseData(controller, chunk)
51
49
  }
52
50
 
53
51
  this.#body += this.#decoder?.decode(chunk, { stream: true }) ?? ''
54
52
  }
55
53
 
56
- onComplete (rawTrailers) {
54
+ onResponseEnd (controller, trailers) {
57
55
  if (this.#statusCode >= 400) {
58
56
  this.#body += this.#decoder?.decode(undefined, { stream: false }) ?? ''
59
57
 
@@ -77,14 +75,14 @@ class ResponseErrorHandler extends DecoratorHandler {
77
75
  Error.stackTraceLimit = stackTraceLimit
78
76
  }
79
77
 
80
- this.#handler.onError(err)
78
+ super.onResponseError(controller, err)
81
79
  } else {
82
- this.#handler.onComplete(rawTrailers)
80
+ super.onResponseEnd(controller, trailers)
83
81
  }
84
82
  }
85
83
 
86
- onError (err) {
87
- this.#handler.onError(err)
84
+ onResponseError (controller, err) {
85
+ super.onResponseError(controller, err)
88
86
  }
89
87
  }
90
88
 
@@ -283,7 +283,7 @@ function parseMIMEType (input) {
283
283
 
284
284
  // 5. If position is past the end of input, then return
285
285
  // failure
286
- if (position.position > input.length) {
286
+ if (position.position >= input.length) {
287
287
  return 'failure'
288
288
  }
289
289
 
@@ -364,7 +364,7 @@ function parseMIMEType (input) {
364
364
  }
365
365
 
366
366
  // 6. If position is past the end of input, then break.
367
- if (position.position > input.length) {
367
+ if (position.position >= input.length) {
368
368
  break
369
369
  }
370
370
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "undici",
3
- "version": "7.0.0",
3
+ "version": "7.1.1",
4
4
  "description": "An HTTP/1.1 client, written from scratch for Node.js",
5
5
  "homepage": "https://undici.nodejs.org",
6
6
  "bugs": {
@@ -106,7 +106,7 @@
106
106
  "prepare": "husky && node ./scripts/platform-shell.js"
107
107
  },
108
108
  "devDependencies": {
109
- "@fastify/busboy": "3.0.0",
109
+ "@fastify/busboy": "3.1.0",
110
110
  "@matteo.collina/tspl": "^0.1.1",
111
111
  "@sinonjs/fake-timers": "^12.0.0",
112
112
  "@types/node": "^18.19.50",
@@ -121,7 +121,7 @@
121
121
  "https-pem": "^3.0.0",
122
122
  "husky": "^9.0.7",
123
123
  "jest": "^29.0.2",
124
- "neostandard": "^0.11.2",
124
+ "neostandard": "^0.12.0",
125
125
  "node-forge": "^1.3.1",
126
126
  "proxy": "^2.1.1",
127
127
  "tsd": "^0.31.2",
package/types/index.d.ts CHANGED
@@ -64,6 +64,7 @@ declare namespace Undici {
64
64
  const caches: typeof import('./cache').caches
65
65
  const interceptors: typeof import('./interceptors').default
66
66
  const cacheStores: {
67
- MemoryCacheStore: typeof import('./cache-interceptor').default.MemoryCacheStore
67
+ MemoryCacheStore: typeof import('./cache-interceptor').default.MemoryCacheStore,
68
+ SqliteCacheStore: typeof import('./cache-interceptor').default.SqliteCacheStore
68
69
  }
69
70
  }