undici 5.4.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 +11 -16
- package/lib/core/request.js +4 -5
- package/lib/fetch/body.js +2 -2
- package/lib/fetch/formdata.js +54 -17
- package/lib/fetch/headers.js +4 -30
- package/lib/fetch/index.js +1 -1
- package/lib/fetch/util.js +29 -1
- package/package.json +1 -1
- package/types/connector.d.ts +4 -5
- package/types/fetch.d.ts +10 -10
- package/types/formdata.d.ts +15 -11
package/README.md
CHANGED
|
@@ -185,13 +185,12 @@ Help us improve the test coverage by following instructions at [nodejs/undici/#9
|
|
|
185
185
|
Basic usage example:
|
|
186
186
|
|
|
187
187
|
```js
|
|
188
|
-
|
|
188
|
+
import { fetch } from 'undici';
|
|
189
189
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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:
|
|
@@ -235,9 +234,7 @@ const data = {
|
|
|
235
234
|
},
|
|
236
235
|
};
|
|
237
236
|
|
|
238
|
-
|
|
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
|
-
|
|
249
|
-
|
|
245
|
+
import { fetch } from 'undici';
|
|
246
|
+
import { Readable } from 'node:stream';
|
|
250
247
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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
|
package/lib/core/request.js
CHANGED
|
@@ -80,13 +80,12 @@ class Request {
|
|
|
80
80
|
this.body = null
|
|
81
81
|
} else if (util.isStream(body)) {
|
|
82
82
|
this.body = body
|
|
83
|
-
} else if (body instanceof DataView) {
|
|
84
|
-
// TODO: Why is DataView special?
|
|
85
|
-
this.body = body.buffer.byteLength ? Buffer.from(body.buffer) : null
|
|
86
|
-
} else if (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) {
|
|
87
|
-
this.body = body.byteLength ? Buffer.from(body) : null
|
|
88
83
|
} else if (util.isBuffer(body)) {
|
|
89
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
|
|
90
89
|
} else if (typeof body === 'string') {
|
|
91
90
|
this.body = body.length ? Buffer.from(body) : null
|
|
92
91
|
} else if (util.isFormDataLike(body) || util.isIterable(body) || util.isBlobLike(body)) {
|
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
|
|
64
|
+
} else if (isArrayBuffer(object) || ArrayBuffer.isView(object)) {
|
|
65
65
|
// BufferSource
|
|
66
66
|
|
|
67
67
|
if (object instanceof DataView) {
|
package/lib/fetch/formdata.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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')
|
|
@@ -187,45 +187,68 @@ class FormData {
|
|
|
187
187
|
return this.constructor.name
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
-
|
|
190
|
+
entries () {
|
|
191
191
|
if (!(this instanceof FormData)) {
|
|
192
192
|
throw new TypeError('Illegal invocation')
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
195
|
+
return makeIterator(
|
|
196
|
+
makeIterable(this[kState], 'entries'),
|
|
197
|
+
'FormData'
|
|
198
|
+
)
|
|
198
199
|
}
|
|
199
200
|
|
|
200
|
-
|
|
201
|
+
keys () {
|
|
201
202
|
if (!(this instanceof FormData)) {
|
|
202
203
|
throw new TypeError('Illegal invocation')
|
|
203
204
|
}
|
|
204
205
|
|
|
205
|
-
|
|
206
|
-
|
|
206
|
+
return makeIterator(
|
|
207
|
+
makeIterable(this[kState], 'keys'),
|
|
208
|
+
'FormData'
|
|
209
|
+
)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
values () {
|
|
213
|
+
if (!(this instanceof FormData)) {
|
|
214
|
+
throw new TypeError('Illegal invocation')
|
|
207
215
|
}
|
|
216
|
+
|
|
217
|
+
return makeIterator(
|
|
218
|
+
makeIterable(this[kState], 'values'),
|
|
219
|
+
'FormData'
|
|
220
|
+
)
|
|
208
221
|
}
|
|
209
222
|
|
|
210
|
-
|
|
223
|
+
/**
|
|
224
|
+
* @param {(value: string, key: string, self: FormData) => void} callbackFn
|
|
225
|
+
* @param {unknown} thisArg
|
|
226
|
+
*/
|
|
227
|
+
forEach (callbackFn, thisArg = globalThis) {
|
|
211
228
|
if (!(this instanceof FormData)) {
|
|
212
229
|
throw new TypeError('Illegal invocation')
|
|
213
230
|
}
|
|
214
231
|
|
|
215
|
-
|
|
216
|
-
|
|
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
|
+
)
|
|
217
236
|
}
|
|
218
|
-
}
|
|
219
237
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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])
|
|
225
246
|
}
|
|
226
247
|
}
|
|
227
248
|
}
|
|
228
249
|
|
|
250
|
+
FormData.prototype[Symbol.iterator] = FormData.prototype.entries
|
|
251
|
+
|
|
229
252
|
function makeEntry (name, value, filename) {
|
|
230
253
|
// To create an entry for name, value, and optionally a filename, run these
|
|
231
254
|
// steps:
|
|
@@ -267,4 +290,18 @@ function makeEntry (name, value, filename) {
|
|
|
267
290
|
return entry
|
|
268
291
|
}
|
|
269
292
|
|
|
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
|
+
|
|
270
307
|
module.exports = { FormData }
|
package/lib/fetch/headers.js
CHANGED
|
@@ -6,6 +6,7 @@ const { validateHeaderName, validateHeaderValue } = require('http')
|
|
|
6
6
|
const { kHeadersList } = require('../core/symbols')
|
|
7
7
|
const { kGuard } = require('./symbols')
|
|
8
8
|
const { kEnumerableProperty } = require('../core/util')
|
|
9
|
+
const { makeIterator } = require('./util')
|
|
9
10
|
|
|
10
11
|
const kHeadersMap = Symbol('headers map')
|
|
11
12
|
const kHeadersSortedMap = Symbol('headers map sorted')
|
|
@@ -73,33 +74,6 @@ function fill (headers, object) {
|
|
|
73
74
|
}
|
|
74
75
|
}
|
|
75
76
|
|
|
76
|
-
// https://tc39.es/ecma262/#sec-%25iteratorprototype%25-object
|
|
77
|
-
const esIteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]()))
|
|
78
|
-
|
|
79
|
-
// https://webidl.spec.whatwg.org/#dfn-iterator-prototype-object
|
|
80
|
-
function makeHeadersIterator (iterator) {
|
|
81
|
-
const i = {
|
|
82
|
-
next () {
|
|
83
|
-
if (Object.getPrototypeOf(this) !== i) {
|
|
84
|
-
throw new TypeError(
|
|
85
|
-
'\'next\' called on an object that does not implement interface Headers Iterator.'
|
|
86
|
-
)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return iterator.next()
|
|
90
|
-
},
|
|
91
|
-
// The class string of an iterator prototype object for a given interface is the
|
|
92
|
-
// result of concatenating the identifier of the interface and the string " Iterator".
|
|
93
|
-
[Symbol.toStringTag]: 'Headers Iterator'
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// The [[Prototype]] internal slot of an iterator prototype object must be %IteratorPrototype%.
|
|
97
|
-
Object.setPrototypeOf(i, esIteratorPrototype)
|
|
98
|
-
// esIteratorPrototype needs to be the prototype of i
|
|
99
|
-
// which is the prototype of an empty object. Yes, it's confusing.
|
|
100
|
-
return Object.setPrototypeOf({}, i)
|
|
101
|
-
}
|
|
102
|
-
|
|
103
77
|
class HeadersList {
|
|
104
78
|
constructor (init) {
|
|
105
79
|
if (init instanceof HeadersList) {
|
|
@@ -306,7 +280,7 @@ class Headers {
|
|
|
306
280
|
throw new TypeError('Illegal invocation')
|
|
307
281
|
}
|
|
308
282
|
|
|
309
|
-
return
|
|
283
|
+
return makeIterator(this[kHeadersSortedMap].keys(), 'Headers')
|
|
310
284
|
}
|
|
311
285
|
|
|
312
286
|
values () {
|
|
@@ -314,7 +288,7 @@ class Headers {
|
|
|
314
288
|
throw new TypeError('Illegal invocation')
|
|
315
289
|
}
|
|
316
290
|
|
|
317
|
-
return
|
|
291
|
+
return makeIterator(this[kHeadersSortedMap].values(), 'Headers')
|
|
318
292
|
}
|
|
319
293
|
|
|
320
294
|
entries () {
|
|
@@ -322,7 +296,7 @@ class Headers {
|
|
|
322
296
|
throw new TypeError('Illegal invocation')
|
|
323
297
|
}
|
|
324
298
|
|
|
325
|
-
return
|
|
299
|
+
return makeIterator(this[kHeadersSortedMap].entries(), 'Headers')
|
|
326
300
|
}
|
|
327
301
|
|
|
328
302
|
/**
|
package/lib/fetch/index.js
CHANGED
|
@@ -1164,7 +1164,7 @@ async function httpRedirectFetch (fetchParams, response) {
|
|
|
1164
1164
|
if (
|
|
1165
1165
|
([301, 302].includes(actualResponse.status) && request.method === 'POST') ||
|
|
1166
1166
|
(actualResponse.status === 303 &&
|
|
1167
|
-
!['GET', '
|
|
1167
|
+
!['GET', 'HEAD'].includes(request.method))
|
|
1168
1168
|
) {
|
|
1169
1169
|
// then:
|
|
1170
1170
|
// 1. Set request’s method to `GET` and request’s body to null.
|
package/lib/fetch/util.js
CHANGED
|
@@ -361,6 +361,33 @@ function serializeJavascriptValueToJSONString (value) {
|
|
|
361
361
|
return result
|
|
362
362
|
}
|
|
363
363
|
|
|
364
|
+
// https://tc39.es/ecma262/#sec-%25iteratorprototype%25-object
|
|
365
|
+
const esIteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]()))
|
|
366
|
+
|
|
367
|
+
// https://webidl.spec.whatwg.org/#dfn-iterator-prototype-object
|
|
368
|
+
function makeIterator (iterator, name) {
|
|
369
|
+
const i = {
|
|
370
|
+
next () {
|
|
371
|
+
if (Object.getPrototypeOf(this) !== i) {
|
|
372
|
+
throw new TypeError(
|
|
373
|
+
`'next' called on an object that does not implement interface ${name} Iterator.`
|
|
374
|
+
)
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return iterator.next()
|
|
378
|
+
},
|
|
379
|
+
// The class string of an iterator prototype object for a given interface is the
|
|
380
|
+
// result of concatenating the identifier of the interface and the string " Iterator".
|
|
381
|
+
[Symbol.toStringTag]: `${name} Iterator`
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// The [[Prototype]] internal slot of an iterator prototype object must be %IteratorPrototype%.
|
|
385
|
+
Object.setPrototypeOf(i, esIteratorPrototype)
|
|
386
|
+
// esIteratorPrototype needs to be the prototype of i
|
|
387
|
+
// which is the prototype of an empty object. Yes, it's confusing.
|
|
388
|
+
return Object.setPrototypeOf({}, i)
|
|
389
|
+
}
|
|
390
|
+
|
|
364
391
|
module.exports = {
|
|
365
392
|
isAborted,
|
|
366
393
|
isCancelled,
|
|
@@ -390,5 +417,6 @@ module.exports = {
|
|
|
390
417
|
isValidReasonPhrase,
|
|
391
418
|
sameOrigin,
|
|
392
419
|
normalizeMethod,
|
|
393
|
-
serializeJavascriptValueToJSONString
|
|
420
|
+
serializeJavascriptValueToJSONString,
|
|
421
|
+
makeIterator
|
|
394
422
|
}
|
package/package.json
CHANGED
package/types/connector.d.ts
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { Socket } from 'net'
|
|
1
|
+
import {TLSSocket, ConnectionOptions} from 'tls'
|
|
2
|
+
import {IpcNetConnectOpts, Socket, TcpNetConnectOpts} from 'net'
|
|
4
3
|
|
|
5
4
|
export = buildConnector
|
|
6
5
|
declare function buildConnector (options?: buildConnector.BuildOptions): typeof buildConnector.connector
|
|
7
6
|
|
|
8
7
|
declare namespace buildConnector {
|
|
9
|
-
export
|
|
8
|
+
export type BuildOptions = (ConnectionOptions | TcpNetConnectOpts | IpcNetConnectOpts) & {
|
|
10
9
|
maxCachedSessions?: number | null;
|
|
11
10
|
socketPath?: string | null;
|
|
12
11
|
timeout?: number | null;
|
|
13
|
-
|
|
12
|
+
port?: number;
|
|
14
13
|
}
|
|
15
14
|
|
|
16
15
|
export interface Options {
|
package/types/fetch.d.ts
CHANGED
|
@@ -38,21 +38,21 @@ export interface BodyMixin {
|
|
|
38
38
|
readonly text: () => Promise<string>
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
export interface
|
|
41
|
+
export interface SpecIterator<T, TReturn = any, TNext = undefined> {
|
|
42
42
|
next(...args: [] | [TNext]): IteratorResult<T, TReturn>;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
export interface
|
|
46
|
-
[Symbol.iterator]():
|
|
45
|
+
export interface SpecIterableIterator<T> extends SpecIterator<T> {
|
|
46
|
+
[Symbol.iterator](): SpecIterableIterator<T>;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
export interface
|
|
50
|
-
[Symbol.iterator]():
|
|
49
|
+
export interface SpecIterable<T> {
|
|
50
|
+
[Symbol.iterator](): SpecIterator<T>;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
export type HeadersInit = string[][] | Record<string, string | ReadonlyArray<string>> | Headers
|
|
54
54
|
|
|
55
|
-
export declare class Headers implements
|
|
55
|
+
export declare class Headers implements SpecIterable<[string, string]> {
|
|
56
56
|
constructor (init?: HeadersInit)
|
|
57
57
|
readonly append: (name: string, value: string) => void
|
|
58
58
|
readonly delete: (name: string) => void
|
|
@@ -64,10 +64,10 @@ export declare class Headers implements HeadersIterable<[string, string]> {
|
|
|
64
64
|
thisArg?: unknown
|
|
65
65
|
) => void
|
|
66
66
|
|
|
67
|
-
readonly keys: () =>
|
|
68
|
-
readonly values: () =>
|
|
69
|
-
readonly entries: () =>
|
|
70
|
-
readonly [Symbol.iterator]: () =>
|
|
67
|
+
readonly keys: () => SpecIterableIterator<string>
|
|
68
|
+
readonly values: () => SpecIterableIterator<string>
|
|
69
|
+
readonly entries: () => SpecIterableIterator<[string, string]>
|
|
70
|
+
readonly [Symbol.iterator]: () => SpecIterator<[string, string]>
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
export type RequestCache =
|
package/types/formdata.d.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
/// <reference types="node" />
|
|
3
3
|
|
|
4
4
|
import { File } from './file'
|
|
5
|
+
import { SpecIterator, SpecIterableIterator } from './fetch'
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* A `string` or `File` that represents a single value from a set of `FormData` key-value pairs.
|
|
@@ -73,32 +74,35 @@ export declare class FormData {
|
|
|
73
74
|
delete(name: string): void
|
|
74
75
|
|
|
75
76
|
/**
|
|
76
|
-
*
|
|
77
|
-
* Each key is a `string`.
|
|
77
|
+
* Executes given callback function for each field of the FormData instance
|
|
78
78
|
*/
|
|
79
|
-
|
|
79
|
+
forEach: (
|
|
80
|
+
callbackfn: (value: FormDataEntryValue, key: string, iterable: FormData) => void,
|
|
81
|
+
thisArg?: unknown
|
|
82
|
+
) => void
|
|
80
83
|
|
|
81
84
|
/**
|
|
82
|
-
* Returns an [`iterator`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) allowing to go through
|
|
83
|
-
*
|
|
85
|
+
* 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.
|
|
86
|
+
* Each key is a `string`.
|
|
84
87
|
*/
|
|
85
|
-
|
|
88
|
+
keys: () => SpecIterableIterator<string>
|
|
86
89
|
|
|
87
90
|
/**
|
|
88
91
|
* 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.
|
|
89
92
|
* Each value is a [`FormDataValue`](https://developer.mozilla.org/en-US/docs/Web/API/FormDataEntryValue).
|
|
90
93
|
*/
|
|
91
|
-
values()
|
|
94
|
+
values: () => SpecIterableIterator<FormDataEntryValue>
|
|
92
95
|
|
|
93
96
|
/**
|
|
94
|
-
*
|
|
97
|
+
* Returns an [`iterator`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) allowing to go through the `FormData` key/value pairs.
|
|
98
|
+
* The key of each pair is a string; the value is a [`FormDataValue`](https://developer.mozilla.org/en-US/docs/Web/API/FormDataEntryValue).
|
|
95
99
|
*/
|
|
96
|
-
|
|
100
|
+
entries: () => SpecIterableIterator<[string, FormDataEntryValue]>
|
|
97
101
|
|
|
98
102
|
/**
|
|
99
|
-
*
|
|
103
|
+
* An alias for FormData#entries()
|
|
100
104
|
*/
|
|
101
|
-
|
|
105
|
+
[Symbol.iterator]: () => SpecIterableIterator<[string, FormDataEntryValue]>
|
|
102
106
|
|
|
103
107
|
readonly [Symbol.toStringTag]: string
|
|
104
108
|
}
|