undici 7.9.0 → 7.11.0

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 (66) hide show
  1. package/README.md +157 -0
  2. package/docs/docs/api/CacheStore.md +23 -3
  3. package/docs/docs/api/Debug.md +13 -13
  4. package/docs/docs/api/DiagnosticsChannel.md +25 -0
  5. package/docs/docs/api/Dispatcher.md +20 -1
  6. package/docs/docs/api/GlobalInstallation.md +91 -0
  7. package/docs/docs/api/MockClient.md +4 -0
  8. package/docs/docs/api/MockPool.md +6 -0
  9. package/docs/docs/api/Pool.md +1 -0
  10. package/docs/docs/api/ProxyAgent.md +3 -0
  11. package/docs/docs/api/RetryAgent.md +6 -1
  12. package/docs/docs/api/RetryHandler.md +1 -0
  13. package/index.js +15 -0
  14. package/lib/api/api-stream.js +1 -1
  15. package/lib/cache/memory-cache-store.js +42 -4
  16. package/lib/cache/sqlite-cache-store.js +1 -1
  17. package/lib/core/connect.js +21 -51
  18. package/lib/core/diagnostics.js +6 -4
  19. package/lib/core/request.js +6 -0
  20. package/lib/core/util.js +0 -45
  21. package/lib/dispatcher/agent.js +25 -15
  22. package/lib/dispatcher/client-h1.js +1 -1
  23. package/lib/dispatcher/pool.js +17 -3
  24. package/lib/dispatcher/proxy-agent.js +90 -3
  25. package/lib/handler/retry-handler.js +110 -56
  26. package/lib/mock/mock-agent.js +8 -8
  27. package/lib/mock/mock-client.js +4 -0
  28. package/lib/mock/mock-pool.js +4 -0
  29. package/lib/util/cache.js +11 -1
  30. package/lib/util/timers.js +11 -9
  31. package/lib/web/cache/cache.js +1 -1
  32. package/lib/web/cache/cachestorage.js +1 -1
  33. package/lib/web/cookies/index.js +1 -1
  34. package/lib/web/eventsource/eventsource.js +3 -6
  35. package/lib/web/eventsource/util.js +1 -1
  36. package/lib/web/fetch/body.js +2 -2
  37. package/lib/web/fetch/dispatcher-weakref.js +0 -41
  38. package/lib/web/fetch/formdata-parser.js +4 -4
  39. package/lib/web/fetch/formdata.js +1 -1
  40. package/lib/web/fetch/headers.js +1 -1
  41. package/lib/web/fetch/index.js +7 -1
  42. package/lib/web/fetch/request.js +1 -1
  43. package/lib/web/fetch/response.js +1 -1
  44. package/lib/web/fetch/util.js +2 -2
  45. package/lib/web/{fetch/webidl.js → webidl/index.js} +57 -9
  46. package/lib/web/websocket/connection.js +4 -3
  47. package/lib/web/websocket/events.js +1 -1
  48. package/lib/web/websocket/frame.js +2 -1
  49. package/lib/web/websocket/stream/websocketerror.js +1 -1
  50. package/lib/web/websocket/stream/websocketstream.js +1 -1
  51. package/lib/web/websocket/websocket.js +4 -4
  52. package/package.json +4 -4
  53. package/types/diagnostics-channel.d.ts +9 -0
  54. package/types/dispatcher.d.ts +3 -2
  55. package/types/env-http-proxy-agent.d.ts +2 -1
  56. package/types/eventsource.d.ts +3 -3
  57. package/types/fetch.d.ts +1 -0
  58. package/types/handlers.d.ts +1 -1
  59. package/types/mock-client.d.ts +2 -0
  60. package/types/mock-interceptor.d.ts +2 -0
  61. package/types/mock-pool.d.ts +2 -0
  62. package/types/pool.d.ts +2 -0
  63. package/types/proxy-agent.d.ts +1 -0
  64. package/types/retry-handler.d.ts +9 -0
  65. package/types/webidl.d.ts +19 -15
  66. package/types/websocket.d.ts +1 -1
