undici 4.6.0 → 4.7.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.
package/README.md CHANGED
@@ -166,6 +166,33 @@ Only supported on Node 16+.
166
166
 
167
167
  This is [experimental](https://nodejs.org/api/documentation.html#documentation_stability_index) and is not yet fully compliant with the Fetch Standard. We plan to ship breaking changes to this feature until it is out of experimental.
168
168
 
169
+ Basic usage example:
170
+
171
+ ```js
172
+ import {fetch} from 'undici';
173
+
174
+ async function fetchJson() {
175
+ const res = await fetch('https://example.com')
176
+ const json = await res.json()
177
+ console.log(json);
178
+ }
179
+ ```
180
+
181
+ #### `response.body`
182
+
183
+ Nodejs has two kinds of streams: [web streams](https://nodejs.org/dist/latest-v16.x/docs/api/webstreams.html) which follow the API of the WHATWG web standard found in browsers, and an older Node-specific [streams API](https://nodejs.org/api/stream.html). `response.body` returns a readable web stream. If you would prefer to work with a Node stream you can convert a web stream using `.fromWeb()`.
184
+
185
+ ```js
186
+ import {fetch} from 'undici';
187
+ import {Readable} from 'node:stream';
188
+
189
+ async function fetchStream() {
190
+ const response = await fetch('https://example.com')
191
+ const readableWebStream = response.body;
192
+ const readableNodeStream = Readable.fromWeb(readableWebStream);
193
+ }
194
+ ```
195
+
169
196
  #### Specification Compliance
170
197
 
171
198
  This section documents parts of the [Fetch Standard](https://fetch.spec.whatwg.org) which Undici does
@@ -79,3 +79,58 @@ diagnosticsChannel.channel('undici:request:error').subscribe(({ request, error }
79
79
  // request is the same object undici:request:create
80
80
  })
81
81
  ```
82
+
83
+ ## `undici:client:sendHeaders`
84
+
85
+ This message is published right before first byte of the request is written to the socket.
86
+
87
+ *Note*: It will publish the exact headers that will be sent to the server in raw foramt.
88
+
89
+ ```js
90
+ import diagnosticsChannel from 'diagnostics_channel'
91
+
92
+ diagnosticsChannel.channel('undici:client:sendHeaders').subscribe(({ request, headers, socket }) => {
93
+ // request is the same object undici:request:create
94
+ console.log(`Full headers list ${headers.split('\r\n')}`);
95
+ })
96
+ ```
97
+
98
+ ## `undici:client:beforeConnect`
99
+
100
+ This message is published before creating a new connection for **any** request.
101
+ You can not assume that this event is related to any specific request.
102
+
103
+ ```js
104
+ import diagnosticsChannel from 'diagnostics_channel'
105
+
106
+ diagnosticsChannel.channel('undici:client:beforeConnect').subscribe(({ connectParams, connector }) => {
107
+ // const { host, hostname, protocol, port, servername } = connectParams
108
+ // connector is a function that creates the socket
109
+ })
110
+ ```
111
+
112
+ ## `undici:client:connected`
113
+
114
+ This message is published after connection established.
115
+
116
+ ```js
117
+ import diagnosticsChannel from 'diagnostics_channel'
118
+
119
+ diagnosticsChannel.channel('undici:client:connected').subscribe(({ socket, connectParams, connector }) => {
120
+ // const { host, hostname, protocol, port, servername } = connectParams
121
+ // connector is a function that creates the socket
122
+ })
123
+ ```
124
+
125
+ ## `undici:client:connectError`
126
+
127
+ This message is published if it did not succeed to create new connection
128
+
129
+ ```js
130
+ import diagnosticsChannel from 'diagnostics_channel'
131
+
132
+ diagnosticsChannel.channel('undici:client:connectError').subscribe(({ error, socket, connectParams, connector }) => {
133
+ // const { host, hostname, protocol, port, servername } = connectParams
134
+ // connector is a function that creates the socket
135
+ console.log(`Connect failed with ${error.message}`)
136
+ })
@@ -20,3 +20,22 @@ import { errors } from 'undici'
20
20
  | `ResponseContentLengthMismatchError` | `UND_ERR_RES_CONTENT_LENGTH_MISMATCH` | response body does not match content-length header |
21
21
  | `InformationalError` | `UND_ERR_INFO` | expected error with reason |
22
22
  | `TrailerMismatchError` | `UND_ERR_TRAILER_MISMATCH` | trailers did not match specification |
23
+
24
+ ### `SocketError`
25
+
26
+ The `SocketError` has a `.socket` property which holds socket metadata:
27
+
28
+ ```ts
29
+ interface SocketInfo {
30
+ localAddress?: string
31
+ localPort?: number
32
+ remoteAddress?: string
33
+ remotePort?: number
34
+ remoteFamily?: string
35
+ timeout?: number
36
+ bytesWritten?: number
37
+ bytesRead?: number
38
+ }
39
+ ```
40
+
41
+ Be aware that in some cases the `.socket` property can be `null`.
@@ -40,7 +40,7 @@ class ConnectHandler extends AsyncResource {
40
40
  }
41
41
 
42
42
  onHeaders () {
43
- throw new SocketError('bad connect')
43
+ throw new SocketError('bad connect', null)
44
44
  }
45
45
 
46
46
  onUpgrade (statusCode, headers, socket) {
@@ -42,7 +42,7 @@ class UpgradeHandler extends AsyncResource {
42
42
  }
43
43
 
44
44
  onHeaders () {
45
- throw new SocketError('bad upgrade')
45
+ throw new SocketError('bad upgrade', null)
46
46
  }
47
47
 
48
48
  onUpgrade (statusCode, headers, socket) {
package/lib/client.js CHANGED
@@ -65,6 +65,21 @@ const {
65
65
  kMaxRedirections
66
66
  } = require('./core/symbols')
67
67
 
68
+ const channels = {}
69
+
70
+ try {
71
+ const diagnosticsChannel = require('diagnostics_channel')
72
+ channels.sendHeaders = diagnosticsChannel.channel('undici:client:sendHeaders')
73
+ channels.beforeConnect = diagnosticsChannel.channel('undici:client:beforeConnect')
74
+ channels.connectError = diagnosticsChannel.channel('undici:client:connectError')
75
+ channels.connected = diagnosticsChannel.channel('undici:client:connected')
76
+ } catch {
77
+ channels.sendHeaders = { hasSubscribers: false }
78
+ channels.beforeConnect = { hasSubscribers: false }
79
+ channels.connectError = { hasSubscribers: false }
80
+ channels.connected = { hasSubscribers: false }
81
+ }
82
+
68
83
  class Client extends Dispatcher {
69
84
  constructor (url, {
70
85
  maxHeaderSize,
@@ -764,13 +779,13 @@ class Parser {
764
779
  // TODO: More statusCode validation?
765
780
 
766
781
  if (statusCode === 100) {
767
- util.destroy(socket, new SocketError('bad response'))
782
+ util.destroy(socket, new SocketError('bad response', util.getSocketInfo(socket)))
768
783
  return -1
769
784
  }
770
785
 
771
786
  /* istanbul ignore if: this can only happen if server is misbehaving */
772
787
  if (upgrade && !request.upgrade) {
773
- util.destroy(socket, new SocketError('bad upgrade'))
788
+ util.destroy(socket, new SocketError('bad upgrade', util.getSocketInfo(socket)))
774
789
  return -1
775
790
  }
776
791
 
@@ -1040,7 +1055,7 @@ function onSocketEnd () {
1040
1055
  return
1041
1056
  }
1042
1057
 
1043
- util.destroy(this, new SocketError('other side closed'))
1058
+ util.destroy(this, new SocketError('other side closed', util.getSocketInfo(this)))
1044
1059
  }
1045
1060
 
1046
1061
  function onSocketClose () {
@@ -1049,7 +1064,7 @@ function onSocketClose () {
1049
1064
  this[kParser].destroy()
1050
1065
  this[kParser] = null
1051
1066
 
1052
- const err = this[kError] || new SocketError('closed')
1067
+ const err = this[kError] || new SocketError('closed', util.getSocketInfo(this))
1053
1068
 
1054
1069
  client[kSocket] = null
1055
1070
 
@@ -1097,6 +1112,20 @@ function connect (client) {
1097
1112
  }
1098
1113
 
1099
1114
  client[kConnecting] = true
1115
+
1116
+ if (channels.beforeConnect.hasSubscribers) {
1117
+ channels.beforeConnect.publish({
1118
+ connectParams: {
1119
+ host,
1120
+ hostname,
1121
+ protocol,
1122
+ port,
1123
+ servername: client[kServerName]
1124
+ },
1125
+ connector: client[kConnector]
1126
+ })
1127
+ }
1128
+
1100
1129
  client[kConnector]({
1101
1130
  host,
1102
1131
  hostname,
@@ -1107,6 +1136,20 @@ function connect (client) {
1107
1136
  client[kConnecting] = false
1108
1137
 
1109
1138
  if (err) {
1139
+ if (channels.connectError.hasSubscribers) {
1140
+ channels.connectError.publish({
1141
+ connectParams: {
1142
+ host,
1143
+ hostname,
1144
+ protocol,
1145
+ port,
1146
+ servername: client[kServerName]
1147
+ },
1148
+ connector: client[kConnector],
1149
+ error: err
1150
+ })
1151
+ }
1152
+
1110
1153
  if (err.code === 'ERR_TLS_CERT_ALTNAME_INVALID') {
1111
1154
  assert(client[kRunning] === 0)
1112
1155
  while (client[kPending] > 0 && client[kQueue][client[kPendingIdx]].servername === client[kServerName]) {
@@ -1136,6 +1179,20 @@ function connect (client) {
1136
1179
  .on('end', onSocketEnd)
1137
1180
  .on('close', onSocketClose)
1138
1181
 
1182
+ if (channels.connected.hasSubscribers) {
1183
+ channels.connected.publish({
1184
+ connectParams: {
1185
+ host,
1186
+ hostname,
1187
+ protocol,
1188
+ port,
1189
+ servername: client[kServerName]
1190
+ },
1191
+ connector: client[kConnector],
1192
+ socket
1193
+ })
1194
+ }
1195
+
1139
1196
  client.emit('connect', client[kUrl], [client])
1140
1197
  }
1141
1198
 
@@ -1412,6 +1469,10 @@ function write (client, request) {
1412
1469
  header += headers
1413
1470
  }
1414
1471
 
1472
+ if (channels.sendHeaders.hasSubscribers) {
1473
+ channels.sendHeaders.publish({ request, headers: header, socket })
1474
+ }
1475
+
1415
1476
  /* istanbul ignore else: assertion */
1416
1477
  if (!body) {
1417
1478
  if (contentLength === 0) {
@@ -29,6 +29,7 @@ function buildConnector ({ maxCachedSessions, socketPath, timeout, ...opts }) {
29
29
  const session = sessionCache.get(servername) || null
30
30
 
31
31
  socket = tls.connect({
32
+ highWaterMark: 16384, // TLS in node can't have bigger HWM anyway...
32
33
  ...options,
33
34
  servername,
34
35
  session,
@@ -61,6 +62,7 @@ function buildConnector ({ maxCachedSessions, socketPath, timeout, ...opts }) {
61
62
  })
62
63
  } else {
63
64
  socket = net.connect({
65
+ highWaterMark: 64 * 1024, // Same as nodejs fs streams.
64
66
  ...options,
65
67
  port: port || 80,
66
68
  host: hostname
@@ -147,12 +147,13 @@ class ClientClosedError extends UndiciError {
147
147
  }
148
148
 
149
149
  class SocketError extends UndiciError {
150
- constructor (message) {
150
+ constructor (message, socket) {
151
151
  super(message)
152
152
  Error.captureStackTrace(this, SocketError)
153
153
  this.name = 'SocketError'
154
154
  this.message = message || 'Socket error'
155
155
  this.code = 'UND_ERR_SOCKET'
156
+ this.socket = socket
156
157
  }
157
158
  }
158
159
 
package/lib/core/util.js CHANGED
@@ -245,6 +245,19 @@ function isDisturbed (body) {
245
245
  ))
246
246
  }
247
247
 
248
+ function getSocketInfo (socket) {
249
+ return {
250
+ localAddress: socket.localAddress,
251
+ localPort: socket.localPort,
252
+ remoteAddress: socket.remoteAddress,
253
+ remotePort: socket.remotePort,
254
+ remoteFamily: socket.remoteFamily,
255
+ timeout: socket.timeout,
256
+ bytesWritten: socket.bytesWritten,
257
+ bytesRead: socket.bytesRead
258
+ }
259
+ }
260
+
248
261
  const kEnumerableProperty = Object.create(null)
249
262
  kEnumerableProperty.enumerable = true
250
263
 
@@ -267,5 +280,6 @@ module.exports = {
267
280
  deepClone,
268
281
  isBuffer,
269
282
  isBlob,
270
- validateHandler
283
+ validateHandler,
284
+ getSocketInfo
271
285
  }
package/lib/fetch/body.js CHANGED
@@ -95,7 +95,7 @@ function extractBody (object, keepalive = false) {
95
95
  yield enc.encode(
96
96
  prefix +
97
97
  `; name="${escape(normalizeLinefeeds(name))}"` +
98
- (value.filename ? `; filename="${escape(value.filename)}"` : '') +
98
+ (value.name ? `; filename="${escape(value.name)}"` : '') +
99
99
  '\r\n' +
100
100
  `Content-Type: ${
101
101
  value.type || 'application/octet-stream'
package/lib/fetch/file.js CHANGED
@@ -45,17 +45,17 @@ class File extends Blob {
45
45
 
46
46
  super(fileBits, { type: t })
47
47
  this[kState] = {
48
- filename: n,
48
+ name: n,
49
49
  lastModified: d
50
50
  }
51
51
  }
52
52
 
53
- get filename () {
53
+ get name () {
54
54
  if (!(this instanceof File)) {
55
55
  throw new TypeError('Illegal invocation')
56
56
  }
57
57
 
58
- return this[kState].filename
58
+ return this[kState].name
59
59
  }
60
60
 
61
61
  get lastModified () {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "undici",
3
- "version": "4.6.0",
3
+ "version": "4.7.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": {
package/types/client.d.ts CHANGED
@@ -39,4 +39,15 @@ declare namespace Client {
39
39
  /** @deprecated use the connect option instead */
40
40
  tls?: TlsOptions | null;
41
41
  }
42
+
43
+ export interface SocketInfo {
44
+ localAddress?: string
45
+ localPort?: number
46
+ remoteAddress?: string
47
+ remotePort?: number
48
+ remoteFamily?: string
49
+ timeout?: number
50
+ bytesWritten?: number
51
+ bytesRead?: number
52
+ }
42
53
  }
package/types/errors.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export = Errors
2
+ import { SocketInfo } from './client'
2
3
 
3
4
  declare namespace Errors {
4
5
  export class UndiciError extends Error { }
@@ -67,6 +68,7 @@ declare namespace Errors {
67
68
  export class SocketError extends UndiciError {
68
69
  name: 'SocketError';
69
70
  code: 'UND_ERR_SOCKET';
71
+ socket: SocketInfo | null
70
72
  }
71
73
 
72
74
  /** Encountered unsupported functionality. */
package/types/file.d.ts CHANGED
@@ -1,13 +1,9 @@
1
- // based on https://unpkg.com/browse/formdata-node@4.0.1/@type/File.d.ts (MIT)
1
+ // Based on https://github.com/octet-stream/form-data/blob/2d0f0dc371517444ce1f22cdde13f51995d0953a/lib/File.ts (MIT)
2
2
  /// <reference types="node" />
3
3
 
4
- import { Blob } from 'buffer'
4
+ import { Blob, BlobOptions } from 'buffer'
5
5
 
6
- export interface FileOptions {
7
- /**
8
- * Returns the media type ([`MIME`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types)) of the file represented by a `File` object.
9
- */
10
- type?: string
6
+ export interface FileOptions extends BlobOptions {
11
7
  /**
12
8
  * The last modified date of the file as the number of milliseconds since the Unix epoch (January 1, 1970 at midnight). Files without a known last modified date return the current date.
13
9
  */
@@ -18,21 +14,21 @@ export declare class File extends Blob {
18
14
  /**
19
15
  * Creates a new File instance.
20
16
  *
21
- * @param fileBits An `Array` strings, or [`Buffer`](https://nodejs.org/dist/latest/docs/api/buffer.html#buffer_class_buffer), [`ArrayBuffer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer), [`ArrayBufferView`](https://developer.mozilla.org/en-US/docs/Web/API/ArrayBufferView), [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) objects, or a mix of any of such objects, that will be put inside the [`File`](https://developer.mozilla.org/en-US/docs/Web/API/File).
22
- * @param name The name of the file.
17
+ * @param fileBits An `Array` strings, or [`ArrayBuffer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer), [`ArrayBufferView`](https://developer.mozilla.org/en-US/docs/Web/API/ArrayBufferView), [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) objects, or a mix of any of such objects, that will be put inside the [`File`](https://developer.mozilla.org/en-US/docs/Web/API/File).
18
+ * @param fileName The name of the file.
23
19
  * @param options An options object containing optional attributes for the file.
24
20
  */
25
- constructor (fileBits: ReadonlyArray<string | NodeJS.ArrayBufferView | Blob>, name: string, options?: FileOptions)
21
+ constructor(fileBits: ReadonlyArray<string | NodeJS.ArrayBufferView | Blob>, fileName: string, options?: FileOptions)
22
+
26
23
  /**
27
24
  * Name of the file referenced by the File object.
28
25
  */
29
- get name (): string
26
+ readonly name: string
27
+
30
28
  /**
31
29
  * The last modified date of the file as the number of milliseconds since the Unix epoch (January 1, 1970 at midnight). Files without a known last modified date return the current date.
32
30
  */
33
- get lastModified (): number
34
- get [Symbol.toStringTag] (): string
35
- stream (): {
36
- [Symbol.asyncIterator]: () => AsyncIterableIterator<Uint8Array>
37
- }
31
+ readonly lastModified: number
32
+
33
+ readonly [Symbol.toStringTag]: string
38
34
  }
@@ -1,4 +1,4 @@
1
- // based on https://unpkg.com/browse/formdata-node@4.0.1/@type/FormData.d.ts (MIT)
1
+ // Based on https://github.com/octet-stream/form-data/blob/2d0f0dc371517444ce1f22cdde13f51995d0953a/lib/FormData.ts (MIT)
2
2
  /// <reference types="node" />
3
3
 
4
4
  import { File } from './file'
@@ -10,12 +10,8 @@ declare type FormDataEntryValue = string | File
10
10
 
11
11
  /**
12
12
  * Provides a way to easily construct a set of key/value pairs representing form fields and their values, which can then be easily sent using fetch().
13
- *
14
- * Note that this object is not a part of Node.js, so you might need to check if an HTTP client of your choice support spec-compliant FormData.
15
- * However, if your HTTP client does not support FormData, you can use [`form-data-encoder`](https://npmjs.com/package/form-data-encoder) package to handle "multipart/form-data" encoding.
16
13
  */
17
14
  export declare class FormData {
18
- constructor ()
19
15
  /**
20
16
  * Appends a new value onto an existing key inside a FormData object,
21
17
  * or adds the key if it does not already exist.
@@ -25,9 +21,10 @@ export declare class FormData {
25
21
  * @param name The name of the field whose data is contained in `value`.
26
22
  * @param value The field's value. This can be [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob)
27
23
  or [`File`](https://developer.mozilla.org/en-US/docs/Web/API/File). If none of these are specified the value is converted to a string.
28
- * @param filename The filename reported to the server, when a Blob or File is passed as the second parameter. The default filename for Blob objects is "blob". The default filename for File objects is the file's filename.
24
+ * @param fileName The filename reported to the server, when a Blob or File is passed as the second parameter. The default filename for Blob objects is "blob". The default filename for File objects is the file's filename.
29
25
  */
30
- readonly append: (name: string, value: unknown, filename?: string) => void
26
+ append(name: string, value: unknown, fileName?: string): void
27
+
31
28
  /**
32
29
  * Set a new value for an existing key inside FormData,
33
30
  * or add the new field if it does not already exist.
@@ -35,10 +32,11 @@ export declare class FormData {
35
32
  * @param name The name of the field whose data is contained in `value`.
36
33
  * @param value The field's value. This can be [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob)
37
34
  or [`File`](https://developer.mozilla.org/en-US/docs/Web/API/File). If none of these are specified the value is converted to a string.
38
- * @param filename The filename reported to the server, when a Blob or File is passed as the second parameter. The default filename for Blob objects is "blob". The default filename for File objects is the file's filename.
35
+ * @param fileName The filename reported to the server, when a Blob or File is passed as the second parameter. The default filename for Blob objects is "blob". The default filename for File objects is the file's filename.
39
36
  *
40
37
  */
41
- readonly set: (name: string, value: unknown, filename?: string) => void
38
+ set(name: string, value: unknown, fileName?: string): void
39
+
42
40
  /**
43
41
  * Returns the first value associated with a given key from within a `FormData` object.
44
42
  * If you expect multiple values and want all of them, use the `getAll()` method instead.
@@ -47,7 +45,8 @@ export declare class FormData {
47
45
  *
48
46
  * @returns A `FormDataEntryValue` containing the value. If the key doesn't exist, the method returns null.
49
47
  */
50
- readonly get: (name: string) => FormDataEntryValue | null
48
+ get(name: string): FormDataEntryValue | null
49
+
51
50
  /**
52
51
  * Returns all the values associated with a given key from within a `FormData` object.
53
52
  *
@@ -55,7 +54,8 @@ export declare class FormData {
55
54
  *
56
55
  * @returns An array of `FormDataEntryValue` whose key matches the value passed in the `name` parameter. If the key doesn't exist, the method returns an empty list.
57
56
  */
58
- readonly getAll: (name: string) => FormDataEntryValue[]
57
+ getAll(name: string): FormDataEntryValue[]
58
+
59
59
  /**
60
60
  * Returns a boolean stating whether a `FormData` object contains a certain key.
61
61
  *
@@ -63,35 +63,42 @@ export declare class FormData {
63
63
  *
64
64
  * @return A boolean value.
65
65
  */
66
- readonly has: (name: string) => boolean
66
+ has(name: string): boolean
67
+
67
68
  /**
68
69
  * Deletes a key and its value(s) from a `FormData` object.
69
70
  *
70
71
  * @param name The name of the key you want to delete.
71
72
  */
72
- readonly delete: (name: string) => void
73
+ delete(name: string): void
74
+
73
75
  /**
74
76
  * Returns an [`iterator`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) allowing to go through all keys contained in this `FormData` object.
75
77
  * Each key is a `string`.
76
78
  */
77
- readonly keys: () => Generator<string>
79
+ keys(): Generator<string>
80
+
78
81
  /**
79
82
  * Returns an [`iterator`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) allowing to go through the `FormData` key/value pairs.
80
83
  * The key of each pair is a string; the value is a [`FormDataValue`](https://developer.mozilla.org/en-US/docs/Web/API/FormDataEntryValue).
81
84
  */
82
- readonly entries: () => Generator<[string, FormDataEntryValue]>
85
+ entries(): Generator<[string, FormDataEntryValue]>
86
+
83
87
  /**
84
88
  * Returns an [`iterator`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) allowing to go through all values contained in this object `FormData` object.
85
89
  * Each value is a [`FormDataValue`](https://developer.mozilla.org/en-US/docs/Web/API/FormDataEntryValue).
86
90
  */
87
- readonly values: () => Generator<FormDataEntryValue>
91
+ values(): Generator<FormDataEntryValue>
92
+
88
93
  /**
89
94
  * An alias for FormData#entries()
90
95
  */
91
- readonly [Symbol.iterator]: () => Generator<[string, FormDataEntryValue], any, unknown>
96
+ [Symbol.iterator](): Generator<[string, FormDataEntryValue], void>
97
+
92
98
  /**
93
99
  * Executes given callback function for each field of the FormData instance
94
100
  */
95
- readonly forEach: (fn: (value: FormDataEntryValue, key: string, fd: FormData) => void, ctx?: unknown) => void
96
- get [Symbol.toStringTag] (): string
101
+ forEach(callback: (value: FormDataEntryValue, key: string, formData: FormData) => void, thisArg?: unknown): void
102
+
103
+ readonly [Symbol.toStringTag]: string
97
104
  }