undici 7.10.0 → 7.12.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 (69) hide show
  1. package/README.md +159 -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 +32 -4
  5. package/docs/docs/api/Dispatcher.md +22 -3
  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/docs/docs/api/WebSocket.md +27 -0
  13. package/index.js +18 -1
  14. package/lib/api/api-stream.js +1 -1
  15. package/lib/api/readable.js +1 -3
  16. package/lib/cache/memory-cache-store.js +3 -3
  17. package/lib/cache/sqlite-cache-store.js +1 -1
  18. package/lib/core/connect.js +21 -51
  19. package/lib/core/diagnostics.js +6 -4
  20. package/lib/core/request.js +12 -1
  21. package/lib/core/tree.js +1 -1
  22. package/lib/core/util.js +0 -45
  23. package/lib/dispatcher/client-h1.js +9 -18
  24. package/lib/dispatcher/proxy-agent.js +2 -1
  25. package/lib/handler/cache-handler.js +4 -1
  26. package/lib/handler/redirect-handler.js +2 -2
  27. package/lib/handler/retry-handler.js +110 -56
  28. package/lib/interceptor/cache.js +2 -2
  29. package/lib/interceptor/redirect.js +1 -1
  30. package/lib/mock/mock-client.js +4 -0
  31. package/lib/mock/mock-pool.js +4 -0
  32. package/lib/util/cache.js +12 -2
  33. package/lib/util/promise.js +28 -0
  34. package/lib/util/timers.js +11 -9
  35. package/lib/web/cache/cache.js +11 -9
  36. package/lib/web/cache/cachestorage.js +1 -1
  37. package/lib/web/cookies/index.js +1 -1
  38. package/lib/web/eventsource/eventsource.js +3 -6
  39. package/lib/web/eventsource/util.js +1 -1
  40. package/lib/web/fetch/body.js +36 -24
  41. package/lib/web/fetch/formdata-parser.js +4 -4
  42. package/lib/web/fetch/formdata.js +1 -1
  43. package/lib/web/fetch/headers.js +1 -1
  44. package/lib/web/fetch/index.js +228 -226
  45. package/lib/web/fetch/request.js +16 -8
  46. package/lib/web/fetch/response.js +6 -4
  47. package/lib/web/fetch/util.js +23 -25
  48. package/lib/web/{fetch/webidl.js → webidl/index.js} +57 -9
  49. package/lib/web/websocket/connection.js +4 -12
  50. package/lib/web/websocket/events.js +1 -1
  51. package/lib/web/websocket/frame.js +2 -1
  52. package/lib/web/websocket/receiver.js +2 -12
  53. package/lib/web/websocket/stream/websocketerror.js +1 -1
  54. package/lib/web/websocket/stream/websocketstream.js +8 -5
  55. package/lib/web/websocket/websocket.js +61 -5
  56. package/package.json +5 -5
  57. package/types/diagnostics-channel.d.ts +9 -0
  58. package/types/dispatcher.d.ts +3 -2
  59. package/types/env-http-proxy-agent.d.ts +2 -1
  60. package/types/eventsource.d.ts +3 -3
  61. package/types/fetch.d.ts +1 -0
  62. package/types/handlers.d.ts +1 -1
  63. package/types/mock-client.d.ts +2 -0
  64. package/types/mock-interceptor.d.ts +2 -0
  65. package/types/mock-pool.d.ts +2 -0
  66. package/types/retry-handler.d.ts +9 -0
  67. package/types/webidl.d.ts +29 -15
  68. package/types/websocket.d.ts +3 -1
  69. package/lib/web/fetch/dispatcher-weakref.js +0 -46
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,17 +14,25 @@ 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
  }
22
32
 
23
33
  /**
24
34
  * @param {Record<string, string[] | string>}
25
- * @return {Record<string, string[] | string>}
35
+ * @returns {Record<string, string[] | string>}
26
36
  */