@@ -29,13 +29,16 @@ class RetryHandler {
29
29
  methods,
30
30
  errorCodes,
31
31
  retryAfter,
32
- statusCodes
32
+ statusCodes,
33
+ throwOnError
33
34
  } = retryOptions ?? {}
34
35
 
36
+ this.error = null
35
37
  this.dispatch = dispatch
36
38
  this.handler = WrapHandler.wrap(handler)
37
39
  this.opts = { ...dispatchOpts, body: wrapRequestBody(opts.body) }
38
40
  this.retryOpts = {
41
+ throwOnError: throwOnError ?? true,
39
42
  retry: retryFn ?? RetryHandler[kRetryHandlerDefaultRetry],
40
43
  retryAfter: retryAfter ?? true,
41
44
  maxTimeout: maxTimeout ?? 30 * 1000, // 30s,
@@ -68,6 +71,50 @@ class RetryHandler {
68
71
  this.etag = null
69
72
  }
70
73
 
74
+ onResponseStartWithRetry (controller, statusCode, headers, statusMessage, err) {
75
+ if (this.retryOpts.throwOnError) {
76
+ // Preserve old behavior for status codes that are not eligible for retry
77
+ if (this.retryOpts.statusCodes.includes(statusCode) === false) {
78
+ this.headersSent = true
79
+ this.handler.onResponseStart?.(controller, statusCode, headers, statusMessage)
80
+ } else {
81
+ this.error = err
82
+ }
83
+
84
+ return
85
+ }
86
+
87
+ if (isDisturbed(this.opts.body)) {
88
+ this.headersSent = true
89
+ this.handler.onResponseStart?.(controller, statusCode, headers, statusMessage)
90
+ return
91
+ }
92
+
93
+ function shouldRetry (passedErr) {
94
+ if (passedErr) {
95
+ this.headersSent = true
96
+
97
+ this.headersSent = true
98
+ this.handler.onResponseStart?.(controller, statusCode, headers, statusMessage)
99
+ controller.resume()
100
+ return
101
+ }
102
+
103
+ this.error = err
104
+ controller.resume()
105
+ }
106
+
107
+ controller.pause()
108
+ this.retryOpts.retry(
109
+ err,
110
+ {
111
+ state: { counter: this.retryCount },
112
+ opts: { retryOptions: this.retryOpts, ...this.opts }
113
+ },
114
+ shouldRetry.bind(this)
115
+ )
116
+ }
117
+
71
118
  onRequestStart (controller, context) {
72
119
  if (!this.headersSent) {
73
120
  this.handler.onRequestStart?.(controller, context)
@@ -137,26 +184,19 @@ class RetryHandler {
137
184
  }
138
185
 
139
186
  onResponseStart (controller, statusCode, headers, statusMessage) {
187
+ this.error = null
140
188
  this.retryCount += 1
141
189
 
142
190
  if (statusCode >= 300) {
143
- if (this.retryOpts.statusCodes.includes(statusCode) === false) {
144
- this.headersSent = true
145
- this.handler.onResponseStart?.(
146
- controller,
147
- statusCode,
148
- headers,
149
- statusMessage
150
- )
151
- return
152
- } else {
153
- throw new RequestRetryError('Request failed', statusCode, {
154
- headers,
155
- data: {
156
- count: this.retryCount
157
- }
158
- })
159
- }
191
+ const err = new RequestRetryError('Request failed', statusCode, {
192
+ headers,
193
+ data: {
194
+ count: this.retryCount
195
+ }
196
+ })
197
+
198
+ this.onResponseStartWithRetry(controller, statusCode, headers, statusMessage, err)
199
+ return
160
200
  }
161
201
 
162
202
  // Checkpoint for resume from where we left it
@@ -175,6 +215,7 @@ class RetryHandler {
175
215
  const contentRange = parseRangeHeader(headers['content-range'])
176
216
  // If no content range
177
217
  if (!contentRange) {
218
+ // We always throw here as we want to indicate that we entred unexpected path
178
219
  throw new RequestRetryError('Content-Range mismatch', statusCode, {
179
220
  headers,
180
221
  data: { count: this.retryCount }
@@ -183,6 +224,7 @@ class RetryHandler {
183
224
 
184
225
  // Let's start with a weak etag check
185
226
  if (this.etag != null && this.etag !== headers.etag) {
227
+ // We always throw here as we want to indicate that we entred unexpected path
186
228
  throw new RequestRetryError('ETag mismatch', statusCode, {
187
229
  headers,
188
230
  data: { count: this.retryCount }
@@ -266,14 +308,52 @@ class RetryHandler {
266
308
  }
267
309
 
268
310
  onResponseData (controller, chunk) {
311
+ if (this.error) {
312
+ return
313
+ }
314
+
269
315
  this.start += chunk.length
270
316
 
271
317
  this.handler.onResponseData?.(controller, chunk)
272
318
  }
273
319
 
274
320
  onResponseEnd (controller, trailers) {
275
- this.retryCount = 0
276
- return this.handler.onResponseEnd?.(controller, trailers)
321
+ if (this.error && this.retryOpts.throwOnError) {
322
+ throw this.error
323
+ }
324
+
325
+ if (!this.error) {
326
+ this.retryCount = 0
327
+ return this.handler.onResponseEnd?.(controller, trailers)
328
+ }
329
+
330
+ this.retry(controller)
331
+ }
332
+
333
+ retry (controller) {
334
+ if (this.start !== 0) {
335
+ const headers = { range: `bytes=${this.start}-${this.end ?? ''}` }
336
+
337
+ // Weak etag check - weak etags will make comparison algorithms never match
338
+ if (this.etag != null) {
339
+ headers['if-match'] = this.etag
340
+ }
341
+
342
+ this.opts = {
343
+ ...this.opts,
344
+ headers: {
345
+ ...this.opts.headers,
346
+ ...headers
347
+ }
348
+ }
349
+ }
350
+
351
+ try {
352
+ this.retryCountCheckpoint = this.retryCount
353
+ this.dispatch(this.opts, this)
354
+ } catch (err) {
355
+ this.handler.onResponseError?.(controller, err)
356
+ }
277
357
  }
278
358
 
279
359
  onResponseError (controller, err) {
@@ -282,6 +362,15 @@ class RetryHandler {
282
362
  return
283
363
  }
284
364
 
365
+ function shouldRetry (returnedErr) {
366
+ if (!returnedErr) {
367
+ this.retry(controller)
368
+ return
369
+ }
370
+
371
+ this.handler?.onResponseError?.(controller, returnedErr)
372
+ }
373
+
285
374
  // We reconcile in case of a mix between network errors
286
375
  // and server error response
287
376
  if (this.retryCount - this.retryCountCheckpoint > 0) {
@@ -299,43 +388,8 @@ class RetryHandler {
299
388
  state: { counter: this.retryCount },
300
389
  opts: { retryOptions: this.retryOpts, ...this.opts }
301
390
  },
302
- onRetry.bind(this)
391
+ shouldRetry.bind(this)
303
392
  )
304
-
305
- /**
306
- * @this {RetryHandler}
307
- * @param {Error} [err]
308
- * @returns
309
- */
310
- function onRetry (err) {
311
- if (err != null || controller?.aborted || isDisturbed(this.opts.body)) {
312
- return this.handler.onResponseError?.(controller, err)
313
- }
314
-
315
- if (this.start !== 0) {
316
- const headers = { range: `bytes=${this.start}-${this.end ?? ''}` }
317
-
318
- // Weak etag check - weak etags will make comparison algorithms never match
319
- if (this.etag != null) {
320
- headers['if-match'] = this.etag
321
- }
322
-
323
- this.opts = {
324
- ...this.opts,
325
- headers: {
326
- ...this.opts.headers,
327
- ...headers
328
- }
329
- }
330
- }
331
-
332
- try {
333
- this.retryCountCheckpoint = this.retryCount
334
- this.dispatch(this.opts, this)
335
- } catch (err) {
336
- this.handler.onResponseError?.(controller, err)
337
- }
338
- }
339
393
  }
340
394
  }
341
395
 
@@ -159,7 +159,7 @@ class MockAgent extends Dispatcher {
159
159
  }
160
160
 
161
161
  [kMockAgentSet] (origin, dispatcher) {
162
- this[kClients].set(origin, dispatcher)
162
+ this[kClients].set(origin, { count: 0, dispatcher })
163
163
  }
164
164
 
165
165
  [kFactory] (origin) {
@@ -171,9 +171,9 @@ class MockAgent extends Dispatcher {
171
171
 
172
172
  [kMockAgentGet] (origin) {
173
173
  // First check if we can immediately find it
174
- const client = this[kClients].get(origin)
175
- if (client) {
176
- return client
174
+ const result = this[kClients].get(origin)
175
+ if (result?.dispatcher) {
176
+ return result.dispatcher
177
177
  }
178
178
 
179
179
  // If the origin is not a string create a dummy parent pool and return to user
@@ -184,11 +184,11 @@ class MockAgent extends Dispatcher {
184
184
  }
185
185
 
186
186
  // If we match, create a pool and assign the same dispatches
187
- for (const [keyMatcher, nonExplicitDispatcher] of Array.from(this[kClients])) {
188
- if (nonExplicitDispatcher && typeof keyMatcher !== 'string' && matchValue(keyMatcher, origin)) {
187
+ for (const [keyMatcher, result] of Array.from(this[kClients])) {
188
+ if (result && typeof keyMatcher !== 'string' && matchValue(keyMatcher, origin)) {
189
189
  const dispatcher = this[kFactory](origin)
190
190
  this[kMockAgentSet](origin, dispatcher)
191
- dispatcher[kDispatches] = nonExplicitDispatcher[kDispatches]
191
+ dispatcher[kDispatches] = result.dispatcher[kDispatches]
192
192
  return dispatcher
193
193
  }
194
194
  }
@@ -202,7 +202,7 @@ class MockAgent extends Dispatcher {
202
202
  const mockAgentClients = this[kClients]
203
203
 
204
204
  return Array.from(mockAgentClients.entries())
205
- .flatMap(([origin, scope]) => scope[kDispatches].map(dispatch => ({ ...dispatch, origin })))
205
+ .flatMap(([origin, result]) => result.dispatcher[kDispatches].map(dispatch => ({ ...dispatch, origin })))
206
206
  .filter(({ pending }) => pending)
207
207
  }
208
208
 
@@ -54,6 +54,10 @@ class MockClient extends Client {
54
54
  )
55
55
  }
56
56
 
57
+ cleanMocks () {
58
+ this[kDispatches] = []
59
+ }
60
+
57
61
  async [kClose] () {
58
62
  await promisify(this[kOriginalClose])()
59
63
  this[kConnected] = 0
@@ -54,6 +54,10 @@ class MockPool extends Pool {
54
54
  )
55
55
  }
56
56
 
57
+ cleanMocks () {
58
+ this[kDispatches] = []
59
+ }
60
+
57
61
  async [kClose] () {
58
62
  await promisify(this[kOriginalClose])()
59
63
  this[kConnected] = 0
package/lib/util/cache.js CHANGED
@@ -4,6 +4,8 @@ const {
4
4
  safeHTTPMethods
5
5
  } = require('../core/util')
6
6
 
7
+ const { serializePathWithQuery } = require('../core/util')
8
+
7
9
  /**
8
10
  * @param {import('../../types/dispatcher.d.ts').default.DispatchOptions} opts
9
11
  */
@@ -12,10 +14,18 @@ function makeCacheKey (opts) {
12
14
  throw new Error('opts.origin is undefined')
13
15
  }
14
16
 
17
+ let fullPath
18
+ try {
19
+ fullPath = serializePathWithQuery(opts.path || '/', opts.query)
20
+ } catch (error) {
21
+ // If fails (path already has query params), use as-is
22
+ fullPath = opts.path || '/'
23
+ }
24
+
15
25
  return {
16
26
  origin: opts.origin.toString(),
17
27
  method: opts.method,
18
- path: opts.path,
28
+ path: fullPath,
19
29
  headers: opts.headers
20
30
  }
21
31
  }
@@ -188,19 +188,21 @@ function onTick () {
188
188
  }
189
189
 
190
190
  function refreshTimeout () {
191
- // If the fastNowTimeout is already set, refresh it.
192
- if (fastNowTimeout) {
191
+ // If the fastNowTimeout is already set and the Timer has the refresh()-
192
+ // method available, call it to refresh the timer.
193
+ // Some timer objects returned by setTimeout may not have a .refresh()
194
+ // method (e.g. mocked timers in tests).
195
+ if (fastNowTimeout?.refresh) {
193
196
  fastNowTimeout.refresh()
194
- // fastNowTimeout is not instantiated yet, create a new Timer.
197
+ // fastNowTimeout is not instantiated yet or refresh is not availabe,
198
+ // create a new Timer.
195
199
  } else {
196
200
  clearTimeout(fastNowTimeout)
197
201
  fastNowTimeout = setTimeout(onTick, TICK_MS)
198
-
199
- // If the Timer has an unref method, call it to allow the process to exit if
200
- // there are no other active handles.
201
- if (fastNowTimeout.unref) {
202
- fastNowTimeout.unref()
203
- }
202
+ // If the Timer has an unref method, call it to allow the process to exit,
203
+ // if there are no other active handles. When using fake timers or mocked
204
+ // environments (like Jest), .unref() may not be defined,
205
+ fastNowTimeout?.unref()
204
206
  }
205
207
  }
206
208
 
@@ -3,7 +3,7 @@
3
3
  const { kConstruct } = require('../../core/symbols')
4
4
  const { urlEquals, getFieldValues } = require('./util')
5
5
  const { kEnumerableProperty, isDisturbed } = require('../../core/util')
6
- const { webidl } = require('../fetch/webidl')
6
+ const { webidl } = require('../webidl')
7
7
  const { cloneResponse, fromInnerResponse, getResponseState } = require('../fetch/response')
8
8
  const { Request, fromInnerRequest, getRequestState } = require('../fetch/request')
9
9
  const { fetching } = require('../fetch/index')
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const { Cache } = require('./cache')
4
- const { webidl } = require('../fetch/webidl')
4
+ const { webidl } = require('../webidl')
5
5
  const { kEnumerableProperty } = require('../../core/util')
6
6
  const { kConstruct } = require('../../core/symbols')
7
7
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  const { parseSetCookie } = require('./parse')
4
4
  const { stringify } = require('./util')
5
- const { webidl } = require('../fetch/webidl')
5
+ const { webidl } = require('../webidl')
6
6
  const { Headers } = require('../fetch/headers')
7
7
 
8
8
  const brandChecks = webidl.brandCheckMultiple([Headers, globalThis.Headers].filter(Boolean))
@@ -3,7 +3,7 @@
3
3
  const { pipeline } = require('node:stream')
4
4
  const { fetching } = require('../fetch')
5
5
  const { makeRequest } = require('../fetch/request')
6
- const { webidl } = require('../fetch/webidl')
6
+ const { webidl } = require('../webidl')
7
7
  const { EventSourceStream } = require('./eventsource-stream')
8
8
  const { parseMIMEType } = require('../fetch/data-url')
9
9
  const { createFastMessageEvent } = require('../websocket/events')
@@ -231,12 +231,9 @@ class EventSource extends EventTarget {
231
231
 
232
232
  // 14. Let processEventSourceEndOfBody given response res be the following step: if res is not a network error, then reestablish the connection.
233
233
  const processEventSourceEndOfBody = (response) => {
234
- if (isNetworkError(response)) {
235
- this.dispatchEvent(new Event('error'))
236
- this.close()
234
+ if (!isNetworkError(response)) {
235
+ return this.#reconnect()
237
236
  }
238
-
239
- this.#reconnect()
240
237
  }
241
238
 
242
239
  // 15. Fetch request, with processResponseEndOfBody set to processEventSourceEndOfBody...
@@ -26,7 +26,7 @@ function isASCIINumber (value) {
26
26
  // https://github.com/nodejs/undici/issues/2664
27
27
  function delay (ms) {
28
28
  return new Promise((resolve) => {
29
- setTimeout(resolve, ms).unref()
29
+ setTimeout(resolve, ms)
30
30
  })
31
31
  }
32
32
 
@@ -10,7 +10,7 @@ const {
10
10
  utf8DecodeBytes
11
11
  } = require('./util')
12
12
  const { FormData, setFormDataState } = require('./formdata')
13
- const { webidl } = require('./webidl')
13
+ const { webidl } = require('../webidl')
14
14
  const { Blob } = require('node:buffer')
15
15
  const assert = require('node:assert')
16
16
  const { isErrored, isDisturbed } = require('node:stream')
@@ -29,7 +29,7 @@ try {
29
29
  const textEncoder = new TextEncoder()
30
30
  function noop () {}
31
31
 
32
- const hasFinalizationRegistry = globalThis.FinalizationRegistry && process.version.indexOf('v18') !== 0
32
+ const hasFinalizationRegistry = globalThis.FinalizationRegistry
33
33
  let streamRegistry
34
34
 
35
35
  if (hasFinalizationRegistry) {
@@ -1,46 +1,5 @@
1
1
  'use strict'
2
2
 
3
- const { kConnected, kSize } = require('../../core/symbols')
4
-
5
- class CompatWeakRef {
6
- constructor (value) {
7
- this.value = value
8
- }
9
-
10
- deref () {
11
- return this.value[kConnected] === 0 && this.value[kSize] === 0
12
- ? undefined
13
- : this.value
14
- }
15
- }
16
-
17
- class CompatFinalizer {
18
- constructor (finalizer) {
19
- this.finalizer = finalizer
20
- }
21
-
22
- register (dispatcher, key) {
23
- if (dispatcher.on) {
24
- dispatcher.on('disconnect', () => {
25
- if (dispatcher[kConnected] === 0 && dispatcher[kSize] === 0) {
26
- this.finalizer(key)
27
- }
28
- })
29
- }
30
- }
31
-
32
- unregister (key) {}
33
- }
34
-
35
3
  module.exports = function () {
36
- // FIXME: remove workaround when the Node bug is backported to v18
37
- // https://github.com/nodejs/node/issues/49344#issuecomment-1741776308
38
- if (process.env.NODE_V8_COVERAGE && process.version.startsWith('v18')) {
39
- process._rawDebug('Using compatibility WeakRef and FinalizationRegistry')
40
- return {
41
- WeakRef: CompatWeakRef,
42
- FinalizationRegistry: CompatFinalizer
43
- }
44
- }
45
4
  return { WeakRef, FinalizationRegistry }
46
5
  }
@@ -1,10 +1,10 @@
1
1
  'use strict'
2
2
 
3
- const { isUSVString, bufferToLowerCasedHeaderName } = require('../../core/util')
3
+ const { bufferToLowerCasedHeaderName } = require('../../core/util')
4
4
  const { utf8DecodeBytes } = require('./util')
5
5
  const { HTTP_TOKEN_CODEPOINTS, isomorphicDecode } = require('./data-url')
6
6
  const { makeEntry } = require('./formdata')
7
- const { webidl } = require('./webidl')
7
+ const { webidl } = require('../webidl')
8
8
  const assert = require('node:assert')
9
9
  const { File: NodeFile } = require('node:buffer')
10
10
 
@@ -200,8 +200,8 @@ function multipartFormDataParser (input, mimeType) {
200
200
  }
201
201
 
202
202
  // 5.12. Assert: name is a scalar value string and value is either a scalar value string or a File object.
203
- assert(isUSVString(name))
204
- assert((typeof value === 'string' && isUSVString(value)) || webidl.is.File(value))
203
+ assert(webidl.is.USVString(name))
204
+ assert((typeof value === 'string' && webidl.is.USVString(value)) || webidl.is.File(value))
205
205
 
206
206
  // 5.13. Create an entry with name and value, and append it to entry list.
207
207
  entryList.push(makeEntry(name, value, filename))
@@ -2,7 +2,7 @@
2
2
 
3
3
  const { iteratorMixin } = require('./util')
4
4
  const { kEnumerableProperty } = require('../../core/util')
5
- const { webidl } = require('./webidl')
5
+ const { webidl } = require('../webidl')
6
6
  const { File: NativeFile } = require('node:buffer')
7
7
  const nodeUtil = require('node:util')
8
8
 
@@ -9,7 +9,7 @@ const {
9
9
  isValidHeaderName,
10
10
  isValidHeaderValue
11
11
  } = require('./util')
12
- const { webidl } = require('./webidl')
12
+ const { webidl } = require('../webidl')
13
13
  const assert = require('node:assert')
14
14
  const util = require('node:util')
15
15
 
@@ -61,7 +61,7 @@ const { Readable, pipeline, finished, isErrored, isReadable } = require('node:st
61
61
  const { addAbortListener, bufferToLowerCasedHeaderName } = require('../../core/util')
62
62
  const { dataURLProcessor, serializeAMimeType, minimizeSupportedMimeType } = require('./data-url')
63
63
  const { getGlobalDispatcher } = require('../../global')
64
- const { webidl } = require('./webidl')
64
+ const { webidl } = require('../webidl')
65
65
  const { STATUS_CODES } = require('node:http')
66
66
  const GET_OR_HEAD = ['GET', 'HEAD']
67
67
 
@@ -2155,6 +2155,12 @@ async function httpNetworkFetch (
2155
2155
  flush: zlib.constants.BROTLI_OPERATION_FLUSH,
2156
2156
  finishFlush: zlib.constants.BROTLI_OPERATION_FLUSH
2157
2157
  }))
2158
+ } else if (coding === 'zstd' && typeof zlib.createZstdDecompress === 'function') {
2159
+ // Node.js v23.8.0+ and v22.15.0+ supports Zstandard
2160
+ decoders.push(zlib.createZstdDecompress({
2161
+ flush: zlib.constants.ZSTD_e_continue,
2162
+ finishFlush: zlib.constants.ZSTD_e_end
2163
+ }))
2158
2164
  } else {
2159
2165
  decoders.length = 0
2160
2166
  break
@@ -23,7 +23,7 @@ const {
23
23
  requestDuplex
24
24
  } = require('./constants')
25
25
  const { kEnumerableProperty, normalizedMethodRecordsBase, normalizedMethodRecords } = util
26
- const { webidl } = require('./webidl')
26
+ const { webidl } = require('../webidl')
27
27
  const { URLSerializer } = require('./data-url')
28
28
  const { kConstruct } = require('../../core/symbols')
29
29
  const assert = require('node:assert')
@@ -18,7 +18,7 @@ const {
18
18
  redirectStatusSet,
19
19
  nullBodyStatus
20
20
  } = require('./constants')
21
- const { webidl } = require('./webidl')
21
+ const { webidl } = require('../webidl')
22
22
  const { URLSerializer } = require('./data-url')
23
23
  const { kConstruct } = require('../../core/symbols')
24
24
  const assert = require('node:assert')
@@ -9,7 +9,7 @@ const { performance } = require('node:perf_hooks')
9
9
  const { ReadableStreamFrom, isValidHTTPToken, normalizedMethodRecordsBase } = require('../../core/util')
10
10
  const assert = require('node:assert')
11
11
  const { isUint8Array } = require('node:util/types')
12
- const { webidl } = require('./webidl')
12
+ const { webidl } = require('../webidl')
13
13
 
14
14
  let supportedHashes = []
15
15
 
@@ -1262,7 +1262,7 @@ async function readAllBytes (reader, successSteps, failureSteps) {
1262
1262
  // 1. If chunk is not a Uint8Array object, call failureSteps
1263
1263
  // with a TypeError and abort these steps.
1264
1264
  if (!isUint8Array(chunk)) {
1265
- failureSteps(TypeError('Received non-Uint8Array chunk'))
1265
+ failureSteps(new TypeError('Received non-Uint8Array chunk'))
1266
1266
  return
1267
1267
  }
1268
1268