undici 7.0.0-alpha.1 → 7.0.0-alpha.2

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 (71) hide show
  1. package/README.md +2 -2
  2. package/docs/docs/api/Client.md +1 -1
  3. package/docs/docs/api/Debug.md +1 -1
  4. package/docs/docs/api/Dispatcher.md +53 -2
  5. package/docs/docs/api/MockAgent.md +2 -0
  6. package/docs/docs/api/MockPool.md +2 -1
  7. package/docs/docs/api/RetryAgent.md +1 -1
  8. package/docs/docs/api/RetryHandler.md +1 -1
  9. package/docs/docs/api/WebSocket.md +45 -3
  10. package/index.js +6 -2
  11. package/lib/api/abort-signal.js +2 -0
  12. package/lib/api/api-pipeline.js +4 -2
  13. package/lib/api/api-request.js +4 -2
  14. package/lib/api/api-stream.js +3 -1
  15. package/lib/api/api-upgrade.js +2 -2
  16. package/lib/api/readable.js +194 -41
  17. package/lib/api/util.js +2 -0
  18. package/lib/core/connect.js +49 -22
  19. package/lib/core/constants.js +11 -9
  20. package/lib/core/diagnostics.js +122 -128
  21. package/lib/core/request.js +4 -4
  22. package/lib/core/symbols.js +2 -0
  23. package/lib/core/tree.js +4 -2
  24. package/lib/core/util.js +220 -39
  25. package/lib/dispatcher/client-h1.js +299 -60
  26. package/lib/dispatcher/client-h2.js +1 -1
  27. package/lib/dispatcher/client.js +24 -7
  28. package/lib/dispatcher/fixed-queue.js +91 -49
  29. package/lib/dispatcher/pool-stats.js +2 -0
  30. package/lib/dispatcher/proxy-agent.js +3 -1
  31. package/lib/handler/redirect-handler.js +2 -2
  32. package/lib/handler/retry-handler.js +2 -2
  33. package/lib/interceptor/dns.js +346 -0
  34. package/lib/mock/mock-agent.js +5 -8
  35. package/lib/mock/mock-client.js +7 -2
  36. package/lib/mock/mock-errors.js +3 -1
  37. package/lib/mock/mock-interceptor.js +8 -6
  38. package/lib/mock/mock-pool.js +7 -2
  39. package/lib/mock/mock-symbols.js +2 -1
  40. package/lib/mock/mock-utils.js +33 -5
  41. package/lib/util/timers.js +50 -6
  42. package/lib/web/cache/cache.js +24 -21
  43. package/lib/web/cache/cachestorage.js +1 -1
  44. package/lib/web/cookies/index.js +6 -4
  45. package/lib/web/fetch/body.js +42 -34
  46. package/lib/web/fetch/constants.js +35 -26
  47. package/lib/web/fetch/formdata-parser.js +14 -3
  48. package/lib/web/fetch/formdata.js +40 -20
  49. package/lib/web/fetch/headers.js +116 -84
  50. package/lib/web/fetch/index.js +65 -59
  51. package/lib/web/fetch/request.js +130 -55
  52. package/lib/web/fetch/response.js +79 -36
  53. package/lib/web/fetch/util.js +104 -57
  54. package/lib/web/fetch/webidl.js +38 -14
  55. package/lib/web/websocket/connection.js +92 -15
  56. package/lib/web/websocket/constants.js +2 -3
  57. package/lib/web/websocket/events.js +4 -2
  58. package/lib/web/websocket/receiver.js +20 -26
  59. package/lib/web/websocket/stream/websocketerror.js +83 -0
  60. package/lib/web/websocket/stream/websocketstream.js +485 -0
  61. package/lib/web/websocket/util.js +115 -10
  62. package/lib/web/websocket/websocket.js +45 -170
  63. package/package.json +6 -6
  64. package/types/interceptors.d.ts +14 -0
  65. package/types/mock-agent.d.ts +3 -0
  66. package/types/readable.d.ts +10 -7
  67. package/types/webidl.d.ts +24 -4
  68. package/types/websocket.d.ts +33 -0
  69. package/lib/mock/pluralizer.js +0 -29
  70. package/lib/web/cache/symbols.js +0 -5
  71. package/lib/web/fetch/symbols.js +0 -8
