undici 7.16.0 → 7.18.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 +53 -1
  2. package/docs/docs/api/Client.md +1 -0
  3. package/docs/docs/api/DiagnosticsChannel.md +57 -0
  4. package/docs/docs/api/Dispatcher.md +86 -0
  5. package/docs/docs/api/RoundRobinPool.md +145 -0
  6. package/docs/docs/api/WebSocket.md +21 -0
  7. package/docs/docs/best-practices/crawling.md +58 -0
  8. package/index.js +4 -1
  9. package/lib/api/api-upgrade.js +2 -1
  10. package/lib/core/connect.js +4 -1
  11. package/lib/core/diagnostics.js +28 -1
  12. package/lib/core/symbols.js +3 -0
  13. package/lib/core/util.js +29 -31
  14. package/lib/dispatcher/balanced-pool.js +10 -0
  15. package/lib/dispatcher/client-h1.js +0 -16
  16. package/lib/dispatcher/client-h2.js +153 -23
  17. package/lib/dispatcher/client.js +7 -2
  18. package/lib/dispatcher/dispatcher-base.js +11 -12
  19. package/lib/dispatcher/h2c-client.js +7 -78
  20. package/lib/dispatcher/pool-base.js +1 -1
  21. package/lib/dispatcher/proxy-agent.js +13 -2
  22. package/lib/dispatcher/round-robin-pool.js +137 -0
  23. package/lib/encoding/index.js +33 -0
  24. package/lib/handler/cache-handler.js +84 -27
  25. package/lib/handler/deduplication-handler.js +216 -0
  26. package/lib/handler/retry-handler.js +0 -2
  27. package/lib/interceptor/cache.js +35 -17
  28. package/lib/interceptor/decompress.js +2 -1
  29. package/lib/interceptor/deduplicate.js +109 -0
  30. package/lib/interceptor/dns.js +55 -13
  31. package/lib/mock/mock-utils.js +1 -2
  32. package/lib/mock/snapshot-agent.js +11 -5
  33. package/lib/mock/snapshot-recorder.js +12 -4
  34. package/lib/mock/snapshot-utils.js +4 -4
  35. package/lib/util/cache.js +29 -1
  36. package/lib/util/runtime-features.js +124 -0
  37. package/lib/web/cookies/parse.js +1 -1
  38. package/lib/web/fetch/body.js +29 -39
  39. package/lib/web/fetch/data-url.js +12 -160
  40. package/lib/web/fetch/formdata-parser.js +204 -127
  41. package/lib/web/fetch/index.js +18 -6
  42. package/lib/web/fetch/request.js +6 -0
  43. package/lib/web/fetch/response.js +2 -3
  44. package/lib/web/fetch/util.js +2 -65
  45. package/lib/web/infra/index.js +229 -0
  46. package/lib/web/subresource-integrity/subresource-integrity.js +6 -5
  47. package/lib/web/webidl/index.js +4 -2
  48. package/lib/web/websocket/connection.js +31 -21
  49. package/lib/web/websocket/frame.js +9 -15
  50. package/lib/web/websocket/stream/websocketstream.js +1 -1
  51. package/lib/web/websocket/util.js +2 -1
  52. package/package.json +5 -4
  53. package/types/agent.d.ts +1 -1
  54. package/types/api.d.ts +2 -2
  55. package/types/balanced-pool.d.ts +2 -1
  56. package/types/cache-interceptor.d.ts +1 -0
  57. package/types/client.d.ts +1 -1
  58. package/types/connector.d.ts +2 -2
  59. package/types/diagnostics-channel.d.ts +2 -2
  60. package/types/dispatcher.d.ts +12 -12
  61. package/types/fetch.d.ts +4 -4
  62. package/types/formdata.d.ts +1 -1
  63. package/types/h2c-client.d.ts +1 -1
  64. package/types/index.d.ts +9 -1
  65. package/types/interceptors.d.ts +36 -2
  66. package/types/pool.d.ts +1 -1
  67. package/types/readable.d.ts +2 -2
  68. package/types/round-robin-pool.d.ts +41 -0
  69. package/types/websocket.d.ts +9 -9
