@gjsify/fetch 0.0.4 → 0.1.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 +27 -2
- package/globals.mjs +12 -0
- package/lib/body.d.ts +69 -0
- package/lib/body.js +375 -0
- package/lib/errors/abort-error.d.ts +7 -0
- package/lib/errors/abort-error.js +9 -0
- package/lib/errors/base.d.ts +6 -0
- package/lib/errors/base.js +17 -0
- package/lib/errors/fetch-error.d.ts +16 -0
- package/lib/errors/fetch-error.js +23 -0
- package/lib/esm/body.js +104 -56
- package/lib/esm/errors/base.js +3 -1
- package/lib/esm/headers.js +116 -131
- package/lib/esm/index.js +145 -190
- package/lib/esm/request.js +42 -41
- package/lib/esm/response.js +19 -4
- package/lib/esm/utils/blob-from.js +2 -98
- package/lib/esm/utils/data-uri.js +23 -0
- package/lib/esm/utils/is.js +7 -3
- package/lib/esm/utils/multipart-parser.js +5 -2
- package/lib/esm/utils/referrer.js +10 -10
- package/lib/esm/utils/soup-helpers.js +22 -0
- package/lib/headers.d.ts +33 -0
- package/lib/headers.js +195 -0
- package/lib/index.d.ts +18 -0
- package/lib/index.js +205 -0
- package/lib/request.d.ts +101 -0
- package/lib/request.js +308 -0
- package/lib/response.d.ts +73 -0
- package/lib/response.js +158 -0
- package/lib/types/index.d.ts +1 -0
- package/lib/types/index.js +1 -0
- package/lib/types/system-error.d.ts +11 -0
- package/lib/types/system-error.js +2 -0
- package/lib/utils/blob-from.d.ts +2 -0
- package/lib/utils/blob-from.js +4 -0
- package/lib/utils/data-uri.d.ts +10 -0
- package/lib/utils/data-uri.js +27 -0
- package/lib/utils/get-search.d.ts +1 -0
- package/lib/utils/get-search.js +8 -0
- package/lib/utils/is-redirect.d.ts +7 -0
- package/lib/utils/is-redirect.js +10 -0
- package/lib/utils/is.d.ts +35 -0
- package/lib/utils/is.js +74 -0
- package/lib/utils/multipart-parser.d.ts +2 -0
- package/lib/utils/multipart-parser.js +396 -0
- package/lib/utils/referrer.d.ts +76 -0
- package/lib/utils/referrer.js +283 -0
- package/lib/utils/soup-helpers.d.ts +12 -0
- package/lib/utils/soup-helpers.js +25 -0
- package/package.json +23 -27
- package/src/body.ts +181 -169
- package/src/errors/base.ts +3 -1
- package/src/headers.ts +155 -202
- package/src/index.spec.ts +268 -3
- package/src/index.ts +199 -312
- package/src/request.ts +84 -75
- package/src/response.ts +48 -18
- package/src/test.mts +1 -1
- package/src/utils/blob-from.ts +4 -164
- package/src/utils/data-uri.ts +29 -0
- package/src/utils/is.ts +15 -15
- package/src/utils/multipart-parser.ts +3 -3
- package/src/utils/referrer.ts +11 -11
- package/src/utils/soup-helpers.ts +37 -0
- package/tsconfig.json +4 -4
- package/tsconfig.tsbuildinfo +1 -0
- package/lib/cjs/body.js +0 -255
- package/lib/cjs/errors/abort-error.js +0 -9
- package/lib/cjs/errors/base.js +0 -17
- package/lib/cjs/errors/fetch-error.js +0 -21
- package/lib/cjs/headers.js +0 -202
- package/lib/cjs/index.js +0 -224
- package/lib/cjs/request.js +0 -281
- package/lib/cjs/response.js +0 -133
- package/lib/cjs/types/index.js +0 -1
- package/lib/cjs/types/system-error.js +0 -1
- package/lib/cjs/utils/blob-from.js +0 -101
- package/lib/cjs/utils/get-search.js +0 -11
- package/lib/cjs/utils/is-redirect.js +0 -7
- package/lib/cjs/utils/is.js +0 -28
- package/lib/cjs/utils/multipart-parser.js +0 -353
- package/lib/cjs/utils/referrer.js +0 -153
- package/test.gjs.js +0 -34758
- package/test.gjs.mjs +0 -53172
- package/test.node.js +0 -1226
- package/test.node.mjs +0 -6273
- package/tsconfig.types.json +0 -8
package/src/body.ts
CHANGED
|
@@ -1,19 +1,15 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Adapted from node-fetch (https://github.com/node-fetch/node-fetch/blob/main/src/body.js)
|
|
3
|
+
// Copyright (c) node-fetch contributors. MIT license.
|
|
4
|
+
// Modifications: Rewritten for GJS using libsoup 3.0 and @gjsify/url
|
|
1
5
|
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
*
|
|
5
|
-
* Body interface provides common methods for Request and Response
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { URLSearchParams } from '@gjsify/deno-runtime/ext/url/00_url';
|
|
9
|
-
import { Blob } from "@gjsify/deno-runtime/ext/web/09_file";
|
|
6
|
+
import { URLSearchParams } from '@gjsify/url';
|
|
7
|
+
import { Blob } from './utils/blob-from.js';
|
|
10
8
|
|
|
11
|
-
import { PassThrough, pipeline as pipelineCb, Readable, Stream, Writable } from 'stream';
|
|
12
|
-
import {
|
|
13
|
-
import { types, deprecate, promisify } from 'util';
|
|
14
|
-
import { Buffer } from 'buffer';
|
|
9
|
+
import { PassThrough, pipeline as pipelineCb, Readable, Stream, Writable } from 'node:stream';
|
|
10
|
+
import { Buffer } from 'node:buffer';
|
|
15
11
|
|
|
16
|
-
import { FormData, formDataToBlob } from 'formdata
|
|
12
|
+
import { FormData, formDataToBlob } from '@gjsify/formdata';
|
|
17
13
|
|
|
18
14
|
import { FetchError } from './errors/fetch-error.js';
|
|
19
15
|
import { FetchBaseError } from './errors/base.js';
|
|
@@ -21,10 +17,33 @@ import { isBlob, isURLSearchParameters } from './utils/is.js';
|
|
|
21
17
|
|
|
22
18
|
import type { Request } from './request.js';
|
|
23
19
|
import type { Response } from './response.js';
|
|
20
|
+
import type { SystemError } from './types/index.js';
|
|
21
|
+
|
|
22
|
+
const pipeline = (source: Readable, dest: Writable): Promise<void> =>
|
|
23
|
+
new Promise((resolve, reject) => {
|
|
24
|
+
pipelineCb(source, dest, (err) => {
|
|
25
|
+
if (err) reject(err);
|
|
26
|
+
else resolve();
|
|
27
|
+
});
|
|
28
|
+
});
|
|
24
29
|
|
|
25
|
-
const pipeline = promisify(pipelineCb);
|
|
26
30
|
const INTERNALS = Symbol('Body internals');
|
|
27
|
-
|
|
31
|
+
|
|
32
|
+
function isAnyArrayBuffer(val: unknown): val is ArrayBuffer {
|
|
33
|
+
return val instanceof ArrayBuffer ||
|
|
34
|
+
(typeof SharedArrayBuffer !== 'undefined' && val instanceof SharedArrayBuffer);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function isBoxedPrimitive(val: unknown): boolean {
|
|
38
|
+
return (
|
|
39
|
+
val instanceof String ||
|
|
40
|
+
val instanceof Number ||
|
|
41
|
+
val instanceof Boolean ||
|
|
42
|
+
(typeof Symbol !== 'undefined' && val instanceof Symbol) ||
|
|
43
|
+
(typeof BigInt !== 'undefined' && val instanceof (BigInt as unknown as typeof Number))
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
28
47
|
/**
|
|
29
48
|
* Body mixin
|
|
30
49
|
*
|
|
@@ -33,7 +52,7 @@ const INTERNALS = Symbol('Body internals');
|
|
|
33
52
|
* @param body Readable stream
|
|
34
53
|
* @param opts Response options
|
|
35
54
|
*/
|
|
36
|
-
export default class Body
|
|
55
|
+
export default class Body {
|
|
37
56
|
|
|
38
57
|
[INTERNALS]: {
|
|
39
58
|
body: null | Buffer | Readable | Blob;
|
|
@@ -51,9 +70,9 @@ export default class Body implements globalThis.Body {
|
|
|
51
70
|
|
|
52
71
|
size = 0;
|
|
53
72
|
|
|
54
|
-
constructor(body: BodyInit | Readable | Blob | Buffer, options:
|
|
73
|
+
constructor(body: BodyInit | Readable | Blob | Buffer | null, options: { size?: number; headers?: unknown } = { size: 0 }) {
|
|
55
74
|
this.size = options.size || 0;
|
|
56
|
-
if (body === null) {
|
|
75
|
+
if (body === null || body === undefined) {
|
|
57
76
|
// Body is undefined or null
|
|
58
77
|
this[INTERNALS].body = null;
|
|
59
78
|
} else if (isURLSearchParameters(body)) {
|
|
@@ -61,61 +80,82 @@ export default class Body implements globalThis.Body {
|
|
|
61
80
|
this[INTERNALS].body = Buffer.from(body.toString())
|
|
62
81
|
} else if (isBlob(body)) {
|
|
63
82
|
// Body is blob
|
|
83
|
+
this[INTERNALS].body = body as Blob;
|
|
64
84
|
} else if (Buffer.isBuffer(body)) {
|
|
65
85
|
// Body is Buffer
|
|
66
|
-
|
|
86
|
+
this[INTERNALS].body = body;
|
|
87
|
+
} else if (isAnyArrayBuffer(body)) {
|
|
67
88
|
// Body is ArrayBuffer
|
|
68
89
|
this[INTERNALS].body = Buffer.from(body);
|
|
69
90
|
} else if (ArrayBuffer.isView(body)) {
|
|
70
91
|
// Body is ArrayBufferView
|
|
71
92
|
this[INTERNALS].body = Buffer.from(body.buffer, body.byteOffset, body.byteLength);
|
|
72
93
|
} else if (body instanceof Readable) {
|
|
73
|
-
// Body is stream
|
|
94
|
+
// Body is Node.js stream
|
|
74
95
|
this[INTERNALS].body = body;
|
|
75
|
-
} else if (
|
|
76
|
-
// Body is
|
|
77
|
-
this[INTERNALS].body =
|
|
96
|
+
} else if (typeof ReadableStream !== 'undefined' && body instanceof ReadableStream) {
|
|
97
|
+
// Body is Web ReadableStream — convert to Node.js Readable
|
|
98
|
+
this[INTERNALS].body = readableStreamToReadable(body);
|
|
78
99
|
} else if (body instanceof FormData) {
|
|
79
100
|
// Body is FormData
|
|
80
|
-
|
|
81
|
-
this[INTERNALS].
|
|
101
|
+
const blob = formDataToBlob(body) as Blob & globalThis.Blob;
|
|
102
|
+
this[INTERNALS].body = blob;
|
|
103
|
+
this[INTERNALS].boundary = blob.type?.split('boundary=')?.[1] ?? '';
|
|
82
104
|
} else if (typeof body === 'string'){
|
|
83
|
-
//
|
|
84
|
-
// coerce to string then buffer
|
|
105
|
+
// String body
|
|
85
106
|
this[INTERNALS].body = Buffer.from(body);
|
|
86
107
|
} else if (body instanceof URLSearchParams){
|
|
87
|
-
// None of the above
|
|
88
|
-
// coerce to string then buffer
|
|
89
108
|
this[INTERNALS].body = Buffer.from(body.toString());
|
|
90
109
|
} else {
|
|
91
110
|
console.warn(`Unknown body type "${typeof body}", try to parse the body to string!`);
|
|
92
|
-
this[INTERNALS].body =
|
|
111
|
+
this[INTERNALS].body = Buffer.from(String(body));
|
|
93
112
|
}
|
|
94
113
|
|
|
95
|
-
//
|
|
96
|
-
|
|
97
|
-
if (Buffer.isBuffer(
|
|
98
|
-
this[INTERNALS].stream = Readable.from(
|
|
99
|
-
} else if (isBlob(
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
this[INTERNALS].stream = body;
|
|
114
|
+
// Set up the internal stream
|
|
115
|
+
const b = this[INTERNALS].body;
|
|
116
|
+
if (Buffer.isBuffer(b)) {
|
|
117
|
+
this[INTERNALS].stream = Readable.from(b);
|
|
118
|
+
} else if (isBlob(b)) {
|
|
119
|
+
this[INTERNALS].stream = Readable.from(blobToAsyncIterable(b as Blob));
|
|
120
|
+
} else if (b instanceof Readable) {
|
|
121
|
+
this[INTERNALS].stream = b;
|
|
104
122
|
}
|
|
105
123
|
|
|
106
|
-
if (
|
|
107
|
-
|
|
108
|
-
const error = error_ instanceof FetchBaseError
|
|
109
|
-
error_
|
|
110
|
-
new FetchError(`Invalid response body while trying to fetch ${(this as
|
|
124
|
+
if (b instanceof Stream) {
|
|
125
|
+
b.on('error', (error_: Error) => {
|
|
126
|
+
const error = error_ instanceof FetchBaseError
|
|
127
|
+
? error_
|
|
128
|
+
: new FetchError(`Invalid response body while trying to fetch ${(this as unknown as Request).url}: ${error_.message}`, 'system', error_ as unknown as SystemError);
|
|
111
129
|
this[INTERNALS].error = error;
|
|
112
130
|
});
|
|
113
131
|
}
|
|
114
132
|
}
|
|
115
133
|
|
|
116
|
-
get body(): ReadableStream<Uint8Array> {
|
|
117
|
-
|
|
118
|
-
|
|
134
|
+
get body(): ReadableStream<Uint8Array> | null {
|
|
135
|
+
const stream = this[INTERNALS].stream;
|
|
136
|
+
if (!stream) return null;
|
|
137
|
+
|
|
138
|
+
// If ReadableStream is available, wrap the Readable into one
|
|
139
|
+
if (typeof ReadableStream !== 'undefined') {
|
|
140
|
+
return new ReadableStream<Uint8Array>({
|
|
141
|
+
start(controller) {
|
|
142
|
+
stream.on('data', (chunk: Buffer | Uint8Array) => {
|
|
143
|
+
controller.enqueue(chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk));
|
|
144
|
+
});
|
|
145
|
+
stream.on('end', () => {
|
|
146
|
+
controller.close();
|
|
147
|
+
});
|
|
148
|
+
stream.on('error', (err: Error) => {
|
|
149
|
+
controller.error(err);
|
|
150
|
+
});
|
|
151
|
+
},
|
|
152
|
+
cancel() {
|
|
153
|
+
stream.destroy();
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return null;
|
|
119
159
|
}
|
|
120
160
|
|
|
121
161
|
get _stream() {
|
|
@@ -128,18 +168,16 @@ export default class Body implements globalThis.Body {
|
|
|
128
168
|
|
|
129
169
|
/**
|
|
130
170
|
* Decode response as ArrayBuffer
|
|
131
|
-
*
|
|
132
|
-
* @return Promise
|
|
133
171
|
*/
|
|
134
|
-
async arrayBuffer() {
|
|
172
|
+
async arrayBuffer(): Promise<ArrayBuffer> {
|
|
135
173
|
const {buffer, byteOffset, byteLength} = await consumeBody(this);
|
|
136
|
-
return buffer.slice(byteOffset, byteOffset + byteLength);
|
|
174
|
+
return buffer.slice(byteOffset, byteOffset + byteLength) as ArrayBuffer;
|
|
137
175
|
}
|
|
138
176
|
|
|
139
|
-
async formData() {
|
|
177
|
+
async formData(): Promise<FormData> {
|
|
140
178
|
const ct = (this as unknown as Request).headers?.get('content-type');
|
|
141
179
|
|
|
142
|
-
if (ct
|
|
180
|
+
if (ct?.startsWith('application/x-www-form-urlencoded')) {
|
|
143
181
|
const formData = new FormData();
|
|
144
182
|
const parameters = new URLSearchParams(await this.text());
|
|
145
183
|
|
|
@@ -156,11 +194,8 @@ export default class Body implements globalThis.Body {
|
|
|
156
194
|
|
|
157
195
|
/**
|
|
158
196
|
* Return raw response as Blob
|
|
159
|
-
*
|
|
160
|
-
* @return Promise
|
|
161
197
|
*/
|
|
162
|
-
|
|
163
|
-
async blob() {
|
|
198
|
+
async blob(): Promise<Blob> {
|
|
164
199
|
const ct = ((this as unknown as Request).headers?.get('content-type')) || (this[INTERNALS].body && (this[INTERNALS].body as Blob).type) || '';
|
|
165
200
|
const buf = await this.arrayBuffer();
|
|
166
201
|
|
|
@@ -171,25 +206,21 @@ export default class Body implements globalThis.Body {
|
|
|
171
206
|
|
|
172
207
|
/**
|
|
173
208
|
* Decode response as json
|
|
174
|
-
*
|
|
175
|
-
* @return Promise
|
|
176
209
|
*/
|
|
177
|
-
async json() {
|
|
210
|
+
async json(): Promise<unknown> {
|
|
178
211
|
const text = await this.text();
|
|
179
212
|
return JSON.parse(text);
|
|
180
213
|
}
|
|
181
214
|
|
|
182
215
|
/**
|
|
183
216
|
* Decode response as text
|
|
184
|
-
*
|
|
185
|
-
* @return Promise
|
|
186
217
|
*/
|
|
187
|
-
async text() {
|
|
218
|
+
async text(): Promise<string> {
|
|
188
219
|
const buffer = await consumeBody(this);
|
|
189
220
|
return new TextDecoder().decode(buffer);
|
|
190
221
|
}
|
|
191
222
|
}
|
|
192
|
-
|
|
223
|
+
|
|
193
224
|
// In browsers, all properties are enumerable.
|
|
194
225
|
Object.defineProperties(Body.prototype, {
|
|
195
226
|
body: {enumerable: true},
|
|
@@ -198,19 +229,12 @@ Object.defineProperties(Body.prototype, {
|
|
|
198
229
|
blob: {enumerable: true},
|
|
199
230
|
json: {enumerable: true},
|
|
200
231
|
text: {enumerable: true},
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
* Consume and convert an entire Body to a Buffer.
|
|
208
|
-
*
|
|
209
|
-
* Ref: https://fetch.spec.whatwg.org/#concept-body-consume-body
|
|
210
|
-
*
|
|
211
|
-
* @return Promise
|
|
212
|
-
*/
|
|
213
|
-
async function consumeBody(data: Body & Partial<Request>) {
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Consume and convert an entire Body to a Buffer.
|
|
236
|
+
*/
|
|
237
|
+
async function consumeBody(data: Body & { url?: string }): Promise<Buffer> {
|
|
214
238
|
if (data[INTERNALS].disturbed) {
|
|
215
239
|
throw new TypeError(`body used already for: ${data.url}`);
|
|
216
240
|
}
|
|
@@ -228,14 +252,12 @@ async function consumeBody(data: Body & Partial<Request>) {
|
|
|
228
252
|
return Buffer.alloc(0);
|
|
229
253
|
}
|
|
230
254
|
|
|
231
|
-
/* c8 ignore next 3 */
|
|
232
255
|
if (!(body instanceof Stream)) {
|
|
233
256
|
return Buffer.alloc(0);
|
|
234
257
|
}
|
|
235
258
|
|
|
236
|
-
// Body is stream
|
|
237
|
-
|
|
238
|
-
const accum = [];
|
|
259
|
+
// Body is stream — consume it
|
|
260
|
+
const accum: (Buffer | Uint8Array)[] = [];
|
|
239
261
|
let accumBytes = 0;
|
|
240
262
|
|
|
241
263
|
try {
|
|
@@ -249,97 +271,69 @@ async function consumeBody(data: Body & Partial<Request>) {
|
|
|
249
271
|
accumBytes += chunk.length;
|
|
250
272
|
accum.push(chunk);
|
|
251
273
|
}
|
|
252
|
-
} catch (error) {
|
|
253
|
-
const
|
|
274
|
+
} catch (error: unknown) {
|
|
275
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
276
|
+
const error_ = error instanceof FetchBaseError ? error : new FetchError(`Invalid response body while trying to fetch ${data.url}: ${err.message}`, 'system', err as unknown as SystemError);
|
|
254
277
|
throw error_;
|
|
255
278
|
}
|
|
256
279
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
return Buffer.from(accum.join(''));
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
return Buffer.concat(accum, accumBytes);
|
|
264
|
-
} catch (error) {
|
|
265
|
-
throw new FetchError(`Could not create Buffer from response body for ${data.url}: ${error.message}`, 'system', error);
|
|
280
|
+
try {
|
|
281
|
+
if (accum.every(c => typeof c === 'string')) {
|
|
282
|
+
return Buffer.from((accum as unknown as string[]).join(''));
|
|
266
283
|
}
|
|
267
|
-
|
|
268
|
-
|
|
284
|
+
|
|
285
|
+
return Buffer.concat(accum as Buffer[], accumBytes);
|
|
286
|
+
} catch (error: unknown) {
|
|
287
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
288
|
+
throw new FetchError(`Could not create Buffer from response body for ${data.url}: ${err.message}`, 'system', err as unknown as SystemError);
|
|
269
289
|
}
|
|
270
290
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
* @param Mixed instance Response or Request instance
|
|
276
|
-
* @param highWaterMark highWaterMark for both PassThrough body streams
|
|
277
|
-
* @return Mixed
|
|
278
|
-
*/
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Clone body given Res/Req instance
|
|
294
|
+
*/
|
|
279
295
|
export const clone = <T extends Request | Response>(instance: T, highWaterMark?: number) => {
|
|
280
296
|
let p1: PassThrough;
|
|
281
297
|
let p2: PassThrough;
|
|
282
298
|
let {body} = instance[INTERNALS];
|
|
283
299
|
|
|
284
|
-
// Don't allow cloning a used body
|
|
285
300
|
if (instance.bodyUsed) {
|
|
286
301
|
throw new Error('cannot clone body after it is used');
|
|
287
302
|
}
|
|
288
303
|
|
|
289
|
-
|
|
290
|
-
// note: we can't clone the form-data object without having it as a dependency
|
|
291
|
-
if ((body instanceof Stream) && (typeof (body as any).getBoundary !== 'function')) {
|
|
292
|
-
// Tee instance body
|
|
304
|
+
if ((body instanceof Stream) && (typeof (body as unknown as Record<string, unknown>).getBoundary !== 'function')) {
|
|
293
305
|
p1 = new PassThrough({highWaterMark});
|
|
294
306
|
p2 = new PassThrough({highWaterMark});
|
|
295
307
|
body.pipe(p1);
|
|
296
308
|
body.pipe(p2);
|
|
297
|
-
// Set instance body to teed body and return the other teed body
|
|
298
309
|
instance[INTERNALS].stream = p1;
|
|
299
310
|
body = p2;
|
|
300
311
|
}
|
|
301
|
-
|
|
312
|
+
|
|
302
313
|
return body;
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
// );
|
|
310
|
-
|
|
311
|
-
/**
|
|
312
|
-
* Performs the operation "extract a `Content-Type` value from |object|" as
|
|
313
|
-
* specified in the specification:
|
|
314
|
-
* https://fetch.spec.whatwg.org/#concept-bodyinit-extract
|
|
315
|
-
*
|
|
316
|
-
* This function assumes that instance.body is present.
|
|
317
|
-
*
|
|
318
|
-
* @param body Any options.body input
|
|
319
|
-
*/
|
|
320
|
-
export const extractContentType = (body: BodyInit | string | ArrayBuffer | Readable | Blob | ArrayBufferView | Buffer | FormData | globalThis.ReadableStream<any> | null, request: Request | Response): string | null => {
|
|
321
|
-
// Body is null or undefined
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Extract a Content-Type value from a body.
|
|
318
|
+
*/
|
|
319
|
+
export const extractContentType = (body: BodyInit | Readable | Blob | Buffer | null, request: Request | Response): string | null => {
|
|
322
320
|
if (body === null) {
|
|
323
321
|
return null;
|
|
324
322
|
}
|
|
325
323
|
|
|
326
|
-
// Body is string
|
|
327
324
|
if (typeof body === 'string') {
|
|
328
325
|
return 'text/plain;charset=UTF-8';
|
|
329
326
|
}
|
|
330
327
|
|
|
331
|
-
// Body is a URLSearchParams
|
|
332
328
|
if (isURLSearchParameters(body)) {
|
|
333
329
|
return 'application/x-www-form-urlencoded;charset=UTF-8';
|
|
334
330
|
}
|
|
335
331
|
|
|
336
|
-
// Body is blob
|
|
337
332
|
if (isBlob(body)) {
|
|
338
333
|
return (body as Blob & globalThis.Blob).type || null;
|
|
339
334
|
}
|
|
340
335
|
|
|
341
|
-
|
|
342
|
-
if (Buffer.isBuffer(body) || types.isAnyArrayBuffer(body) || ArrayBuffer.isView(body)) {
|
|
336
|
+
if (Buffer.isBuffer(body) || isAnyArrayBuffer(body) || ArrayBuffer.isView(body)) {
|
|
343
337
|
return null;
|
|
344
338
|
}
|
|
345
339
|
|
|
@@ -347,69 +341,87 @@ export const extractContentType = (body: BodyInit | string | ArrayBuffer | Reada
|
|
|
347
341
|
return `multipart/form-data; boundary=${request[INTERNALS].boundary}`;
|
|
348
342
|
}
|
|
349
343
|
|
|
350
|
-
// Detect form data input from form-data module
|
|
351
|
-
// if (body && typeof body.getBoundary === 'function') {
|
|
352
|
-
// return `multipart/form-data;boundary=${getNonSpecFormDataBoundary(body)}`;
|
|
353
|
-
// }
|
|
354
|
-
|
|
355
|
-
// Body is stream - can't really do much about this
|
|
356
344
|
if (body instanceof Stream) {
|
|
357
345
|
return null;
|
|
358
346
|
}
|
|
359
347
|
|
|
360
|
-
// Body constructor defaults other things to string
|
|
361
348
|
return 'text/plain;charset=UTF-8';
|
|
362
349
|
};
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
*
|
|
368
|
-
* ref: https://fetch.spec.whatwg.org/#concept-body-total-bytes
|
|
369
|
-
*
|
|
370
|
-
* @param request Request object with the body property.
|
|
371
|
-
*/
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Get total bytes of a body.
|
|
353
|
+
*/
|
|
372
354
|
export const getTotalBytes = (request: Request): number | null => {
|
|
373
355
|
const { body } = request[INTERNALS];
|
|
374
356
|
|
|
375
|
-
// Body is null or undefined
|
|
376
357
|
if (body === null) {
|
|
377
358
|
return 0;
|
|
378
359
|
}
|
|
379
360
|
|
|
380
|
-
// Body is Blob
|
|
381
361
|
if (isBlob(body)) {
|
|
382
362
|
return (body as Blob).size;
|
|
383
363
|
}
|
|
384
364
|
|
|
385
|
-
// Body is Buffer
|
|
386
365
|
if (Buffer.isBuffer(body)) {
|
|
387
366
|
return body.length;
|
|
388
367
|
}
|
|
389
368
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
return anyBody.hasKnownLength && anyBody.hasKnownLength() ? anyBody.getLengthSync() : null;
|
|
369
|
+
if (body && typeof (body as unknown as Record<string, unknown>).getLengthSync === 'function') {
|
|
370
|
+
const streamBody = body as unknown as { getLengthSync(): number; hasKnownLength?(): boolean };
|
|
371
|
+
return streamBody.hasKnownLength && streamBody.hasKnownLength() ? streamBody.getLengthSync() : null;
|
|
394
372
|
}
|
|
395
373
|
|
|
396
|
-
// Body is stream
|
|
397
374
|
return null;
|
|
398
375
|
};
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
* @param obj.body Body object from the Body instance.
|
|
405
|
-
*/
|
|
406
|
-
export const writeToStream = async (dest: Writable, {body}): Promise<void> => {
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Write a Body to a Node.js WritableStream.
|
|
379
|
+
*/
|
|
380
|
+
export const writeToStream = async (dest: Writable, {body}: {body: Readable | null}): Promise<void> => {
|
|
407
381
|
if (body === null) {
|
|
408
|
-
// Body is null
|
|
409
382
|
dest.end();
|
|
410
383
|
} else {
|
|
411
|
-
// Body is stream
|
|
412
384
|
await pipeline(body, dest);
|
|
413
385
|
}
|
|
414
|
-
|
|
415
|
-
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Convert a Web ReadableStream to a Node.js Readable.
|
|
390
|
+
*/
|
|
391
|
+
function readableStreamToReadable(webStream: ReadableStream): Readable {
|
|
392
|
+
const reader = webStream.getReader();
|
|
393
|
+
return new Readable({
|
|
394
|
+
async read() {
|
|
395
|
+
try {
|
|
396
|
+
const { done, value } = await reader.read();
|
|
397
|
+
if (done) {
|
|
398
|
+
this.push(null);
|
|
399
|
+
} else {
|
|
400
|
+
this.push(Buffer.from(value));
|
|
401
|
+
}
|
|
402
|
+
} catch (err) {
|
|
403
|
+
this.destroy(err as Error);
|
|
404
|
+
}
|
|
405
|
+
},
|
|
406
|
+
destroy(_err, callback) {
|
|
407
|
+
reader.cancel().then(() => callback(null), callback);
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Convert a Blob to an async iterable for Readable.from().
|
|
414
|
+
*/
|
|
415
|
+
async function* blobToAsyncIterable(blob: Blob): AsyncIterable<Uint8Array> {
|
|
416
|
+
if (typeof blob.stream === 'function') {
|
|
417
|
+
const reader = (blob.stream() as unknown as ReadableStream).getReader();
|
|
418
|
+
while (true) {
|
|
419
|
+
const { done, value } = await reader.read();
|
|
420
|
+
if (done) break;
|
|
421
|
+
yield value;
|
|
422
|
+
}
|
|
423
|
+
} else {
|
|
424
|
+
// Fallback: read the entire blob at once
|
|
425
|
+
yield new Uint8Array(await blob.arrayBuffer());
|
|
426
|
+
}
|
|
427
|
+
}
|
package/src/errors/base.ts
CHANGED
|
@@ -5,7 +5,9 @@ export class FetchBaseError extends Error {
|
|
|
5
5
|
constructor(message: string, type?: string) {
|
|
6
6
|
super(message);
|
|
7
7
|
// Hide custom error implementation details from end-users
|
|
8
|
-
Error.captureStackTrace
|
|
8
|
+
if (typeof Error.captureStackTrace === 'function') {
|
|
9
|
+
Error.captureStackTrace(this, this.constructor);
|
|
10
|
+
}
|
|
9
11
|
|
|
10
12
|
this.type = type;
|
|
11
13
|
}
|