undici 5.2.0 → 5.5.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
@@ -185,20 +185,19 @@ Help us improve the test coverage by following instructions at [nodejs/undici/#9
185
185
  Basic usage example:
186
186
 
187
187
  ```js
188
- import {fetch} from 'undici';
188
+ import { fetch } from 'undici';
189
189
 
190
- async function fetchJson() {
191
- const res = await fetch('https://example.com')
192
- const json = await res.json()
193
- console.log(json);
194
- }
190
+
191
+ const res = await fetch('https://example.com')
192
+ const json = await res.json()
193
+ console.log(json);
195
194
  ```
196
195
 
197
196
  You can pass an optional dispatcher to `fetch` as:
198
197
 
199
198
  ```js
200
199
  import { fetch, Agent } from 'undici'
201
-
200
+
202
201
  const res = await fetch('https://example.com', {
203
202
  // Mocks are also supported
204
203
  dispatcher: new Agent({
@@ -235,9 +234,7 @@ const data = {
235
234
  },
236
235
  };
237
236
 
238
- (async () => {
239
- await fetch("https://example.com", { body: data, method: 'POST' });
240
- })();
237
+ await fetch("https://example.com", { body: data, method: 'POST' });
241
238
  ```
242
239
 
243
240
  #### `response.body`
@@ -245,14 +242,12 @@ const data = {
245
242
  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()`.
246
243
 
247
244
  ```js
248
- import {fetch} from 'undici';
249
- import {Readable} from 'node:stream';
245
+ import { fetch } from 'undici';
246
+ import { Readable } from 'node:stream';
250
247
 
251
- async function fetchStream() {
252
- const response = await fetch('https://example.com')
253
- const readableWebStream = response.body;
254
- const readableNodeStream = Readable.fromWeb(readableWebStream);
255
- }
248
+ const response = await fetch('https://example.com')
249
+ const readableWebStream = response.body;
250
+ const readableNodeStream = Readable.fromWeb(readableWebStream);
256
251
  ```
257
252
 
258
253
  #### Specification Compliance
@@ -288,6 +283,15 @@ const headers = await fetch(url)
288
283
  .then(res => res.headers)
289
284
  ```
290
285
 
286
+ ##### Forbidden and Safelisted Header Names
287
+
288
+ * https://fetch.spec.whatwg.org/#cors-safelisted-response-header-name
289
+ * https://fetch.spec.whatwg.org/#forbidden-header-name
290
+ * https://fetch.spec.whatwg.org/#forbidden-response-header-name
291
+ * https://github.com/wintercg/fetch/issues/6
292
+
293
+ The [Fetch Standard](https://fetch.spec.whatwg.org) requires implementations to exclude certain headers from requests and responses. In browser environments, some headers are forbidden so the user agent remains in full control over them. In Undici, these constraints are removed to give more control to the user.
294
+
291
295
  ### `undici.upgrade([url, options]): Promise`
292
296
 
293
297
  Upgrade to a different protocol. See [MDN - HTTP - Protocol upgrade mechanism](https://developer.mozilla.org/en-US/docs/Web/HTTP/Protocol_upgrade_mechanism) for more details.
@@ -375,6 +379,7 @@ Refs: https://fetch.spec.whatwg.org/#atomic-http-redirect-handling
375
379
  * [__Daniele Belardi__](https://github.com/dnlup), <https://www.npmjs.com/~dnlup>
376
380
  * [__Ethan Arrowood__](https://github.com/ethan-arrowood), <https://www.npmjs.com/~ethan_arrowood>
377
381
  * [__Matteo Collina__](https://github.com/mcollina), <https://www.npmjs.com/~matteo.collina>
382
+ * [__Matthew Aitken__](https://github.com/KhafraDev), <https://www.npmjs.com/~khaf>
378
383
  * [__Robert Nagy__](https://github.com/ronag), <https://www.npmjs.com/~ronag>
379
384
  * [__Szymon Marczak__](https://github.com/szmarczak), <https://www.npmjs.com/~szmarczak>
380
385
  * [__Tomas Della Vedova__](https://github.com/delvedor), <https://www.npmjs.com/~delvedor>
@@ -194,18 +194,20 @@ Returns: `Boolean` - `false` if dispatcher is busy and further dispatch calls wo
194
194
  * **method** `string`
195
195
  * **body** `string | Buffer | Uint8Array | stream.Readable | Iterable | AsyncIterable | null` (optional) - Default: `null`
196
196
  * **headers** `UndiciHeaders | string[]` (optional) - Default: `null`.
197
+ * **query** `Record<string, any> | null` (optional) - Default: `null` - Query string params to be embedded in the request URL. Note that both keys and values of query are encoded using `encodeURIComponent`. If for some reason you need to send them unencoded, embed query params into path directly instead.
197
198
  * **idempotent** `boolean` (optional) - Default: `true` if `method` is `'HEAD'` or `'GET'` - Whether the requests can be safely retried or not. If `false` the request won't be sent until all preceding requests in the pipeline has completed.
198
199
  * **blocking** `boolean` (optional) - Default: `false` - Whether the response is expected to take a long time and would end up blocking the pipeline. When this is set to `true` further pipelining will be avoided on the same connection until headers have been received.
199
200
  * **upgrade** `string | null` (optional) - Default: `null` - Upgrade the request. Should be used to specify the kind of upgrade i.e. `'Websocket'`.
200
201
  * **bodyTimeout** `number | null` (optional) - The timeout after which a request will time out, in milliseconds. Monitors time between receiving body data. Use `0` to disable it entirely. Defaults to 30 seconds.
201
202
  * **headersTimeout** `number | null` (optional) - The amount of time the parser will wait to receive the complete HTTP headers. Defaults to 30 seconds.
203
+ * **throwOnError** `boolean` (optional) - Default: `false` - Whether Undici should throw an error upon receiving a 4xx or 5xx response from the server.
202
204
 
203
205
  #### Parameter: `DispatchHandler`
204
206
 
205
207
  * **onConnect** `(abort: () => void, context: object) => void` - Invoked before request is dispatched on socket. May be invoked multiple times when a request is retried when the request at the head of the pipeline fails.
206
208
  * **onError** `(error: Error) => void` - Invoked when an error has occurred. May not throw.
207
209
  * **onUpgrade** `(statusCode: number, headers: Buffer[], socket: Duplex) => void` (optional) - Invoked when request is upgraded. Required if `DispatchOptions.upgrade` is defined or `DispatchOptions.method === 'CONNECT'`.
208
- * **onHeaders** `(statusCode: number, headers: Buffer[], resume: () => void) => boolean` - Invoked when statusCode and headers have been received. May be invoked multiple times due to 1xx informational headers. Not required for `upgrade` requests.
210
+ * **onHeaders** `(statusCode: number, headers: Buffer[], resume: () => void, statusText: string) => boolean` - Invoked when statusCode and headers have been received. May be invoked multiple times due to 1xx informational headers. Not required for `upgrade` requests.
209
211
  * **onData** `(chunk: Buffer) => boolean` - Invoked when response payload data is received. Not required for `upgrade` requests.
210
212
  * **onComplete** `(trailers: Buffer[]) => void` - Invoked when response payload and trailers have been received and the request has completed. Not required for `upgrade` requests.
211
213
  * **onBodySent** `(chunk: string | Buffer | Uint8Array) => void` - Invoked when a body chunk is sent to the server. Not required. For a stream or iterable body this will be invoked for every chunk. For other body types, it will be invoked once after the body is sent.
@@ -57,6 +57,7 @@ Returns: `MockInterceptor` corresponding to the input options.
57
57
  * **method** `string | RegExp | (method: string) => boolean` - a matcher for the HTTP request method.
58
58
  * **body** `string | RegExp | (body: string) => boolean` - (optional) - a matcher for the HTTP request body.
59
59
  * **headers** `Record<string, string | RegExp | (body: string) => boolean`> - (optional) - a matcher for the HTTP request headers. To be intercepted, a request must match all defined headers. Extra headers not defined here may (or may not) be included in the request and do not affect the interception in any way.
60
+ * **query** `Record<string, any> | null` - (optional) - a matcher for the HTTP request query string params.
60
61
 
61
62
  ### Return: `MockInterceptor`
62
63
 
@@ -101,4 +101,36 @@ const badRequest = await bankTransfer('1234567890', '100')
101
101
  // subsequent request to origin http://localhost:3000 was not allowed (net.connect disabled)
102
102
  ```
103
103
 
104
+ ## Reply with data based on request
104
105
 
106
+ If the mocked response needs to be dynamically derived from the request parameters, you can provide a function instead of an object to `reply`
107
+
108
+ ```js
109
+ mockPool.intercept({
110
+ path: '/bank-transfer',
111
+ method: 'POST',
112
+ headers: {
113
+ 'X-TOKEN-SECRET': 'SuperSecretToken',
114
+ },
115
+ body: JSON.stringify({
116
+ recepient: '1234567890',
117
+ amount: '100'
118
+ })
119
+ }).reply(200, (opts) => {
120
+ // do something with opts
121
+
122
+ return { message: 'transaction processed' }
123
+ })
124
+ ```
125
+
126
+ in this case opts will be
127
+
128
+ ```
129
+ {
130
+ method: 'POST',
131
+ headers: { 'X-TOKEN-SECRET': 'SuperSecretToken' },
132
+ body: '{"recepient":"1234567890","amount":"100"}',
133
+ origin: 'http://localhost:3000',
134
+ path: '/bank-transfer'
135
+ }
136
+ ```
package/index.d.ts CHANGED
@@ -16,6 +16,7 @@ import { request, pipeline, stream, connect, upgrade } from './types/api'
16
16
  export * from './types/fetch'
17
17
  export * from './types/file'
18
18
  export * from './types/formdata'
19
+ export * from './types/diagnostics-channel'
19
20
  export { Interceptable } from './types/mock-interceptor'
20
21
 
21
22
  export { Dispatcher, BalancedPool, Pool, Client, buildConnector, errors, Agent, request, stream, pipeline, connect, upgrade, setGlobalDispatcher, getGlobalDispatcher, MockClient, MockPool, MockAgent, mockErrors, ProxyAgent }
@@ -3,7 +3,8 @@
3
3
  const Readable = require('./readable')
4
4
  const {
5
5
  InvalidArgumentError,
6
- RequestAbortedError
6
+ RequestAbortedError,
7
+ ResponseStatusCodeError
7
8
  } = require('../core/errors')
8
9
  const util = require('../core/util')
9
10
  const { AsyncResource } = require('async_hooks')
@@ -15,7 +16,7 @@ class RequestHandler extends AsyncResource {
15
16
  throw new InvalidArgumentError('invalid opts')
16
17
  }
17
18
 
18
- const { signal, method, opaque, body, onInfo, responseHeaders } = opts
19
+ const { signal, method, opaque, body, onInfo, responseHeaders, throwOnError } = opts
19
20
 
20
21
  try {
21
22
  if (typeof callback !== 'function') {
@@ -51,6 +52,7 @@ class RequestHandler extends AsyncResource {
51
52
  this.trailers = {}
52
53
  this.context = null
53
54
  this.onInfo = onInfo || null
55
+ this.throwOnError = throwOnError
54
56
 
55
57
  if (util.isStream(body)) {
56
58
  body.on('error', (err) => {
@@ -70,7 +72,7 @@ class RequestHandler extends AsyncResource {
70
72
  this.context = context
71
73
  }
72
74
 
73
- onHeaders (statusCode, rawHeaders, resume) {
75
+ onHeaders (statusCode, rawHeaders, resume, statusMessage) {
74
76
  const { callback, opaque, abort, context } = this
75
77
 
76
78
  if (statusCode < 200) {
@@ -89,6 +91,13 @@ class RequestHandler extends AsyncResource {
89
91
  const headers = this.responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders)
90
92
 
91
93
  if (callback !== null) {
94
+ if (this.throwOnError && statusCode >= 400) {
95
+ this.runInAsyncScope(callback, null,
96
+ new ResponseStatusCodeError(`Response status code ${statusCode}${statusMessage ? `: ${statusMessage}` : ''}`, statusCode, headers)
97
+ )
98
+ return
99
+ }
100
+
92
101
  this.runInAsyncScope(callback, null, null, {
93
102
  statusCode,
94
103
  headers,
package/lib/client.js CHANGED
@@ -873,6 +873,11 @@ class Parser {
873
873
  // have been queued since then.
874
874
  util.destroy(socket, new InformationalError('reset'))
875
875
  return constants.ERROR.PAUSED
876
+ } else if (client[kPipelining] === 1) {
877
+ // We must wait a full event loop cycle to reuse this socket to make sure
878
+ // that non-spec compliant servers are not closing the connection even if they
879
+ // said they won't.
880
+ setImmediate(resume, client)
876
881
  } else {
877
882
  resume(client)
878
883
  }
@@ -56,6 +56,19 @@ class BodyTimeoutError extends UndiciError {
56
56
  }
57
57
  }
58
58
 
59
+ class ResponseStatusCodeError extends UndiciError {
60
+ constructor (message, statusCode, headers) {
61
+ super(message)
62
+ Error.captureStackTrace(this, ResponseStatusCodeError)
63
+ this.name = 'ResponseStatusCodeError'
64
+ this.message = message || 'Response Status Code Error'
65
+ this.code = 'UND_ERR_RESPONSE_STATUS_CODE'
66
+ this.status = statusCode
67
+ this.statusCode = statusCode
68
+ this.headers = headers
69
+ }
70
+ }
71
+
59
72
  class InvalidArgumentError extends UndiciError {
60
73
  constructor (message) {
61
74
  super(message)
@@ -186,6 +199,7 @@ module.exports = {
186
199
  BodyTimeoutError,
187
200
  RequestContentLengthMismatchError,
188
201
  ConnectTimeoutError,
202
+ ResponseStatusCodeError,
189
203
  InvalidArgumentError,
190
204
  InvalidReturnValueError,
191
205
  RequestAbortedError,
@@ -4,8 +4,8 @@ const {
4
4
  InvalidArgumentError,
5
5
  NotSupportedError
6
6
  } = require('./errors')
7
- const util = require('./util')
8
7
  const assert = require('assert')
8
+ const util = require('./util')
9
9
 
10
10
  const kHandler = Symbol('handler')
11
11
 
@@ -38,11 +38,13 @@ class Request {
38
38
  method,
39
39
  body,
40
40
  headers,
41
+ query,
41
42
  idempotent,
42
43
  blocking,
43
44
  upgrade,
44
45
  headersTimeout,
45
- bodyTimeout
46
+ bodyTimeout,
47
+ throwOnError
46
48
  }, handler) {
47
49
  if (typeof path !== 'string') {
48
50
  throw new InvalidArgumentError('path must be a string')
@@ -70,19 +72,20 @@ class Request {
70
72
 
71
73
  this.bodyTimeout = bodyTimeout
72
74
 
75
+ this.throwOnError = throwOnError === true
76
+
73
77
  this.method = method
74
78
 
75
79
  if (body == null) {
76
80
  this.body = null
77
81
  } else if (util.isStream(body)) {
78
82
  this.body = body
79
- } else if (body instanceof DataView) {
80
- // TODO: Why is DataView special?
81
- this.body = body.buffer.byteLength ? Buffer.from(body.buffer) : null
82
- } else if (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) {
83
- this.body = body.byteLength ? Buffer.from(body) : null
84
83
  } else if (util.isBuffer(body)) {
85
84
  this.body = body.byteLength ? body : null
85
+ } else if (ArrayBuffer.isView(body)) {
86
+ this.body = body.buffer.byteLength ? Buffer.from(body.buffer, body.byteOffset, body.byteLength) : null
87
+ } else if (body instanceof ArrayBuffer) {
88
+ this.body = body.byteLength ? Buffer.from(body) : null
86
89
  } else if (typeof body === 'string') {
87
90
  this.body = body.length ? Buffer.from(body) : null
88
91
  } else if (util.isFormDataLike(body) || util.isIterable(body) || util.isBlobLike(body)) {
@@ -97,7 +100,7 @@ class Request {
97
100
 
98
101
  this.upgrade = upgrade || null
99
102
 
100
- this.path = path
103
+ this.path = query ? util.buildURL(path, query) : path
101
104
 
102
105
  this.origin = origin
103
106
 
package/lib/core/util.js CHANGED
@@ -26,6 +26,51 @@ function isBlobLike (object) {
26
26
  )
27
27
  }
28
28
 
29
+ function isObject (val) {
30
+ return val !== null && typeof val === 'object'
31
+ }
32
+
33
+ // this escapes all non-uri friendly characters
34
+ function encode (val) {
35
+ return encodeURIComponent(val)
36
+ }
37
+
38
+ // based on https://github.com/axios/axios/blob/63e559fa609c40a0a460ae5d5a18c3470ffc6c9e/lib/helpers/buildURL.js (MIT license)
39
+ function buildURL (url, queryParams) {
40
+ if (url.includes('?') || url.includes('#')) {
41
+ throw new Error('Query params cannot be passed when url already contains "?" or "#".')
42
+ }
43
+ if (!isObject(queryParams)) {
44
+ throw new Error('Query params must be an object')
45
+ }
46
+
47
+ const parts = []
48
+ for (let [key, val] of Object.entries(queryParams)) {
49
+ if (val === null || typeof val === 'undefined') {
50
+ continue
51
+ }
52
+
53
+ if (!Array.isArray(val)) {
54
+ val = [val]
55
+ }
56
+
57
+ for (const v of val) {
58
+ if (isObject(v)) {
59
+ throw new Error('Passing object as a query param is not supported, please serialize to string up-front')
60
+ }
61
+ parts.push(encode(key) + '=' + encode(v))
62
+ }
63
+ }
64
+
65
+ const serializedParams = parts.join('&')
66
+
67
+ if (serializedParams) {
68
+ url += '?' + serializedParams
69
+ }
70
+
71
+ return url
72
+ }
73
+
29
74
  function parseURL (url) {
30
75
  if (typeof url === 'string') {
31
76
  url = new URL(url)
@@ -357,5 +402,6 @@ module.exports = {
357
402
  isBuffer,
358
403
  validateHandler,
359
404
  getSocketInfo,
360
- isFormDataLike
405
+ isFormDataLike,
406
+ buildURL
361
407
  }
package/lib/fetch/body.js CHANGED
@@ -9,7 +9,7 @@ const { kBodyUsed } = require('../core/symbols')
9
9
  const assert = require('assert')
10
10
  const { NotSupportedError } = require('../core/errors')
11
11
  const { isErrored } = require('../core/util')
12
- const { isUint8Array } = require('util/types')
12
+ const { isUint8Array, isArrayBuffer } = require('util/types')
13
13
 
14
14
  let ReadableStream
15
15
 
@@ -61,7 +61,7 @@ function extractBody (object, keepalive = false) {
61
61
 
62
62
  // Set Content-Type to `application/x-www-form-urlencoded;charset=UTF-8`.
63
63
  contentType = 'application/x-www-form-urlencoded;charset=UTF-8'
64
- } else if (object instanceof ArrayBuffer || ArrayBuffer.isView(object)) {
64
+ } else if (isArrayBuffer(object) || ArrayBuffer.isView(object)) {
65
65
  // BufferSource
66
66
 
67
67
  if (object instanceof DataView) {
@@ -1,28 +1,5 @@
1
1
  'use strict'
2
2
 
3
- const forbiddenHeaderNames = [
4
- 'accept-charset',
5
- 'accept-encoding',
6
- 'access-control-request-headers',
7
- 'access-control-request-method',
8
- 'connection',
9
- 'content-length',
10
- 'cookie',
11
- 'cookie2',
12
- 'date',
13
- 'dnt',
14
- 'expect',
15
- 'host',
16
- 'keep-alive',
17
- 'origin',
18
- 'referer',
19
- 'te',
20
- 'trailer',
21
- 'transfer-encoding',
22
- 'upgrade',
23
- 'via'
24
- ]
25
-
26
3
  const corsSafeListedMethods = ['GET', 'HEAD', 'POST']
27
4
 
28
5
  const nullBodyStatus = [101, 204, 205, 304]
@@ -58,9 +35,6 @@ const requestCache = [
58
35
  'only-if-cached'
59
36
  ]
60
37
 
61
- // https://fetch.spec.whatwg.org/#forbidden-response-header-name
62
- const forbiddenResponseHeaderNames = ['set-cookie', 'set-cookie2']
63
-
64
38
  const requestBodyHeader = [
65
39
  'content-encoding',
66
40
  'content-language',
@@ -86,12 +60,8 @@ const subresource = [
86
60
  ''
87
61
  ]
88
62
 
89
- const corsSafeListedResponseHeaderNames = [] // TODO
90
-
91
63
  module.exports = {
92
64
  subresource,
93
- forbiddenResponseHeaderNames,
94
- corsSafeListedResponseHeaderNames,
95
65
  forbiddenMethods,
96
66
  requestBodyHeader,
97
67
  referrerPolicy,
@@ -99,7 +69,6 @@ module.exports = {
99
69
  requestMode,
100
70
  requestCredentials,
101
71
  requestCache,
102
- forbiddenHeaderNames,
103
72
  redirectStatus,
104
73
  corsSafeListedMethods,
105
74
  nullBodyStatus,
package/lib/fetch/file.js CHANGED
@@ -69,10 +69,6 @@ class File extends Blob {
69
69
  }
70
70
 
71
71
  get [Symbol.toStringTag] () {
72
- if (!(this instanceof File)) {
73
- throw new TypeError('Illegal invocation')
74
- }
75
-
76
72
  return this.constructor.name
77
73
  }
78
74
  }
@@ -190,10 +186,6 @@ class FileLike {
190
186
  }
191
187
 
192
188
  get [Symbol.toStringTag] () {
193
- if (!(this instanceof FileLike)) {
194
- throw new TypeError('Illegal invocation')
195
- }
196
-
197
189
  return 'File'
198
190
  }
199
191
  }
@@ -1,11 +1,13 @@
1
1
  'use strict'
2
2
 
3
- const { isBlobLike, isFileLike, toUSVString } = require('./util')
3
+ const { isBlobLike, isFileLike, toUSVString, makeIterator } = require('./util')
4
4
  const { kState } = require('./symbols')
5
5
  const { File, FileLike } = require('./file')
6
6
  const { Blob } = require('buffer')
7
7
 
8
8
  class FormData {
9
+ static name = 'FormData'
10
+
9
11
  constructor (...args) {
10
12
  if (args.length > 0 && !(args[0]?.constructor?.name === 'HTMLFormElement')) {
11
13
  throw new TypeError(
@@ -182,52 +184,71 @@ class FormData {
182
184
  }
183
185
 
184
186
  get [Symbol.toStringTag] () {
187
+ return this.constructor.name
188
+ }
189
+
190
+ entries () {
185
191
  if (!(this instanceof FormData)) {
186
192
  throw new TypeError('Illegal invocation')
187
193
  }
188
194
 
189
- return this.constructor.name
195
+ return makeIterator(
196
+ makeIterable(this[kState], 'entries'),
197
+ 'FormData'
198
+ )
190
199
  }
191
200
 
192
- * entries () {
201
+ keys () {
193
202
  if (!(this instanceof FormData)) {
194
203
  throw new TypeError('Illegal invocation')
195
204
  }
196
205
 
197
- for (const pair of this) {
198
- yield pair
199
- }
206
+ return makeIterator(
207
+ makeIterable(this[kState], 'keys'),
208
+ 'FormData'
209
+ )
200
210
  }
201
211
 
202
- * keys () {
212
+ values () {
203
213
  if (!(this instanceof FormData)) {
204
214
  throw new TypeError('Illegal invocation')
205
215
  }
206
216
 
207
- for (const [key] of this) {
208
- yield key
209
- }
217
+ return makeIterator(
218
+ makeIterable(this[kState], 'values'),
219
+ 'FormData'
220
+ )
210
221
  }
211
222
 
212
- * values () {
223
+ /**
224
+ * @param {(value: string, key: string, self: FormData) => void} callbackFn
225
+ * @param {unknown} thisArg
226
+ */
227
+ forEach (callbackFn, thisArg = globalThis) {
213
228
  if (!(this instanceof FormData)) {
214
229
  throw new TypeError('Illegal invocation')
215
230
  }
216
231
 
217
- for (const [, value] of this) {
218
- yield value
232
+ if (arguments.length < 1) {
233
+ throw new TypeError(
234
+ `Failed to execute 'forEach' on 'FormData': 1 argument required, but only ${arguments.length} present.`
235
+ )
219
236
  }
220
- }
221
237
 
222
- * [Symbol.iterator] () {
223
- // The value pairs to iterate over are this’s entry list’s entries with
224
- // the key being the name and the value being the value.
225
- for (const { name, value } of this[kState]) {
226
- yield [name, value]
238
+ if (typeof callbackFn !== 'function') {
239
+ throw new TypeError(
240
+ "Failed to execute 'forEach' on 'FormData': parameter 1 is not of type 'Function'."
241
+ )
242
+ }
243
+
244
+ for (const [key, value] of this) {
245
+ callbackFn.apply(thisArg, [value, key, this])
227
246
  }
228
247
  }
229
248
  }
230
249
 
250
+ FormData.prototype[Symbol.iterator] = FormData.prototype.entries
251
+
231
252
  function makeEntry (name, value, filename) {
232
253
  // To create an entry for name, value, and optionally a filename, run these
233
254
  // steps:
@@ -245,8 +266,8 @@ function makeEntry (name, value, filename) {
245
266
  // object, representing the same bytes, whose name attribute value is "blob".
246
267
  if (isBlobLike(value) && !isFileLike(value)) {
247
268
  value = value instanceof Blob
248
- ? new File([value], 'blob')
249
- : new FileLike(value, 'blob')
269
+ ? new File([value], 'blob', value)
270
+ : new FileLike(value, 'blob', value)
250
271
  }
251
272
 
252
273
  // 4. If value is (now) a File object and filename is given, then set value to a
@@ -258,8 +279,8 @@ function makeEntry (name, value, filename) {
258
279
  // creating one more File instance doesn't make much sense....
259
280
  if (isFileLike(value) && filename != null) {
260
281
  value = value instanceof File
261
- ? new File([value], filename)
262
- : new FileLike(value, filename)
282
+ ? new File([value], filename, value)
283
+ : new FileLike(value, filename, value)
263
284
  }
264
285
 
265
286
  // 5. Set entry’s value to value.
@@ -269,4 +290,18 @@ function makeEntry (name, value, filename) {
269
290
  return entry
270
291
  }
271
292
 
272
- module.exports = { FormData: globalThis.FormData ?? FormData }
293
+ function * makeIterable (entries, type) {
294
+ // The value pairs to iterate over are this’s entry list’s entries
295
+ // with the key being the name and the value being the value.
296
+ for (const { name, value } of entries) {
297
+ if (type === 'entries') {
298
+ yield [name, value]
299
+ } else if (type === 'values') {
300
+ yield value
301
+ } else {
302
+ yield name
303
+ }
304
+ }
305
+ }
306
+
307
+ module.exports = { FormData }