undici 7.10.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 (60) hide show
  1. package/README.md +157 -0
  2. package/docs/docs/api/CacheStore.md +3 -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/ProxyAgent.md +2 -0
  10. package/docs/docs/api/RetryAgent.md +6 -1
  11. package/docs/docs/api/RetryHandler.md +1 -0
  12. package/index.js +15 -0
  13. package/lib/api/api-stream.js +1 -1
  14. package/lib/cache/memory-cache-store.js +3 -3
  15. package/lib/cache/sqlite-cache-store.js +1 -1
  16. package/lib/core/connect.js +21 -51
  17. package/lib/core/diagnostics.js +6 -4
  18. package/lib/core/request.js +6 -0
  19. package/lib/core/util.js +0 -45
  20. package/lib/dispatcher/client-h1.js +1 -1
  21. package/lib/dispatcher/proxy-agent.js +2 -1
  22. package/lib/handler/retry-handler.js +110 -56
  23. package/lib/mock/mock-client.js +4 -0
  24. package/lib/mock/mock-pool.js +4 -0
  25. package/lib/util/cache.js +11 -1
  26. package/lib/util/timers.js +11 -9
  27. package/lib/web/cache/cache.js +1 -1
  28. package/lib/web/cache/cachestorage.js +1 -1
  29. package/lib/web/cookies/index.js +1 -1
  30. package/lib/web/eventsource/eventsource.js +3 -6
  31. package/lib/web/eventsource/util.js +1 -1
  32. package/lib/web/fetch/body.js +2 -2
  33. package/lib/web/fetch/dispatcher-weakref.js +0 -41
  34. package/lib/web/fetch/formdata-parser.js +4 -4
  35. package/lib/web/fetch/formdata.js +1 -1
  36. package/lib/web/fetch/headers.js +1 -1
  37. package/lib/web/fetch/index.js +7 -1
  38. package/lib/web/fetch/request.js +1 -1
  39. package/lib/web/fetch/response.js +1 -1
  40. package/lib/web/fetch/util.js +2 -2
  41. package/lib/web/{fetch/webidl.js → webidl/index.js} +57 -9
  42. package/lib/web/websocket/connection.js +4 -3
  43. package/lib/web/websocket/events.js +1 -1
  44. package/lib/web/websocket/frame.js +2 -1
  45. package/lib/web/websocket/stream/websocketerror.js +1 -1
  46. package/lib/web/websocket/stream/websocketstream.js +1 -1
  47. package/lib/web/websocket/websocket.js +4 -4
  48. package/package.json +4 -4
  49. package/types/diagnostics-channel.d.ts +9 -0
  50. package/types/dispatcher.d.ts +3 -2
  51. package/types/env-http-proxy-agent.d.ts +2 -1
  52. package/types/eventsource.d.ts +3 -3
  53. package/types/fetch.d.ts +1 -0
  54. package/types/handlers.d.ts +1 -1
  55. package/types/mock-client.d.ts +2 -0
  56. package/types/mock-interceptor.d.ts +2 -0
  57. package/types/mock-pool.d.ts +2 -0
  58. package/types/retry-handler.d.ts +9 -0
  59. package/types/webidl.d.ts +19 -15
  60. package/types/websocket.d.ts +1 -1
package/lib/core/util.js CHANGED
@@ -6,7 +6,6 @@ const { IncomingMessage } = require('node:http')
6
6
  const stream = require('node:stream')
7
7
  const net = require('node:net')
8
8
  const { Blob } = require('node:buffer')
9
- const nodeUtil = require('node:util')
10
9
  const { stringify } = require('node:querystring')
11
10
  const { EventEmitter: EE } = require('node:events')
12
11
  const timers = require('../util/timers')
@@ -660,48 +659,6 @@ function addAbortListener (signal, listener) {
660
659
  return () => signal.removeListener('abort', listener)
661
660
  }
662
661
 
663
- /**
664
- * @function
665
- * @param {string} value
666
- * @returns {string}
667
- */
668
- const toUSVString = (() => {
669
- if (typeof String.prototype.toWellFormed === 'function') {
670
- /**
671
- * @param {string} value
672
- * @returns {string}
673
- */
674
- return (value) => `${value}`.toWellFormed()
675
- } else {
676
- /**
677
- * @param {string} value
678
- * @returns {string}
679
- */
680
- return nodeUtil.toUSVString
681
- }
682
- })()
683
-
684
- /**
685
- * @param {*} value
686
- * @returns {boolean}
687
- */
688
- // TODO: move this to webidl
689
- const isUSVString = (() => {
690
- if (typeof String.prototype.isWellFormed === 'function') {
691
- /**
692
- * @param {*} value
693
- * @returns {boolean}
694
- */
695
- return (value) => `${value}`.isWellFormed()
696
- } else {
697
- /**
698
- * @param {*} value
699
- * @returns {boolean}
700
- */
701
- return (value) => toUSVString(value) === `${value}`
702
- }
703
- })()
704
-
705
662
  /**
706
663
  * @see https://tools.ietf.org/html/rfc7230#section-3.2.6
707
664
  * @param {number} c
@@ -943,8 +900,6 @@ Object.setPrototypeOf(normalizedMethodRecords, null)
943
900
  module.exports = {
944
901
  kEnumerableProperty,
945
902
  isDisturbed,
946
- toUSVString,
947
- isUSVString,
948
903
  isBlobLike,
949
904
  parseOrigin,
950
905
  parseURL,
@@ -249,7 +249,7 @@ class Parser {
249
249
  this.timeout = timers.setFastTimeout(onParserTimeout, delay, new WeakRef(this))
250
250
  } else {
251
251
  this.timeout = setTimeout(onParserTimeout, delay, new WeakRef(this))
252
- this.timeout.unref()
252
+ this.timeout?.unref()
253
253
  }
254
254
  }
255
255
 
@@ -144,7 +144,8 @@ class ProxyAgent extends DispatcherBase {
144
144
  signal: opts.signal,
145
145
  headers: {
146
146
  ...this[kProxyHeaders],
147
- host: opts.host
147
+ host: opts.host,
148
+ ...(opts.connections == null || opts.connections > 0 ? { 'proxy-connection': 'keep-alive' } : {})
148
149
  },
149
150
  servername: this[kProxyTls]?.servername || proxyHostname
150
151
  })
@@ -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
 
@@ -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