@whatwg-node/fetch 0.5.2 → 0.5.4-alpha-20221225155445-dfcb269

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.4-alpha-20221225155445-dfcb269
4
+
5
+ ### Patch Changes
6
+
7
+ - [#154](https://github.com/ardatan/whatwg-node/pull/154) [`8324adc`](https://github.com/ardatan/whatwg-node/commit/8324adcc1383eb16fd6d0a6417842e0b8d9b82ba) Thanks [@ardatan](https://github.com/ardatan)! - New Fetch API implementation for Node
8
+
9
+ ## 0.5.3
10
+
11
+ ### Patch Changes
12
+
13
+ - [`188ac01`](https://github.com/ardatan/whatwg-node/commit/188ac01dab264ed483dbc3b897e6958b49085922) Thanks [@ardatan](https://github.com/ardatan)! - Fix destroy for Node 18
14
+
3
15
  ## 0.5.2
4
16
 
5
17
  ### Patch Changes
@@ -8,77 +8,28 @@ module.exports = function createNodePonyfill(opts = {}) {
8
8
  return globalThis;
9
9
  }
10
10
 
11
- const ponyfills = {};
12
-
13
- if (!opts.useNodeFetch) {
14
- ponyfills.fetch = globalThis.fetch;
15
- ponyfills.Headers = globalThis.Headers;
16
- ponyfills.Request = globalThis.Request;
17
- ponyfills.Response = globalThis.Response;
18
- ponyfills.FormData = globalThis.FormData;
19
- ponyfills.File = globalThis.File;
20
- }
21
-
22
- ponyfills.AbortController = globalThis.AbortController;
23
- ponyfills.ReadableStream = globalThis.ReadableStream;
24
- ponyfills.WritableStream = globalThis.WritableStream;
25
- ponyfills.TransformStream = globalThis.TransformStream;
26
- ponyfills.Blob = globalThis.Blob;
27
- ponyfills.crypto = globalThis.crypto;
28
-
29
- if (!ponyfills.AbortController) {
30
- const abortControllerModule = require("abort-controller");
31
- ponyfills.AbortController =
32
- abortControllerModule.default || abortControllerModule;
33
- }
34
-
35
- if (!ponyfills.Blob) {
36
- const bufferModule = require('buffer')
37
- ponyfills.Blob = bufferModule.Blob;
38
- }
39
-
40
- if (!ponyfills.Blob) {
41
- const formDataModule = require("formdata-node");
42
- ponyfills.Blob = formDataModule.Blob
43
- }
11
+ const newNodeFetch = require('@whatwg-node/node-fetch');
44
12
 
45
- if (!ponyfills.ReadableStream) {
46
- try {
47
- const streamsWeb = require("stream/web");
48
-
49
- ponyfills.ReadableStream = streamsWeb.ReadableStream;
50
- ponyfills.WritableStream = streamsWeb.WritableStream;
51
- ponyfills.TransformStream = streamsWeb.TransformStream;
52
- } catch (e) {
53
- const streamsWeb = require("web-streams-polyfill/ponyfill");
54
- ponyfills.ReadableStream = streamsWeb.ReadableStream;
55
- ponyfills.WritableStream = streamsWeb.WritableStream;
56
- ponyfills.TransformStream = streamsWeb.TransformStream;
57
- }
58
- }
59
-
60
- ponyfills.btoa = globalThis.btoa
61
- if (!ponyfills.btoa) {
62
- ponyfills.btoa = function btoa(data) {
63
- return Buffer.from(data, 'binary').toString('base64');
64
- };
65
- }
13
+ const ponyfills = {};
66
14
 
67
- ponyfills.TextEncoder = function TextEncoder(encoding = 'utf-8') {
68
- return {
69
- encode(str) {
70
- return Buffer.from(str, encoding);
71
- }
72
- }
73
- }
74
-
75
- ponyfills.TextDecoder = function TextDecoder(encoding = 'utf-8') {
76
- return {
77
- decode(buf) {
78
- return Buffer.from(buf).toString(encoding);
79
- }
80
- }
81
- }
15
+ ponyfills.AbortController = newNodeFetch.AbortController;
16
+ ponyfills.AbortError = newNodeFetch.AbortError;
17
+ ponyfills.AbortSignal = newNodeFetch.AbortSignal;
18
+ ponyfills.Blob = newNodeFetch.Blob;
19
+ ponyfills.Body = newNodeFetch.Body;
20
+ ponyfills.fetch = newNodeFetch.fetch;
21
+ ponyfills.File = newNodeFetch.File;
22
+ ponyfills.FormData = newNodeFetch.FormData;
23
+ ponyfills.Headers = newNodeFetch.Headers;
24
+ ponyfills.ReadableStream = newNodeFetch.ReadableStream;
25
+ ponyfills.Request = newNodeFetch.Request;
26
+ ponyfills.Response = newNodeFetch.Response;
27
+ ponyfills.TextEncoder = newNodeFetch.TextEncoder;
28
+ ponyfills.TextDecoder = newNodeFetch.TextDecoder;
29
+ ponyfills.btoa = newNodeFetch.btoa;
30
+
31
+ const getFormDataMethod = require("./getFormDataMethod");
32
+ ponyfills.Body.prototype.formData = getFormDataMethod(ponyfills.File, opts.formDataLimits)
82
33
 
83
34
  if (!ponyfills.crypto) {
84
35
  const cryptoModule = require("crypto");
@@ -90,203 +41,6 @@ module.exports = function createNodePonyfill(opts = {}) {
90
41
  ponyfills.crypto = new cryptoPonyfill.Crypto();
91
42
  }
92
43
 
93
- // If any of classes of Fetch API is missing, we need to ponyfill them.
94
- if (!ponyfills.fetch ||
95
- !ponyfills.Request ||
96
- !ponyfills.Headers ||
97
- !ponyfills.Response ||
98
- !ponyfills.FormData ||
99
- !ponyfills.File ||
100
- opts.useNodeFetch) {
101
-
102
- const [
103
- nodeMajorStr,
104
- nodeMinorStr
105
- ] = process.versions.node.split('.');
106
-
107
- const nodeMajor = parseInt(nodeMajorStr);
108
- const nodeMinor = parseInt(nodeMinorStr);
109
- const getFormDataMethod = require('./getFormDataMethod');
110
-
111
- if (!opts.useNodeFetch && (nodeMajor > 16 || (nodeMajor === 16 && nodeMinor >= 5))) {
112
- const undici = require("undici");
113
-
114
- if (!ponyfills.Headers) {
115
- ponyfills.Headers = undici.Headers;
116
- }
117
-
118
- const streams = require("stream");
119
-
120
- const OriginalRequest = ponyfills.Request || undici.Request;
121
-
122
- class Request extends OriginalRequest {
123
- constructor(requestOrUrl, options) {
124
- if (typeof requestOrUrl === "string" || requestOrUrl instanceof URL) {
125
- if (options != null && typeof options === "object" && !options.duplex) {
126
- options.duplex = 'half';
127
- }
128
- super(requestOrUrl, options);
129
- const contentType = this.headers.get("content-type");
130
- if (contentType && contentType.startsWith("multipart/form-data")) {
131
- this.headers.set("content-type", contentType.split(', ')[0]);
132
- }
133
- } else {
134
- super(requestOrUrl);
135
- }
136
- this.formData = getFormDataMethod(undici.File, opts.formDataLimits);
137
- }
138
- }
139
-
140
- ponyfills.Request = Request;
141
-
142
- const originalFetch = ponyfills.fetch || undici.fetch;
143
-
144
- const fetch = function (requestOrUrl, options) {
145
- if (typeof requestOrUrl === "string" || requestOrUrl instanceof URL) {
146
- if (options != null && typeof options === "object" && !options.duplex) {
147
- options.duplex = 'half';
148
- }
149
- // We cannot use our ctor because it leaks on Node 18's global fetch
150
- return originalFetch(requestOrUrl, options);
151
- }
152
- if (requestOrUrl.url.startsWith('file:')) {
153
- return handleFileRequest(requestOrUrl.url, ponyfills.Response);
154
- }
155
- return originalFetch(requestOrUrl);
156
- };
157
-
158
- ponyfills.fetch = fetch;
159
-
160
- if (!ponyfills.Response) {
161
- ponyfills.Response = undici.Response;
162
- }
163
-
164
- if (!ponyfills.FormData) {
165
- ponyfills.FormData = undici.FormData;
166
- }
167
-
168
- if (!ponyfills.File) {
169
- ponyfills.File = undici.File
170
- }
171
- } else {
172
- const nodeFetch = require("node-fetch");
173
- const realFetch = ponyfills.fetch || nodeFetch.default || nodeFetch;
174
- if (!ponyfills.Headers) {
175
- ponyfills.Headers = nodeFetch.Headers;
176
- // Sveltekit
177
- if (globalThis.Headers && nodeMajor < 18) {
178
- Object.defineProperty(globalThis.Headers, Symbol.hasInstance, {
179
- value(obj) {
180
- return obj && obj.get && obj.set && obj.delete && obj.has && obj.append;
181
- },
182
- configurable: true,
183
- })
184
- }
185
- }
186
- const formDataEncoderModule = require("form-data-encoder");
187
- const streams = require("stream");
188
- const formDataModule = require("formdata-node");
189
- if (!ponyfills.FormData) {
190
- ponyfills.FormData = formDataModule.FormData
191
- }
192
- if (!ponyfills.File) {
193
- ponyfills.File = formDataModule.File
194
- }
195
-
196
- const OriginalRequest = ponyfills.Request || nodeFetch.Request;
197
-
198
- class Request extends OriginalRequest {
199
- constructor(requestOrUrl, options) {
200
- if (typeof requestOrUrl === "string" || requestOrUrl instanceof URL) {
201
- // Support schemaless URIs on the server for parity with the browser.
202
- // Ex: //github.com/ -> https://github.com/
203
- if (/^\/\//.test(requestOrUrl.toString())) {
204
- requestOrUrl = "https:" + requestOrUrl.toString();
205
- }
206
- const fixedOptions = {
207
- ...options
208
- };
209
- fixedOptions.headers = new ponyfills.Headers(fixedOptions.headers || {});
210
- fixedOptions.headers.set('Connection', 'keep-alive');
211
- if (fixedOptions.body != null) {
212
- if (fixedOptions.body[Symbol.toStringTag] === 'FormData') {
213
- const encoder = new formDataEncoderModule.FormDataEncoder(fixedOptions.body)
214
- for (const headerKey in encoder.headers) {
215
- fixedOptions.headers.set(headerKey, encoder.headers[headerKey])
216
- }
217
- fixedOptions.body = streams.Readable.from(encoder);
218
- } else if (fixedOptions.body[Symbol.toStringTag] === 'ReadableStream') {
219
- fixedOptions.body = readableStreamToReadable(fixedOptions.body);
220
- }
221
- }
222
- super(requestOrUrl, fixedOptions);
223
- } else {
224
- super(requestOrUrl);
225
- }
226
- this.formData = getFormDataMethod(formDataModule.File, opts.formDataLimits);
227
- }
228
- }
229
- ponyfills.Request = Request;
230
- const fetch = function (requestOrUrl, options) {
231
- if (typeof requestOrUrl === "string" || requestOrUrl instanceof URL) {
232
- return fetch(new Request(requestOrUrl, options));
233
- }
234
- if (requestOrUrl.url.startsWith('file:')) {
235
- return handleFileRequest(requestOrUrl.url, ponyfills.Response);
236
- }
237
- const abortCtrl = new ponyfills.AbortController();
238
-
239
- return realFetch(requestOrUrl, {
240
- ...options,
241
- signal: abortCtrl.signal
242
- }).then(res => {
243
- return new Proxy(res, {
244
- get(target, prop, receiver) {
245
- if (prop === 'body') {
246
- return new Proxy(res.body, {
247
- get(target, prop, receiver) {
248
- if (prop === Symbol.asyncIterator) {
249
- return () => {
250
- const originalAsyncIterator = target[Symbol.asyncIterator]();
251
- return {
252
- next() {
253
- return originalAsyncIterator.next();
254
- },
255
- return() {
256
- abortCtrl.abort();
257
- return originalAsyncIterator.return();
258
- },
259
- throw(error) {
260
- abortCtrl.abort(error);
261
- return originalAsyncIterator.throw(error);
262
- }
263
- }
264
- }
265
- }
266
- return Reflect.get(target, prop, receiver);
267
- }
268
- })
269
- }
270
- return Reflect.get(target, prop, receiver);
271
- }
272
- })
273
- });
274
- };
275
-
276
- ponyfills.fetch = fetch;
277
-
278
- const OriginalResponse = ponyfills.Response || nodeFetch.Response;
279
- ponyfills.Response = function Response(body, init) {
280
- if (body != null && body[Symbol.toStringTag] === 'ReadableStream') {
281
- const actualBody = readableStreamToReadable(body);
282
- // Polyfill ReadableStream is not working well with node-fetch's Response
283
- return new OriginalResponse(actualBody, init);
284
- }
285
- return new OriginalResponse(body, init);
286
- };
287
-
288
- }
289
- }
290
44
 
291
45
  if (!ponyfills.Response.redirect) {
292
46
  ponyfills.Response.redirect = function (url, status = 302) {
@@ -1,5 +1,4 @@
1
1
  const busboy = require('busboy');
2
- const { resolve } = require('path');
3
2
  const streams = require("stream");
4
3
 
5
4
  module.exports = function getFormDataMethod(File, limits) {
@@ -12,13 +12,14 @@ module.exports = function readableStreamToReadable(readableStream) {
12
12
  }
13
13
  })
14
14
  },
15
- async destroy() {
15
+ async destroy(err, callback) {
16
16
  try {
17
17
  reader.cancel();
18
18
  reader.releaseLock();
19
19
  await readableStream.cancel();
20
+ callback();
20
21
  } catch (error) {
21
- console.log(error);
22
+ callback(error);
22
23
  }
23
24
  }
24
25
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@whatwg-node/fetch",
3
- "version": "0.5.2",
3
+ "version": "0.5.4-alpha-20221225155445-dfcb269",
4
4
  "description": "Cross Platform Smart Fetch Ponyfill",
5
5
  "author": "Arda TANRIKULU <ardatanrikulu@gmail.com>",
6
6
  "repository": {
@@ -19,13 +19,7 @@
19
19
  },
20
20
  "dependencies": {
21
21
  "@peculiar/webcrypto": "^1.4.0",
22
- "abort-controller": "^3.0.0",
23
- "busboy": "^1.6.0",
24
- "form-data-encoder": "^1.7.1",
25
- "formdata-node": "^4.3.1",
26
- "node-fetch": "^2.6.7",
27
- "undici": "^5.12.0",
28
- "web-streams-polyfill": "^3.2.0"
22
+ "busboy": "^1.6.0"
29
23
  },
30
24
  "publishConfig": {
31
25
  "access": "public"
@@ -1,36 +1,34 @@
1
- import { createTestContainer } from '../../server/test/create-test-container';
1
+ import * as fetchAPI from '@whatwg-node/fetch'
2
2
 
3
3
  describe('getFormDataMethod', () => {
4
- createTestContainer(fetchAPI => {
5
- it('should parse fields correctly', async () => {
6
- const formData = new fetchAPI.FormData();
7
- formData.append('greetings', 'Hello world!');
8
- formData.append('bye', 'Goodbye world!');
9
- const request = new fetchAPI.Request('http://localhost:8080', {
10
- method: 'POST',
11
- body: formData,
12
- });
13
- const formdata = await request.formData();
14
- expect(formdata.get('greetings')).toBe('Hello world!');
15
- expect(formdata.get('bye')).toBe('Goodbye world!');
4
+ it('should parse fields correctly', async () => {
5
+ const formData = new fetchAPI.FormData();
6
+ formData.append('greetings', 'Hello world!');
7
+ formData.append('bye', 'Goodbye world!');
8
+ const request = new fetchAPI.Request('http://localhost:8080', {
9
+ method: 'POST',
10
+ body: formData,
16
11
  });
17
- it('should parse and receive text files correctly', async () => {
18
- const formData = new fetchAPI.FormData();
19
- const greetingsFile = new fetchAPI.File(['Hello world!'], 'greetings.txt', { type: 'text/plain' });
20
- const byeFile = new fetchAPI.File(['Goodbye world!'], 'bye.txt', { type: 'text/plain' });
21
- formData.append('greetings', greetingsFile);
22
- formData.append('bye', byeFile);
23
- const request = new fetchAPI.Request('http://localhost:8080', {
24
- method: 'POST',
25
- body: formData,
26
- });
27
- const formdata = await request.formData();
28
- const receivedGreetingsFile = formdata.get('greetings') as File;
29
- const receivedGreetingsText = await receivedGreetingsFile.text();
30
- expect(receivedGreetingsText).toBe('Hello world!');
31
- const receivedByeFile = formdata.get('bye') as File;
32
- const receivedByeText = await receivedByeFile.text();
33
- expect(receivedByeText).toBe('Goodbye world!');
12
+ const formdata = await request.formData();
13
+ expect(formdata.get('greetings')).toBe('Hello world!');
14
+ expect(formdata.get('bye')).toBe('Goodbye world!');
15
+ });
16
+ it('should parse and receive text files correctly', async () => {
17
+ const formData = new fetchAPI.FormData();
18
+ const greetingsFile = new fetchAPI.File(['Hello world!'], 'greetings.txt', { type: 'text/plain' });
19
+ const byeFile = new fetchAPI.File(['Goodbye world!'], 'bye.txt', { type: 'text/plain' });
20
+ formData.append('greetings', greetingsFile);
21
+ formData.append('bye', byeFile);
22
+ const request = new fetchAPI.Request('http://localhost:8080', {
23
+ method: 'POST',
24
+ body: formData,
34
25
  });
26
+ const formdata = await request.formData();
27
+ const receivedGreetingsFile = formdata.get('greetings') as File;
28
+ const receivedGreetingsText = await receivedGreetingsFile.text();
29
+ expect(receivedGreetingsText).toBe('Hello world!');
30
+ const receivedByeFile = formdata.get('bye') as File;
31
+ const receivedByeText = await receivedByeFile.text();
32
+ expect(receivedByeText).toBe('Goodbye world!');
35
33
  });
36
34
  });