undici 6.21.0 → 7.0.0-alpha.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/README.md +27 -46
  2. package/docs/docs/api/Agent.md +14 -17
  3. package/docs/docs/api/BalancedPool.md +16 -16
  4. package/docs/docs/api/CacheStore.md +131 -0
  5. package/docs/docs/api/Client.md +12 -14
  6. package/docs/docs/api/Debug.md +1 -1
  7. package/docs/docs/api/Dispatcher.md +98 -194
  8. package/docs/docs/api/EnvHttpProxyAgent.md +12 -13
  9. package/docs/docs/api/MockAgent.md +5 -3
  10. package/docs/docs/api/MockClient.md +5 -5
  11. package/docs/docs/api/MockPool.md +4 -3
  12. package/docs/docs/api/Pool.md +15 -16
  13. package/docs/docs/api/PoolStats.md +1 -1
  14. package/docs/docs/api/ProxyAgent.md +3 -3
  15. package/docs/docs/api/RedirectHandler.md +1 -1
  16. package/docs/docs/api/RetryAgent.md +1 -1
  17. package/docs/docs/api/RetryHandler.md +4 -4
  18. package/docs/docs/api/WebSocket.md +46 -4
  19. package/docs/docs/api/api-lifecycle.md +11 -11
  20. package/docs/docs/best-practices/mocking-request.md +2 -2
  21. package/docs/docs/best-practices/proxy.md +1 -1
  22. package/index.d.ts +1 -1
  23. package/index.js +23 -7
  24. package/lib/api/abort-signal.js +2 -0
  25. package/lib/api/api-connect.js +3 -1
  26. package/lib/api/api-pipeline.js +7 -6
  27. package/lib/api/api-request.js +33 -48
  28. package/lib/api/api-stream.js +39 -50
  29. package/lib/api/api-upgrade.js +5 -3
  30. package/lib/api/readable.js +235 -62
  31. package/lib/api/util.js +2 -0
  32. package/lib/cache/memory-cache-store.js +177 -0
  33. package/lib/cache/sqlite-cache-store.js +446 -0
  34. package/lib/core/constants.js +35 -10
  35. package/lib/core/diagnostics.js +122 -128
  36. package/lib/core/errors.js +6 -6
  37. package/lib/core/request.js +13 -11
  38. package/lib/core/symbols.js +2 -1
  39. package/lib/core/tree.js +9 -1
  40. package/lib/core/util.js +237 -49
  41. package/lib/dispatcher/agent.js +3 -17
  42. package/lib/dispatcher/balanced-pool.js +5 -8
  43. package/lib/dispatcher/client-h1.js +379 -134
  44. package/lib/dispatcher/client-h2.js +173 -107
  45. package/lib/dispatcher/client.js +19 -32
  46. package/lib/dispatcher/dispatcher-base.js +6 -35
  47. package/lib/dispatcher/dispatcher.js +7 -24
  48. package/lib/dispatcher/fixed-queue.js +91 -49
  49. package/lib/dispatcher/pool-stats.js +2 -0
  50. package/lib/dispatcher/pool.js +3 -6
  51. package/lib/dispatcher/proxy-agent.js +3 -6
  52. package/lib/handler/cache-handler.js +393 -0
  53. package/lib/handler/cache-revalidation-handler.js +124 -0
  54. package/lib/handler/decorator-handler.js +27 -0
  55. package/lib/handler/redirect-handler.js +54 -59
  56. package/lib/handler/retry-handler.js +77 -109
  57. package/lib/handler/unwrap-handler.js +96 -0
  58. package/lib/handler/wrap-handler.js +98 -0
  59. package/lib/interceptor/cache.js +350 -0
  60. package/lib/interceptor/dns.js +375 -0
  61. package/lib/interceptor/dump.js +2 -2
  62. package/lib/interceptor/redirect.js +11 -14
  63. package/lib/interceptor/response-error.js +18 -7
  64. package/lib/llhttp/constants.d.ts +97 -0
  65. package/lib/llhttp/constants.js +412 -192
  66. package/lib/llhttp/constants.js.map +1 -0
  67. package/lib/llhttp/llhttp-wasm.js +11 -1
  68. package/lib/llhttp/llhttp_simd-wasm.js +11 -1
  69. package/lib/llhttp/utils.d.ts +2 -0
  70. package/lib/llhttp/utils.js +9 -9
  71. package/lib/llhttp/utils.js.map +1 -0
  72. package/lib/mock/mock-agent.js +5 -8
  73. package/lib/mock/mock-client.js +9 -4
  74. package/lib/mock/mock-errors.js +3 -1
  75. package/lib/mock/mock-interceptor.js +8 -6
  76. package/lib/mock/mock-pool.js +9 -4
  77. package/lib/mock/mock-symbols.js +3 -1
  78. package/lib/mock/mock-utils.js +29 -5
  79. package/lib/util/cache.js +360 -0
  80. package/lib/web/cache/cache.js +24 -21
  81. package/lib/web/cache/cachestorage.js +1 -1
  82. package/lib/web/cookies/index.js +29 -14
  83. package/lib/web/cookies/parse.js +8 -3
  84. package/lib/web/eventsource/eventsource-stream.js +9 -8
  85. package/lib/web/eventsource/eventsource.js +10 -6
  86. package/lib/web/fetch/body.js +43 -41
  87. package/lib/web/fetch/constants.js +12 -5
  88. package/lib/web/fetch/data-url.js +3 -3
  89. package/lib/web/fetch/formdata-parser.js +72 -45
  90. package/lib/web/fetch/formdata.js +65 -54
  91. package/lib/web/fetch/headers.js +118 -86
  92. package/lib/web/fetch/index.js +58 -67
  93. package/lib/web/fetch/request.js +136 -77
  94. package/lib/web/fetch/response.js +87 -56
  95. package/lib/web/fetch/util.js +259 -109
  96. package/lib/web/fetch/webidl.js +113 -68
  97. package/lib/web/websocket/connection.js +76 -147
  98. package/lib/web/websocket/constants.js +70 -10
  99. package/lib/web/websocket/events.js +4 -2
  100. package/lib/web/websocket/frame.js +45 -3
  101. package/lib/web/websocket/receiver.js +29 -33
  102. package/lib/web/websocket/sender.js +18 -13
  103. package/lib/web/websocket/stream/websocketerror.js +83 -0
  104. package/lib/web/websocket/stream/websocketstream.js +485 -0
  105. package/lib/web/websocket/util.js +128 -77
  106. package/lib/web/websocket/websocket.js +234 -135
  107. package/package.json +24 -36
  108. package/scripts/strip-comments.js +3 -1
  109. package/types/agent.d.ts +7 -7
  110. package/types/api.d.ts +24 -24
  111. package/types/balanced-pool.d.ts +11 -11
  112. package/types/cache-interceptor.d.ts +172 -0
  113. package/types/client.d.ts +11 -12
  114. package/types/cookies.d.ts +2 -0
  115. package/types/diagnostics-channel.d.ts +10 -10
  116. package/types/dispatcher.d.ts +113 -90
  117. package/types/env-http-proxy-agent.d.ts +2 -2
  118. package/types/errors.d.ts +53 -47
  119. package/types/fetch.d.ts +17 -16
  120. package/types/formdata.d.ts +7 -7
  121. package/types/global-dispatcher.d.ts +4 -4
  122. package/types/global-origin.d.ts +5 -5
  123. package/types/handlers.d.ts +7 -7
  124. package/types/header.d.ts +157 -1
  125. package/types/index.d.ts +44 -46
  126. package/types/interceptors.d.ts +25 -8
  127. package/types/mock-agent.d.ts +21 -18
  128. package/types/mock-client.d.ts +4 -4
  129. package/types/mock-errors.d.ts +3 -3
  130. package/types/mock-interceptor.d.ts +19 -19
  131. package/types/mock-pool.d.ts +4 -4
  132. package/types/patch.d.ts +0 -4
  133. package/types/pool-stats.d.ts +8 -8
  134. package/types/pool.d.ts +12 -12
  135. package/types/proxy-agent.d.ts +4 -4
  136. package/types/readable.d.ts +18 -15
  137. package/types/retry-agent.d.ts +1 -1
  138. package/types/retry-handler.d.ts +10 -10
  139. package/types/util.d.ts +3 -3
  140. package/types/utility.d.ts +7 -0
  141. package/types/webidl.d.ts +44 -6
  142. package/types/websocket.d.ts +34 -1
  143. package/docs/docs/api/DispatchInterceptor.md +0 -60
  144. package/lib/interceptor/redirect-interceptor.js +0 -21
  145. package/lib/mock/pluralizer.js +0 -29
  146. package/lib/web/cache/symbols.js +0 -5
  147. package/lib/web/fetch/file.js +0 -126
  148. package/lib/web/fetch/symbols.js +0 -9
  149. package/lib/web/fileapi/encoding.js +0 -290
  150. package/lib/web/fileapi/filereader.js +0 -344
  151. package/lib/web/fileapi/progressevent.js +0 -78
  152. package/lib/web/fileapi/symbols.js +0 -10
  153. package/lib/web/fileapi/util.js +0 -391
  154. package/lib/web/websocket/symbols.js +0 -12
  155. package/types/file.d.ts +0 -39
  156. package/types/filereader.d.ts +0 -54