@@ -0,0 +1,229 @@
1
+ 'use strict'
2
+
3
+ const assert = require('node:assert')
4
+ const { utf8DecodeBytes } = require('../../encoding')
5
+
6
+ /**
7
+ * @param {(char: string) => boolean} condition
8
+ * @param {string} input
9
+ * @param {{ position: number }} position
10
+ * @returns {string}
11
+ *
12
+ * @see https://infra.spec.whatwg.org/#collect-a-sequence-of-code-points
13
+ */
14
+ function collectASequenceOfCodePoints (condition, input, position) {
15
+ // 1. Let result be the empty string.
16
+ let result = ''
17
+
18
+ // 2. While position doesn’t point past the end of input and the
19
+ // code point at position within input meets the condition condition:
20
+ while (position.position < input.length && condition(input[position.position])) {
21
+ // 1. Append that code point to the end of result.
22
+ result += input[position.position]
23
+
24
+ // 2. Advance position by 1.
25
+ position.position++
26
+ }
27
+
28
+ // 3. Return result.
29
+ return result
30
+ }
31
+
32
+ /**
33
+ * A faster collectASequenceOfCodePoints that only works when comparing a single character.
34
+ * @param {string} char
35
+ * @param {string} input
36
+ * @param {{ position: number }} position
37
+ * @returns {string}
38
+ *
39
+ * @see https://infra.spec.whatwg.org/#collect-a-sequence-of-code-points
40
+ */
41
+ function collectASequenceOfCodePointsFast (char, input, position) {
42
+ const idx = input.indexOf(char, position.position)
43
+ const start = position.position
44
+
45
+ if (idx === -1) {
46
+ position.position = input.length
47
+ return input.slice(start)
48
+ }
49
+
50
+ position.position = idx
51
+ return input.slice(start, position.position)
52
+ }
53
+
54
+ const ASCII_WHITESPACE_REPLACE_REGEX = /[\u0009\u000A\u000C\u000D\u0020]/g // eslint-disable-line no-control-regex
55
+
56
+ /**
57
+ * @param {string} data
58
+ * @returns {Uint8Array | 'failure'}
59
+ *
60
+ * @see https://infra.spec.whatwg.org/#forgiving-base64-decode
61
+ */
62
+ function forgivingBase64 (data) {
63
+ // 1. Remove all ASCII whitespace from data.
64
+ data = data.replace(ASCII_WHITESPACE_REPLACE_REGEX, '')
65
+
66
+ let dataLength = data.length
67
+ // 2. If data’s code point length divides by 4 leaving
68
+ // no remainder, then:
69
+ if (dataLength % 4 === 0) {
70
+ // 1. If data ends with one or two U+003D (=) code points,
71
+ // then remove them from data.
72
+ if (data.charCodeAt(dataLength - 1) === 0x003D) {
73
+ --dataLength
74
+ if (data.charCodeAt(dataLength - 1) === 0x003D) {
75
+ --dataLength
76
+ }
77
+ }
78
+ }
79
+
80
+ // 3. If data’s code point length divides by 4 leaving
81
+ // a remainder of 1, then return failure.
82
+ if (dataLength % 4 === 1) {
83
+ return 'failure'
84
+ }
85
+
86
+ // 4. If data contains a code point that is not one of
87
+ // U+002B (+)
88
+ // U+002F (/)
89
+ // ASCII alphanumeric
90
+ // then return failure.
91
+ if (/[^+/0-9A-Za-z]/.test(data.length === dataLength ? data : data.substring(0, dataLength))) {
92
+ return 'failure'
93
+ }
94
+
95
+ const buffer = Buffer.from(data, 'base64')
96
+ return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength)
97
+ }
98
+
99
+ /**
100
+ * @param {number} char
101
+ * @returns {boolean}
102
+ *
103
+ * @see https://infra.spec.whatwg.org/#ascii-whitespace
104
+ */
105
+ function isASCIIWhitespace (char) {
106
+ return (
107
+ char === 0x09 || // \t
108
+ char === 0x0a || // \n
109
+ char === 0x0c || // \f
110
+ char === 0x0d || // \r
111
+ char === 0x20 // space
112
+ )
113
+ }
114
+
115
+ /**
116
+ * @param {Uint8Array} input
117
+ * @returns {string}
118
+ *
119
+ * @see https://infra.spec.whatwg.org/#isomorphic-decode
120
+ */
121
+ function isomorphicDecode (input) {
122
+ // 1. To isomorphic decode a byte sequence input, return a string whose code point
123
+ // length is equal to input’s length and whose code points have the same values
124
+ // as the values of input’s bytes, in the same order.
125
+ const length = input.length
126
+ if ((2 << 15) - 1 > length) {
127
+ return String.fromCharCode.apply(null, input)
128
+ }
129
+ let result = ''
130
+ let i = 0
131
+ let addition = (2 << 15) - 1
132
+ while (i < length) {
133
+ if (i + addition > length) {
134
+ addition = length - i
135
+ }
136
+ result += String.fromCharCode.apply(null, input.subarray(i, i += addition))
137
+ }
138
+ return result
139
+ }
140
+
141
+ const invalidIsomorphicEncodeValueRegex = /[^\x00-\xFF]/ // eslint-disable-line no-control-regex
142
+
143
+ /**
144
+ * @param {string} input
145
+ * @returns {string}
146
+ *
147
+ * @see https://infra.spec.whatwg.org/#isomorphic-encode
148
+ */
149
+ function isomorphicEncode (input) {
150
+ // 1. Assert: input contains no code points greater than U+00FF.
151
+ assert(!invalidIsomorphicEncodeValueRegex.test(input))
152
+
153
+ // 2. Return a byte sequence whose length is equal to input’s code
154
+ // point length and whose bytes have the same values as the
155
+ // values of input’s code points, in the same order
156
+ return input
157
+ }
158
+
159
+ /**
160
+ * @see https://infra.spec.whatwg.org/#parse-json-bytes-to-a-javascript-value
161
+ * @param {Uint8Array} bytes
162
+ */
163
+ function parseJSONFromBytes (bytes) {
164
+ return JSON.parse(utf8DecodeBytes(bytes))
165
+ }
166
+
167
+ /**
168
+ * @param {string} str
169
+ * @param {boolean} [leading=true]
170
+ * @param {boolean} [trailing=true]
171
+ * @returns {string}
172
+ *
173
+ * @see https://infra.spec.whatwg.org/#strip-leading-and-trailing-ascii-whitespace
174
+ */
175
+ function removeASCIIWhitespace (str, leading = true, trailing = true) {
176
+ return removeChars(str, leading, trailing, isASCIIWhitespace)
177
+ }
178
+
179
+ /**
180
+ * @param {string} str
181
+ * @param {boolean} leading
182
+ * @param {boolean} trailing
183
+ * @param {(charCode: number) => boolean} predicate
184
+ * @returns {string}
185
+ */
186
+ function removeChars (str, leading, trailing, predicate) {
187
+ let lead = 0
188
+ let trail = str.length - 1
189
+
190
+ if (leading) {
191
+ while (lead < str.length && predicate(str.charCodeAt(lead))) lead++
192
+ }
193
+
194
+ if (trailing) {
195
+ while (trail > 0 && predicate(str.charCodeAt(trail))) trail--
196
+ }
197
+
198
+ return lead === 0 && trail === str.length - 1 ? str : str.slice(lead, trail + 1)
199
+ }
200
+
201
+ // https://infra.spec.whatwg.org/#serialize-a-javascript-value-to-a-json-string
202
+ function serializeJavascriptValueToJSONString (value) {
203
+ // 1. Let result be ? Call(%JSON.stringify%, undefined, « value »).
204
+ const result = JSON.stringify(value)
205
+
206
+ // 2. If result is undefined, then throw a TypeError.
207
+ if (result === undefined) {
208
+ throw new TypeError('Value is not JSON serializable')
209
+ }
210
+
211
+ // 3. Assert: result is a string.
212
+ assert(typeof result === 'string')
213
+
214
+ // 4. Return result.
215
+ return result
216
+ }
217
+
218
+ module.exports = {
219
+ collectASequenceOfCodePoints,
220
+ collectASequenceOfCodePointsFast,
221
+ forgivingBase64,
222
+ isASCIIWhitespace,
223
+ isomorphicDecode,
224
+ isomorphicEncode,
225
+ parseJSONFromBytes,
226
+ removeASCIIWhitespace,
227
+ removeChars,
228
+ serializeJavascriptValueToJSONString
229
+ }
@@ -1,6 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const assert = require('node:assert')
4
+ const { runtimeFeatures } = require('../../util/runtime-features.js')
4
5
 
