@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 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; // To enable: import {fetch} from 'cross-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
- options = options || {};
202
- options.headers = new ponyfills.Headers(options.headers || {});
203
- options.headers.set('Connection', 'keep-alive');
204
- if (options.body != null) {
205
- if (options.body[Symbol.toStringTag] === 'FormData') {
206
- const encoder = new formDataEncoderModule.FormDataEncoder(options.body)
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
- options.headers.set(headerKey, encoder.headers[headerKey])
209
+ fixedOptions.headers.set(headerKey, encoder.headers[headerKey])
209
210
  }
210
- options.body = streams.Readable.from(encoder.encode());
211
- }
212
- if (options.body[Symbol.toStringTag] === 'ReadableStream') {
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, options);
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
- let file$;
61
- if (limits && limits.fieldsFirst) {
62
- resolve(formData);
63
- const fakeFileObj = {
64
- name: filename,
65
- type: mimeType,
66
- }
67
- Object.setPrototypeOf(fakeFileObj, File.prototype);
68
- formData.set(name, new Proxy(fakeFileObj, {
69
- get: (target, prop) => {
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$.catch(reject);
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
- 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.releaseLock();
13
- await readableStream.cancel();
14
- return Promise.resolve({ done: true });
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@whatwg-node/fetch",
3
- "version": "0.4.6",
3
+ "version": "0.5.0",
4
4
  "description": "Cross Platform Smart Fetch Ponyfill",
5
5
  "author": "Arda TANRIKULU <ardatanrikulu@gmail.com>",
6
6
  "repository": {
@@ -1,13 +1,8 @@
1
- import { createFetch } from '@whatwg-node/fetch';
1
+ import { createTestContainer } from '../../server/test/create-test-container';
2
2
 
3
3
  describe('getFormDataMethod', () => {
4
- ['fieldsFirst:true', 'fieldsFirst:false'].forEach(fieldsFirstFlag => {
5
- const fetchAPI = createFetch({
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
  });