package/lib/core/util.js CHANGED
@@ -28,6 +28,10 @@ class BodyAsyncIterable {
28
28
  }
29
29
  }
30
30
 
31
+ /**
32
+ * @param {*} body
33
+ * @returns {*}
34
+ */
31
35
  function wrapRequestBody (body) {
32
36
  if (isStream(body)) {
33
37
  // TODO (fix): Provide some way for the user to cache the file to e.g. /tmp
@@ -67,13 +71,19 @@ function wrapRequestBody (body) {
67
71
  }
68
72
  }
69
73
 
70
- function nop () {}
71
-
74
+ /**
75
+ * @param {*} obj
76
+ * @returns {obj is import('node:stream').Stream}
77
+ */
72
78
  function isStream (obj) {
73
79
  return obj && typeof obj === 'object' && typeof obj.pipe === 'function' && typeof obj.on === 'function'
74
80
  }
75
81
 
76
- // based on https://github.com/node-fetch/fetch-blob/blob/8ab587d34080de94140b54f07168451e7d0b655e/index.js#L229-L241 (MIT License)
82
+ /**
83
+ * @param {*} object
84
+ * @returns {object is Blob}
85
+ * based on https://github.com/node-fetch/fetch-blob/blob/8ab587d34080de94140b54f07168451e7d0b655e/index.js#L229-L241 (MIT License)
86
+ */
77
87
  function isBlobLike (object) {
78
88
  if (object === null) {
79
89
  return false
@@ -91,7 +101,12 @@ function isBlobLike (object) {
91
101
  }
92
102
  }
93
103
 
94
- function buildURL (url, queryParams) {
104
+ /**
105
+ * @param {string} url The URL to add the query params to
106
+ * @param {import('node:querystring').ParsedUrlQueryInput} queryParams The object to serialize into a URL query string
107
+ * @returns {string} The URL with the query params added
108
+ */
109
+ function serializePathWithQuery (url, queryParams) {
95
110
  if (url.includes('?') || url.includes('#')) {
96
111
  throw new Error('Query params cannot be passed when url already contains "?" or "#".')
97
112
  }
@@ -105,6 +120,10 @@ function buildURL (url, queryParams) {
105
120
  return url
106
121
  }
107
122
 
123
+ /**
124
+ * @param {number|string|undefined} port
125
+ * @returns {boolean}
126
+ */
108
127
  function isValidPort (port) {
109
128
  const value = parseInt(port, 10)
110
129
  return (
@@ -114,6 +133,12 @@ function isValidPort (port) {
114
133
  )
115
134
  }
116
135
 
136
+ /**
137
+ * Check if the value is a valid http or https prefixed string.
138
+ *
139
+ * @param {string} value
140
+ * @returns {boolean}
141
+ */
117
142
  function isHttpOrHttpsPrefixed (value) {
118
143
  return (
119
144
  value != null &&
@@ -131,8 +156,15 @@ function isHttpOrHttpsPrefixed (value) {
131
156
  )
132
157
  }
133
158
 
159
+ /**
160
+ * @param {string|URL|Record<string,string>} url
161
+ * @returns {URL}
162
+ */
134
163
  function parseURL (url) {
135
164
  if (typeof url === 'string') {
165
+ /**
166
+ * @type {URL}
167
+ */
136
168
  url = new URL(url)
137
169
 
138
170
  if (!isHttpOrHttpsPrefixed(url.origin || url.protocol)) {
@@ -202,6 +234,10 @@ function parseURL (url) {
202
234
  return url
203
235
  }
204
236
 
237
+ /**
238
+ * @param {string|URL|Record<string, string>} url
239
+ * @returns {URL}
240
+ */
205
241
  function parseOrigin (url) {
206
242
  url = parseURL(url)
207
243
 
@@ -212,6 +248,10 @@ function parseOrigin (url) {
212
248
  return url
213
249
  }
214
250
 
251
+ /**
252
+ * @param {string} host
253
+ * @returns {string}
254
+ */
215
255
  function getHostname (host) {
216
256
  if (host[0] === '[') {
217
257
  const idx = host.indexOf(']')
@@ -226,8 +266,12 @@ function getHostname (host) {
226
266
  return host.substring(0, idx)
227
267
  }
228
268
 
229
- // IP addresses are not valid server names per RFC6066
230
- // > Currently, the only server names supported are DNS hostnames
269
+ /**
270
+ * IP addresses are not valid server names per RFC6066
271
+ * Currently, the only server names supported are DNS hostnames
272
+ * @param {string|null} host
273
+ * @returns {string|null}
274
+ */
231
275
  function getServerName (host) {
232
276
  if (!host) {
233
277
  return null
@@ -243,18 +287,36 @@ function getServerName (host) {
243
287
  return servername
244
288
  }
245
289
 
290
+ /**
291
+ * @function
292
+ * @template T
293
+ * @param {T} obj
294
+ * @returns {T}
295
+ */
246
296
  function deepClone (obj) {
247
297
  return JSON.parse(JSON.stringify(obj))
248
298
  }
249
299
 
300
+ /**
301
+ * @param {*} obj
302
+ * @returns {obj is AsyncIterable}
303
+ */
250
304
  function isAsyncIterable (obj) {
251
305
  return !!(obj != null && typeof obj[Symbol.asyncIterator] === 'function')
252
306
  }
253
307
 
308
+ /**
309
+ * @param {*} obj
310
+ * @returns {obj is Iterable}
311
+ */
254
312
  function isIterable (obj) {
255
313
  return !!(obj != null && (typeof obj[Symbol.iterator] === 'function' || typeof obj[Symbol.asyncIterator] === 'function'))
256
314
  }
257
315
 
316
+ /**
317
+ * @param {Blob|Buffer|import ('stream').Stream} body
318
+ * @returns {number|null}
319
+ */
258
320
  function bodyLength (body) {
259
321
  if (body == null) {
260
322
  return 0
@@ -272,10 +334,19 @@ function bodyLength (body) {
272
334
  return null
273
335
  }
274
336
 
337
+ /**
338
+ * @param {import ('stream').Stream} body
339
+ * @returns {boolean}
340
+ */
275
341
  function isDestroyed (body) {
276
342
  return body && !!(body.destroyed || body[kDestroyed] || (stream.isDestroyed?.(body)))
277
343
  }
278
344
 
345
+ /**
346
+ * @param {import ('stream').Stream} stream
347
+ * @param {Error} [err]
348
+ * @returns {void}
349
+ */
279
350
  function destroy (stream, err) {
280
351
  if (stream == null || !isStream(stream) || isDestroyed(stream)) {
281
352
  return
@@ -300,8 +371,12 @@ function destroy (stream, err) {
300
371
  }
301
372
 
302
373
  const KEEPALIVE_TIMEOUT_EXPR = /timeout=(\d+)/
374
+ /**
375
+ * @param {string} val
376
+ * @returns {number | null}
377
+ */
303
378
  function parseKeepAliveTimeout (val) {
304
- const m = val.toString().match(KEEPALIVE_TIMEOUT_EXPR)
379
+ const m = val.match(KEEPALIVE_TIMEOUT_EXPR)
305
380
  return m ? parseInt(m[1], 10) * 1000 : null
306
381
  }
307
382
 
@@ -326,12 +401,13 @@ function bufferToLowerCasedHeaderName (value) {
326
401
  }
327
402
 
328
403
  /**
329
- * @param {Record<string, string | string[]> | (Buffer | string | (Buffer | string)[])[]} headers
404
+ * @param {(Buffer | string)[]} headers
330
405
  * @param {Record<string, string | string[]>} [obj]
331
406
  * @returns {Record<string, string | string[]>}
332
407
  */
333
408
  function parseHeaders (headers, obj) {
334
409
  if (obj === undefined) obj = {}
410
+
335
411
  for (let i = 0; i < headers.length; i += 2) {
336
412
  const key = headerNameToString(headers[i])
337
413
  let val = obj[key]
@@ -360,9 +436,16 @@ function parseHeaders (headers, obj) {
360
436
  return obj
361
437
  }
362
438
 
439
+ /**
440
+ * @param {Buffer[]} headers
441
+ * @returns {string[]}
442
+ */
363
443
  function parseRawHeaders (headers) {
364
- const len = headers.length
365
- const ret = new Array(len)
444
+ const headersLength = headers.length
445
+ /**
446
+ * @type {string[]}
447
+ */
448
+ const ret = new Array(headersLength)
366
449
 
367
450
  let hasContentLength = false
368
451
  let contentDispositionIdx = -1
@@ -370,7 +453,7 @@ function parseRawHeaders (headers) {
370
453
  let val
371
454
  let kLen = 0
372
455
 
373
- for (let n = 0; n < headers.length; n += 2) {
456
+ for (let n = 0; n < headersLength; n += 2) {
374
457
  key = headers[n]
375
458
  val = headers[n + 1]
376
459
 
@@ -395,16 +478,44 @@ function parseRawHeaders (headers) {
395
478
  return ret
396
479
  }
397
480
 
481
+ /**
482
+ * @param {string[]} headers
483
+ * @param {Buffer[]} headers
484
+ */
485
+ function encodeRawHeaders (headers) {
486
+ if (!Array.isArray(headers)) {
487
+ throw new TypeError('expected headers to be an array')
488
+ }
489
+ return headers.map(x => Buffer.from(x))
490
+ }
491
+
492
+ /**
493
+ * @param {*} buffer
494
+ * @returns {buffer is Buffer}
495
+ */
398
496
  function isBuffer (buffer) {
399
497
  // See, https://github.com/mcollina/undici/pull/319
400
498
  return buffer instanceof Uint8Array || Buffer.isBuffer(buffer)
401
499
  }
402
500
 
403
- function validateHandler (handler, method, upgrade) {
501
+ /**
502
+ * Asserts that the handler object is a request handler.
503
+ *
504
+ * @param {object} handler
505
+ * @param {string} method
506
+ * @param {string} [upgrade]
507
+ * @returns {asserts handler is import('../api/api-request').RequestHandler}
508
+ */
509
+ function assertRequestHandler (handler, method, upgrade) {
404
510
  if (!handler || typeof handler !== 'object') {
405
511
  throw new InvalidArgumentError('handler must be an object')
406
512
  }
407
513
 
514
+ if (typeof handler.onRequestStart === 'function') {
515
+ // TODO (fix): More checks...
516
+ return
517
+ }
518
+
408
519
  if (typeof handler.onConnect !== 'function') {
409
520
  throw new InvalidArgumentError('invalid onConnect method')
410
521
  }
@@ -436,21 +547,33 @@ function validateHandler (handler, method, upgrade) {
436
547
  }
437
548
  }
438
549
 
439
- // A body is disturbed if it has been read from and it cannot
440
- // be re-used without losing state or data.
550
+ /**
551
+ * A body is disturbed if it has been read from and it cannot be re-used without
552
+ * losing state or data.
553
+ * @param {import('node:stream').Readable} body
554
+ * @returns {boolean}
555
+ */
441
556
  function isDisturbed (body) {
442
557
  // TODO (fix): Why is body[kBodyUsed] needed?
443
558
  return !!(body && (stream.isDisturbed(body) || body[kBodyUsed]))
444
559
  }
445
560
 
446
- function isErrored (body) {
447
- return !!(body && stream.isErrored(body))
448
- }
449
-
450
- function isReadable (body) {
451
- return !!(body && stream.isReadable(body))
452
- }
561
+ /**
562
+ * @typedef {object} SocketInfo
563
+ * @property {string} [localAddress]
564
+ * @property {number} [localPort]
565
+ * @property {string} [remoteAddress]
566
+ * @property {number} [remotePort]
567
+ * @property {string} [remoteFamily]
568
+ * @property {number} [timeout]
569
+ * @property {number} bytesWritten
570
+ * @property {number} bytesRead
571
+ */
453
572
 
573
+ /**
574
+ * @param {import('net').Socket} socket
575
+ * @returns {SocketInfo}
576
+ */
454
577
  function getSocketInfo (socket) {
455
578
  return {
456
579
  localAddress: socket.localAddress,
@@ -464,7 +587,10 @@ function getSocketInfo (socket) {
464
587
  }
465
588
  }
466
589
 
467
- /** @type {globalThis['ReadableStream']} */
590
+ /**
591
+ * @param {Iterable} iterable
592
+ * @returns {ReadableStream}
593
+ */
468
594
  function ReadableStreamFrom (iterable) {
469
595
  // We cannot use ReadableStream.from here because it does not return a byte stream.
470
596
 
@@ -489,7 +615,7 @@ function ReadableStreamFrom (iterable) {
489
615
  }
490
616
  return controller.desiredSize > 0
491
617
  },
492
- async cancel (reason) {
618
+ async cancel () {
493
619
  await iterator.return()
494
620
  },
495
621
  type: 'bytes'
@@ -497,8 +623,12 @@ function ReadableStreamFrom (iterable) {
497
623
  )
498
624
  }
499
625
 
500
- // The chunk should be a FormData instance and contains
501
- // all the required methods.
626
+ /**
627
+ * The object should be a FormData instance and contains all the required
628
+ * methods.
629
+ * @param {*} object
630
+ * @returns {object is FormData}
631
+ */
502
632
  function isFormDataLike (object) {
503
633
  return (
504
634
  object &&
@@ -518,31 +648,56 @@ function addAbortListener (signal, listener) {
518
648
  signal.addEventListener('abort', listener, { once: true })
519
649
  return () => signal.removeEventListener('abort', listener)
520
650
  }
521
- signal.addListener('abort', listener)
651
+ signal.once('abort', listener)
522
652
  return () => signal.removeListener('abort', listener)
523
653
  }
524
654
 
525
- const hasToWellFormed = typeof String.prototype.toWellFormed === 'function'
526
- const hasIsWellFormed = typeof String.prototype.isWellFormed === 'function'
527
-
528
655
  /**
529
- * @param {string} val
656
+ * @function
657
+ * @param {string} value
658
+ * @returns {string}
530
659
  */
531
- function toUSVString (val) {
532
- return hasToWellFormed ? `${val}`.toWellFormed() : nodeUtil.toUSVString(val)
533
- }
660
+ const toUSVString = (() => {
661
+ if (typeof String.prototype.toWellFormed === 'function') {
662
+ /**
663
+ * @param {string} value
664
+ * @returns {string}
665
+ */
666
+ return (value) => `${value}`.toWellFormed()
667
+ } else {
668
+ /**
669
+ * @param {string} value
670
+ * @returns {string}
671
+ */
672
+ return nodeUtil.toUSVString
673
+ }
674
+ })()
534
675
 
535
676
  /**
536
- * @param {string} val
677
+ * @param {*} value
678
+ * @returns {boolean}
537
679
  */
538
680
  // TODO: move this to webidl
539
- function isUSVString (val) {
540
- return hasIsWellFormed ? `${val}`.isWellFormed() : toUSVString(val) === `${val}`
541
- }
681
+ const isUSVString = (() => {
682
+ if (typeof String.prototype.isWellFormed === 'function') {
683
+ /**
684
+ * @param {*} value
685
+ * @returns {boolean}
686
+ */
687
+ return (value) => `${value}`.isWellFormed()
688
+ } else {
689
+ /**
690
+ * @param {*} value
691
+ * @returns {boolean}
692
+ */
693
+ return (value) => toUSVString(value) === `${value}`
694
+ }
695
+ })()
542
696
 
543
697
  /**
544
698
  * @see https://tools.ietf.org/html/rfc7230#section-3.2.6
545
699
  * @param {number} c
700
+ * @returns {boolean}
546
701
  */
547
702
  function isTokenCharCode (c) {
548
703
  switch (c) {
@@ -573,6 +728,7 @@ function isTokenCharCode (c) {
573
728
 
574
729
  /**
575
730
  * @param {string} characters
731
+ * @returns {boolean}
576
732
  */
577
733
  function isValidHTTPToken (characters) {
578
734
  if (characters.length === 0) {
@@ -599,17 +755,31 @@ const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/
599
755
 
600
756
  /**
601
757
  * @param {string} characters
758
+ * @returns {boolean}
602
759
  */
603
760
  function isValidHeaderValue (characters) {
604
761
  return !headerCharRegex.test(characters)
605
762
  }
606
763
 
607
- // Parsed accordingly to RFC 9110
608
- // https://www.rfc-editor.org/rfc/rfc9110#field.content-range
764
+ const rangeHeaderRegex = /^bytes (\d+)-(\d+)\/(\d+)?$/
765
+
766
+ /**
767
+ * @typedef {object} RangeHeader
768
+ * @property {number} start
769
+ * @property {number | null} end
770
+ * @property {number | null} size
771
+ */
772
+
773
+ /**
774
+ * Parse accordingly to RFC 9110
775
+ * @see https://www.rfc-editor.org/rfc/rfc9110#field.content-range
776
+ * @param {string} [range]
777
+ * @returns {RangeHeader|null}
778
+ */
609
779
  function parseRangeHeader (range) {
610
780
  if (range == null || range === '') return { start: 0, end: null, size: null }
611
781
 
612
- const m = range ? range.match(/^bytes (\d+)-(\d+)\/(\d+)?$/) : null
782
+ const m = range ? range.match(rangeHeaderRegex) : null
613
783
  return m
614
784
  ? {
615
785
  start: parseInt(m[1]),
@@ -619,6 +789,13 @@ function parseRangeHeader (range) {
619
789
  : null
620
790
  }
621
791
 
792
+ /**
793
+ * @template {import("events").EventEmitter} T
794
+ * @param {T} obj
795
+ * @param {string} name
796
+ * @param {(...args: any[]) => void} listener
797
+ * @returns {T}
798
+ */
622
799
  function addListener (obj, name, listener) {
623
800
  const listeners = (obj[kListeners] ??= [])
624
801
  listeners.push([name, listener])
@@ -626,13 +803,26 @@ function addListener (obj, name, listener) {
626
803
  return obj
627
804
  }
628
805
 
806
+ /**
807
+ * @template {import("events").EventEmitter} T
808
+ * @param {T} obj
809
+ * @returns {T}
810
+ */
629
811
  function removeAllListeners (obj) {
630
- for (const [name, listener] of obj[kListeners] ?? []) {
631
- obj.removeListener(name, listener)
812
+ if (obj[kListeners] != null) {
813
+ for (const [name, listener] of obj[kListeners]) {
814
+ obj.removeListener(name, listener)
815
+ }
816
+ obj[kListeners] = null
632
817
  }
633
- obj[kListeners] = null
818
+ return obj
634
819
  }
635
820
 
821
+ /**
822
+ * @param {import ('../dispatcher/client')} client
823
+ * @param {import ('../core/request')} request
824
+ * @param {Error} err
825
+ */
636
826
  function errorRequest (client, request, err) {
637
827
  try {
638
828
  request.onError(err)
@@ -672,10 +862,7 @@ Object.setPrototypeOf(normalizedMethodRecords, null)
672
862
 
673
863
  module.exports = {
674
864
  kEnumerableProperty,
675
- nop,
676
865
  isDisturbed,
677
- isErrored,
678
- isReadable,
679
866
  toUSVString,
680
867
  isUSVString,
681
868
  isBlobLike,
@@ -692,6 +879,7 @@ module.exports = {
692
879
  removeAllListeners,
693
880
  errorRequest,
694
881
  parseRawHeaders,
882
+ encodeRawHeaders,
695
883
  parseHeaders,
696
884
  parseKeepAliveTimeout,
697
885
  destroy,
@@ -699,10 +887,10 @@ module.exports = {
699
887
  deepClone,
700
888
  ReadableStreamFrom,
701
889
  isBuffer,
702
- validateHandler,
890
+ assertRequestHandler,
703
891
  getSocketInfo,
704
892
  isFormDataLike,
705
- buildURL,
893
+ serializePathWithQuery,
706
894
  addAbortListener,
707
895
  isValidHTTPToken,
708
896
  isValidHeaderValue,
@@ -714,6 +902,6 @@ module.exports = {
714
902
  isHttpOrHttpsPrefixed,
715
903
  nodeMajor,
716
904
  nodeMinor,
717
- safeHTTPMethods: ['GET', 'HEAD', 'OPTIONS', 'TRACE'],
905
+ safeHTTPMethods: Object.freeze(['GET', 'HEAD', 'OPTIONS', 'TRACE']),
718
906
  wrapRequestBody
719
907
  }
@@ -1,17 +1,15 @@
1
1
  'use strict'
2
2
 
3
3
  const { InvalidArgumentError } = require('../core/errors')
4
- const { kClients, kRunning, kClose, kDestroy, kDispatch, kInterceptors } = require('../core/symbols')
4
+ const { kClients, kRunning, kClose, kDestroy, kDispatch } = require('../core/symbols')
5
5
  const DispatcherBase = require('./dispatcher-base')
6
6
  const Pool = require('./pool')
7
7
  const Client = require('./client')
8
8
  const util = require('../core/util')
9
- const createRedirectInterceptor = require('../interceptor/redirect-interceptor')
10
9
 
11
10
  const kOnConnect = Symbol('onConnect')
12
11
  const kOnDisconnect = Symbol('onDisconnect')
13
12
  const kOnConnectionError = Symbol('onConnectionError')
14
- const kMaxRedirections = Symbol('maxRedirections')
15
13
  const kOnDrain = Symbol('onDrain')
16
14
  const kFactory = Symbol('factory')
17
15
  const kOptions = Symbol('options')
@@ -23,9 +21,7 @@ function defaultFactory (origin, opts) {
23
21
  }
24
22
 
25
23
  class Agent extends DispatcherBase {
26
- constructor ({ factory = defaultFactory, maxRedirections = 0, connect, ...options } = {}) {
27
- super()
28
-
24
+ constructor ({ factory = defaultFactory, connect, ...options } = {}) {
29
25
  if (typeof factory !== 'function') {
30
26
  throw new InvalidArgumentError('factory must be a function.')
31
27
  }
@@ -34,23 +30,13 @@ class Agent extends DispatcherBase {
34
30
  throw new InvalidArgumentError('connect must be a function or an object')
35
31
  }
36
32
 
37
- if (!Number.isInteger(maxRedirections) || maxRedirections < 0) {
38
- throw new InvalidArgumentError('maxRedirections must be a positive number')
39
- }
33
+ super()
40
34
 
41
35
  if (connect && typeof connect !== 'function') {
42
36
  connect = { ...connect }
43
37
  }
44
38
 
45
- this[kInterceptors] = options.interceptors?.Agent && Array.isArray(options.interceptors.Agent)
46
- ? options.interceptors.Agent
47
- : [createRedirectInterceptor({ maxRedirections })]
48
-
49
39
  this[kOptions] = { ...util.deepClone(options), connect }
50
- this[kOptions].interceptors = options.interceptors
51
- ? { ...options.interceptors }
52
- : undefined
53
- this[kMaxRedirections] = maxRedirections
54
40
  this[kFactory] = factory
55
41
  this[kClients] = new Map()
56
42
 
@@ -13,7 +13,7 @@ const {
13
13
  kGetDispatcher
14
14
  } = require('./pool-base')
15
15
  const Pool = require('./pool')
16
- const { kUrl, kInterceptors } = require('../core/symbols')
16
+ const { kUrl } = require('../core/symbols')
17
17
  const { parseOrigin } = require('../core/util')
18
18
  const kFactory = Symbol('factory')
19
19
 
@@ -50,6 +50,10 @@ function defaultFactory (origin, opts) {
50
50
 
51
51
  class BalancedPool extends PoolBase {
52
52
  constructor (upstreams = [], { factory = defaultFactory, ...opts } = {}) {
53
+ if (typeof factory !== 'function') {
54
+ throw new InvalidArgumentError('factory must be a function.')
55
+ }
56
+
53
57
  super()
54
58
 
55
59
  this[kOptions] = opts
@@ -63,13 +67,6 @@ class BalancedPool extends PoolBase {
63
67
  upstreams = [upstreams]
64
68
  }
65
69
 
66
- if (typeof factory !== 'function') {
67
- throw new InvalidArgumentError('factory must be a function.')
68
- }
69
-
70
- this[kInterceptors] = opts.interceptors?.BalancedPool && Array.isArray(opts.interceptors.BalancedPool)
71
- ? opts.interceptors.BalancedPool
72
- : []
73
70
  this[kFactory] = factory
74
71
 
75
72
  for (const upstream of upstreams) {