5
6
  /**
6
7
  * @typedef {object} Metadata
@@ -29,9 +30,10 @@ const assert = require('node:assert')
29
30
  const validSRIHashAlgorithmTokenSet = new Map([['sha256', 0], ['sha384', 1], ['sha512', 2]])
30
31
 
31
32
  // https://nodejs.org/api/crypto.html#determining-if-crypto-support-is-unavailable
32
- /** @type {import('crypto')} */
33
+ /** @type {import('node:crypto')} */
33
34
  let crypto
34
- try {
35
+
36
+ if (runtimeFeatures.has('crypto')) {
35
37
  crypto = require('node:crypto')
36
38
  const cryptoHashes = crypto.getHashes()
37
39
 
@@ -46,8 +48,7 @@ try {
46
48
  validSRIHashAlgorithmTokenSet.delete(algorithm)
47
49
  }
48
50
  }
49
- /* c8 ignore next 4 */
50
- } catch {
51
+ } else {
51
52
  // If crypto is not available, we cannot support SRI.
52
53
  validSRIHashAlgorithmTokenSet.clear()
53
54
  }
@@ -81,7 +82,7 @@ const isValidSRIHashAlgorithm = /** @type {IsValidSRIHashAlgorithm} */ (
81
82
  *
82
83
  * @see https://w3c.github.io/webappsec-subresource-integrity/#does-response-match-metadatalist
83
84
  */
84
- const bytesMatch = crypto === undefined || validSRIHashAlgorithmTokenSet.size === 0
85
+ const bytesMatch = runtimeFeatures.has('crypto') === false || validSRIHashAlgorithmTokenSet.size === 0
85
86
  // If node is not built with OpenSSL support, we cannot check
86
87
  // a request's integrity, so allow it by default (the spec will
87
88
  // allow requests if an invalid hash is given, as precedence).
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const { types, inspect } = require('node:util')
4
- const { markAsUncloneable } = require('node:worker_threads')
4
+ const { runtimeFeatures } = require('../../util/runtime-features')
5
5
 
6
6
  const UNDEFINED = 1
7
7
  const BOOLEAN = 2
@@ -157,7 +157,9 @@ webidl.util.TypeValueToString = function (o) {
157
157
  }
158
158
  }
