undici 7.15.0 → 7.17.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 (93) hide show
  1. package/README.md +48 -2
  2. package/docs/docs/api/Agent.md +1 -0
  3. package/docs/docs/api/Client.md +1 -0
  4. package/docs/docs/api/DiagnosticsChannel.md +57 -0
  5. package/docs/docs/api/Dispatcher.md +86 -0
  6. package/docs/docs/api/Errors.md +0 -1
  7. package/docs/docs/api/RoundRobinPool.md +145 -0
  8. package/docs/docs/api/WebSocket.md +21 -0
  9. package/docs/docs/best-practices/crawling.md +58 -0
  10. package/index-fetch.js +2 -2
  11. package/index.js +8 -9
  12. package/lib/api/api-request.js +22 -8
  13. package/lib/api/api-upgrade.js +2 -1
  14. package/lib/api/readable.js +7 -5
  15. package/lib/core/connect.js +4 -1
  16. package/lib/core/diagnostics.js +28 -1
  17. package/lib/core/errors.js +217 -13
  18. package/lib/core/request.js +5 -1
  19. package/lib/core/symbols.js +3 -0
  20. package/lib/core/util.js +61 -41
  21. package/lib/dispatcher/agent.js +19 -7
  22. package/lib/dispatcher/balanced-pool.js +10 -0
  23. package/lib/dispatcher/client-h1.js +18 -23
  24. package/lib/dispatcher/client-h2.js +166 -26
  25. package/lib/dispatcher/client.js +64 -59
  26. package/lib/dispatcher/dispatcher-base.js +20 -16
  27. package/lib/dispatcher/env-http-proxy-agent.js +12 -16
  28. package/lib/dispatcher/fixed-queue.js +15 -39
  29. package/lib/dispatcher/h2c-client.js +7 -78
  30. package/lib/dispatcher/pool-base.js +60 -43
  31. package/lib/dispatcher/pool.js +2 -2
  32. package/lib/dispatcher/proxy-agent.js +27 -11
  33. package/lib/dispatcher/round-robin-pool.js +137 -0
  34. package/lib/encoding/index.js +33 -0
  35. package/lib/global.js +19 -1
  36. package/lib/handler/cache-handler.js +84 -27
  37. package/lib/handler/deduplication-handler.js +216 -0
  38. package/lib/handler/retry-handler.js +0 -2
  39. package/lib/interceptor/cache.js +94 -15
  40. package/lib/interceptor/decompress.js +2 -1
  41. package/lib/interceptor/deduplicate.js +109 -0
  42. package/lib/interceptor/dns.js +55 -13
  43. package/lib/mock/mock-agent.js +4 -4
  44. package/lib/mock/mock-errors.js +10 -0
  45. package/lib/mock/mock-utils.js +13 -12
  46. package/lib/mock/snapshot-agent.js +11 -5
  47. package/lib/mock/snapshot-recorder.js +12 -4
  48. package/lib/mock/snapshot-utils.js +4 -4
  49. package/lib/util/cache.js +29 -1
  50. package/lib/util/date.js +534 -140
  51. package/lib/util/runtime-features.js +124 -0
  52. package/lib/web/cookies/index.js +1 -1
  53. package/lib/web/cookies/parse.js +1 -1
  54. package/lib/web/eventsource/eventsource-stream.js +2 -2
  55. package/lib/web/eventsource/eventsource.js +34 -29
  56. package/lib/web/eventsource/util.js +1 -9
  57. package/lib/web/fetch/body.js +45 -61
  58. package/lib/web/fetch/data-url.js +12 -160
  59. package/lib/web/fetch/formdata-parser.js +204 -127
  60. package/lib/web/fetch/index.js +21 -19
  61. package/lib/web/fetch/request.js +6 -0
  62. package/lib/web/fetch/response.js +4 -7
  63. package/lib/web/fetch/util.js +10 -79
  64. package/lib/web/infra/index.js +229 -0
  65. package/lib/web/subresource-integrity/subresource-integrity.js +6 -5
  66. package/lib/web/webidl/index.js +207 -44
  67. package/lib/web/websocket/connection.js +33 -22
  68. package/lib/web/websocket/events.js +1 -1
  69. package/lib/web/websocket/frame.js +9 -15
  70. package/lib/web/websocket/stream/websocketerror.js +22 -1
  71. package/lib/web/websocket/stream/websocketstream.js +17 -8
  72. package/lib/web/websocket/util.js +2 -1
  73. package/lib/web/websocket/websocket.js +32 -42
  74. package/package.json +9 -7
  75. package/types/agent.d.ts +2 -1
  76. package/types/api.d.ts +2 -2
  77. package/types/balanced-pool.d.ts +2 -1
  78. package/types/cache-interceptor.d.ts +1 -0
  79. package/types/client.d.ts +1 -1
  80. package/types/connector.d.ts +2 -2
  81. package/types/diagnostics-channel.d.ts +2 -2
  82. package/types/dispatcher.d.ts +12 -12
  83. package/types/errors.d.ts +5 -15
  84. package/types/fetch.d.ts +4 -4
  85. package/types/formdata.d.ts +1 -1
  86. package/types/h2c-client.d.ts +1 -1
  87. package/types/index.d.ts +9 -1
  88. package/types/interceptors.d.ts +36 -2
  89. package/types/pool.d.ts +1 -1
  90. package/types/readable.d.ts +2 -2
  91. package/types/round-robin-pool.d.ts +41 -0
  92. package/types/webidl.d.ts +82 -21
  93. package/types/websocket.d.ts +9 -9