27
37
  function normaliseHeaders (opts) {
28
38
  let headers
@@ -0,0 +1,28 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ * @template {*} T
5
+ * @typedef {Object} DeferredPromise
6
+ * @property {Promise<T>} promise
7
+ * @property {(value?: T) => void} resolve
8
+ * @property {(reason?: any) => void} reject
9
+ */
10
+
11
+ /**
12
+ * @template {*} T
13
+ * @returns {DeferredPromise<T>} An object containing a promise and its resolve/reject methods.
14
+ */
15
+ function createDeferredPromise () {
16
+ let res
17
+ let rej
18
+ const promise = new Promise((resolve, reject) => {
19
+ res = resolve
20
+ rej = reject
21
+ })
22
+
23
+ return { promise, resolve: res, reject: rej }
24
+ }
25
+
26
+ module.exports = {
27
+ createDeferredPromise
28
+ }
@@ -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
 
@@ -1,14 +1,16 @@
1
1
  'use strict'
2
2
 
3
+ const assert = require('node:assert')
4
+
3
5
  const { kConstruct } = require('../../core/symbols')
4
6
  const { urlEquals, getFieldValues } = require('./util')
5
7
  const { kEnumerableProperty, isDisturbed } = require('../../core/util')
6
- const { webidl } = require('../fetch/webidl')
8
+ const { webidl } = require('../webidl')
7
9
  const { cloneResponse, fromInnerResponse, getResponseState } = require('../fetch/response')
8
10
  const { Request, fromInnerRequest, getRequestState } = require('../fetch/request')
9
11
  const { fetching } = require('../fetch/index')
10
- const { urlIsHttpHttpsScheme, createDeferredPromise, readAllBytes } = require('../fetch/util')
11
- const assert = require('node:assert')
12
+ const { urlIsHttpHttpsScheme, readAllBytes } = require('../fetch/util')
13
+ const { createDeferredPromise } = require('../../util/promise')
12
14
 
13
15
  /**
14
16
  * @see https://w3c.github.io/ServiceWorker/#dfn-cache-batch-operation
@@ -46,7 +48,7 @@ class Cache {
46
48
  const prefix = 'Cache.match'
47
49
  webidl.argumentLengthCheck(arguments, 1, prefix)
48
50
 
49
- request = webidl.converters.RequestInfo(request, prefix, 'request')
51
+ request = webidl.converters.RequestInfo(request)
50
52
  options = webidl.converters.CacheQueryOptions(options, prefix, 'options')
51
53
 
52
54
  const p = this.#internalMatchAll(request, options, 1)
@@ -62,7 +64,7 @@ class Cache {
62
64
  webidl.brandCheck(this, Cache)
63
65
 
64
66
  const prefix = 'Cache.matchAll'
65
- if (request !== undefined) request = webidl.converters.RequestInfo(request, prefix, 'request')
67
+ if (request !== undefined) request = webidl.converters.RequestInfo(request)
66
68
  options = webidl.converters.CacheQueryOptions(options, prefix, 'options')
67
69
 
68
70
  return this.#internalMatchAll(request, options)
@@ -74,7 +76,7 @@ class Cache {
74
76
  const prefix = 'Cache.add'
75
77
  webidl.argumentLengthCheck(arguments, 1, prefix)
76
78
 
77
- request = webidl.converters.RequestInfo(request, prefix, 'request')
79
+ request = webidl.converters.RequestInfo(request)
78
80
 
79
81
  // 1.
80
82
  const requests = [request]
@@ -262,7 +264,7 @@ class Cache {
262
264
  const prefix = 'Cache.put'
263
265
  webidl.argumentLengthCheck(arguments, 2, prefix)
264
266
 
265
- request = webidl.converters.RequestInfo(request, prefix, 'request')
267
+ request = webidl.converters.RequestInfo(request)
266
268
  response = webidl.converters.Response(response, prefix, 'response')
267
269
 
268
270
  // 1.
@@ -393,7 +395,7 @@ class Cache {
393
395
  const prefix = 'Cache.delete'
394
396
  webidl.argumentLengthCheck(arguments, 1, prefix)
395
397
 
396
- request = webidl.converters.RequestInfo(request, prefix, 'request')
398
+ request = webidl.converters.RequestInfo(request)
397
399
  options = webidl.converters.CacheQueryOptions(options, prefix, 'options')
398
400
 
399
401
  /**
@@ -458,7 +460,7 @@ class Cache {
458
460
 
459
461
  const prefix = 'Cache.keys'
460
462
 
461
- if (request !== undefined) request = webidl.converters.RequestInfo(request, prefix, 'request')
463
+ if (request !== undefined) request = webidl.converters.RequestInfo(request)
462
464
  options = webidl.converters.CacheQueryOptions(options, prefix, 'options')
463
465
 
464
466
  // 1.
@@ -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
 
@@ -4,19 +4,20 @@ const util = require('../../core/util')
4
4
  const {
5
5
  ReadableStreamFrom,
6
6
  readableStreamClose,
7
- createDeferredPromise,
8
7
  fullyReadBody,
9
8
  extractMimeType,
10
9
  utf8DecodeBytes
11
10
  } = require('./util')
12
11
  const { FormData, setFormDataState } = require('./formdata')
13
- const { webidl } = require('./webidl')
12
+ const { webidl } = require('../webidl')
14
13
  const { Blob } = require('node:buffer')
15
14
  const assert = require('node:assert')
16
15
  const { isErrored, isDisturbed } = require('node:stream')
17
16
  const { isArrayBuffer } = require('node:util/types')
18
17
  const { serializeAMimeType } = require('./data-url')
19
18
  const { multipartFormDataParser } = require('./formdata-parser')
19
+ const { createDeferredPromise } = require('../../util/promise')
20
+
20
21
  let random
21
22
 
22
23
  try {
@@ -29,19 +30,22 @@ try {
29
30
  const textEncoder = new TextEncoder()
30
31
  function noop () {}
31
32
 
32
- const hasFinalizationRegistry = globalThis.FinalizationRegistry && process.version.indexOf('v18') !== 0
33
- let streamRegistry
34
-
35
- if (hasFinalizationRegistry) {
36
- streamRegistry = new FinalizationRegistry((weakRef) => {
37
- const stream = weakRef.deref()
38
- if (stream && !stream.locked && !isDisturbed(stream) && !isErrored(stream)) {
39
- stream.cancel('Response object has been garbage collected').catch(noop)
40
- }
41
- })
42
- }
33
+ const streamRegistry = new FinalizationRegistry((weakRef) => {
34
+ const stream = weakRef.deref()
35
+ if (stream && !stream.locked && !isDisturbed(stream) && !isErrored(stream)) {
36
+ stream.cancel('Response object has been garbage collected').catch(noop)
37
+ }
38
+ })
43
39
 
44
- // https://fetch.spec.whatwg.org/#concept-bodyinit-extract
40
+ /**
41
+ * Extract a body with type from a byte sequence or BodyInit object
42
+ *
43
+ * @param {import('../../../types').BodyInit} object - The BodyInit object to extract from
44
+ * @param {boolean} [keepalive=false] - If true, indicates that the body
45
+ * @returns {[{stream: ReadableStream, source: any, length: number | null}, string | null]} - Returns a tuple containing the body and its type
46
+ *
47
+ * @see https://fetch.spec.whatwg.org/#concept-bodyinit-extract
48
+ */
45
49
  function extractBody (object, keepalive = false) {
46
50
  // 1. Let stream be null.
47
51
  let stream = null
@@ -267,7 +271,22 @@ function extractBody (object, keepalive = false) {
267
271
  return [body, type]
268
272
  }
269
273
 
270
- // https://fetch.spec.whatwg.org/#bodyinit-safely-extract
274
+ /**
275
+ * @typedef {object} ExtractBodyResult
276
+ * @property {ReadableStream<Uint8Array<ArrayBuffer>>} stream - The ReadableStream containing the body data
277
+ * @property {any} source - The original source of the body data
278
+ * @property {number | null} length - The length of the body data, or null
279
+ */
280
+
281
+ /**
282
+ * Safely extract a body with type from a byte sequence or BodyInit object.
283
+ *
284
+ * @param {import('../../../types').BodyInit} object - The BodyInit object to extract from
285
+ * @param {boolean} [keepalive=false] - If true, indicates that the body
286
+ * @returns {[ExtractBodyResult, string | null]} - Returns a tuple containing the body and its type
287
+ *
288
+ * @see https://fetch.spec.whatwg.org/#bodyinit-safely-extract
289
+ */
271
290
  function safelyExtractBody (object, keepalive = false) {
272
291
  // To safely extract a body and a `Content-Type` value from
273
292
  // a byte sequence or BodyInit object object, run these steps:
@@ -275,9 +294,7 @@ function safelyExtractBody (object, keepalive = false) {
275
294
  // 1. If object is a ReadableStream object, then:
276
295
  if (webidl.is.ReadableStream(object)) {
277
296
  // Assert: object is neither disturbed nor locked.
278
- // istanbul ignore next
279
297
  assert(!util.isDisturbed(object), 'The body has already been consumed.')
280
- // istanbul ignore next
281
298
  assert(!object.locked, 'The stream is locked.')
282
299
  }
283
300
 
@@ -285,17 +302,13 @@ function safelyExtractBody (object, keepalive = false) {
285
302
  return extractBody(object, keepalive)
286
303
  }
287
304
 
288
- function cloneBody (instance, body) {
305
+ function cloneBody (body) {
289
306
  // To clone a body body, run these steps:
290
307
 
291
308
  // https://fetch.spec.whatwg.org/#concept-body-clone
292
309
 
293
310
  // 1. Let « out1, out2 » be the result of teeing body’s stream.
294
- const [out1, out2] = body.stream.tee()
295
-
296
- if (hasFinalizationRegistry) {
297
- streamRegistry.register(instance, new WeakRef(out1))
298
- }
311
+ const { 0: out1, 1: out2 } = body.stream.tee()
299
312
 
300
313
  // 2. Set body’s stream to out1.
301
314
  body.stream = out1
@@ -527,6 +540,5 @@ module.exports = {
527
540
  cloneBody,
528
541
  mixinBody,
529
542
  streamRegistry,
530
- hasFinalizationRegistry,
531
543
  bodyUnusable
532
544
  }
@@ -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