159
159
 
160
- webidl.util.markAsUncloneable = markAsUncloneable || (() => {})
160
+ webidl.util.markAsUncloneable = runtimeFeatures.has('markAsUncloneable')
161
+ ? require('node:worker_threads').markAsUncloneable
162
+ : () => {}
161
163
 
162
164
  // https://webidl.spec.whatwg.org/#abstract-opdef-converttoint
163
165
  webidl.util.ConvertToInt = function (V, bitLength, signedness, flags) {
@@ -1,22 +1,20 @@
1
1
  'use strict'
2
2
 
3
3
  const { uid, states, sentCloseFrameState, emptyBuffer, opcodes } = require('./constants')
4
- const { parseExtensions, isClosed, isClosing, isEstablished, validateCloseCodeAndReason } = require('./util')
4
+ const { parseExtensions, isClosed, isClosing, isEstablished, isConnecting, validateCloseCodeAndReason } = require('./util')
5
5
  const { makeRequest } = require('../fetch/request')
6
6
  const { fetching } = require('../fetch/index')
7
7
  const { Headers, getHeadersList } = require('../fetch/headers')
8
8
  const { getDecodeSplit } = require('../fetch/util')
9
9
  const { WebsocketFrameSend } = require('./frame')
10
10
  const assert = require('node:assert')
11
+ const { runtimeFeatures } = require('../../util/runtime-features')
11
12
 
12
- /** @type {import('crypto')} */
13
- let crypto
14
- try {
15
- crypto = require('node:crypto')
16
- /* c8 ignore next 3 */
17
- } catch {
13
+ const crypto = runtimeFeatures.has('crypto')
14
+ ? require('node:crypto')
15
+ : null
18
16
 
19
- }
17
+ let warningEmitted = false
20
18
 
21
19
  /**
22
20
  * @see https://websockets.spec.whatwg.org/#concept-websocket-establish
@@ -95,17 +93,27 @@ function establishWebSocketConnection (url, protocols, client, handler, options)
95
93
  useParallelQueue: true,
96
94
  dispatcher: options.dispatcher,
97
95
  processResponse (response) {
98
- if (response.type === 'error') {
99
- // If the WebSocket connection could not be established, it is also said
100
- // that _The WebSocket Connection is Closed_, but not _cleanly_.
101
- handler.readyState = states.CLOSED
102
- }
103
-
104
96
  // 1. If response is a network error or its status is not 101,
105
97
  // fail the WebSocket connection.
98
+ // if (response.type === 'error' || ((response.socket?.session != null && response.status !== 200) && response.status !== 101)) {
106
99
  if (response.type === 'error' || response.status !== 101) {
107
- failWebsocketConnection(handler, 1002, 'Received network error or non-101 status code.', response.error)
108
- return
100
+ // The presence of a session property on the socket indicates HTTP2
101
+ // HTTP1
102
+ if (response.socket?.session == null) {
103
+ failWebsocketConnection(handler, 1002, 'Received network error or non-101 status code.', response.error)
104
+ return
105
+ }
106
+
107
+ // HTTP2
108
+ if (response.status !== 200) {
109
+ failWebsocketConnection(handler, 1002, 'Received network error or non-200 status code.', response.error)
110
+ return
111
+ }
112
+ }
113
+
114
+ if (warningEmitted === false && response.socket?.session != null) {
115
+ process.emitWarning('WebSocket over HTTP2 is experimental, and subject to change.', 'ExperimentalWarning')
116
+ warningEmitted = true
109
117
  }
110
118
 
111
119
  // 2. If protocols is not the empty list and extracting header
@@ -127,7 +135,8 @@ function establishWebSocketConnection (url, protocols, client, handler, options)
127
135
  // header field contains a value that is not an ASCII case-
128
136
  // insensitive match for the value "websocket", the client MUST
129
137
  // _Fail the WebSocket Connection_.
130
- if (response.headersList.get('Upgrade')?.toLowerCase() !== 'websocket') {
138
+ // For H2, no upgrade header is expected.
139
+ if (response.socket.session == null && response.headersList.get('Upgrade')?.toLowerCase() !== 'websocket') {
131
140
  failWebsocketConnection(handler, 1002, 'Server did not set Upgrade header to "websocket".')
132
141
  return
133
142
  }
@@ -136,7 +145,8 @@ function establishWebSocketConnection (url, protocols, client, handler, options)
136
145
  // |Connection| header field doesn't contain a token that is an
137
146
  // ASCII case-insensitive match for the value "Upgrade", the client
138
147
  // MUST _Fail the WebSocket Connection_.
139
- if (response.headersList.get('Connection')?.toLowerCase() !== 'upgrade') {
148
+ // For H2, no connection header is expected.
149
+ if (response.socket.session == null && response.headersList.get('Connection')?.toLowerCase() !== 'upgrade') {
140
150
  failWebsocketConnection(handler, 1002, 'Server did not set Connection header to "upgrade".')
141
151
  return
142
152
  }
@@ -149,7 +159,7 @@ function establishWebSocketConnection (url, protocols, client, handler, options)
149
159
  // trailing whitespace, the client MUST _Fail the WebSocket
150
160
  // Connection_.
151
161
  const secWSAccept = response.headersList.get('Sec-WebSocket-Accept')
152
- const digest = crypto.createHash('sha1').update(keyValue + uid).digest('base64')
162
+ const digest = crypto.hash('sha1', keyValue + uid, 'base64')
153
163
  if (secWSAccept !== digest) {
154
164
  failWebsocketConnection(handler, 1002, 'Incorrect hash received in Sec-WebSocket-Accept header.')
155
165
  return
@@ -303,10 +313,10 @@ function failWebsocketConnection (handler, code, reason, cause) {
303
313
 
304
314
  handler.controller.abort()
305
315
 
306
- if (!handler.socket) {
316
+ if (isConnecting(handler.readyState)) {
307
317
  // If the connection was not established, we must still emit an 'error' and 'close' events
308
318
  handler.onSocketClose()
309
- } else if (handler.socket.destroyed === false) {
319
+ } else if (handler.socket?.destroyed === false) {
310
320
  handler.socket.destroy()
311
321
  }
312
322
  }
@@ -1,33 +1,27 @@
1
1
  'use strict'
2
2
 
3
+ const { runtimeFeatures } = require('../../util/runtime-features')
3
4
  const { maxUnsigned16Bit, opcodes } = require('./constants')
4
5
 
5
6
  const BUFFER_SIZE = 8 * 1024
6
7
 
7
- /** @type {import('crypto')} */
8
- let crypto
9
8
  let buffer = null
10
9
  let bufIdx = BUFFER_SIZE
11
10
 
12
- try {
13
- crypto = require('node:crypto')
14
- /* c8 ignore next 3 */
15
- } catch {
16
- crypto = {
17
- // not full compatibility, but minimum.
18
- randomFillSync: function randomFillSync (buffer, _offset, _size) {
19
- for (let i = 0; i < buffer.length; ++i) {
20
- buffer[i] = Math.random() * 255 | 0
21
- }
22
- return buffer
11
+ const randomFillSync = runtimeFeatures.has('crypto')
12
+ ? require('node:crypto').randomFillSync
13
+ // not full compatibility, but minimum.
14
+ : function randomFillSync (buffer, _offset, _size) {
15
+ for (let i = 0; i < buffer.length; ++i) {
16
+ buffer[i] = Math.random() * 255 | 0
23
17
  }
18
+ return buffer
24
19
  }
25
- }
26
20
 
27
21
  function generateMask () {
28
22
  if (bufIdx === BUFFER_SIZE) {
29
23
  bufIdx = 0
30
- crypto.randomFillSync((buffer ??= Buffer.allocUnsafeSlow(BUFFER_SIZE)), 0, BUFFER_SIZE)
24
+ randomFillSync((buffer ??= Buffer.allocUnsafeSlow(BUFFER_SIZE)), 0, BUFFER_SIZE)
31
25
  }
32
26
  return [buffer[bufIdx++], buffer[bufIdx++], buffer[bufIdx++], buffer[bufIdx++]]
33
27
  }
@@ -10,8 +10,8 @@ const { channels } = require('../../../core/diagnostics')
10
10
  const { WebsocketFrameSend } = require('../frame')
11
11
  const { ByteParser } = require('../receiver')
12
12
  const { WebSocketError, createUnvalidatedWebSocketError } = require('./websocketerror')
13
- const { utf8DecodeBytes } = require('../../fetch/util')
14
13
  const { kEnumerableProperty } = require('../../../core/util')
14
+ const { utf8DecodeBytes } = require('../../../encoding')
15
15
 
16
16
  let emittedExperimentalWarning = false
17
17
 
@@ -2,7 +2,8 @@
2
2
 
3
3
  const { states, opcodes } = require('./constants')
4
4
  const { isUtf8 } = require('node:buffer')
5
- const { collectASequenceOfCodePointsFast, removeHTTPWhitespace } = require('../fetch/data-url')
5
+ const { removeHTTPWhitespace } = require('../fetch/data-url')
6
+ const { collectASequenceOfCodePointsFast } = require('../infra')
6
7
 
7
8
  /**
8
9
  * @param {number} readyState
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "undici",
3
- "version": "7.16.0",
3
+ "version": "7.18.0",
4
4
  "description": "An HTTP/1.1 client, written from scratch for Node.js",
5
5
  "homepage": "https://undici.nodejs.org",
6
6
  "bugs": {
@@ -69,12 +69,12 @@
69
69
  "lint:fix": "eslint --fix --cache",
70
70
  "test": "npm run test:javascript && cross-env NODE_V8_COVERAGE= npm run test:typescript",
71
71
  "test:javascript": "npm run test:javascript:no-jest && npm run test:jest",
72
- "test:javascript:no-jest": "npm run generate-pem && npm run test:unit && npm run test:fetch && npm run test:node-fetch && npm run test:cache && npm run test:cache-interceptor && npm run test:interceptors && npm run test:cookies && npm run test:eventsource && npm run test:subresource-integrity && npm run test:wpt && npm run test:websocket && npm run test:node-test && npm run test:cache-tests",
72
+ "test:javascript:no-jest": "npm run generate-pem && npm run test:unit && npm run test:fetch && npm run test:node-fetch && npm run test:infra && npm run test:cache && npm run test:cache-interceptor && npm run test:interceptors && npm run test:cookies && npm run test:eventsource && npm run test:subresource-integrity && npm run test:wpt && npm run test:websocket && npm run test:node-test && npm run test:cache-tests",
73
73
  "test:javascript:without-intl": "npm run test:javascript:no-jest",
74
74
  "test:busboy": "borp -p \"test/busboy/*.js\"",
75
75
  "test:cache": "borp -p \"test/cache/*.js\"",
76
- "test:sqlite": "cross-env NODE_OPTIONS=--experimental-sqlite borp -p \"test/cache-interceptor/*.js\"",
77
76
  "test:cache-interceptor": "borp -p \"test/cache-interceptor/*.js\"",
77
+ "test:cache-interceptor:sqlite": "cross-env NODE_OPTIONS=--experimental-sqlite npm run test:cache-interceptor",
78
78
  "test:cookies": "borp -p \"test/cookie/*.js\"",
79
79
  "test:eventsource": "npm run build:node && borp --expose-gc -p \"test/eventsource/*.js\"",
80
80
  "test:fuzzing": "node test/fuzzing/fuzzing.test.js",
@@ -83,6 +83,7 @@
83
83
  "test:h2": "npm run test:h2:core && npm run test:h2:fetch",
84
84
  "test:h2:core": "borp -p \"test/+(http2|h2)*.js\"",
85
85
  "test:h2:fetch": "npm run build:node && borp -p \"test/fetch/http2*.js\"",
86
+ "test:infra": "borp -p \"test/infra/*.js\"",
86
87
  "test:interceptors": "borp -p \"test/interceptors/*.js\"",
87
88
  "test:jest": "cross-env NODE_V8_COVERAGE= jest",
88
89
  "test:unit": "borp --expose-gc -p \"test/*.js\"",
@@ -112,7 +113,7 @@
112
113
  "@matteo.collina/tspl": "^0.2.0",
113
114
  "@metcoder95/https-pem": "^1.0.0",
114
115
  "@sinonjs/fake-timers": "^12.0.0",
115
- "@types/node": "^18.19.50",
116
+ "@types/node": "^20.19.22",
116
117
  "abort-controller": "^3.0.0",
117
118
  "borp": "^0.20.0",
118
119
  "c8": "^10.0.0",
package/types/agent.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { URL } from 'url'
1
+ import { URL } from 'node:url'
2
2
  import Pool from './pool'
3
3
  import Dispatcher from './dispatcher'
4
4
  import TClientStats from './client-stats'
package/types/api.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { URL, UrlObject } from 'url'
2
- import { Duplex } from 'stream'
1
+ import { URL, UrlObject } from 'node:url'
2
+ import { Duplex } from 'node:stream'
3
3
  import Dispatcher from './dispatcher'
4
4
 
5
5
  /** Performs an HTTP request. */
@@ -1,6 +1,6 @@
1
1
  import Pool from './pool'
2
2
  import Dispatcher from './dispatcher'
3
- import { URL } from 'url'
3
+ import { URL } from 'node:url'
4
4
 
5
5
  export default BalancedPool
6
6
 
@@ -11,6 +11,7 @@ declare class BalancedPool extends Dispatcher {
11
11
 
12
12
  addUpstream (upstream: string | URL): BalancedPool
13
13
  removeUpstream (upstream: string | URL): BalancedPool
14
+ getUpstream (upstream: string | URL): Pool | undefined
14
15
  upstreams: Array<string>
15
16
 
16
17
  /** `true` after `pool.close()` has been called. */
@@ -38,6 +38,7 @@ declare namespace CacheHandler {
38
38
  * @default 'shared'
39
39
  */
40
40
  type?: 'shared' | 'private'
41
+
41
42
  }
42
43
 
43
44
  export interface CacheControlDirectives {
package/types/client.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { URL } from 'url'
1
+ import { URL } from 'node:url'
2
2
  import Dispatcher from './dispatcher'
3
3
  import buildConnector from './connector'
4
4
  import TClientStats from './client-stats'
@@ -1,5 +1,5 @@
1
- import { TLSSocket, ConnectionOptions } from 'tls'
2
- import { IpcNetConnectOpts, Socket, TcpNetConnectOpts } from 'net'
1
+ import { TLSSocket, ConnectionOptions } from 'node:tls'
2
+ import { IpcNetConnectOpts, Socket, TcpNetConnectOpts } from 'node:net'
3
3
 
4
4
  export default buildConnector
5
5
  declare function buildConnector (options?: buildConnector.BuildOptions): buildConnector.connector
@@ -1,5 +1,5 @@
1
- import { Socket } from 'net'
2
- import { URL } from 'url'
1
+ import { Socket } from 'node:net'
2
+ import { URL } from 'node:url'
3
3
  import buildConnector from './connector'
4
4
  import Dispatcher from './dispatcher'
5
5
 
@@ -1,7 +1,7 @@
1
- import { URL } from 'url'
2
- import { Duplex, Readable, Writable } from 'stream'
3
- import { EventEmitter } from 'events'
4
- import { Blob } from 'buffer'
1
+ import { URL } from 'node:url'
2
+ import { Duplex, Readable, Writable } from 'node:stream'
3
+ import { EventEmitter } from 'node:events'
4
+ import { Blob } from 'node:buffer'
5
5
  import { IncomingHttpHeaders } from './header'
6
6
  import BodyReadable from './readable'
7
7
  import { FormData } from './formdata'
@@ -19,30 +19,30 @@ declare class Dispatcher extends EventEmitter {
19
19
  /** Dispatches a request. This API is expected to evolve through semver-major versions and is less stable than the preceding higher level APIs. It is primarily intended for library developers who implement higher level APIs on top of this. */
20
20
  dispatch (options: Dispatcher.DispatchOptions, handler: Dispatcher.DispatchHandler): boolean
21
21
  /** Starts two-way communications with the requested resource. */
22
- connect<TOpaque = null>(options: Dispatcher.ConnectOptions<TOpaque>): Promise<Dispatcher.ConnectData<TOpaque>>
23
22
  connect<TOpaque = null>(options: Dispatcher.ConnectOptions<TOpaque>, callback: (err: Error | null, data: Dispatcher.ConnectData<TOpaque>) => void): void
23
+ connect<TOpaque = null>(options: Dispatcher.ConnectOptions<TOpaque>): Promise<Dispatcher.ConnectData<TOpaque>>
24
24
  /** Compose a chain of dispatchers */
25
25
  compose (dispatchers: Dispatcher.DispatcherComposeInterceptor[]): Dispatcher.ComposedDispatcher
26
26
  compose (...dispatchers: Dispatcher.DispatcherComposeInterceptor[]): Dispatcher.ComposedDispatcher
27
27
  /** Performs an HTTP request. */
28
- request<TOpaque = null>(options: Dispatcher.RequestOptions<TOpaque>): Promise<Dispatcher.ResponseData<TOpaque>>
29
28
  request<TOpaque = null>(options: Dispatcher.RequestOptions<TOpaque>, callback: (err: Error | null, data: Dispatcher.ResponseData<TOpaque>) => void): void
29
+ request<TOpaque = null>(options: Dispatcher.RequestOptions<TOpaque>): Promise<Dispatcher.ResponseData<TOpaque>>
30
30
  /** For easy use with `stream.pipeline`. */
31
31
  pipeline<TOpaque = null>(options: Dispatcher.PipelineOptions<TOpaque>, handler: Dispatcher.PipelineHandler<TOpaque>): Duplex
32
32
  /** A faster version of `Dispatcher.request`. */
33
- stream<TOpaque = null>(options: Dispatcher.RequestOptions<TOpaque>, factory: Dispatcher.StreamFactory<TOpaque>): Promise<Dispatcher.StreamData<TOpaque>>
34
33
  stream<TOpaque = null>(options: Dispatcher.RequestOptions<TOpaque>, factory: Dispatcher.StreamFactory<TOpaque>, callback: (err: Error | null, data: Dispatcher.StreamData<TOpaque>) => void): void
34
+ stream<TOpaque = null>(options: Dispatcher.RequestOptions<TOpaque>, factory: Dispatcher.StreamFactory<TOpaque>): Promise<Dispatcher.StreamData<TOpaque>>
35
35
  /** Upgrade to a different protocol. */
36
- upgrade (options: Dispatcher.UpgradeOptions): Promise<Dispatcher.UpgradeData>
37
36
  upgrade (options: Dispatcher.UpgradeOptions, callback: (err: Error | null, data: Dispatcher.UpgradeData) => void): void
37
+ upgrade (options: Dispatcher.UpgradeOptions): Promise<Dispatcher.UpgradeData>
38
38
  /** Closes the client and gracefully waits for enqueued requests to complete before invoking the callback (or returning a promise if no callback is provided). */
39
- close (): Promise<void>
40
39
  close (callback: () => void): void
40
+ close (): Promise<void>
41
41
  /** Destroy the client abruptly with the given err. All the pending and running requests will be asynchronously aborted and error. Waits until socket is closed before invoking the callback (or returning a promise if no callback is provided). Since this operation is asynchronously dispatched there might still be some progress on dispatched requests. */
42
- destroy (): Promise<void>
43
- destroy (err: Error | null): Promise<void>
44
- destroy (callback: () => void): void
45
42
  destroy (err: Error | null, callback: () => void): void
43
+ destroy (callback: () => void): void
44
+ destroy (err: Error | null): Promise<void>
45
+ destroy (): Promise<void>
46
46
 
47
47
  on (eventName: 'connect', callback: (origin: URL, targets: readonly Dispatcher[]) => void): this
48
48
  on (eventName: 'disconnect', callback: (origin: URL, targets: readonly Dispatcher[], error: Errors.UndiciError) => void): this
package/types/fetch.d.ts CHANGED
@@ -2,9 +2,9 @@
2
2
  // and https://github.com/node-fetch/node-fetch/blob/914ce6be5ec67a8bab63d68510aabf07cb818b6d/index.d.ts (MIT license)
3
3
  /// <reference types="node" />
4
4
 
5
- import { Blob } from 'buffer'
6
- import { URL, URLSearchParams } from 'url'
7
- import { ReadableStream } from 'stream/web'
5
+ import { Blob } from 'node:buffer'
6
+ import { URL, URLSearchParams } from 'node:url'
7
+ import { ReadableStream } from 'node:stream/web'
8
8
  import { FormData } from './formdata'
9
9
  import { HeaderRecord } from './header'
10
10
  import Dispatcher from './dispatcher'
@@ -207,5 +207,5 @@ export declare class Response extends BodyMixin {
207
207
 
208
208
  static error (): Response
209
209
  static json (data: any, init?: ResponseInit): Response
210
- static redirect (url: string | URL, status: ResponseRedirectStatus): Response
210
+ static redirect (url: string | URL, status?: ResponseRedirectStatus): Response
211
211
  }
@@ -1,7 +1,7 @@
1
1
  // Based on https://github.com/octet-stream/form-data/blob/2d0f0dc371517444ce1f22cdde13f51995d0953a/lib/FormData.ts (MIT)
2
2
  /// <reference types="node" />
3
3
 
4
- import { File } from 'buffer'
4
+ import { File } from 'node:buffer'
5
5
  import { SpecIterableIterator } from './fetch'
6
6
 
7
7
  /**
@@ -1,4 +1,4 @@
1
- import { URL } from 'url'
1
+ import { URL } from 'node:url'
2
2
  import Dispatcher from './dispatcher'
3
3
  import buildConnector from './connector'
4
4