@@ -6,13 +6,12 @@ const { states, opcodes, sentCloseFrameState } = require('../constants')
6
6
  const { webidl } = require('../../webidl')
7
7
  const { getURLRecord, isValidSubprotocol, isEstablished, utf8Decode } = require('../util')
8
8
  const { establishWebSocketConnection, failWebsocketConnection, closeWebSocketConnection } = require('../connection')
9
- const { isArrayBuffer } = require('node:util/types')
10
9
  const { channels } = require('../../../core/diagnostics')
11
10
  const { WebsocketFrameSend } = require('../frame')
12
11
  const { ByteParser } = require('../receiver')
13
12
  const { WebSocketError, createUnvalidatedWebSocketError } = require('./websocketerror')
14
- const { utf8DecodeBytes } = require('../../fetch/util')
15
13
  const { kEnumerableProperty } = require('../../../core/util')
14
+ const { utf8DecodeBytes } = require('../../../encoding')
16
15
 
17
16
  let emittedExperimentalWarning = false
18
17
 
@@ -46,7 +45,6 @@ class WebSocketStream {
46
45
  #handler = {
47
46
  // https://whatpr.org/websockets/48/7b748d3...d5570f3.html#feedback-to-websocket-stream-from-the-protocol
48
47
  onConnectionEstablished: (response, extensions) => this.#onConnectionEstablished(response, extensions),
49
- onFail: (_code, _reason) => {},
50
48
  onMessage: (opcode, data) => this.#onMessage(opcode, data),
51
49
  onParserError: (err) => failWebsocketConnection(this.#handler, null, err.message),
52
50
  onParserDrain: () => this.#handler.socket.resume(),
@@ -200,6 +198,9 @@ class WebSocketStream {
200
198
  }
201
199
 
202
200
  #write (chunk) {
201
+ // See /websockets/stream/tentative/write.any.html
202
+ chunk = webidl.converters.WebSocketStreamWrite(chunk)
203
+
203
204
  // 1. Let promise be a new promise created in stream ’s relevant realm .
204
205
  const promise = createDeferredPromise()
205
206
 
@@ -210,9 +211,9 @@ class WebSocketStream {
210
211
  let opcode = null
211
212
 
212
213
  // 4. If chunk is a BufferSource ,
213
- if (ArrayBuffer.isView(chunk) || isArrayBuffer(chunk)) {
214
+ if (webidl.is.BufferSource(chunk)) {
214
215
  // 4.1. Set data to a copy of the bytes given chunk .
215
- data = new Uint8Array(ArrayBuffer.isView(chunk) ? new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength) : chunk)
216
+ data = new Uint8Array(ArrayBuffer.isView(chunk) ? new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength) : chunk.slice())
216
217
 
217
218
  // 4.2. Set opcode to a binary frame opcode.
218
219
  opcode = opcodes.BINARY
@@ -227,7 +228,7 @@ class WebSocketStream {
227
228
  string = webidl.converters.DOMString(chunk)
228
229
  } catch (e) {
229
230
  promise.reject(e)
230
- return
231
+ return promise.promise
231
232
  }
232
233
 
233
234
  // 5.2. Set data to the result of UTF-8 encoding string .
@@ -250,7 +251,7 @@ class WebSocketStream {
250
251
  }
251
252
 
252
253
  // 6.3. Queue a global task on the WebSocket task source given stream ’s relevant global object to resolve promise with undefined.
253
- return promise
254
+ return promise.promise
254
255
  }
255
256
 
256
257
  /** @type {import('../websocket').Handler['onConnectionEstablished']} */
@@ -476,7 +477,7 @@ webidl.converters.WebSocketStreamOptions = webidl.dictionaryConverter([
476
477
  webidl.converters.WebSocketCloseInfo = webidl.dictionaryConverter([
477
478
  {
478
479
  key: 'closeCode',
479
- converter: (V) => webidl.converters['unsigned short'](V, { enforceRange: true })
480
+ converter: (V) => webidl.converters['unsigned short'](V, webidl.attributes.EnforceRange)
480
481
  },
481
482
  {
482
483
  key: 'reason',
@@ -485,4 +486,12 @@ webidl.converters.WebSocketCloseInfo = webidl.dictionaryConverter([
485
486
  }
486
487
  ])
487
488
 
489
+ webidl.converters.WebSocketStreamWrite = function (V) {
490
+ if (typeof V === 'string') {
491
+ return webidl.converters.USVString(V)
492
+ }
493
+
494
+ return webidl.converters.BufferSource(V)
495
+ }
496
+
488
497
  module.exports = { WebSocketStream }
@@ -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
@@ -28,7 +28,6 @@ const { channels } = require('../../core/diagnostics')
28
28
  /**
29
29
  * @typedef {object} Handler
30
30
  * @property {(response: any, extensions?: string[]) => void} onConnectionEstablished
31
- * @property {(code: number, reason: any) => void} onFail
32
31
  * @property {(opcode: number, data: Buffer) => void} onMessage
33
32
  * @property {(error: Error) => void} onParserError
34
33
  * @property {() => void} onParserDrain
@@ -64,7 +63,6 @@ class WebSocket extends EventTarget {
64
63
  /** @type {Handler} */
65
64
  #handler = {
66
65
  onConnectionEstablished: (response, extensions) => this.#onConnectionEstablished(response, extensions),
67
- onFail: (code, reason, cause) => this.#onFail(code, reason, cause),
68
66
  onMessage: (opcode, data) => this.#onMessage(opcode, data),
69
67
  onParserError: (err) => failWebsocketConnection(this.#handler, null, err.message),
70
68
  onParserDrain: () => this.#onParserDrain(),
@@ -195,7 +193,7 @@ class WebSocket extends EventTarget {
195
193
  const prefix = 'WebSocket.close'
196
194
 
197
195
  if (code !== undefined) {
198
- code = webidl.converters['unsigned short'](code, prefix, 'code', { clamp: true })
196
+ code = webidl.converters['unsigned short'](code, prefix, 'code', webidl.attributes.Clamp)
199
197
  }
200
198
 
201
199
  if (reason !== undefined) {
@@ -355,9 +353,11 @@ class WebSocket extends EventTarget {
355
353
  this.removeEventListener('open', this.#events.open)
356
354
  }
357
355
 
358
- if (typeof fn === 'function') {
356
+ const listener = webidl.converters.EventHandlerNonNull(fn)
357
+
358
+ if (listener !== null) {
359
+ this.addEventListener('open', listener)
359
360
  this.#events.open = fn
360
- this.addEventListener('open', fn)
361
361
  } else {
362
362
  this.#events.open = null
363
363
  }
@@ -376,9 +376,11 @@ class WebSocket extends EventTarget {
376
376
  this.removeEventListener('error', this.#events.error)
377
377
  }
378
378
 
379
- if (typeof fn === 'function') {
379
+ const listener = webidl.converters.EventHandlerNonNull(fn)
380
+
381
+ if (listener !== null) {
382
+ this.addEventListener('error', listener)
380
383
  this.#events.error = fn
381
- this.addEventListener('error', fn)
382
384
  } else {
383
385
  this.#events.error = null
384
386
  }
@@ -397,9 +399,11 @@ class WebSocket extends EventTarget {
397
399
  this.removeEventListener('close', this.#events.close)
398
400
  }
399
401
 
400
- if (typeof fn === 'function') {
402
+ const listener = webidl.converters.EventHandlerNonNull(fn)
403
+
404
+ if (listener !== null) {
405
+ this.addEventListener('close', listener)
401
406
  this.#events.close = fn
402
- this.addEventListener('close', fn)
403
407
  } else {
404
408
  this.#events.close = null
405
409
  }
@@ -418,9 +422,11 @@ class WebSocket extends EventTarget {
418
422
  this.removeEventListener('message', this.#events.message)
419
423
  }
420
424
 
421
- if (typeof fn === 'function') {
425
+ const listener = webidl.converters.EventHandlerNonNull(fn)
426
+
427
+ if (listener !== null) {
428
+ this.addEventListener('message', listener)
422
429
  this.#events.message = fn
423
- this.addEventListener('message', fn)
424
430
  } else {
425
431
  this.#events.message = null
426
432
  }
@@ -498,26 +504,6 @@ class WebSocket extends EventTarget {
498
504
  }
499
505
  }
500
506
 
501
- #onFail (code, reason, cause) {
502
- if (reason) {
503
- // TODO: process.nextTick
504
- fireEvent('error', this, (type, init) => new ErrorEvent(type, init), {
505
- error: new Error(reason, cause ? { cause } : undefined),
506
- message: reason
507
- })
508
- }
509
-
510
- if (!this.#handler.wasEverConnected) {
511
- this.#handler.readyState = states.CLOSED
512
-
513
- // If the WebSocket connection could not be established, it is also said
514
- // that _The WebSocket Connection is Closed_, but not _cleanly_.
515
- fireEvent('close', this, (type, init) => new CloseEvent(type, init), {
516
- wasClean: false, code, reason
517
- })
518
- }
519
- }
520
-
521
507
  #onMessage (type, data) {
522
508
  // 1. If ready state is not OPEN (1), then return.
523
509
  if (this.#handler.readyState !== states.OPEN) {
@@ -578,18 +564,11 @@ class WebSocket extends EventTarget {
578
564
  let code = 1005
579
565
  let reason = ''
580
566
 
581
- const result = this.#parser.closingInfo
567
+ const result = this.#parser?.closingInfo
582
568
 
583
569
  if (result && !result.error) {
584
570
  code = result.code ?? 1005
585
571
  reason = result.reason
586
- } else if (!this.#handler.closeState.has(sentCloseFrameState.RECEIVED)) {
587
- // If _The WebSocket
588
- // Connection is Closed_ and no Close control frame was received by the
589
- // endpoint (such as could occur if the underlying transport connection
590
- // is lost), _The WebSocket Connection Close Code_ is considered to be
591
- // 1006.
592
- code = 1006
593
572
  }
594
573
 
595
574
  // 1. Change the ready state to CLOSED (3).
@@ -599,7 +578,18 @@ class WebSocket extends EventTarget {
599
578
  // connection, or if the WebSocket connection was closed
600
579
  // after being flagged as full, fire an event named error
601
580
  // at the WebSocket object.
602
- // TODO
581
+ if (!this.#handler.closeState.has(sentCloseFrameState.RECEIVED)) {
582
+ // If _The WebSocket
583
+ // Connection is Closed_ and no Close control frame was received by the
584
+ // endpoint (such as could occur if the underlying transport connection
585
+ // is lost), _The WebSocket Connection Close Code_ is considered to be
586
+ // 1006.
587
+ code = 1006
588
+
589
+ fireEvent('error', this, (type, init) => new ErrorEvent(type, init), {
590
+ error: new TypeError(reason)
591
+ })
592
+ }
603
593
 
604
594
  // 3. Fire an event named close at the WebSocket object,
605
595
  // using CloseEvent, with the wasClean attribute
@@ -708,7 +698,7 @@ webidl.converters.WebSocketInit = webidl.dictionaryConverter([
708
698
  {
709
699
  key: 'protocols',
710
700
  converter: webidl.converters['DOMString or sequence<DOMString>'],
711
- defaultValue: () => new Array(0)
701
+ defaultValue: () => []
712
702
  },
713
703
  {
714
704
  key: 'dispatcher',
@@ -735,7 +725,7 @@ webidl.converters.WebSocketSendData = function (V) {
735
725
  return V
736
726
  }
737
727
 
738
- if (ArrayBuffer.isView(V) || isArrayBuffer(V)) {
728
+ if (webidl.is.BufferSource(V)) {
739
729
  return V
740
730
  }
741
731
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "undici",
3
- "version": "7.15.0",
3
+ "version": "7.17.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": {
@@ -67,14 +67,14 @@
67
67
  "generate-pem": "node scripts/generate-pem.js",
68
68
  "lint": "eslint --cache",
69
69
  "lint:fix": "eslint --fix --cache",
70
- "test": "npm run test:javascript && cross-env NODE_V8_COVERAGE= npm run test:typescript",
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:node-fetch && npm run test:cache && npm run test:cache-interceptor && npm run test:interceptors && npm run test:fetch && 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": "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\"",
@@ -95,8 +96,8 @@
95
96
  "test:websocket": "borp -p \"test/websocket/**/*.js\"",
96
97
  "test:websocket:autobahn": "node test/autobahn/client.js",
97
98
  "test:websocket:autobahn:report": "node test/autobahn/report.js",
98
- "test:wpt": "node test/wpt/start-fetch.mjs && node test/wpt/start-mimesniff.mjs && node test/wpt/start-xhr.mjs && node test/wpt/start-websockets.mjs && node test/wpt/start-cacheStorage.mjs && node test/wpt/start-eventsource.mjs",
99
- "test:wpt:withoutintl": "node test/wpt/start-fetch.mjs && node test/wpt/start-mimesniff.mjs && node test/wpt/start-xhr.mjs && node test/wpt/start-cacheStorage.mjs && node test/wpt/start-eventsource.mjs",
99
+ "test:wpt:setup": "node test/web-platform-tests/wpt-runner.mjs setup",
100
+ "test:wpt": "npm run test:wpt:setup && node test/web-platform-tests/wpt-runner.mjs run /fetch /mimesniff /xhr /websockets /serviceWorkers /eventsource",
100
101
  "test:cache-tests": "node test/cache-interceptor/cache-tests.mjs --ci",
101
102
  "coverage": "npm run coverage:clean && cross-env NODE_V8_COVERAGE=./coverage/tmp npm run test:javascript && npm run coverage:report",
102
103
  "coverage:ci": "npm run coverage:clean && cross-env NODE_V8_COVERAGE=./coverage/tmp npm run test:javascript && npm run coverage:report:ci",
@@ -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",
@@ -123,6 +124,7 @@
123
124
  "fast-check": "^4.1.1",
124
125
  "husky": "^9.0.7",
125
126
  "jest": "^30.0.5",
127
+ "jsondiffpatch": "^0.7.3",
126
128
  "neostandard": "^0.12.0",
127
129
  "node-forge": "^1.3.1",
128
130
  "proxy": "^2.1.1",
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'
@@ -24,6 +24,7 @@ declare namespace Agent {
24
24
  factory?(origin: string | URL, opts: Object): Dispatcher;
25
25
 
26
26
  interceptors?: { Agent?: readonly Dispatcher.DispatchInterceptor[] } & Pool.Options['interceptors']
27
+ maxOrigins?: number
27
28
  }
28
29
 
29
30
  export interface DispatchOptions extends Dispatcher.DispatchOptions {
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/errors.d.ts CHANGED
@@ -49,21 +49,6 @@ declare namespace Errors {
49
49
  headers: IncomingHttpHeaders | string[] | null
50
50
  }
51
51
 
52
- export class ResponseStatusCodeError extends UndiciError {
53
- constructor (
54
- message?: string,
55
- statusCode?: number,
56
- headers?: IncomingHttpHeaders | string[] | null,
57
- body?: null | Record<string, any> | string
58
- )
59
- name: 'ResponseStatusCodeError'
60
- code: 'UND_ERR_RESPONSE_STATUS_CODE'
61
- body: null | Record<string, any> | string
62
- status: number
63
- statusCode: number
64
- headers: IncomingHttpHeaders | string[] | null
65
- }
66
-
67
52
  /** Passed an invalid argument. */
68
53
  export class InvalidArgumentError extends UndiciError {
69
54
  name: 'InvalidArgumentError'
@@ -168,4 +153,9 @@ declare namespace Errors {
168
153
  name: 'SecureProxyConnectionError'
169
154
  code: 'UND_ERR_PRX_TLS'
170
155
  }
156
+
157
+ class MaxOriginsReachedError extends UndiciError {
158
+ name: 'MaxOriginsReachedError'
159
+ code: 'UND_ERR_MAX_ORIGINS_REACHED'
160
+ }
171
161
  }
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
 
package/types/index.d.ts CHANGED
@@ -5,6 +5,7 @@ import Pool from './pool'
5
5
  import { RedirectHandler, DecoratorHandler } from './handlers'
6
6
 
7
7
  import BalancedPool from './balanced-pool'
8
+ import RoundRobinPool from './round-robin-pool'
8
9
  import Client from './client'
9
10
  import H2CClient from './h2c-client'
10
11
  import buildConnector from './connector'
@@ -23,6 +24,12 @@ import RetryAgent from './retry-agent'
23
24
  import { request, pipeline, stream, connect, upgrade } from './api'
24
25
  import interceptors from './interceptors'
25
26
 
27
+ import CacheInterceptor from './cache-interceptor'
28
+ declare const cacheStores: {
29
+ MemoryCacheStore: typeof CacheInterceptor.MemoryCacheStore;
30
+ SqliteCacheStore: typeof CacheInterceptor.SqliteCacheStore;
31
+ }
32
+
26
33
  export * from './util'
27
34
  export * from './cookies'
28
35
  export * from './eventsource'
@@ -36,7 +43,7 @@ export { Interceptable } from './mock-interceptor'
36
43
 
37
44
  declare function globalThisInstall (): void
38
45
 
39
- export { Dispatcher, BalancedPool, Pool, Client, buildConnector, errors, Agent, request, stream, pipeline, connect, upgrade, setGlobalDispatcher, getGlobalDispatcher, setGlobalOrigin, getGlobalOrigin, interceptors, MockClient, MockPool, MockAgent, SnapshotAgent, MockCallHistory, MockCallHistoryLog, mockErrors, ProxyAgent, EnvHttpProxyAgent, RedirectHandler, DecoratorHandler, RetryHandler, RetryAgent, H2CClient, globalThisInstall as install }
46
+ export { Dispatcher, BalancedPool, RoundRobinPool, Pool, Client, buildConnector, errors, Agent, request, stream, pipeline, connect, upgrade, setGlobalDispatcher, getGlobalDispatcher, setGlobalOrigin, getGlobalOrigin, interceptors, cacheStores, MockClient, MockPool, MockAgent, SnapshotAgent, MockCallHistory, MockCallHistoryLog, mockErrors, ProxyAgent, EnvHttpProxyAgent, RedirectHandler, DecoratorHandler, RetryHandler, RetryAgent, H2CClient, globalThisInstall as install }
40
47
  export default Undici
41
48
 
42
49
  declare namespace Undici {
@@ -46,6 +53,7 @@ declare namespace Undici {
46
53
  const DecoratorHandler: typeof import ('./handlers').DecoratorHandler
47
54
  const RetryHandler: typeof import ('./retry-handler').default
48
55
  const BalancedPool: typeof import('./balanced-pool').default
56
+ const RoundRobinPool: typeof import('./round-robin-pool').default
49
57
  const Client: typeof import('./client').default
50
58
  const H2CClient: typeof import('./h2c-client').default
51
59
  const buildConnector: typeof import('./connector').default
@@ -19,14 +19,47 @@ declare namespace Interceptors {
19
19
 
20
20
  // DNS interceptor
21
21
  export type DNSInterceptorRecord = { address: string, ttl: number, family: 4 | 6 }
22
- export type DNSInterceptorOriginRecords = { 4: { ips: DNSInterceptorRecord[] } | null, 6: { ips: DNSInterceptorRecord[] } | null }
22
+ export type DNSInterceptorOriginRecords = { records: { 4: { ips: DNSInterceptorRecord[] } | null, 6: { ips: DNSInterceptorRecord[] } | null } }
23
+ export type DNSStorage = {
24
+ size: number
25
+ get(origin: string): DNSInterceptorOriginRecords | null
26
+ set(origin: string, records: DNSInterceptorOriginRecords | null, options: { ttl: number }): void
27
+ delete(origin: string): void
28
+ full(): boolean
29
+ }
23
30
  export type DNSInterceptorOpts = {
24
31
  maxTTL?: number
25
32
  maxItems?: number
26
- lookup?: (hostname: string, options: LookupOptions, callback: (err: NodeJS.ErrnoException | null, addresses: DNSInterceptorRecord[]) => void) => void
33
+ lookup?: (origin: URL, options: LookupOptions, callback: (err: NodeJS.ErrnoException | null, addresses: DNSInterceptorRecord[]) => void) => void
27
34
  pick?: (origin: URL, records: DNSInterceptorOriginRecords, affinity: 4 | 6) => DNSInterceptorRecord
28
35
  dualStack?: boolean
29
36
  affinity?: 4 | 6
37
+ storage?: DNSStorage
38
+ }
39
+
40
+ // Deduplicate interceptor
41
+ export type DeduplicateMethods = 'GET' | 'HEAD' | 'OPTIONS' | 'TRACE'
42
+ export type DeduplicateInterceptorOpts = {
43
+ /**
44
+ * The HTTP methods to deduplicate.
45
+ * Note: Only safe HTTP methods can be deduplicated.
46
+ * @default ['GET']
47
+ */
48
+ methods?: DeduplicateMethods[]
49
+ /**
50
+ * Header names that, if present in a request, will cause the request to skip deduplication.
51
+ * Header name matching is case-insensitive.
52
+ * @default []
53
+ */
54
+ skipHeaderNames?: string[]
55
+ /**
56
+ * Header names to exclude from the deduplication key.
57
+ * Requests with different values for these headers will still be deduplicated together.
58
+ * Useful for headers like `x-request-id` that vary per request but shouldn't affect deduplication.
59
+ * Header name matching is case-insensitive.
60
+ * @default []
61
+ */
62
+ excludeHeaderNames?: string[]
30
63
  }
31
64
 
32
65
  export function dump (opts?: DumpInterceptorOpts): Dispatcher.DispatcherComposeInterceptor
@@ -36,4 +69,5 @@ declare namespace Interceptors {
36
69
  export function responseError (opts?: ResponseErrorInterceptorOpts): Dispatcher.DispatcherComposeInterceptor
37
70
  export function dns (opts?: DNSInterceptorOpts): Dispatcher.DispatcherComposeInterceptor
38
71
  export function cache (opts?: CacheInterceptorOpts): Dispatcher.DispatcherComposeInterceptor
72
+ export function deduplicate (opts?: DeduplicateInterceptorOpts): Dispatcher.DispatcherComposeInterceptor
39
73
  }
package/types/pool.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import Client from './client'
2
2
  import TPoolStats from './pool-stats'
3
- import { URL } from 'url'
3
+ import { URL } from 'node:url'
4
4
  import Dispatcher from './dispatcher'
5
5
 
6
6
  export default Pool
@@ -1,5 +1,5 @@
1
- import { Readable } from 'stream'
2
- import { Blob } from 'buffer'
1
+ import { Readable } from 'node:stream'
2
+ import { Blob } from 'node:buffer'
3
3
 
4
4
  export default BodyReadable
5
5