@@ -3,7 +3,7 @@
3
3
  const { webidl } = require('../fetch/webidl')
4
4
  const { URLSerializer } = require('../fetch/data-url')
5
5
  const { environmentSettingsObject } = require('../fetch/util')
6
- const { staticPropertyDescriptors, states, sentCloseFrameState, sendHints, opcodes, emptyBuffer } = require('./constants')
6
+ const { staticPropertyDescriptors, states, sentCloseFrameState, sendHints, opcodes } = require('./constants')
7
7
  const {
8
8
  isConnecting,
9
9
  isEstablished,
@@ -13,7 +13,7 @@ const {
13
13
  failWebsocketConnection,
14
14
  utf8Decode,
15
15
  toArrayBuffer,
16
- isClosed
16
+ getURLRecord
17
17
  } = require('./util')
18
18
  const { establishWebSocketConnection, closeWebSocketConnection } = require('./connection')
19
19
  const { ByteParser } = require('./receiver')
@@ -22,15 +22,13 @@ const { getGlobalDispatcher } = require('../../global')
22
22
  const { types } = require('node:util')
23
23
  const { ErrorEvent, CloseEvent, createFastMessageEvent } = require('./events')
24
24
  const { SendQueue } = require('./sender')
25
- const { WebsocketFrameSend } = require('./frame')
26
25
  const { channels } = require('../../core/diagnostics')
27
26
 
28
27
  /**
29
28
  * @typedef {object} Handler
30
29
  * @property {(response: any, extensions?: string[]) => void} onConnectionEstablished
31
- * @property {(reason: any) => void} onFail
30
+ * @property {(code: number, reason: any) => void} onFail
32
31
  * @property {(opcode: number, data: Buffer) => void} onMessage
33
- * @property {(code: number, reason: any, reasonByteLength: number) => void} onClose
34
32
  * @property {(error: Error) => void} onParserError
35
33
  * @property {() => void} onParserDrain
36
34
  * @property {(chunk: Buffer) => void} onSocketData
@@ -39,8 +37,9 @@ const { channels } = require('../../core/diagnostics')
39
37
  *
40
38
  * @property {number} readyState
41
39
  * @property {import('stream').Duplex} socket
42
- * @property {number} closeState
43
- * @property {boolean} receivedClose
40
+ * @property {Set<number>} closeState
41
+ * @property {import('../fetch/index').Fetch} controller
42
+ * @property {boolean} [wasEverConnected=false]
44
43
  */
45
44
 
46
45
  // https://websockets.spec.whatwg.org/#interface-definition
@@ -62,10 +61,9 @@ class WebSocket extends EventTarget {
62
61
  /** @type {Handler} */
63
62
  #handler = {
64
63
  onConnectionEstablished: (response, extensions) => this.#onConnectionEstablished(response, extensions),
65
- onFail: (reason) => this.#onFail(reason),
64
+ onFail: (code, reason) => this.#onFail(code, reason),
66
65
  onMessage: (opcode, data) => this.#onMessage(opcode, data),
67
- onClose: (code, reason, reasonByteLength) => this.#onClose(code, reason, reasonByteLength),
68
- onParserError: (err) => this.#onParserError(err),
66
+ onParserError: (err) => failWebsocketConnection(this.#handler, null, err.message),
69
67
  onParserDrain: () => this.#onParserDrain(),
70
68
  onSocketData: (chunk) => {
71
69
  if (!this.#parser.write(chunk)) {
@@ -85,12 +83,12 @@ class WebSocket extends EventTarget {
85
83
 
86
84
  readyState: states.CONNECTING,
87
85
  socket: null,
88
- closeState: sentCloseFrameState.NOT_SENT,
89
- receivedClose: false
86
+ closeState: new Set(),
87
+ controller: null,
88
+ wasEverConnected: false
90
89
  }
91
90
 
92
91
  #url
93
- #controller
94
92
  #binaryType
95
93
  /** @type {import('./receiver').ByteParser} */
96
94
  #parser
@@ -113,45 +111,16 @@ class WebSocket extends EventTarget {
113
111
  // 1. Let baseURL be this's relevant settings object's API base URL.
114
112
  const baseURL = environmentSettingsObject.settingsObject.baseUrl
115
113
 
116
- // 1. Let urlRecord be the result of applying the URL parser to url with baseURL.
117
- let urlRecord
114
+ // 2. Let urlRecord be the result of getting a URL record given url and baseURL.
115
+ const urlRecord = getURLRecord(url, baseURL)
118
116
 
119
- try {
120
- urlRecord = new URL(url, baseURL)
121
- } catch (e) {
122
- // 3. If urlRecord is failure, then throw a "SyntaxError" DOMException.
123
- throw new DOMException(e, 'SyntaxError')
124
- }
125
-
126
- // 4. If urlRecord’s scheme is "http", then set urlRecord’s scheme to "ws".
127
- if (urlRecord.protocol === 'http:') {
128
- urlRecord.protocol = 'ws:'
129
- } else if (urlRecord.protocol === 'https:') {
130
- // 5. Otherwise, if urlRecord’s scheme is "https", set urlRecord’s scheme to "wss".
131
- urlRecord.protocol = 'wss:'
132
- }
133
-
134
- // 6. If urlRecord’s scheme is not "ws" or "wss", then throw a "SyntaxError" DOMException.
135
- if (urlRecord.protocol !== 'ws:' && urlRecord.protocol !== 'wss:') {
136
- throw new DOMException(
137
- `Expected a ws: or wss: protocol, got ${urlRecord.protocol}`,
138
- 'SyntaxError'
139
- )
140
- }
141
-
142
- // 7. If urlRecord’s fragment is non-null, then throw a "SyntaxError"
143
- // DOMException.
144
- if (urlRecord.hash || urlRecord.href.endsWith('#')) {
145
- throw new DOMException('Got fragment', 'SyntaxError')
146
- }
147
-
148
- // 8. If protocols is a string, set protocols to a sequence consisting
117
+ // 3. If protocols is a string, set protocols to a sequence consisting
149
118
  // of just that string.
150
119
  if (typeof protocols === 'string') {
151
120
  protocols = [protocols]
152
121
  }
153
122
 
154
- // 9. If any of the values in protocols occur more than once or otherwise
123
+ // 4. If any of the values in protocols occur more than once or otherwise
155
124
  // fail to match the requirements for elements that comprise the value
156
125
  // of `Sec-WebSocket-Protocol` fields as defined by The WebSocket
157
126
  // protocol, then throw a "SyntaxError" DOMException.
@@ -163,17 +132,16 @@ class WebSocket extends EventTarget {
163
132
  throw new DOMException('Invalid Sec-WebSocket-Protocol value', 'SyntaxError')
164
133
  }
165
134
 
166
- // 10. Set this's url to urlRecord.
135
+ // 5. Set this's url to urlRecord.
167
136
  this.#url = new URL(urlRecord.href)
168
137
 
169
- // 11. Let client be this's relevant settings object.
138
+ // 6. Let client be this's relevant settings object.
170
139
  const client = environmentSettingsObject.settingsObject
171
140
 
172
- // 12. Run this step in parallel:
173
-
174
- // 1. Establish a WebSocket connection given urlRecord, protocols,
175
- // and client.
176
- this.#controller = establishWebSocketConnection(
141
+ // 7. Run this step in parallel:
142
+ // 7.1. Establish a WebSocket connection given urlRecord, protocols,
143
+ // and client.
144
+ this.#handler.controller = establishWebSocketConnection(
177
145
  urlRecord,
178
146
  protocols,
179
147
  client,
@@ -186,8 +154,6 @@ class WebSocket extends EventTarget {
186
154
  // be CONNECTING (0).
187
155
  this.#handler.readyState = WebSocket.CONNECTING
188
156
 
189
- this.#handler.closeState = sentCloseFrameState.NOT_SENT
190
-
191
157
  // The extensions attribute must initially return the empty string.
192
158
 
193
159
  // The protocol attribute must initially return the empty string.
@@ -215,34 +181,14 @@ class WebSocket extends EventTarget {
215
181
  reason = webidl.converters.USVString(reason)
216
182
  }
217
183
 
218
- // 1. If code is present, but is neither an integer equal to 1000 nor an
219
- // integer in the range 3000 to 4999, inclusive, throw an
220
- // "InvalidAccessError" DOMException.
221
- if (code !== undefined) {
222
- if (code !== 1000 && (code < 3000 || code > 4999)) {
223
- throw new DOMException('invalid code', 'InvalidAccessError')
224
- }
225
- }
226
-
227
- let reasonByteLength = 0
184
+ // 1. If code is the special value "missing", then set code to null.
185
+ code ??= null
228
186
 
229
- // 2. If reason is present, then run these substeps:
230
- if (reason !== undefined) {
231
- // 1. Let reasonBytes be the result of encoding reason.
232
- // 2. If reasonBytes is longer than 123 bytes, then throw a
233
- // "SyntaxError" DOMException.
234
- reasonByteLength = Buffer.byteLength(reason)
235
-
236
- if (reasonByteLength > 123) {
237
- throw new DOMException(
238
- `Reason must be less than 123 bytes; received ${reasonByteLength}`,
239
- 'SyntaxError'
240
- )
241
- }
242
- }
187
+ // 2. If reason is the special value "missing", then set reason to the empty string.
188
+ reason ??= ''
243
189
 
244
- // 3. Run the first matching steps from the following list:
245
- closeWebSocketConnection(this.#handler, code, reason, reasonByteLength)
190
+ // 3. Close the WebSocket with this, code, and reason.
191
+ closeWebSocketConnection(this.#handler, code, reason, true)
246
192
  }
247
193
 
248
194
  /**
@@ -324,7 +270,7 @@ class WebSocket extends EventTarget {
324
270
  this.#sendQueue.add(data, () => {
325
271
  this.#bufferedAmount -= data.byteLength
326
272
  }, sendHints.typedArray)
327
- } else if (data instanceof Blob) {
273
+ } else if (webidl.is.Blob(data)) {
328
274
  // If the WebSocket connection is established, and the WebSocket
329
275
  // closing handshake has not yet started, then the user agent must
330
276
  // send a WebSocket Message comprised of data using a binary frame
@@ -515,15 +461,7 @@ class WebSocket extends EventTarget {
515
461
  fireEvent('open', this)
516
462
  }
517
463
 
518
- #onFail (reason) {
519
- this.#controller.abort()
520
-
521
- if (this.#handler.socket && !this.#handler.socket.destroyed) {
522
- this.#handler.socket.destroy()
523
- }
524
-
525
- this.#handler.readyState = states.CLOSED
526
-
464
+ #onFail (code, reason) {
527
465
  if (reason) {
528
466
  // TODO: process.nextTick
529
467
  fireEvent('error', this, (type, init) => new ErrorEvent(type, init), {
@@ -531,6 +469,16 @@ class WebSocket extends EventTarget {
531
469
  message: reason
532
470
  })
533
471
  }
472
+
473
+ if (!this.#handler.wasEverConnected) {
474
+ this.#handler.readyState = states.CLOSED
475
+
476
+ // If the WebSocket connection could not be established, it is also said
477
+ // that _The WebSocket Connection is Closed_, but not _cleanly_.
478
+ fireEvent('close', this, (type, init) => new CloseEvent(type, init), {
479
+ wasClean: false, code, reason
480
+ })
481
+ }
534
482
  }
535
483
 
536
484
  #onMessage (type, data) {
@@ -548,7 +496,7 @@ class WebSocket extends EventTarget {
548
496
  try {
549
497
  dataForEvent = utf8Decode(data)
550
498
  } catch {
551
- failWebsocketConnection(this.#handler, 'Received invalid UTF-8 in text frame.')
499
+ failWebsocketConnection(this.#handler, 1007, 'Received invalid UTF-8 in text frame.')
552
500
  return
553
501
  }
554
502
  } else if (type === opcodes.BINARY) {
@@ -574,81 +522,6 @@ class WebSocket extends EventTarget {
574
522
  })
575
523
  }
576
524
 
577
- #onClose (code, reason, reasonByteLength) {
578
- if (isClosing(this.#handler.readyState) || isClosed(this.#handler.readyState)) {
579
- // If this's ready state is CLOSING (2) or CLOSED (3)
580
- // Do nothing.
581
- } else if (!isEstablished(this.#handler.readyState)) {
582
- // If the WebSocket connection is not yet established
583
- // Fail the WebSocket connection and set this's ready state
584
- // to CLOSING (2).
585
- failWebsocketConnection(this.#handler, 'Connection was closed before it was established.')
586
- this.#handler.readyState = states.CLOSING
587
- } else if (this.#handler.closeState === sentCloseFrameState.NOT_SENT) {
588
- // If the WebSocket closing handshake has not yet been started
589
- // Start the WebSocket closing handshake and set this's ready
590
- // state to CLOSING (2).
591
- // - If neither code nor reason is present, the WebSocket Close
592
- // message must not have a body.
593
- // - If code is present, then the status code to use in the
594
- // WebSocket Close message must be the integer given by code.
595
- // - If reason is also present, then reasonBytes must be
596
- // provided in the Close message after the status code.
597
-
598
- this.#handler.closeState = sentCloseFrameState.PROCESSING
599
-
600
- const frame = new WebsocketFrameSend()
601
-
602
- // If neither code nor reason is present, the WebSocket Close
603
- // message must not have a body.
604
-
605
- // If code is present, then the status code to use in the
606
- // WebSocket Close message must be the integer given by code.
607
- if (code !== undefined && reason === undefined) {
608
- frame.frameData = Buffer.allocUnsafe(2)
609
- frame.frameData.writeUInt16BE(code, 0)
610
- } else if (code !== undefined && reason !== undefined) {
611
- // If reason is also present, then reasonBytes must be
612
- // provided in the Close message after the status code.
613
- frame.frameData = Buffer.allocUnsafe(2 + reasonByteLength)
614
- frame.frameData.writeUInt16BE(code, 0)
615
- // the body MAY contain UTF-8-encoded data with value /reason/
616
- frame.frameData.write(reason, 2, 'utf-8')
617
- } else {
618
- frame.frameData = emptyBuffer
619
- }
620
-
621
- this.#handler.socket.write(frame.createFrame(opcodes.CLOSE))
622
-
623
- this.#handler.closeState = sentCloseFrameState.SENT
624
-
625
- // Upon either sending or receiving a Close control frame, it is said
626
- // that _The WebSocket Closing Handshake is Started_ and that the
627
- // WebSocket connection is in the CLOSING state.
628
- this.#handler.readyState = states.CLOSING
629
- } else {
630
- // Otherwise
631
- // Set this's ready state to CLOSING (2).
632
- this.#handler.readyState = states.CLOSING
633
- }
634
- }
635
-
636
- #onParserError (err) {
637
- let message
638
- let code
639
-
640
- if (err instanceof CloseEvent) {
641
- message = err.reason
642
- code = err.code
643
- } else {
644
- message = err.message
645
- }
646
-
647
- fireEvent('error', this, () => new ErrorEvent('error', { error: err, message }))
648
-
649
- closeWebSocketConnection(this.#handler, code)
650
- }
651
-
652
525
  #onParserDrain () {
653
526
  this.#handler.socket.resume()
654
527
  }
@@ -661,7 +534,9 @@ class WebSocket extends EventTarget {
661
534
  // If the TCP connection was closed after the
662
535
  // WebSocket closing handshake was completed, the WebSocket connection
663
536
  // is said to have been closed _cleanly_.
664
- const wasClean = this.#handler.closeState === sentCloseFrameState.SENT && this.#handler.receivedClose
537
+ const wasClean =
538
+ this.#handler.closeState.has(sentCloseFrameState.SENT) &&
539
+ this.#handler.closeState.has(sentCloseFrameState.RECEIVED)
665
540
 
666
541
  let code = 1005
667
542
  let reason = ''
@@ -671,7 +546,7 @@ class WebSocket extends EventTarget {
671
546
  if (result && !result.error) {
672
547
  code = result.code ?? 1005
673
548
  reason = result.reason
674
- } else if (!this.#handler.receivedClose) {
549
+ } else if (!this.#handler.closeState.has(sentCloseFrameState.RECEIVED)) {
675
550
  // If _The WebSocket
676
551
  // Connection is Closed_ and no Close control frame was received by the
677
552
  // endpoint (such as could occur if the underlying transport connection
@@ -793,7 +668,7 @@ webidl.converters['DOMString or sequence<DOMString> or WebSocketInit'] = functio
793
668
 
794
669
  webidl.converters.WebSocketSendData = function (V) {
795
670
  if (webidl.util.Type(V) === webidl.util.Types.OBJECT) {
796
- if (V instanceof Blob) {
671
+ if (webidl.is.Blob(V)) {
797
672
  return V
798
673
  }
799
674
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "undici",
3
- "version": "7.0.0-alpha.1",
3
+ "version": "7.0.0-alpha.2",
4
4
  "description": "An HTTP/1.1 client, written from scratch for Node.js",
5
5
  "homepage": "https://undici.nodejs.org",
6
6
  "bugs": {
@@ -62,7 +62,7 @@
62
62
  "main": "index.js",
63
63
  "types": "index.d.ts",
64
64
  "scripts": {
65
- "build:node": "npx esbuild@0.19.10 index-fetch.js --bundle --platform=node --outfile=undici-fetch.js --define:esbuildDetection=1 --keep-names && node scripts/strip-comments.js",
65
+ "build:node": "esbuild index-fetch.js --bundle --platform=node --outfile=undici-fetch.js --define:esbuildDetection=1 --keep-names && node scripts/strip-comments.js",
66
66
  "prebuild:wasm": "node build/wasm.js --prebuild",
67
67
  "build:wasm": "node build/wasm.js --docker",
68
68
  "generate-pem": "node scripts/generate-pem.js",
@@ -105,12 +105,13 @@
105
105
  "@fastify/busboy": "3.0.0",
106
106
  "@matteo.collina/tspl": "^0.1.1",
107
107
  "@sinonjs/fake-timers": "^11.1.0",
108
- "@types/node": "~18.17.19",
108
+ "@types/node": "^18.19.50",
109
109
  "abort-controller": "^3.0.0",
110
110
  "borp": "^0.17.0",
111
111
  "c8": "^10.0.0",
112
112
  "cross-env": "^7.0.3",
113
113
  "dns-packet": "^5.4.0",
114
+ "esbuild": "^0.24.0",
114
115
  "eslint": "^9.9.0",
115
116
  "fast-check": "^3.17.1",
116
117
  "https-pem": "^3.0.0",
@@ -118,10 +119,9 @@
118
119
  "jest": "^29.0.2",
119
120
  "neostandard": "^0.11.2",
120
121
  "node-forge": "^1.3.1",
121
- "pre-commit": "^1.2.2",
122
122
  "proxy": "^2.1.1",
123
- "tsd": "^0.31.0",
124
- "typescript": "^5.0.2",
123
+ "tsd": "^0.31.2",
124
+ "typescript": "^5.6.2",
125
125
  "ws": "^8.11.0"
126
126
  },
127
127
  "engines": {
@@ -1,5 +1,6 @@
1
1
  import Dispatcher from './dispatcher'
2
2
  import RetryHandler from './retry-handler'
3
+ import { LookupOptions } from 'node:dns'
3
4
 
4
5
  export default Interceptors
5
6
 
@@ -7,11 +8,24 @@ declare namespace Interceptors {
7
8
  export type DumpInterceptorOpts = { maxSize?: number }
8
9
  export type RetryInterceptorOpts = RetryHandler.RetryOptions
9
10
  export type RedirectInterceptorOpts = { maxRedirections?: number }
11
+
10
12
  export type ResponseErrorInterceptorOpts = { throwOnError: boolean }
13
+ // DNS interceptor
14
+ export type DNSInterceptorRecord = { address: string, ttl: number, family: 4 | 6 }
15
+ export type DNSInterceptorOriginRecords = { 4: { ips: DNSInterceptorRecord[] } | null, 6: { ips: DNSInterceptorRecord[] } | null }
16
+ export type DNSInterceptorOpts = {
17
+ maxTTL?: number
18
+ maxItems?: number
19
+ lookup?: (hostname: string, options: LookupOptions, callback: (err: NodeJS.ErrnoException | null, addresses: DNSInterceptorRecord[]) => void) => void
20
+ pick?: (origin: URL, records: DNSInterceptorOriginRecords, affinity: 4 | 6) => DNSInterceptorRecord
21
+ dualStack?: boolean
22
+ affinity?: 4 | 6
23
+ }
11
24
 
12
25
  export function createRedirectInterceptor (opts: RedirectInterceptorOpts): Dispatcher.DispatcherComposeInterceptor
13
26
  export function dump (opts?: DumpInterceptorOpts): Dispatcher.DispatcherComposeInterceptor
14
27
  export function retry (opts?: RetryInterceptorOpts): Dispatcher.DispatcherComposeInterceptor
15
28
  export function redirect (opts?: RedirectInterceptorOpts): Dispatcher.DispatcherComposeInterceptor
16
29
  export function responseError (opts?: ResponseErrorInterceptorOpts): Dispatcher.DispatcherComposeInterceptor
30
+ export function dns (opts?: DNSInterceptorOpts): Dispatcher.DispatcherComposeInterceptor
17
31
  }
@@ -46,5 +46,8 @@ declare namespace MockAgent {
46
46
  export interface Options extends Agent.Options {
47
47
  /** A custom agent to be encapsulated by the MockAgent. */
48
48
  agent?: Dispatcher;
49
+
50
+ /** Ignore trailing slashes in the path */
51
+ ignoreTrailingSlash?: boolean;
49
52
  }
50
53
  }
@@ -4,11 +4,13 @@ import { Blob } from 'buffer'
4
4
  export default BodyReadable
5
5
 
6
6
  declare class BodyReadable extends Readable {
7
- constructor (
8
- resume?: (this: Readable, size: number) => void | null,
9
- abort?: () => void | null,
10
- contentType?: string
11
- )
7
+ constructor (opts: {
8
+ resume: (this: Readable, size: number) => void | null;
9
+ abort: () => void | null;
10
+ contentType?: string;
11
+ contentLength?: number;
12
+ highWaterMark?: number;
13
+ })
12
14
 
13
15
  /** Consumes and returns the body as a string
14
16
  * https://fetch.spec.whatwg.org/#dom-body-text
@@ -59,7 +61,8 @@ declare class BodyReadable extends Readable {
59
61
  readonly body: never | undefined
60
62
 
61
63
  /** Dumps the response body by reading `limit` number of bytes.
62
- * @param opts.limit Number of bytes to read (optional) - Default: 262144
64
+ * @param opts.limit Number of bytes to read (optional) - Default: 131072
65
+ * @param opts.signal AbortSignal to cancel the operation (optional)
63
66
  */
64
- dump (opts?: { limit: number }): Promise<void>
67
+ dump (opts?: { limit: number; signal?: AbortSignal }): Promise<void>
65
68
  }
package/types/webidl.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  // These types are not exported, and are only used internally
2
+ import * as undici from './index'
2
3
 
3
4
  /**
4
5
  * Take in an unknown value and return one that is of type T
@@ -49,7 +50,7 @@ interface WebidlUtil {
49
50
  /**
50
51
  * @see https://tc39.es/ecma262/#sec-ecmascript-data-types-and-values
51
52
  */
52
- Type (object: unknown): WebIDLTypes
53
+ Type (object: unknown): WebIDLTypes[keyof WebIDLTypes]
53
54
 
54
55
  TypeValueToString (o: unknown):
55
56
  | 'Undefined'
@@ -82,6 +83,8 @@ interface WebidlUtil {
82
83
  * Stringifies {@param V}
83
84
  */
84
85
  Stringify (V: any): string
86
+
87
+ MakeTypeAssertion <T extends { prototype: T }>(Prototype: T['prototype']): (arg: any) => arg is T
85
88
  }
86
89
 
87
90
  interface WebidlConverters {
@@ -173,10 +176,27 @@ interface WebidlConverters {
173
176
  [Key: string]: (...args: any[]) => unknown
174
177
  }
175
178
 
179
+ type IsAssertion<T> = (arg: any) => arg is T
180
+
181
+ interface WebidlIs {
182
+ Request: IsAssertion<undici.Request>
183
+ Response: IsAssertion<undici.Response>
184
+ ReadableStream: IsAssertion<ReadableStream>
185
+ Blob: IsAssertion<Blob>
186
+ URLSearchParams: IsAssertion<URLSearchParams>
187
+ File: IsAssertion<File>
188
+ FormData: IsAssertion<undici.FormData>
189
+ URL: IsAssertion<URL>
190
+ WebSocketError: IsAssertion<undici.WebSocketError>
191
+ AbortSignal: IsAssertion<AbortSignal>
192
+ MessagePort: IsAssertion<MessagePort>
193
+ }
194
+
176
195
  export interface Webidl {
177
196
  errors: WebidlErrors
178
197
  util: WebidlUtil
179
198
  converters: WebidlConverters
199
+ is: WebidlIs
180
200
 
181
201
  /**
182
202
  * @description Performs a brand-check on {@param V} to ensure it is a
@@ -184,7 +204,7 @@ export interface Webidl {
184
204
  */
185
205
  brandCheck <Interface extends new () => unknown>(V: unknown, cls: Interface): asserts V is Interface
186
206
 
187
- brandCheckMultiple <Interfaces extends (new () => unknown)[]> (V: unknown, list: Interfaces): asserts V is Interfaces[number]
207
+ brandCheckMultiple <Interfaces extends (new () => unknown)[]> (list: Interfaces): (V: any) => asserts V is Interfaces[number]
188
208
 
189
209
  /**
190
210
  * @see https://webidl.spec.whatwg.org/#es-sequence
@@ -207,11 +227,11 @@ export interface Webidl {
207
227
  * Similar to {@link Webidl.brandCheck} but allows skipping the check if third party
208
228
  * interfaces are allowed.
209
229
  */
210
- interfaceConverter <Interface>(cls: Interface): (
230
+ interfaceConverter <Interface>(typeCheck: IsAssertion<Interface>, name: string): (
211
231
  V: unknown,
212
232
  prefix: string,
213
233
  argument: string
214
- ) => asserts V is typeof cls
234
+ ) => asserts V is Interface
215
235
 
216
236
  // TODO(@KhafraDev): a type could likely be implemented that can infer the return type
217
237
  // from the converters given?
@@ -148,3 +148,36 @@ interface WebSocketInit {
148
148
  dispatcher?: Dispatcher,
149
149
  headers?: HeadersInit
150
150
  }
151
+
152
+ interface WebSocketStreamOptions {
153
+ protocols?: string | string[]
154
+ signal?: AbortSignal
155
+ }
156
+
157
+ interface WebSocketCloseInfo {
158
+ closeCode: number
159
+ reason: string
160
+ }
161
+
162
+ interface WebSocketStream {
163
+ closed: Promise<WebSocketCloseInfo>
164
+ opened: Promise<{
165
+ extensions: string
166
+ protocol: string
167
+ readable: ReadableStream
168
+ writable: WritableStream
169
+ }>
170
+ url: string
171
+ }
172
+
173
+ export declare const WebSocketStream: {
174
+ prototype: WebSocketStream
175
+ new (url: string | URL, options?: WebSocketStreamOptions): WebSocketStream
176
+ }
177
+
178
+ interface WebSocketError extends Event, WebSocketCloseInfo {}
179
+
180
+ export declare const WebSocketError: {
181
+ prototype: WebSocketError
182
+ new (type: string, init?: WebSocketCloseInfo): WebSocketError
183
+ }
@@ -1,29 +0,0 @@
1
- 'use strict'
2
-
3
- const singulars = {
4
- pronoun: 'it',
5
- is: 'is',
6
- was: 'was',
7
- this: 'this'
8
- }
9
-
10
- const plurals = {
11
- pronoun: 'they',
12
- is: 'are',
13
- was: 'were',
14
- this: 'these'
15
- }
16
-
17
- module.exports = class Pluralizer {
18
- constructor (singular, plural) {
19
- this.singular = singular
20
- this.plural = plural
21
- }
22
-
23
- pluralize (count) {
24
- const one = count === 1
25
- const keys = one ? singulars : plurals
26
- const noun = one ? this.singular : this.plural
27
- return { ...keys, count, noun }
28
- }
29
- }
@@ -1,5 +0,0 @@
1
- 'use strict'
2
-
3
- module.exports = {
4
- kConstruct: require('../../core/symbols').kConstruct
5
- }
@@ -1,8 +0,0 @@
1
- 'use strict'
2
-
3
- module.exports = {
4
- kHeaders: Symbol('headers'),
5
- kSignal: Symbol('signal'),
6
- kState: Symbol('state'),
7
- kDispatcher: Symbol('dispatcher')
8
- }