@whatwg-node/fetch 0.4.6 → 0.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/CHANGELOG.md +12 -0
- package/dist/create-node-ponyfill.js +21 -21
- package/dist/getFormDataMethod.js +13 -74
- package/dist/index.d.ts +0 -2
- package/dist/readableStreamToReadable.js +16 -15
- package/package.json +1 -1
- package/tests/getFormDataMethod.spec.ts +5 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @whatwg-node/fetch
|
|
2
2
|
|
|
3
|
+
## 0.5.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [`ab5fb52`](https://github.com/ardatan/whatwg-node/commit/ab5fb524753bc7a210b1aaf2e1580566907d4713) Thanks [@ardatan](https://github.com/ardatan)! - Drop broken `fieldsFirst` flag
|
|
8
|
+
|
|
9
|
+
## 0.4.7
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [`e59cbb6`](https://github.com/ardatan/whatwg-node/commit/e59cbb667dfcbdd9c0cf609fd56dbd904ac85cbd) Thanks [@ardatan](https://github.com/ardatan)! - Do not patch global Headers if it is native, and support URL as a first parameter of `fetch`
|
|
14
|
+
|
|
3
15
|
## 0.4.6
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
|
@@ -11,7 +11,7 @@ module.exports = function createNodePonyfill(opts = {}) {
|
|
|
11
11
|
const ponyfills = {};
|
|
12
12
|
|
|
13
13
|
if (!opts.useNodeFetch) {
|
|
14
|
-
ponyfills.fetch = globalThis.fetch;
|
|
14
|
+
ponyfills.fetch = globalThis.fetch;
|
|
15
15
|
ponyfills.Headers = globalThis.Headers;
|
|
16
16
|
ponyfills.Request = globalThis.Request;
|
|
17
17
|
ponyfills.Response = globalThis.Response;
|
|
@@ -121,8 +121,7 @@ module.exports = function createNodePonyfill(opts = {}) {
|
|
|
121
121
|
|
|
122
122
|
class Request extends OriginalRequest {
|
|
123
123
|
constructor(requestOrUrl, options) {
|
|
124
|
-
if (typeof requestOrUrl === "string") {
|
|
125
|
-
options = options || {};
|
|
124
|
+
if (typeof requestOrUrl === "string" || requestOrUrl instanceof URL) {
|
|
126
125
|
super(requestOrUrl, options);
|
|
127
126
|
const contentType = this.headers.get("content-type");
|
|
128
127
|
if (contentType && contentType.startsWith("multipart/form-data")) {
|
|
@@ -140,7 +139,7 @@ module.exports = function createNodePonyfill(opts = {}) {
|
|
|
140
139
|
const originalFetch = ponyfills.fetch || undici.fetch;
|
|
141
140
|
|
|
142
141
|
const fetch = function (requestOrUrl, options) {
|
|
143
|
-
if (typeof requestOrUrl === "string") {
|
|
142
|
+
if (typeof requestOrUrl === "string" || requestOrUrl instanceof URL) {
|
|
144
143
|
// We cannot use our ctor because it leaks on Node 18's global fetch
|
|
145
144
|
return originalFetch(requestOrUrl, options);
|
|
146
145
|
}
|
|
@@ -169,7 +168,7 @@ module.exports = function createNodePonyfill(opts = {}) {
|
|
|
169
168
|
if (!ponyfills.Headers) {
|
|
170
169
|
ponyfills.Headers = nodeFetch.Headers;
|
|
171
170
|
// Sveltekit
|
|
172
|
-
if (globalThis.Headers) {
|
|
171
|
+
if (globalThis.Headers && nodeMajor < 18) {
|
|
173
172
|
Object.defineProperty(globalThis.Headers, Symbol.hasInstance, {
|
|
174
173
|
value(obj) {
|
|
175
174
|
return obj && obj.get && obj.set && obj.delete && obj.has && obj.append;
|
|
@@ -192,28 +191,29 @@ module.exports = function createNodePonyfill(opts = {}) {
|
|
|
192
191
|
|
|
193
192
|
class Request extends OriginalRequest {
|
|
194
193
|
constructor(requestOrUrl, options) {
|
|
195
|
-
if (typeof requestOrUrl === "string") {
|
|
194
|
+
if (typeof requestOrUrl === "string" || requestOrUrl instanceof URL) {
|
|
196
195
|
// Support schemaless URIs on the server for parity with the browser.
|
|
197
196
|
// Ex: //github.com/ -> https://github.com/
|
|
198
|
-
if (/^\/\//.test(requestOrUrl)) {
|
|
199
|
-
requestOrUrl = "https:" + requestOrUrl;
|
|
197
|
+
if (/^\/\//.test(requestOrUrl.toString())) {
|
|
198
|
+
requestOrUrl = "https:" + requestOrUrl.toString();
|
|
200
199
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
200
|
+
const fixedOptions = {
|
|
201
|
+
...options
|
|
202
|
+
};
|
|
203
|
+
fixedOptions.headers = new ponyfills.Headers(fixedOptions.headers || {});
|
|
204
|
+
fixedOptions.headers.set('Connection', 'keep-alive');
|
|
205
|
+
if (fixedOptions.body != null) {
|
|
206
|
+
if (fixedOptions.body[Symbol.toStringTag] === 'FormData') {
|
|
207
|
+
const encoder = new formDataEncoderModule.FormDataEncoder(fixedOptions.body)
|
|
207
208
|
for (const headerKey in encoder.headers) {
|
|
208
|
-
|
|
209
|
+
fixedOptions.headers.set(headerKey, encoder.headers[headerKey])
|
|
209
210
|
}
|
|
210
|
-
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
options.body = readableStreamToReadable(options.body);
|
|
211
|
+
fixedOptions.body = streams.Readable.from(encoder);
|
|
212
|
+
} else if (fixedOptions.body[Symbol.toStringTag] === 'ReadableStream') {
|
|
213
|
+
fixedOptions.body = readableStreamToReadable(fixedOptions.body);
|
|
214
214
|
}
|
|
215
215
|
}
|
|
216
|
-
super(requestOrUrl,
|
|
216
|
+
super(requestOrUrl, fixedOptions);
|
|
217
217
|
} else {
|
|
218
218
|
super(requestOrUrl);
|
|
219
219
|
}
|
|
@@ -222,7 +222,7 @@ module.exports = function createNodePonyfill(opts = {}) {
|
|
|
222
222
|
}
|
|
223
223
|
ponyfills.Request = Request;
|
|
224
224
|
const fetch = function (requestOrUrl, options) {
|
|
225
|
-
if (typeof requestOrUrl === "string") {
|
|
225
|
+
if (typeof requestOrUrl === "string" || requestOrUrl instanceof URL) {
|
|
226
226
|
return fetch(new Request(requestOrUrl, options));
|
|
227
227
|
}
|
|
228
228
|
if (requestOrUrl.url.startsWith('file:')) {
|
|
@@ -3,31 +3,6 @@ const { resolve } = require('path');
|
|
|
3
3
|
const streams = require("stream");
|
|
4
4
|
|
|
5
5
|
module.exports = function getFormDataMethod(File, limits) {
|
|
6
|
-
function consumeStreamAsFile({
|
|
7
|
-
name,
|
|
8
|
-
filename,
|
|
9
|
-
mimeType,
|
|
10
|
-
fileStream,
|
|
11
|
-
formData,
|
|
12
|
-
}) {
|
|
13
|
-
return new Promise((resolve, reject) => {
|
|
14
|
-
const chunks = [];
|
|
15
|
-
fileStream.on('limit', () => {
|
|
16
|
-
reject(new Error(`File size limit exceeded: ${limits.fileSize} bytes`));
|
|
17
|
-
})
|
|
18
|
-
fileStream.on('data', (chunk) => {
|
|
19
|
-
chunks.push(...chunk);
|
|
20
|
-
})
|
|
21
|
-
fileStream.on('close', () => {
|
|
22
|
-
if (fileStream.truncated) {
|
|
23
|
-
reject(new Error(`File size limit exceeded: ${limits.fileSize} bytes`));
|
|
24
|
-
}
|
|
25
|
-
const file = new File([new Uint8Array(chunks)], filename, { type: mimeType });
|
|
26
|
-
formData.set(name, file);
|
|
27
|
-
resolve(file);
|
|
28
|
-
});
|
|
29
|
-
})
|
|
30
|
-
}
|
|
31
6
|
|
|
32
7
|
return function formData() {
|
|
33
8
|
if (this.body == null) {
|
|
@@ -57,56 +32,20 @@ module.exports = function getFormDataMethod(File, limits) {
|
|
|
57
32
|
reject(new Error(`Fields limit exceeded: ${limits.fields}`));
|
|
58
33
|
})
|
|
59
34
|
bb.on('file', (name, fileStream, { filename, mimeType }) => {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
switch(prop) {
|
|
71
|
-
case 'name':
|
|
72
|
-
return filename;
|
|
73
|
-
case 'type':
|
|
74
|
-
return mimeType;
|
|
75
|
-
case 'stream':
|
|
76
|
-
return () => fileStream;
|
|
77
|
-
case 'size':
|
|
78
|
-
throw new Error(`Cannot access file size before consuming the stream.`);
|
|
79
|
-
case 'slice':
|
|
80
|
-
throw new Error(`Cannot slice file before consuming the stream.`);
|
|
81
|
-
case 'text':
|
|
82
|
-
case 'arrayBuffer':
|
|
83
|
-
return () => {
|
|
84
|
-
if (!file$) {
|
|
85
|
-
file$ = consumeStreamAsFile({
|
|
86
|
-
name,
|
|
87
|
-
filename,
|
|
88
|
-
mimeType,
|
|
89
|
-
fileStream,
|
|
90
|
-
formData,
|
|
91
|
-
})
|
|
92
|
-
}
|
|
93
|
-
return file$.then(file => file[prop]());
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
},
|
|
97
|
-
}))
|
|
98
|
-
} else {
|
|
99
|
-
if (!file$) {
|
|
100
|
-
file$ = consumeStreamAsFile({
|
|
101
|
-
name,
|
|
102
|
-
filename,
|
|
103
|
-
mimeType,
|
|
104
|
-
fileStream,
|
|
105
|
-
formData,
|
|
106
|
-
})
|
|
35
|
+
const chunks = [];
|
|
36
|
+
fileStream.on('limit', () => {
|
|
37
|
+
reject(new Error(`File size limit exceeded: ${limits.fileSize} bytes`));
|
|
38
|
+
})
|
|
39
|
+
fileStream.on('data', (chunk) => {
|
|
40
|
+
chunks.push(Buffer.from(chunk));
|
|
41
|
+
})
|
|
42
|
+
fileStream.on('close', () => {
|
|
43
|
+
if (fileStream.truncated) {
|
|
44
|
+
reject(new Error(`File size limit exceeded: ${limits.fileSize} bytes`));
|
|
107
45
|
}
|
|
108
|
-
file
|
|
109
|
-
|
|
46
|
+
const file = new File(chunks, filename, { type: mimeType });
|
|
47
|
+
formData.set(name, file);
|
|
48
|
+
});
|
|
110
49
|
})
|
|
111
50
|
bb.on('filesLimit', () => {
|
|
112
51
|
reject(new Error(`Files limit exceeded: ${limits.files}`));
|
package/dist/index.d.ts
CHANGED
|
@@ -47,8 +47,6 @@ declare module "@whatwg-node/fetch" {
|
|
|
47
47
|
parts?: number;
|
|
48
48
|
/* For multipart forms, the max number of header key-value pairs to parse. Default: 2000. */
|
|
49
49
|
headerSize?: number;
|
|
50
|
-
/* For multipart forms, enable this if your data has fields first, then files. Default: false. */
|
|
51
|
-
fieldsFirst?: boolean;
|
|
52
50
|
}
|
|
53
51
|
export const createFetch: (opts?: { useNodeFetch?: boolean; formDataLimits?: FormDataLimits }) => ({
|
|
54
52
|
fetch: typeof _fetch,
|
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
const streams = require('stream');
|
|
2
2
|
|
|
3
3
|
module.exports = function readableStreamToReadable(readableStream) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
4
|
+
return streams.Readable.from({
|
|
5
|
+
[Symbol.asyncIterator]() {
|
|
6
|
+
const reader = readableStream.getReader();
|
|
7
|
+
return {
|
|
8
|
+
next() {
|
|
9
|
+
return reader.read();
|
|
10
|
+
},
|
|
11
|
+
async return() {
|
|
12
|
+
reader.cancel();
|
|
13
|
+
reader.releaseLock();
|
|
14
|
+
await readableStream.cancel();
|
|
15
|
+
return { done: true };
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
});
|
|
19
20
|
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createTestContainer } from '../../server/test/create-test-container';
|
|
2
2
|
|
|
3
3
|
describe('getFormDataMethod', () => {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
formDataLimits: {
|
|
7
|
-
fieldsFirst: fieldsFirstFlag === 'fieldsFirst:true',
|
|
8
|
-
},
|
|
9
|
-
});
|
|
10
|
-
describe(fieldsFirstFlag, () => {
|
|
4
|
+
createTestContainer(
|
|
5
|
+
fetchAPI => {
|
|
11
6
|
it('should parse fields correctly', async () => {
|
|
12
7
|
const formData = new fetchAPI.FormData();
|
|
13
8
|
formData.append('greetings', 'Hello world!');
|
|
@@ -38,6 +33,6 @@ describe('getFormDataMethod', () => {
|
|
|
38
33
|
const receivedByeText = await receivedByeFile.text();
|
|
39
34
|
expect(receivedByeText).toBe('Goodbye world!');
|
|
40
35
|
});
|
|
41
|
-
}
|
|
42
|
-
|
|
36
|
+
}
|
|
37
|
+
);
|
|
43
38
|
});
|