@whatwg-node/node-fetch 0.7.22-alpha-20250527020114-bf7a9c1c4bf32e1ca6edd3faf2d7a81370ded65c → 0.7.22-alpha-20250708000919-bf844a824a46e1c16efe3e873fe5561b22db87b3
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/cjs/Body.js +119 -79
- package/cjs/Response.js +52 -0
- package/cjs/fetchNodeHttp.js +8 -6
- package/esm/Body.js +122 -82
- package/esm/Response.js +52 -0
- package/esm/fetchNodeHttp.js +8 -6
- package/package.json +1 -1
- package/typings/ReadableStream.d.cts +3 -3
- package/typings/ReadableStream.d.ts +3 -3
package/cjs/Body.js
CHANGED
@@ -3,8 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.PonyfillBody = void 0;
|
4
4
|
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
5
5
|
const node_buffer_1 = require("node:buffer");
|
6
|
+
const node_http_1 = require("node:http");
|
6
7
|
const node_stream_1 = require("node:stream");
|
7
|
-
const promises_1 = require("node:stream/promises");
|
8
8
|
const busboy_1 = require("@fastify/busboy");
|
9
9
|
const promise_helpers_1 = require("@whatwg-node/promise-helpers");
|
10
10
|
const Blob_js_1 = require("./Blob.js");
|
@@ -33,7 +33,7 @@ class PonyfillBody {
|
|
33
33
|
this.bodyInit = bodyInit;
|
34
34
|
this.options = options;
|
35
35
|
this._signal = options.signal || null;
|
36
|
-
const { bodyFactory, contentType, contentLength, bodyType, buffer } = processBodyInit(bodyInit);
|
36
|
+
const { bodyFactory, contentType, contentLength, bodyType, buffer } = processBodyInit(bodyInit, options?.signal);
|
37
37
|
this._bodyFactory = bodyFactory;
|
38
38
|
this.contentType = contentType;
|
39
39
|
this.contentLength = contentLength;
|
@@ -109,14 +109,14 @@ class PonyfillBody {
|
|
109
109
|
_doCollectChunksFromReadableJob() {
|
110
110
|
if (this.bodyType === BodyInitType.AsyncIterable) {
|
111
111
|
if (Array.fromAsync) {
|
112
|
-
return Array.fromAsync(this.bodyInit)
|
112
|
+
return (0, promise_helpers_1.handleMaybePromise)(() => Array.fromAsync(this.bodyInit), chunks => {
|
113
113
|
this._chunks = chunks;
|
114
114
|
return this._chunks;
|
115
115
|
});
|
116
116
|
}
|
117
117
|
const iterator = this.bodyInit[Symbol.asyncIterator]();
|
118
118
|
const chunks = [];
|
119
|
-
const collectValue = () => iterator.next()
|
119
|
+
const collectValue = () => (0, promise_helpers_1.handleMaybePromise)(() => iterator.next(), ({ value, done }) => {
|
120
120
|
if (value) {
|
121
121
|
chunks.push(value);
|
122
122
|
}
|
@@ -131,30 +131,23 @@ class PonyfillBody {
|
|
131
131
|
const _body = this.generateBody();
|
132
132
|
if (!_body) {
|
133
133
|
this._chunks = [];
|
134
|
-
return this._chunks;
|
135
|
-
}
|
136
|
-
if (_body.readable.destroyed) {
|
137
|
-
// If the stream is already destroyed, we can resolve immediately
|
138
|
-
this._chunks = [];
|
139
|
-
return this._chunks;
|
134
|
+
return (0, utils_js_1.fakePromise)(this._chunks);
|
140
135
|
}
|
141
136
|
const chunks = [];
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
deferred.resolve(chunks);
|
151
|
-
});
|
152
|
-
_body.readable.once('error', function errorOnStream(err) {
|
153
|
-
deferred.reject(err);
|
137
|
+
return new Promise((resolve, reject) => {
|
138
|
+
_body.readable.on('data', chunk => {
|
139
|
+
chunks.push(chunk);
|
140
|
+
});
|
141
|
+
_body.readable.once('error', reject);
|
142
|
+
_body.readable.once('end', () => {
|
143
|
+
resolve((this._chunks = chunks));
|
144
|
+
});
|
154
145
|
});
|
155
|
-
return deferred.promise;
|
156
146
|
}
|
157
147
|
_collectChunksFromReadable() {
|
148
|
+
if (this._chunks) {
|
149
|
+
return (0, utils_js_1.fakePromise)(this._chunks);
|
150
|
+
}
|
158
151
|
this._chunks ||= this._doCollectChunksFromReadableJob();
|
159
152
|
return this._chunks;
|
160
153
|
}
|
@@ -181,15 +174,13 @@ class PonyfillBody {
|
|
181
174
|
});
|
182
175
|
return (0, utils_js_1.fakePromise)(this._blob);
|
183
176
|
}
|
184
|
-
return (0, utils_js_1.fakePromise)()
|
185
|
-
.then(() => this._collectChunksFromReadable())
|
186
|
-
.then(chunks => {
|
177
|
+
return (0, utils_js_1.fakePromise)((0, promise_helpers_1.handleMaybePromise)(() => this._collectChunksFromReadable(), chunks => {
|
187
178
|
this._blob = new Blob_js_1.PonyfillBlob(chunks, {
|
188
179
|
type: this.contentType || '',
|
189
180
|
size: this.contentLength,
|
190
181
|
});
|
191
182
|
return this._blob;
|
192
|
-
});
|
183
|
+
}));
|
193
184
|
}
|
194
185
|
_formData = null;
|
195
186
|
formData(opts) {
|
@@ -209,57 +200,93 @@ class PonyfillBody {
|
|
209
200
|
...this.options.formDataLimits,
|
210
201
|
...opts?.formDataLimits,
|
211
202
|
};
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
const bb = new busboy_1.Busboy({
|
217
|
-
headers: {
|
218
|
-
'content-length': typeof this.contentLength === 'number'
|
219
|
-
? this.contentLength.toString()
|
220
|
-
: this.contentLength || '',
|
221
|
-
'content-type': this.contentType || '',
|
222
|
-
},
|
223
|
-
limits: formDataLimits,
|
224
|
-
defCharset: 'utf-8',
|
225
|
-
});
|
226
|
-
bb.on('field', (name, value, fieldnameTruncated, valueTruncated) => {
|
227
|
-
if (fieldnameTruncated) {
|
228
|
-
bb.destroy(new Error(`Field name size exceeded: ${formDataLimits?.fieldNameSize} bytes`));
|
203
|
+
return new Promise((resolve, reject) => {
|
204
|
+
const stream = this.body?.readable;
|
205
|
+
if (!stream) {
|
206
|
+
return reject(new Error('No stream available'));
|
229
207
|
}
|
230
|
-
|
231
|
-
|
208
|
+
// form data file that is currently being processed, it's
|
209
|
+
// important to keep track of it in case the stream ends early
|
210
|
+
let currFile = null;
|
211
|
+
const bb = new busboy_1.Busboy({
|
212
|
+
headers: {
|
213
|
+
'content-length': typeof this.contentLength === 'number'
|
214
|
+
? this.contentLength.toString()
|
215
|
+
: this.contentLength || '',
|
216
|
+
'content-type': this.contentType || '',
|
217
|
+
},
|
218
|
+
limits: formDataLimits,
|
219
|
+
defCharset: 'utf-8',
|
220
|
+
});
|
221
|
+
if (this._signal) {
|
222
|
+
(0, node_stream_1.addAbortSignal)(this._signal, bb);
|
232
223
|
}
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
224
|
+
let completed = false;
|
225
|
+
const complete = (err) => {
|
226
|
+
if (completed)
|
227
|
+
return;
|
228
|
+
completed = true;
|
229
|
+
stream.unpipe(bb);
|
230
|
+
bb.destroy();
|
231
|
+
if (currFile) {
|
232
|
+
currFile.destroy();
|
233
|
+
currFile = null;
|
234
|
+
}
|
235
|
+
if (err) {
|
236
|
+
reject(err);
|
237
|
+
}
|
238
|
+
else {
|
239
|
+
// no error occured, this is a successful end/complete/finish
|
240
|
+
resolve(this._formData);
|
241
|
+
}
|
242
|
+
};
|
243
|
+
// we dont need to listen to the stream close event because bb will close or error when necessary
|
244
|
+
// stream.on('close', complete);
|
245
|
+
// stream can be aborted, for example
|
246
|
+
stream.on('error', complete);
|
247
|
+
bb.on('field', (name, value, fieldnameTruncated, valueTruncated) => {
|
248
|
+
if (fieldnameTruncated) {
|
249
|
+
return complete(new Error(`Field name size exceeded: ${formDataLimits?.fieldNameSize} bytes`));
|
250
|
+
}
|
251
|
+
if (valueTruncated) {
|
252
|
+
return complete(new Error(`Field value size exceeded: ${formDataLimits?.fieldSize} bytes`));
|
253
|
+
}
|
254
|
+
this._formData.set(name, value);
|
239
255
|
});
|
240
|
-
|
241
|
-
|
256
|
+
bb.on('file', (name, fileStream, filename, _transferEncoding, mimeType) => {
|
257
|
+
currFile = fileStream;
|
258
|
+
const chunks = [];
|
259
|
+
fileStream.on('data', chunk => {
|
260
|
+
chunks.push(chunk);
|
261
|
+
});
|
262
|
+
fileStream.on('error', complete);
|
263
|
+
fileStream.on('limit', () => {
|
264
|
+
complete(new Error(`File size limit exceeded: ${formDataLimits?.fileSize} bytes`));
|
265
|
+
});
|
266
|
+
fileStream.on('close', () => {
|
267
|
+
if (fileStream.truncated) {
|
268
|
+
complete(new Error(`File size limit exceeded: ${formDataLimits?.fileSize} bytes`));
|
269
|
+
}
|
270
|
+
currFile = null;
|
271
|
+
const file = new File_js_1.PonyfillFile(chunks, filename, { type: mimeType });
|
272
|
+
this._formData.set(name, file);
|
273
|
+
});
|
242
274
|
});
|
243
|
-
|
244
|
-
|
245
|
-
bb.destroy(new Error(`File size limit exceeded: ${formDataLimits?.fileSize} bytes`));
|
246
|
-
}
|
247
|
-
const file = new File_js_1.PonyfillFile(chunks, filename, { type: mimeType });
|
248
|
-
this._formData.set(name, file);
|
275
|
+
bb.on('fieldsLimit', () => {
|
276
|
+
complete(new Error(`Fields limit exceeded: ${formDataLimits?.fields}`));
|
249
277
|
});
|
278
|
+
bb.on('filesLimit', () => {
|
279
|
+
complete(new Error(`Files limit exceeded: ${formDataLimits?.files}`));
|
280
|
+
});
|
281
|
+
bb.on('partsLimit', () => {
|
282
|
+
complete(new Error(`Parts limit exceeded: ${formDataLimits?.parts}`));
|
283
|
+
});
|
284
|
+
bb.on('end', complete);
|
285
|
+
bb.on('finish', complete);
|
286
|
+
bb.on('close', complete);
|
287
|
+
bb.on('error', complete);
|
288
|
+
stream.pipe(bb);
|
250
289
|
});
|
251
|
-
bb.on('fieldsLimit', () => {
|
252
|
-
bb.destroy(new Error(`Fields limit exceeded: ${formDataLimits?.fields}`));
|
253
|
-
});
|
254
|
-
bb.on('filesLimit', () => {
|
255
|
-
bb.destroy(new Error(`Files limit exceeded: ${formDataLimits?.files}`));
|
256
|
-
});
|
257
|
-
bb.on('partsLimit', () => {
|
258
|
-
bb.destroy(new Error(`Parts limit exceeded: ${formDataLimits?.parts}`));
|
259
|
-
});
|
260
|
-
return (0, promises_1.pipeline)(stream, bb, {
|
261
|
-
signal: this._signal || undefined,
|
262
|
-
}).then(() => this._formData);
|
263
290
|
}
|
264
291
|
buffer() {
|
265
292
|
if (this._buffer) {
|
@@ -296,16 +323,14 @@ class PonyfillBody {
|
|
296
323
|
});
|
297
324
|
}
|
298
325
|
}
|
299
|
-
return (0, utils_js_1.fakePromise)()
|
300
|
-
.then(() => this._collectChunksFromReadable())
|
301
|
-
.then(chunks => {
|
326
|
+
return (0, utils_js_1.fakePromise)((0, promise_helpers_1.handleMaybePromise)(() => this._collectChunksFromReadable(), chunks => {
|
302
327
|
if (chunks.length === 1) {
|
303
328
|
this._buffer = chunks[0];
|
304
329
|
return this._buffer;
|
305
330
|
}
|
306
331
|
this._buffer = node_buffer_1.Buffer.concat(chunks);
|
307
332
|
return this._buffer;
|
308
|
-
});
|
333
|
+
}));
|
309
334
|
}
|
310
335
|
bytes() {
|
311
336
|
return this.buffer();
|
@@ -348,7 +373,7 @@ class PonyfillBody {
|
|
348
373
|
}
|
349
374
|
}
|
350
375
|
exports.PonyfillBody = PonyfillBody;
|
351
|
-
function processBodyInit(bodyInit) {
|
376
|
+
function processBodyInit(bodyInit, signal) {
|
352
377
|
if (bodyInit == null) {
|
353
378
|
return {
|
354
379
|
bodyFactory: () => null,
|
@@ -357,10 +382,11 @@ function processBodyInit(bodyInit) {
|
|
357
382
|
};
|
358
383
|
}
|
359
384
|
if (typeof bodyInit === 'string') {
|
385
|
+
const contentLength = node_buffer_1.Buffer.byteLength(bodyInit);
|
360
386
|
return {
|
361
387
|
bodyType: BodyInitType.String,
|
362
|
-
contentType:
|
363
|
-
contentLength
|
388
|
+
contentType: 'text/plain;charset=UTF-8',
|
389
|
+
contentLength,
|
364
390
|
bodyFactory() {
|
365
391
|
const readable = node_stream_1.Readable.from(node_buffer_1.Buffer.from(bodyInit, 'utf-8'));
|
366
392
|
return new ReadableStream_js_1.PonyfillReadableStream(readable);
|
@@ -430,6 +456,20 @@ function processBodyInit(bodyInit) {
|
|
430
456
|
},
|
431
457
|
};
|
432
458
|
}
|
459
|
+
if (bodyInit instanceof node_http_1.IncomingMessage) {
|
460
|
+
const passThrough = (0, utils_js_1.wrapIncomingMessageWithPassthrough)({
|
461
|
+
incomingMessage: bodyInit,
|
462
|
+
signal,
|
463
|
+
});
|
464
|
+
return {
|
465
|
+
bodyType: BodyInitType.Readable,
|
466
|
+
contentType: null,
|
467
|
+
contentLength: null,
|
468
|
+
bodyFactory() {
|
469
|
+
return new ReadableStream_js_1.PonyfillReadableStream(passThrough);
|
470
|
+
},
|
471
|
+
};
|
472
|
+
}
|
433
473
|
if (bodyInit instanceof node_stream_1.Readable) {
|
434
474
|
return {
|
435
475
|
bodyType: BodyInitType.Readable,
|
package/cjs/Response.js
CHANGED
@@ -4,6 +4,7 @@ exports.PonyfillResponse = void 0;
|
|
4
4
|
const node_http_1 = require("node:http");
|
5
5
|
const Body_js_1 = require("./Body.js");
|
6
6
|
const Headers_js_1 = require("./Headers.js");
|
7
|
+
const JSON_CONTENT_TYPE = 'application/json; charset=utf-8';
|
7
8
|
class PonyfillResponse extends Body_js_1.PonyfillBody {
|
8
9
|
headers;
|
9
10
|
constructor(body, init) {
|
@@ -49,6 +50,57 @@ class PonyfillResponse extends Body_js_1.PonyfillBody {
|
|
49
50
|
}
|
50
51
|
static json(data, init) {
|
51
52
|
const bodyInit = JSON.stringify(data);
|
53
|
+
if (!init) {
|
54
|
+
init = {
|
55
|
+
headers: {
|
56
|
+
'content-type': JSON_CONTENT_TYPE,
|
57
|
+
'content-length': Buffer.byteLength(bodyInit).toString(),
|
58
|
+
},
|
59
|
+
};
|
60
|
+
}
|
61
|
+
else if (!init.headers) {
|
62
|
+
init.headers = {
|
63
|
+
'content-type': JSON_CONTENT_TYPE,
|
64
|
+
'content-length': Buffer.byteLength(bodyInit).toString(),
|
65
|
+
};
|
66
|
+
}
|
67
|
+
else if ((0, Headers_js_1.isHeadersLike)(init.headers)) {
|
68
|
+
if (!init.headers.has('content-type')) {
|
69
|
+
init.headers.set('content-type', JSON_CONTENT_TYPE);
|
70
|
+
}
|
71
|
+
if (!init.headers.has('content-length')) {
|
72
|
+
init.headers.set('content-length', Buffer.byteLength(bodyInit).toString());
|
73
|
+
}
|
74
|
+
}
|
75
|
+
else if (Array.isArray(init.headers)) {
|
76
|
+
let contentTypeExists = false;
|
77
|
+
let contentLengthExists = false;
|
78
|
+
for (const [key] of init.headers) {
|
79
|
+
if (contentLengthExists && contentTypeExists) {
|
80
|
+
break;
|
81
|
+
}
|
82
|
+
if (!contentTypeExists && key.toLowerCase() === 'content-type') {
|
83
|
+
contentTypeExists = true;
|
84
|
+
}
|
85
|
+
else if (!contentLengthExists && key.toLowerCase() === 'content-length') {
|
86
|
+
contentLengthExists = true;
|
87
|
+
}
|
88
|
+
}
|
89
|
+
if (!contentTypeExists) {
|
90
|
+
init.headers.push(['content-type', JSON_CONTENT_TYPE]);
|
91
|
+
}
|
92
|
+
if (!contentLengthExists) {
|
93
|
+
init.headers.push(['content-length', Buffer.byteLength(bodyInit).toString()]);
|
94
|
+
}
|
95
|
+
}
|
96
|
+
else if (typeof init.headers === 'object') {
|
97
|
+
if (init.headers?.['content-type'] == null) {
|
98
|
+
init.headers['content-type'] = JSON_CONTENT_TYPE;
|
99
|
+
}
|
100
|
+
if (init.headers?.['content-length'] == null) {
|
101
|
+
init.headers['content-length'] = Buffer.byteLength(bodyInit).toString();
|
102
|
+
}
|
103
|
+
}
|
52
104
|
return new PonyfillResponse(bodyInit, init);
|
53
105
|
}
|
54
106
|
[Symbol.toStringTag] = 'Response';
|
package/cjs/fetchNodeHttp.js
CHANGED
@@ -95,12 +95,14 @@ function fetchNodeHttp(fetchRequest) {
|
|
95
95
|
return;
|
96
96
|
}
|
97
97
|
}
|
98
|
-
outputStream
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
98
|
+
if (outputStream != null) {
|
99
|
+
outputStream = (0, utils_js_1.wrapIncomingMessageWithPassthrough)({
|
100
|
+
incomingMessage: nodeResponse,
|
101
|
+
passThrough: outputStream,
|
102
|
+
signal,
|
103
|
+
onError: reject,
|
104
|
+
});
|
105
|
+
}
|
104
106
|
const statusCode = nodeResponse.statusCode || 200;
|
105
107
|
let statusText = nodeResponse.statusMessage || node_http_1.STATUS_CODES[statusCode];
|
106
108
|
if (statusText == null) {
|
package/esm/Body.js
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
2
2
|
import { Buffer } from 'node:buffer';
|
3
|
-
import {
|
4
|
-
import {
|
3
|
+
import { IncomingMessage } from 'node:http';
|
4
|
+
import { addAbortSignal, Readable } from 'node:stream';
|
5
5
|
import { Busboy } from '@fastify/busboy';
|
6
|
-
import {
|
6
|
+
import { handleMaybePromise } from '@whatwg-node/promise-helpers';
|
7
7
|
import { hasArrayBufferMethod, hasBufferMethod, hasBytesMethod, PonyfillBlob } from './Blob.js';
|
8
8
|
import { PonyfillFile } from './File.js';
|
9
9
|
import { getStreamFromFormData, PonyfillFormData } from './FormData.js';
|
10
10
|
import { PonyfillReadableStream } from './ReadableStream.js';
|
11
|
-
import { fakePromise, isArrayBufferView } from './utils.js';
|
11
|
+
import { fakePromise, isArrayBufferView, wrapIncomingMessageWithPassthrough } from './utils.js';
|
12
12
|
var BodyInitType;
|
13
13
|
(function (BodyInitType) {
|
14
14
|
BodyInitType["ReadableStream"] = "ReadableStream";
|
@@ -30,7 +30,7 @@ export class PonyfillBody {
|
|
30
30
|
this.bodyInit = bodyInit;
|
31
31
|
this.options = options;
|
32
32
|
this._signal = options.signal || null;
|
33
|
-
const { bodyFactory, contentType, contentLength, bodyType, buffer } = processBodyInit(bodyInit);
|
33
|
+
const { bodyFactory, contentType, contentLength, bodyType, buffer } = processBodyInit(bodyInit, options?.signal);
|
34
34
|
this._bodyFactory = bodyFactory;
|
35
35
|
this.contentType = contentType;
|
36
36
|
this.contentLength = contentLength;
|
@@ -106,14 +106,14 @@ export class PonyfillBody {
|
|
106
106
|
_doCollectChunksFromReadableJob() {
|
107
107
|
if (this.bodyType === BodyInitType.AsyncIterable) {
|
108
108
|
if (Array.fromAsync) {
|
109
|
-
return Array.fromAsync(this.bodyInit)
|
109
|
+
return handleMaybePromise(() => Array.fromAsync(this.bodyInit), chunks => {
|
110
110
|
this._chunks = chunks;
|
111
111
|
return this._chunks;
|
112
112
|
});
|
113
113
|
}
|
114
114
|
const iterator = this.bodyInit[Symbol.asyncIterator]();
|
115
115
|
const chunks = [];
|
116
|
-
const collectValue = () => iterator.next()
|
116
|
+
const collectValue = () => handleMaybePromise(() => iterator.next(), ({ value, done }) => {
|
117
117
|
if (value) {
|
118
118
|
chunks.push(value);
|
119
119
|
}
|
@@ -128,30 +128,23 @@ export class PonyfillBody {
|
|
128
128
|
const _body = this.generateBody();
|
129
129
|
if (!_body) {
|
130
130
|
this._chunks = [];
|
131
|
-
return this._chunks;
|
132
|
-
}
|
133
|
-
if (_body.readable.destroyed) {
|
134
|
-
// If the stream is already destroyed, we can resolve immediately
|
135
|
-
this._chunks = [];
|
136
|
-
return this._chunks;
|
131
|
+
return fakePromise(this._chunks);
|
137
132
|
}
|
138
133
|
const chunks = [];
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
deferred.resolve(chunks);
|
148
|
-
});
|
149
|
-
_body.readable.once('error', function errorOnStream(err) {
|
150
|
-
deferred.reject(err);
|
134
|
+
return new Promise((resolve, reject) => {
|
135
|
+
_body.readable.on('data', chunk => {
|
136
|
+
chunks.push(chunk);
|
137
|
+
});
|
138
|
+
_body.readable.once('error', reject);
|
139
|
+
_body.readable.once('end', () => {
|
140
|
+
resolve((this._chunks = chunks));
|
141
|
+
});
|
151
142
|
});
|
152
|
-
return deferred.promise;
|
153
143
|
}
|
154
144
|
_collectChunksFromReadable() {
|
145
|
+
if (this._chunks) {
|
146
|
+
return fakePromise(this._chunks);
|
147
|
+
}
|
155
148
|
this._chunks ||= this._doCollectChunksFromReadableJob();
|
156
149
|
return this._chunks;
|
157
150
|
}
|
@@ -178,15 +171,13 @@ export class PonyfillBody {
|
|
178
171
|
});
|
179
172
|
return fakePromise(this._blob);
|
180
173
|
}
|
181
|
-
return fakePromise()
|
182
|
-
.then(() => this._collectChunksFromReadable())
|
183
|
-
.then(chunks => {
|
174
|
+
return fakePromise(handleMaybePromise(() => this._collectChunksFromReadable(), chunks => {
|
184
175
|
this._blob = new PonyfillBlob(chunks, {
|
185
176
|
type: this.contentType || '',
|
186
177
|
size: this.contentLength,
|
187
178
|
});
|
188
179
|
return this._blob;
|
189
|
-
});
|
180
|
+
}));
|
190
181
|
}
|
191
182
|
_formData = null;
|
192
183
|
formData(opts) {
|
@@ -206,57 +197,93 @@ export class PonyfillBody {
|
|
206
197
|
...this.options.formDataLimits,
|
207
198
|
...opts?.formDataLimits,
|
208
199
|
};
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
const bb = new Busboy({
|
214
|
-
headers: {
|
215
|
-
'content-length': typeof this.contentLength === 'number'
|
216
|
-
? this.contentLength.toString()
|
217
|
-
: this.contentLength || '',
|
218
|
-
'content-type': this.contentType || '',
|
219
|
-
},
|
220
|
-
limits: formDataLimits,
|
221
|
-
defCharset: 'utf-8',
|
222
|
-
});
|
223
|
-
bb.on('field', (name, value, fieldnameTruncated, valueTruncated) => {
|
224
|
-
if (fieldnameTruncated) {
|
225
|
-
bb.destroy(new Error(`Field name size exceeded: ${formDataLimits?.fieldNameSize} bytes`));
|
200
|
+
return new Promise((resolve, reject) => {
|
201
|
+
const stream = this.body?.readable;
|
202
|
+
if (!stream) {
|
203
|
+
return reject(new Error('No stream available'));
|
226
204
|
}
|
227
|
-
|
228
|
-
|
205
|
+
// form data file that is currently being processed, it's
|
206
|
+
// important to keep track of it in case the stream ends early
|
207
|
+
let currFile = null;
|
208
|
+
const bb = new Busboy({
|
209
|
+
headers: {
|
210
|
+
'content-length': typeof this.contentLength === 'number'
|
211
|
+
? this.contentLength.toString()
|
212
|
+
: this.contentLength || '',
|
213
|
+
'content-type': this.contentType || '',
|
214
|
+
},
|
215
|
+
limits: formDataLimits,
|
216
|
+
defCharset: 'utf-8',
|
217
|
+
});
|
218
|
+
if (this._signal) {
|
219
|
+
addAbortSignal(this._signal, bb);
|
229
220
|
}
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
221
|
+
let completed = false;
|
222
|
+
const complete = (err) => {
|
223
|
+
if (completed)
|
224
|
+
return;
|
225
|
+
completed = true;
|
226
|
+
stream.unpipe(bb);
|
227
|
+
bb.destroy();
|
228
|
+
if (currFile) {
|
229
|
+
currFile.destroy();
|
230
|
+
currFile = null;
|
231
|
+
}
|
232
|
+
if (err) {
|
233
|
+
reject(err);
|
234
|
+
}
|
235
|
+
else {
|
236
|
+
// no error occured, this is a successful end/complete/finish
|
237
|
+
resolve(this._formData);
|
238
|
+
}
|
239
|
+
};
|
240
|
+
// we dont need to listen to the stream close event because bb will close or error when necessary
|
241
|
+
// stream.on('close', complete);
|
242
|
+
// stream can be aborted, for example
|
243
|
+
stream.on('error', complete);
|
244
|
+
bb.on('field', (name, value, fieldnameTruncated, valueTruncated) => {
|
245
|
+
if (fieldnameTruncated) {
|
246
|
+
return complete(new Error(`Field name size exceeded: ${formDataLimits?.fieldNameSize} bytes`));
|
247
|
+
}
|
248
|
+
if (valueTruncated) {
|
249
|
+
return complete(new Error(`Field value size exceeded: ${formDataLimits?.fieldSize} bytes`));
|
250
|
+
}
|
251
|
+
this._formData.set(name, value);
|
236
252
|
});
|
237
|
-
|
238
|
-
|
253
|
+
bb.on('file', (name, fileStream, filename, _transferEncoding, mimeType) => {
|
254
|
+
currFile = fileStream;
|
255
|
+
const chunks = [];
|
256
|
+
fileStream.on('data', chunk => {
|
257
|
+
chunks.push(chunk);
|
258
|
+
});
|
259
|
+
fileStream.on('error', complete);
|
260
|
+
fileStream.on('limit', () => {
|
261
|
+
complete(new Error(`File size limit exceeded: ${formDataLimits?.fileSize} bytes`));
|
262
|
+
});
|
263
|
+
fileStream.on('close', () => {
|
264
|
+
if (fileStream.truncated) {
|
265
|
+
complete(new Error(`File size limit exceeded: ${formDataLimits?.fileSize} bytes`));
|
266
|
+
}
|
267
|
+
currFile = null;
|
268
|
+
const file = new PonyfillFile(chunks, filename, { type: mimeType });
|
269
|
+
this._formData.set(name, file);
|
270
|
+
});
|
239
271
|
});
|
240
|
-
|
241
|
-
|
242
|
-
bb.destroy(new Error(`File size limit exceeded: ${formDataLimits?.fileSize} bytes`));
|
243
|
-
}
|
244
|
-
const file = new PonyfillFile(chunks, filename, { type: mimeType });
|
245
|
-
this._formData.set(name, file);
|
272
|
+
bb.on('fieldsLimit', () => {
|
273
|
+
complete(new Error(`Fields limit exceeded: ${formDataLimits?.fields}`));
|
246
274
|
});
|
275
|
+
bb.on('filesLimit', () => {
|
276
|
+
complete(new Error(`Files limit exceeded: ${formDataLimits?.files}`));
|
277
|
+
});
|
278
|
+
bb.on('partsLimit', () => {
|
279
|
+
complete(new Error(`Parts limit exceeded: ${formDataLimits?.parts}`));
|
280
|
+
});
|
281
|
+
bb.on('end', complete);
|
282
|
+
bb.on('finish', complete);
|
283
|
+
bb.on('close', complete);
|
284
|
+
bb.on('error', complete);
|
285
|
+
stream.pipe(bb);
|
247
286
|
});
|
248
|
-
bb.on('fieldsLimit', () => {
|
249
|
-
bb.destroy(new Error(`Fields limit exceeded: ${formDataLimits?.fields}`));
|
250
|
-
});
|
251
|
-
bb.on('filesLimit', () => {
|
252
|
-
bb.destroy(new Error(`Files limit exceeded: ${formDataLimits?.files}`));
|
253
|
-
});
|
254
|
-
bb.on('partsLimit', () => {
|
255
|
-
bb.destroy(new Error(`Parts limit exceeded: ${formDataLimits?.parts}`));
|
256
|
-
});
|
257
|
-
return pipeline(stream, bb, {
|
258
|
-
signal: this._signal || undefined,
|
259
|
-
}).then(() => this._formData);
|
260
287
|
}
|
261
288
|
buffer() {
|
262
289
|
if (this._buffer) {
|
@@ -293,16 +320,14 @@ export class PonyfillBody {
|
|
293
320
|
});
|
294
321
|
}
|
295
322
|
}
|
296
|
-
return fakePromise()
|
297
|
-
.then(() => this._collectChunksFromReadable())
|
298
|
-
.then(chunks => {
|
323
|
+
return fakePromise(handleMaybePromise(() => this._collectChunksFromReadable(), chunks => {
|
299
324
|
if (chunks.length === 1) {
|
300
325
|
this._buffer = chunks[0];
|
301
326
|
return this._buffer;
|
302
327
|
}
|
303
328
|
this._buffer = Buffer.concat(chunks);
|
304
329
|
return this._buffer;
|
305
|
-
});
|
330
|
+
}));
|
306
331
|
}
|
307
332
|
bytes() {
|
308
333
|
return this.buffer();
|
@@ -344,7 +369,7 @@ export class PonyfillBody {
|
|
344
369
|
});
|
345
370
|
}
|
346
371
|
}
|
347
|
-
function processBodyInit(bodyInit) {
|
372
|
+
function processBodyInit(bodyInit, signal) {
|
348
373
|
if (bodyInit == null) {
|
349
374
|
return {
|
350
375
|
bodyFactory: () => null,
|
@@ -353,10 +378,11 @@ function processBodyInit(bodyInit) {
|
|
353
378
|
};
|
354
379
|
}
|
355
380
|
if (typeof bodyInit === 'string') {
|
381
|
+
const contentLength = Buffer.byteLength(bodyInit);
|
356
382
|
return {
|
357
383
|
bodyType: BodyInitType.String,
|
358
|
-
contentType:
|
359
|
-
contentLength
|
384
|
+
contentType: 'text/plain;charset=UTF-8',
|
385
|
+
contentLength,
|
360
386
|
bodyFactory() {
|
361
387
|
const readable = Readable.from(Buffer.from(bodyInit, 'utf-8'));
|
362
388
|
return new PonyfillReadableStream(readable);
|
@@ -426,6 +452,20 @@ function processBodyInit(bodyInit) {
|
|
426
452
|
},
|
427
453
|
};
|
428
454
|
}
|
455
|
+
if (bodyInit instanceof IncomingMessage) {
|
456
|
+
const passThrough = wrapIncomingMessageWithPassthrough({
|
457
|
+
incomingMessage: bodyInit,
|
458
|
+
signal,
|
459
|
+
});
|
460
|
+
return {
|
461
|
+
bodyType: BodyInitType.Readable,
|
462
|
+
contentType: null,
|
463
|
+
contentLength: null,
|
464
|
+
bodyFactory() {
|
465
|
+
return new PonyfillReadableStream(passThrough);
|
466
|
+
},
|
467
|
+
};
|
468
|
+
}
|
429
469
|
if (bodyInit instanceof Readable) {
|
430
470
|
return {
|
431
471
|
bodyType: BodyInitType.Readable,
|
package/esm/Response.js
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
import { STATUS_CODES } from 'node:http';
|
2
2
|
import { PonyfillBody } from './Body.js';
|
3
3
|
import { isHeadersLike, PonyfillHeaders } from './Headers.js';
|
4
|
+
const JSON_CONTENT_TYPE = 'application/json; charset=utf-8';
|
4
5
|
export class PonyfillResponse extends PonyfillBody {
|
5
6
|
headers;
|
6
7
|
constructor(body, init) {
|
@@ -46,6 +47,57 @@ export class PonyfillResponse extends PonyfillBody {
|
|
46
47
|
}
|
47
48
|
static json(data, init) {
|
48
49
|
const bodyInit = JSON.stringify(data);
|
50
|
+
if (!init) {
|
51
|
+
init = {
|
52
|
+
headers: {
|
53
|
+
'content-type': JSON_CONTENT_TYPE,
|
54
|
+
'content-length': Buffer.byteLength(bodyInit).toString(),
|
55
|
+
},
|
56
|
+
};
|
57
|
+
}
|
58
|
+
else if (!init.headers) {
|
59
|
+
init.headers = {
|
60
|
+
'content-type': JSON_CONTENT_TYPE,
|
61
|
+
'content-length': Buffer.byteLength(bodyInit).toString(),
|
62
|
+
};
|
63
|
+
}
|
64
|
+
else if (isHeadersLike(init.headers)) {
|
65
|
+
if (!init.headers.has('content-type')) {
|
66
|
+
init.headers.set('content-type', JSON_CONTENT_TYPE);
|
67
|
+
}
|
68
|
+
if (!init.headers.has('content-length')) {
|
69
|
+
init.headers.set('content-length', Buffer.byteLength(bodyInit).toString());
|
70
|
+
}
|
71
|
+
}
|
72
|
+
else if (Array.isArray(init.headers)) {
|
73
|
+
let contentTypeExists = false;
|
74
|
+
let contentLengthExists = false;
|
75
|
+
for (const [key] of init.headers) {
|
76
|
+
if (contentLengthExists && contentTypeExists) {
|
77
|
+
break;
|
78
|
+
}
|
79
|
+
if (!contentTypeExists && key.toLowerCase() === 'content-type') {
|
80
|
+
contentTypeExists = true;
|
81
|
+
}
|
82
|
+
else if (!contentLengthExists && key.toLowerCase() === 'content-length') {
|
83
|
+
contentLengthExists = true;
|
84
|
+
}
|
85
|
+
}
|
86
|
+
if (!contentTypeExists) {
|
87
|
+
init.headers.push(['content-type', JSON_CONTENT_TYPE]);
|
88
|
+
}
|
89
|
+
if (!contentLengthExists) {
|
90
|
+
init.headers.push(['content-length', Buffer.byteLength(bodyInit).toString()]);
|
91
|
+
}
|
92
|
+
}
|
93
|
+
else if (typeof init.headers === 'object') {
|
94
|
+
if (init.headers?.['content-type'] == null) {
|
95
|
+
init.headers['content-type'] = JSON_CONTENT_TYPE;
|
96
|
+
}
|
97
|
+
if (init.headers?.['content-length'] == null) {
|
98
|
+
init.headers['content-length'] = Buffer.byteLength(bodyInit).toString();
|
99
|
+
}
|
100
|
+
}
|
49
101
|
return new PonyfillResponse(bodyInit, init);
|
50
102
|
}
|
51
103
|
[Symbol.toStringTag] = 'Response';
|
package/esm/fetchNodeHttp.js
CHANGED
@@ -92,12 +92,14 @@ export function fetchNodeHttp(fetchRequest) {
|
|
92
92
|
return;
|
93
93
|
}
|
94
94
|
}
|
95
|
-
outputStream
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
95
|
+
if (outputStream != null) {
|
96
|
+
outputStream = wrapIncomingMessageWithPassthrough({
|
97
|
+
incomingMessage: nodeResponse,
|
98
|
+
passThrough: outputStream,
|
99
|
+
signal,
|
100
|
+
onError: reject,
|
101
|
+
});
|
102
|
+
}
|
101
103
|
const statusCode = nodeResponse.statusCode || 200;
|
102
104
|
let statusText = nodeResponse.statusMessage || STATUS_CODES[statusCode];
|
103
105
|
if (statusText == null) {
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@whatwg-node/node-fetch",
|
3
|
-
"version": "0.7.22-alpha-
|
3
|
+
"version": "0.7.22-alpha-20250708000919-bf844a824a46e1c16efe3e873fe5561b22db87b3",
|
4
4
|
"description": "Fetch API implementation for Node",
|
5
5
|
"sideEffects": false,
|
6
6
|
"dependencies": {
|
@@ -10,9 +10,9 @@ export declare class PonyfillReadableStream<T> implements ReadableStream<T> {
|
|
10
10
|
getReader(): ReadableStreamDefaultReader<T>;
|
11
11
|
[Symbol.asyncIterator](): {
|
12
12
|
[Symbol.asyncIterator](): /*elided*/ any;
|
13
|
-
next: () => Promise<IteratorResult<any,
|
14
|
-
return: () => Promise<IteratorResult<any,
|
15
|
-
throw: (err: Error) => Promise<IteratorResult<any,
|
13
|
+
next: () => Promise<IteratorResult<any, undefined>>;
|
14
|
+
return: () => Promise<IteratorResult<any, undefined>>;
|
15
|
+
throw: (err: Error) => Promise<IteratorResult<any, undefined>>;
|
16
16
|
};
|
17
17
|
tee(): [ReadableStream<T>, ReadableStream<T>];
|
18
18
|
private pipeToWriter;
|
@@ -10,9 +10,9 @@ export declare class PonyfillReadableStream<T> implements ReadableStream<T> {
|
|
10
10
|
getReader(): ReadableStreamDefaultReader<T>;
|
11
11
|
[Symbol.asyncIterator](): {
|
12
12
|
[Symbol.asyncIterator](): /*elided*/ any;
|
13
|
-
next: () => Promise<IteratorResult<any,
|
14
|
-
return: () => Promise<IteratorResult<any,
|
15
|
-
throw: (err: Error) => Promise<IteratorResult<any,
|
13
|
+
next: () => Promise<IteratorResult<any, undefined>>;
|
14
|
+
return: () => Promise<IteratorResult<any, undefined>>;
|
15
|
+
throw: (err: Error) => Promise<IteratorResult<any, undefined>>;
|
16
16
|
};
|
17
17
|
tee(): [ReadableStream<T>, ReadableStream<T>];
|
18
18
|
private pipeToWriter;
|