New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details
Socket
Book a DemoSign in
Socket

@web-std/fetch

Package Overview
Dependencies
Maintainers
1
Versions
19
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@web-std/fetch - npm Package Compare versions

Comparing version
2.0.3
to
2.1.0
+103
./@types/index.test-d.ts
import { expectType, expectAssignable } from 'tsd';
import AbortController from 'abort-controller';
import fetch, { Request, Response, Headers, Body, FetchError, AbortError } from '.';
import * as _fetch from '.';
import __fetch = require('.');
async function run() {
const getRes = await fetch('https://bigfile.com/test.zip');
expectType<boolean>(getRes.ok);
expectType<number>(getRes.size);
expectType<number>(getRes.status);
expectType<string>(getRes.statusText);
expectType<() => Response>(getRes.clone);
// Test async iterator over body
expectType<NodeJS.ReadableStream | null>(getRes.body);
if (getRes.body) {
for await (const data of getRes.body) {
expectType<Buffer | string>(data);
}
}
// Test Buffer
expectType<Buffer>(await getRes.buffer());
// Test arrayBuffer
expectType<ArrayBuffer>(await getRes.arrayBuffer());
// Test JSON, returns unknown
expectType<unknown>(await getRes.json());
// Headers iterable
expectType<Headers>(getRes.headers);
// Post
try {
const request = new Request('http://byjka.com/buka');
expectType<string>(request.url);
expectType<Headers>(request.headers);
const headers = new Headers({ byaka: 'buke' });
expectType<(a: string, b: string) => void>(headers.append);
expectType<(a: string) => string | null>(headers.get);
expectType<(name: string, value: string) => void>(headers.set);
expectType<(name: string) => void>(headers.delete);
expectType<() => IterableIterator<string>>(headers.keys);
expectType<() => IterableIterator<[string, string]>>(headers.entries);
expectType<() => IterableIterator<[string, string]>>(headers[Symbol.iterator]);
const postRes = await fetch(request, { method: 'POST', headers });
expectType<Blob>(await postRes.blob());
} catch (error) {
if (error instanceof FetchError) {
throw new TypeError(error.errno);
}
if (error instanceof AbortError) {
throw error;
}
}
// export *
const wildRes = await _fetch('https://google.com');
expectType<boolean>(wildRes.ok);
expectType<number>(wildRes.size);
expectType<number>(wildRes.status);
expectType<string>(wildRes.statusText);
expectType<() => Response>(wildRes.clone);
// export = require
const reqRes = await __fetch('https://google.com');
expectType<boolean>(reqRes.ok);
expectType<number>(reqRes.size);
expectType<number>(reqRes.status);
expectType<string>(reqRes.statusText);
expectType<() => Response>(reqRes.clone);
// Others
const response = new Response();
expectType<string>(response.url);
expectAssignable<Body>(response);
const abortController = new AbortController()
const request = new Request('url', { signal: abortController.signal });
expectAssignable<Body>(request);
new Headers({ 'Header': 'value' });
// new Headers(['header', 'value']); // should not work
new Headers([['header', 'value']]);
new Headers(new Headers());
new Headers([
new Set(['a', '1']),
['b', '2'],
new Map([['a', null], ['3', null]]).keys()
]);
fetch.isRedirect = (code: number) => true;
}
run().finally(() => {
console.log('✅');
});
+353
-144

@@ -14,5 +14,11 @@ 'use strict';

const crypto = require('crypto');
const multipartParser = require('@ssttevee/multipart-parser');
const url = require('url');
require('@web-std/form-data');
class FetchBaseError extends Error {
/**
* @param {string} message
* @param {string} type
*/
constructor(message, type) {

@@ -36,3 +42,13 @@ super(message);

/**
* @typedef {{ address?: string, code: string, dest?: string, errno: number, info?: object, message: string, path?: string, port?: number, syscall: string}} SystemError
* @typedef {{
* address?: string
* code: string
* dest?: string
* errno: number
* info?: object
* message: string
* path?: string
* port?: number
* syscall: string
* }} SystemError
*/

@@ -46,3 +62,3 @@

* @param {string} message - Error message for human
* @param {string} [type] - Error type for machine
* @param {string} type - Error type for machine
* @param {SystemError} [systemError] - For Node.js system error

@@ -73,16 +89,16 @@ */

*
* @param {*} obj
* @param {any} object
* @return {obj is URLSearchParams}
*/
const isURLSearchParameters = object => {
const isURLSearchParameters = (object) => {
return (
typeof object === 'object' &&
typeof object.append === 'function' &&
typeof object.delete === 'function' &&
typeof object.get === 'function' &&
typeof object.getAll === 'function' &&
typeof object.has === 'function' &&
typeof object.set === 'function' &&
typeof object.sort === 'function' &&
object[NAME] === 'URLSearchParams'
typeof object === "object" &&
typeof object.append === "function" &&
typeof object.delete === "function" &&
typeof object.get === "function" &&
typeof object.getAll === "function" &&
typeof object.has === "function" &&
typeof object.set === "function" &&
typeof object.sort === "function" &&
object[NAME] === "URLSearchParams"
);

@@ -97,9 +113,9 @@ };

*/
const isBlob = object => {
const isBlob = (object) => {
return (
typeof object === 'object' &&
typeof object.arrayBuffer === 'function' &&
typeof object.type === 'string' &&
typeof object.stream === 'function' &&
typeof object.constructor === 'function' &&
typeof object === "object" &&
typeof object.arrayBuffer === "function" &&
typeof object.type === "string" &&
typeof object.stream === "function" &&
typeof object.constructor === "function" &&
/^(Blob|File)$/.test(object[NAME])

@@ -117,13 +133,13 @@ );

return (
typeof object === 'object' &&
typeof object.append === 'function' &&
typeof object.set === 'function' &&
typeof object.get === 'function' &&
typeof object.getAll === 'function' &&
typeof object.delete === 'function' &&
typeof object.keys === 'function' &&
typeof object.values === 'function' &&
typeof object.entries === 'function' &&
typeof object.constructor === 'function' &&
object[NAME] === 'FormData'
typeof object === "object" &&
typeof object.append === "function" &&
typeof object.set === "function" &&
typeof object.get === "function" &&
typeof object.getAll === "function" &&
typeof object.delete === "function" &&
typeof object.keys === "function" &&
typeof object.values === "function" &&
typeof object.entries === "function" &&
typeof object.constructor === "function" &&
object[NAME] === "FormData"
);

@@ -138,8 +154,8 @@ }

*/
const isMultipartFormDataStream = value => {
const isMultipartFormDataStream = (value) => {
return (
value instanceof Stream &&
typeof value.getBoundary === 'function' &&
typeof value.hasKnownLength === 'function' &&
typeof value.getLengthSync === 'function'
value instanceof Stream === true &&
typeof value.getBoundary === "function" &&
typeof value.hasKnownLength === "function" &&
typeof value.getLengthSync === "function"
);

@@ -151,11 +167,9 @@ };

*
* @param {*} obj
* @param {any} object
* @return {obj is AbortSignal}
*/
const isAbortSignal = object => {
const isAbortSignal = (object) => {
return (
typeof object === 'object' && (
object[NAME] === 'AbortSignal' ||
object[NAME] === 'EventTarget'
)
typeof object === "object" &&
(object[NAME] === "AbortSignal" || object[NAME] === "EventTarget")
);

@@ -170,11 +184,18 @@ };

*/
const isReadableStream = value => {
const isReadableStream = (value) => {
return (
typeof value === 'object' &&
typeof value.getReader === 'function' &&
typeof value.cancel === 'function' &&
typeof value.tee === 'function'
typeof value === "object" &&
typeof value.getReader === "function" &&
typeof value.cancel === "function" &&
typeof value.tee === "function"
);
};
/**
*
* @param {any} value
* @returns {value is Iterable<unknown>}
*/
const isIterable = (value) => value && Symbol.iterator in value;
const carriage = '\r\n';

@@ -203,4 +224,5 @@ const dashes = '-'.repeat(2);

if (isBlob(field)) {
header += `; filename="${field.name}"${carriage}`;
header += `Content-Type: ${field.type || 'application/octet-stream'}`;
const { name = 'blob', type } = /** @type {Blob & {name?:string}} */ (field);
header += `; filename="${name}"${carriage}`;
header += `Content-Type: ${type || 'application/octet-stream'}`;
}

@@ -260,2 +282,24 @@

/**
* @param {Body & {headers?:Headers}} source
*/
const toFormData = async ({ body, headers }) => {
const contentType = headers?.get('Content-Type') || '';
const [type, boundary] = contentType.split(/\s*;\s*boundary=/);
if (type === 'multipart/form-data' && boundary != null && body != null) {
const form = new FormData();
const parts = multipartParser.iterateMultipart(body, boundary);
for await (const { name, data, filename, contentType } of parts) {
if (filename) {
form.append(name, new File([data], filename, { type: contentType }));
} else {
form.append(name, new TextDecoder().decode(data), filename);
}
}
return form
} else {
throw new TypeError('Could not parse content as FormData.')
}
};
const encoder = new util.TextEncoder();

@@ -275,3 +319,2 @@ const decoder = new util.TextDecoder();

// @ts-check
const {readableHighWaterMark} = new Stream.Readable();

@@ -286,6 +329,3 @@

* Ref: https://fetch.spec.whatwg.org/#body
*
* @param {BodyInit} body Readable stream
* @param Object opts Response options
* @return Void
* @implements {globalThis.Body}
*/

@@ -295,3 +335,3 @@

/**
* @param {BodyInit|Stream} body
* @param {BodyInit|Stream|null} body
* @param {{size?:number}} options

@@ -385,3 +425,3 @@ */

get headers() {
return null;
return undefined;
}

@@ -439,2 +479,10 @@

}
/**
* @returns {Promise<FormData>}
*/
async formData() {
return toFormData(this)
}
}

@@ -449,3 +497,4 @@

json: {enumerable: true},
text: {enumerable: true}
text: {enumerable: true},
formData: {enumerable: true}
});

@@ -482,4 +531,5 @@

// get ready to actually consume the body
/** @type {[Uint8Array|null, Uint8Array[], number]} */
const [buffer, chunks, limit] = data.size > 0 ?
[new Uint8Array(data.size), null, data.size] :
[new Uint8Array(data.size), [], data.size] :
[null, [], Infinity];

@@ -510,3 +560,3 @@ let offset = 0;

if (offset < buffer.byteLength) {
throw new FetchError(`Premature close of server response while trying to fetch ${data.url}`);
throw new FetchError(`Premature close of server response while trying to fetch ${data.url}`, 'premature-close');
} else {

@@ -521,7 +571,9 @@ return buffer;

throw error;
// @ts-expect-error - we know it will have a name
} else if (error && error.name === 'AbortError') {
throw error;
} else {
const e = /** @type {import('./errors/fetch-error').SystemError} */(error);
// Other errors, such as incorrect content-encoding
throw new FetchError(`Invalid response body while trying to fetch ${data.url}: ${error.message}`, 'system', error);
throw new FetchError(`Invalid response body while trying to fetch ${data.url}: ${e.message}`, 'system', e);
}

@@ -545,2 +597,3 @@ }

// @ts-expect-error - could be null
const [left, right] = body.tee();

@@ -601,3 +654,2 @@ instance[INTERNALS$2].body = left;

this.reader = null;
this.state = null;
}

@@ -629,2 +681,5 @@

/**
* @returns {Promise<IteratorResult<T, void>>}
*/
async return() {

@@ -638,2 +693,7 @@ if (this.reader) {

/**
*
* @param {any} error
* @returns {Promise<IteratorResult<T, void>>}
*/
async throw(error) {

@@ -701,3 +761,3 @@ await this.getReader().cancel(error);

try {
while (controller.desiredSize > 0) {
while (controller.desiredSize || 0 > 0) {
// eslint-disable-next-line no-await-in-loop

@@ -717,2 +777,5 @@ const next = await this.source.next();

/**
* @param {any} [reason]
*/
cancel(reason) {

@@ -737,4 +800,4 @@ if (reason) {

const pump = new StreamPump(source);
const stream =
/** @type {ReadableStream<Uint8Array>} */(new ReadableStream$1(pump, pump));
const stream = new ReadableStream$1(pump, pump);
// @ts-ignore - web-streams-polyfill API is incompatible
return stream;

@@ -765,2 +828,6 @@ };

/**
* @param {Uint8Array} chunk
* @returns
*/
size(chunk) {

@@ -785,2 +852,5 @@ return chunk.byteLength;

/**
* @param {any} [reason]
*/
cancel(reason) {

@@ -807,3 +877,3 @@ if (this.stream.destroy) {

const available = this.controller.desiredSize - bytes.byteLength;
const available = (this.controller.desiredSize || 0) - bytes.byteLength;
this.controller.enqueue(bytes);

@@ -839,2 +909,5 @@ if (available <= 0) {

/**
* @param {Error} error
*/
error(error) {

@@ -854,4 +927,10 @@ if (this.controller) {

const validateHeaderName = typeof http.validateHeaderName === 'function' ?
http.validateHeaderName :
const validators = /** @type {{validateHeaderName?:(name:string) => any, validateHeaderValue?:(name:string, value:string) => any}} */
(http);
const validateHeaderName = typeof validators.validateHeaderName === 'function' ?
validators.validateHeaderName :
/**
* @param {string} name
*/
name => {

@@ -865,4 +944,8 @@ if (!/^[\^`\-\w!#$%&'*+.|~]+$/.test(name)) {

const validateHeaderValue = typeof http.validateHeaderValue === 'function' ?
http.validateHeaderValue :
const validateHeaderValue = typeof validators.validateHeaderValue === 'function' ?
validators.validateHeaderValue :
/**
* @param {string} name
* @param {string} value
*/
(name, value) => {

@@ -887,2 +970,3 @@ if (/[^\t\u0020-\u007E\u0080-\u00FF]/.test(value)) {

*
* @implements {globalThis.Headers}
*/

@@ -905,32 +989,24 @@ class Headers extends URLSearchParams {

}
} else if (init == null) ; else if (typeof init === 'object' && !util.types.isBoxedPrimitive(init)) {
const method = init[Symbol.iterator];
// eslint-disable-next-line no-eq-null, eqeqeq
if (method == null) {
// Record<ByteString, ByteString>
result.push(...Object.entries(init));
} else {
if (typeof method !== 'function') {
throw new TypeError('Header pairs must be iterable');
}
} else if (init == null) ; else if (isIterable(init)) {
// Sequence<sequence<ByteString>>
// Note: per spec we have to first exhaust the lists then process them
result = [...init]
.map(pair => {
if (
typeof pair !== 'object' || util.types.isBoxedPrimitive(pair)
) {
throw new TypeError('Each header pair must be an iterable object');
}
// Sequence<sequence<ByteString>>
// Note: per spec we have to first exhaust the lists then process them
result = [...init]
.map(pair => {
if (
typeof pair !== 'object' || util.types.isBoxedPrimitive(pair)
) {
throw new TypeError('Each header pair must be an iterable object');
}
return [...pair];
}).map(pair => {
if (pair.length !== 2) {
throw new TypeError('Each header pair must be a name/value tuple');
}
return [...pair];
}).map(pair => {
if (pair.length !== 2) {
throw new TypeError('Each header pair must be a name/value tuple');
}
return [...pair];
});
}
return [...pair];
});
} else if (typeof init === "object" && init !== null) {
// Record<ByteString, ByteString>
result.push(...Object.entries(init));
} else {

@@ -948,3 +1024,3 @@ throw new TypeError('Failed to construct \'Headers\': The provided value is not of type \'(sequence<sequence<ByteString>> or record<ByteString, ByteString>)');

}) :
undefined;
[];

@@ -960,2 +1036,6 @@ super(result);

case 'set':
/**
* @param {string} name
* @param {string} value
*/
return (name, value) => {

@@ -974,4 +1054,8 @@ validateHeaderName(name);

case 'getAll':
/**
* @param {string} name
*/
return name => {
validateHeaderName(name);
// @ts-ignore
return URLSearchParams.prototype[p].call(

@@ -1005,2 +1089,6 @@ receiver,

/**
*
* @param {string} name
*/
get(name) {

@@ -1020,2 +1108,7 @@ const values = this.getAll(name);

/**
* @param {(value: string, key: string, parent: this) => void} callback
* @param {any} thisArg
* @returns {void}
*/
forEach(callback, thisArg = undefined) {

@@ -1027,5 +1120,8 @@ for (const name of this.keys()) {

/**
* @returns {IterableIterator<string>}
*/
* values() {
for (const name of this.keys()) {
yield this.get(name);
yield /** @type {string} */(this.get(name));
}

@@ -1035,7 +1131,7 @@ }

/**
* @type {() => IterableIterator<[string, string]>}
* @returns {IterableIterator<[string, string]>}
*/
* entries() {
for (const name of this.keys()) {
yield [name, this.get(name)];
yield [name, /** @type {string} */(this.get(name))];
}

@@ -1057,3 +1153,3 @@ }

return result;
}, {});
}, /** @type {Record<string, string[]>} */({}));
}

@@ -1076,3 +1172,3 @@

return result;
}, {});
}, /** @type {Record<string, string|string[]>} */({}));
}

@@ -1090,3 +1186,3 @@ }

return result;
}, {})
}, /** @type {Record<string, {enumerable:true}>} */ ({}))
);

@@ -1109,3 +1205,3 @@

return result;
}, [])
}, /** @type {string[][]} */([]))
.filter(([name, value]) => {

@@ -1146,8 +1242,16 @@ try {

* Response class
*
* @param Stream body Readable stream
* @param Object opts Response options
* @return Void
*
* @typedef {Object} Ext
* @property {number} [size]
* @property {string} [url]
* @property {number} [counter]
* @property {number} [highWaterMark]
*
* @implements {globalThis.Response}
*/
class Response extends Body {
/**
* @param {BodyInit|import('stream').Stream|null} [body] - Readable stream
* @param {ResponseInit & Ext} [options] - Response options
*/
constructor(body = null, options = {}) {

@@ -1171,3 +1275,3 @@ super(body, options);

headers,
counter: options.counter,
counter: options.counter || 0,
highWaterMark: options.highWaterMark

@@ -1177,2 +1281,9 @@ };

/**
* @type {ResponseType}
*/
get type() {
return "default"
}
get url() {

@@ -1212,6 +1323,6 @@ return this[INTERNALS$1].url || '';

*
* @return Response
* @returns {Response}
*/
clone() {
return new Response(clone(this, this.highWaterMark), {
return new Response(clone(this), {
url: this.url,

@@ -1221,4 +1332,2 @@ status: this.status,

headers: this.headers,
ok: this.ok,
redirected: this.redirected,
size: this.size

@@ -1261,2 +1370,6 @@ });

/**
* @param {URL} parsedURL
* @returns {string}
*/
const getSearch = parsedURL => {

@@ -1287,41 +1400,75 @@ if (parsedURL.search) {

/**
* Request class
* @implements {globalThis.Request}
*
* @typedef {Object} RequestState
* @property {string} method
* @property {RequestRedirect} redirect
* @property {globalThis.Headers} headers
* @property {URL} parsedURL
* @property {AbortSignal|null} signal
*
* @typedef {Object} RequestExtraOptions
* @property {number} [follow]
* @property {boolean} [compress]
* @property {number} [size]
* @property {number} [counter]
* @property {Agent} [agent]
* @property {number} [highWaterMark]
* @property {boolean} [insecureHTTPParser]
*
* @typedef {((url:URL) => import('http').Agent) | import('http').Agent} Agent
*
* @typedef {Object} RequestOptions
* @property {string} [method]
* @property {ReadableStream<Uint8Array>|null} [body]
* @property {globalThis.Headers} [headers]
* @property {RequestRedirect} [redirect]
*
*/
class Request extends Body {
/**
* @param {string|Request} input Url or Request instance
* @param {RequestInit} init Custom options
* @param {string|Request|URL} info Url or Request instance
* @param {RequestInit & RequestExtraOptions} init Custom options
*/
constructor(input, init = {}) {
constructor(info, init = {}) {
let parsedURL;
/** @type {RequestOptions & RequestExtraOptions} */
let settings;
// Normalize input and force URL to be encoded as UTF-8 (https://github.com/node-fetch/node-fetch/issues/245)
if (isRequest(input)) {
parsedURL = new URL(input.url);
if (isRequest(info)) {
parsedURL = new URL(info.url);
settings = (info);
} else {
parsedURL = new URL(input);
input = {};
parsedURL = new URL(info);
settings = {};
}
let method = init.method || input.method || 'GET';
let method = init.method || settings.method || 'GET';
method = method.toUpperCase();
const inputBody = init.body != null
? init.body
: (isRequest(info) && info.body !== null)
? clone(info)
: null;
// eslint-disable-next-line no-eq-null, eqeqeq
if (((init.body != null || isRequest(input)) && input.body !== null) &&
(method === 'GET' || method === 'HEAD')) {
if (inputBody != null && (method === 'GET' || method === 'HEAD')) {
throw new TypeError('Request with GET/HEAD method cannot have body');
}
const inputBody = init.body ?
init.body :
(isRequest(input) && input.body !== null ?
clone(input) :
null);
super(inputBody, {
size: init.size || input.size || 0
size: init.size || settings.size || 0
});
const input = settings;
const headers = new Headers(init.headers || input.headers || {});
const headers = /** @type {globalThis.Headers} */
(new Headers(init.headers || input.headers || {}));

@@ -1335,8 +1482,7 @@ if (inputBody !== null && !headers.has('Content-Type')) {

let signal = isRequest(input) ?
input.signal :
null;
if ('signal' in init) {
signal = init.signal;
}
let signal = 'signal' in init
? init.signal
: isRequest(input)
? input.signal
: null;

@@ -1348,2 +1494,3 @@ // eslint-disable-next-line no-eq-null, eqeqeq

/** @type {RequestState} */
this[INTERNALS] = {

@@ -1354,14 +1501,63 @@ method,

parsedURL,
signal
signal: signal || null
};
/** @type {boolean} */
this.keepalive;
// Node-fetch-only options
/** @type {number} */
this.follow = init.follow === undefined ? (input.follow === undefined ? 20 : input.follow) : init.follow;
/** @type {boolean} */
this.compress = init.compress === undefined ? (input.compress === undefined ? true : input.compress) : init.compress;
/** @type {number} */
this.counter = init.counter || input.counter || 0;
/** @type {Agent|undefined} */
this.agent = init.agent || input.agent;
/** @type {number} */
this.highWaterMark = init.highWaterMark || input.highWaterMark || 16384;
/** @type {boolean} */
this.insecureHTTPParser = init.insecureHTTPParser || input.insecureHTTPParser || false;
}
/**
* @type {RequestCache}
*/
get cache() {
return "default"
}
/**
* @type {RequestCredentials}
*/
get credentials() {
return "same-origin"
}
/**
* @type {RequestDestination}
*/
get destination() {
return ""
}
get integrity() {
return ""
}
/** @type {RequestMode} */
get mode() {
return "cors"
}
/** @type {string} */
get referrer() {
return ""
}
/** @type {ReferrerPolicy} */
get referrerPolicy() {
return ""
}
get method() {

@@ -1372,3 +1568,3 @@ return this[INTERNALS].method;

/**
* @type {URL}
* @type {string}
*/

@@ -1379,2 +1575,5 @@ get url() {

/**
* @type {globalThis.Headers}
*/
get headers() {

@@ -1388,3 +1587,7 @@ return this[INTERNALS].headers;

/**
* @returns {AbortSignal}
*/
get signal() {
// @ts-ignore
return this[INTERNALS].signal;

@@ -1396,3 +1599,3 @@ }

*
* @return Request
* @return {globalThis.Request}
*/

@@ -1421,3 +1624,3 @@ clone() {

*
* @param {Request} request - A Request instance
* @param {Request & Record<INTERNALS, RequestState>} request - A Request instance
*/

@@ -1484,5 +1687,7 @@ const getNodeRequestOptions = request => {

search: parsedURL.search,
// @ts-ignore - it does not has a query
query: parsedURL.query,
href: parsedURL.href,
method: request.method,
// @ts-ignore - not sure what this supposed to do
headers: headers[Symbol.for('nodejs.util.inspect.custom')](),

@@ -1500,2 +1705,6 @@ insecureHTTPParser: request.insecureHTTPParser,

class AbortError extends FetchBaseError {
/**
* @param {string} message
* @param {string} [type]
*/
constructor(message, type = 'aborted') {

@@ -1535,3 +1744,3 @@ super(message, type);

if (options.protocol === 'data:') {
const data = dataUriToBuffer(request.url);
const data = dataUriToBuffer(request.url.toString());
const response = new Response(data, {headers: {'Content-Type': data.typeFull}});

@@ -1589,2 +1798,3 @@ resolve(response);

request_.on('error', err => {
// @ts-expect-error - err may not be SystemError
reject(new FetchError(`request to ${request.url} failed, reason: ${err.message}`, 'system', err));

@@ -1644,3 +1854,3 @@ finalize();

if (locationURL !== null) {
headers.set('Location', locationURL);
headers.set('Location', locationURL.toString());
}

@@ -1664,3 +1874,2 @@

// Create a new Request object.
/** @type {RequestInit} */
const requestOptions = {

@@ -1667,0 +1876,0 @@ headers: new Headers(request.headers),

{
"name": "@web-std/fetch",
"version": "2.0.3",
"version": "2.1.0",
"description": "Web compatible Fetch API implementation for node.js",

@@ -12,3 +12,4 @@ "main": "./dist/index.cjs",

"import": "./src/index.js",
"require": "./dist/index.cjs"
"require": "./dist/index.cjs",
"types": "./@types/index.d.ts"
},

@@ -20,3 +21,3 @@ "./package.json": "./package.json"

"dist",
"@types/index.d.ts"
"@types"
],

@@ -31,5 +32,5 @@ "types": "./@types/index.d.ts",

"coverage": "c8 report --reporter=text-lcov | coveralls",
"test-types": "tsd",
"typecheck": "tsc --build",
"lint": "xo",
"prepublishOnly": "node ./test/commonjs/test-artifact.js"
"prepublishOnly": "node ./test/commonjs/test-artifact.js && tsc --build"
},

@@ -73,3 +74,4 @@ "repository": {

"tsd": "^0.13.1",
"xo": "^0.33.1"
"xo": "^0.33.1",
"typescript": "^4.4.4"
},

@@ -79,3 +81,4 @@ "dependencies": {

"data-uri-to-buffer": "^3.0.1",
"web-streams-polyfill": "^3.0.2"
"web-streams-polyfill": "^3.1.1",
"@ssttevee/multipart-parser": "^0.1.9"
},

@@ -82,0 +85,0 @@ "esm": {

@@ -16,6 +16,5 @@ // @ts-check

import {FetchBaseError} from './errors/base.js';
import {formDataIterator, getBoundary, getFormDataLength} from './utils/form-data.js';
import {formDataIterator, getBoundary, getFormDataLength, toFormData} from './utils/form-data.js';
import {isBlob, isURLSearchParameters, isFormData, isMultipartFormDataStream, isReadableStream} from './utils/is.js';
import * as utf8 from './utils/utf8.js';
const {readableHighWaterMark} = new Stream.Readable();

@@ -30,6 +29,3 @@

* Ref: https://fetch.spec.whatwg.org/#body
*
* @param {BodyInit} body Readable stream
* @param Object opts Response options
* @return Void
* @implements {globalThis.Body}
*/

@@ -39,3 +35,3 @@

/**
* @param {BodyInit|Stream} body
* @param {BodyInit|Stream|null} body
* @param {{size?:number}} options

@@ -129,3 +125,3 @@ */

get headers() {
return null;
return undefined;
}

@@ -183,2 +179,10 @@

}
/**
* @returns {Promise<FormData>}
*/
async formData() {
return toFormData(this)
}
}

@@ -193,3 +197,4 @@

json: {enumerable: true},
text: {enumerable: true}
text: {enumerable: true},
formData: {enumerable: true}
});

@@ -226,4 +231,5 @@

// get ready to actually consume the body
/** @type {[Uint8Array|null, Uint8Array[], number]} */
const [buffer, chunks, limit] = data.size > 0 ?
[new Uint8Array(data.size), null, data.size] :
[new Uint8Array(data.size), [], data.size] :
[null, [], Infinity];

@@ -254,3 +260,3 @@ let offset = 0;

if (offset < buffer.byteLength) {
throw new FetchError(`Premature close of server response while trying to fetch ${data.url}`);
throw new FetchError(`Premature close of server response while trying to fetch ${data.url}`, 'premature-close');
} else {

@@ -265,7 +271,9 @@ return buffer;

throw error;
// @ts-expect-error - we know it will have a name
} else if (error && error.name === 'AbortError') {
throw error;
} else {
const e = /** @type {import('./errors/fetch-error').SystemError} */(error)
// Other errors, such as incorrect content-encoding
throw new FetchError(`Invalid response body while trying to fetch ${data.url}: ${error.message}`, 'system', error);
throw new FetchError(`Invalid response body while trying to fetch ${data.url}: ${e.message}`, 'system', e);
}

@@ -289,2 +297,3 @@ }

// @ts-expect-error - could be null
const [left, right] = body.tee();

@@ -345,3 +354,2 @@ instance[INTERNALS].body = left;

this.reader = null;
this.state = null;
}

@@ -373,2 +381,5 @@

/**
* @returns {Promise<IteratorResult<T, void>>}
*/
async return() {

@@ -382,2 +393,7 @@ if (this.reader) {

/**
*
* @param {any} error
* @returns {Promise<IteratorResult<T, void>>}
*/
async throw(error) {

@@ -445,3 +461,3 @@ await this.getReader().cancel(error);

try {
while (controller.desiredSize > 0) {
while (controller.desiredSize || 0 > 0) {
// eslint-disable-next-line no-await-in-loop

@@ -461,2 +477,5 @@ const next = await this.source.next();

/**
* @param {any} [reason]
*/
cancel(reason) {

@@ -481,4 +500,4 @@ if (reason) {

const pump = new StreamPump(source);
const stream =
/** @type {ReadableStream<Uint8Array>} */(new ReadableStream(pump, pump));
const stream = new ReadableStream(pump, pump);
// @ts-ignore - web-streams-polyfill API is incompatible
return stream;

@@ -509,2 +528,6 @@ };

/**
* @param {Uint8Array} chunk
* @returns
*/
size(chunk) {

@@ -529,2 +552,5 @@ return chunk.byteLength;

/**
* @param {any} [reason]
*/
cancel(reason) {

@@ -551,3 +577,3 @@ if (this.stream.destroy) {

const available = this.controller.desiredSize - bytes.byteLength;
const available = (this.controller.desiredSize || 0) - bytes.byteLength;
this.controller.enqueue(bytes);

@@ -583,2 +609,5 @@ if (available <= 0) {

/**
* @param {Error} error
*/
error(error) {

@@ -585,0 +614,0 @@ if (this.controller) {

@@ -7,2 +7,6 @@ import {FetchBaseError} from './base.js';

export class AbortError extends FetchBaseError {
/**
* @param {string} message
* @param {string} [type]
*/
constructor(message, type = 'aborted') {

@@ -9,0 +13,0 @@ super(message, type);

'use strict';
export class FetchBaseError extends Error {
/**
* @param {string} message
* @param {string} type
*/
constructor(message, type) {

@@ -5,0 +9,0 @@ super(message);

@@ -5,3 +5,13 @@

/**
* @typedef {{ address?: string, code: string, dest?: string, errno: number, info?: object, message: string, path?: string, port?: number, syscall: string}} SystemError
* @typedef {{
* address?: string
* code: string
* dest?: string
* errno: number
* info?: object
* message: string
* path?: string
* port?: number
* syscall: string
* }} SystemError
*/

@@ -15,3 +25,3 @@

* @param {string} message - Error message for human
* @param {string} [type] - Error type for machine
* @param {string} type - Error type for machine
* @param {SystemError} [systemError] - For Node.js system error

@@ -18,0 +28,0 @@ */

@@ -9,5 +9,12 @@ /**

import http from 'http';
import { isIterable } from './utils/is.js'
const validateHeaderName = typeof http.validateHeaderName === 'function' ?
http.validateHeaderName :
const validators = /** @type {{validateHeaderName?:(name:string) => any, validateHeaderValue?:(name:string, value:string) => any}} */
(http)
const validateHeaderName = typeof validators.validateHeaderName === 'function' ?
validators.validateHeaderName :
/**
* @param {string} name
*/
name => {

@@ -21,4 +28,8 @@ if (!/^[\^`\-\w!#$%&'*+.|~]+$/.test(name)) {

const validateHeaderValue = typeof http.validateHeaderValue === 'function' ?
http.validateHeaderValue :
const validateHeaderValue = typeof validators.validateHeaderValue === 'function' ?
validators.validateHeaderValue :
/**
* @param {string} name
* @param {string} value
*/
(name, value) => {

@@ -43,2 +54,3 @@ if (/[^\t\u0020-\u007E\u0080-\u00FF]/.test(value)) {

*
* @implements {globalThis.Headers}
*/

@@ -63,32 +75,24 @@ export default class Headers extends URLSearchParams {

// No op
} else if (typeof init === 'object' && !types.isBoxedPrimitive(init)) {
const method = init[Symbol.iterator];
// eslint-disable-next-line no-eq-null, eqeqeq
if (method == null) {
// Record<ByteString, ByteString>
result.push(...Object.entries(init));
} else {
if (typeof method !== 'function') {
throw new TypeError('Header pairs must be iterable');
}
} else if (isIterable(init)) {
// Sequence<sequence<ByteString>>
// Note: per spec we have to first exhaust the lists then process them
result = [...init]
.map(pair => {
if (
typeof pair !== 'object' || types.isBoxedPrimitive(pair)
) {
throw new TypeError('Each header pair must be an iterable object');
}
// Sequence<sequence<ByteString>>
// Note: per spec we have to first exhaust the lists then process them
result = [...init]
.map(pair => {
if (
typeof pair !== 'object' || types.isBoxedPrimitive(pair)
) {
throw new TypeError('Each header pair must be an iterable object');
}
return [...pair];
}).map(pair => {
if (pair.length !== 2) {
throw new TypeError('Each header pair must be a name/value tuple');
}
return [...pair];
}).map(pair => {
if (pair.length !== 2) {
throw new TypeError('Each header pair must be a name/value tuple');
}
return [...pair];
});
}
return [...pair];
});
} else if (typeof init === "object" && init !== null) {
// Record<ByteString, ByteString>
result.push(...Object.entries(init));
} else {

@@ -106,3 +110,3 @@ throw new TypeError('Failed to construct \'Headers\': The provided value is not of type \'(sequence<sequence<ByteString>> or record<ByteString, ByteString>)');

}) :
undefined;
[];

@@ -118,2 +122,6 @@ super(result);

case 'set':
/**
* @param {string} name
* @param {string} value
*/
return (name, value) => {

@@ -132,4 +140,8 @@ validateHeaderName(name);

case 'getAll':
/**
* @param {string} name
*/
return name => {
validateHeaderName(name);
// @ts-ignore
return URLSearchParams.prototype[p].call(

@@ -163,2 +175,6 @@ receiver,

/**
*
* @param {string} name
*/
get(name) {

@@ -178,2 +194,7 @@ const values = this.getAll(name);

/**
* @param {(value: string, key: string, parent: this) => void} callback
* @param {any} thisArg
* @returns {void}
*/
forEach(callback, thisArg = undefined) {

@@ -185,5 +206,8 @@ for (const name of this.keys()) {

/**
* @returns {IterableIterator<string>}
*/
* values() {
for (const name of this.keys()) {
yield this.get(name);
yield /** @type {string} */(this.get(name));
}

@@ -193,7 +217,7 @@ }

/**
* @type {() => IterableIterator<[string, string]>}
* @returns {IterableIterator<[string, string]>}
*/
* entries() {
for (const name of this.keys()) {
yield [name, this.get(name)];
yield [name, /** @type {string} */(this.get(name))];
}

@@ -215,3 +239,3 @@ }

return result;
}, {});
}, /** @type {Record<string, string[]>} */({}));
}

@@ -234,3 +258,3 @@

return result;
}, {});
}, /** @type {Record<string, string|string[]>} */({}));
}

@@ -248,3 +272,3 @@ }

return result;
}, {})
}, /** @type {Record<string, {enumerable:true}>} */ ({}))
);

@@ -267,3 +291,3 @@

return result;
}, [])
}, /** @type {string[][]} */([]))
.filter(([name, value]) => {

@@ -270,0 +294,0 @@ try {

@@ -25,2 +25,3 @@ /**

import {Blob} from '@web-std/blob';
import {FormData} from '@web-std/form-data';

@@ -50,3 +51,3 @@ const {ReadableStream} = WebStreams;

if (options.protocol === 'data:') {
const data = dataUriToBuffer(request.url);
const data = dataUriToBuffer(request.url.toString());
const response = new Response(data, {headers: {'Content-Type': data.typeFull}});

@@ -104,2 +105,3 @@ resolve(response);

request_.on('error', err => {
// @ts-expect-error - err may not be SystemError
reject(new FetchError(`request to ${request.url} failed, reason: ${err.message}`, 'system', err));

@@ -159,3 +161,3 @@ finalize();

if (locationURL !== null) {
headers.set('Location', locationURL);
headers.set('Location', locationURL.toString());
}

@@ -179,3 +181,2 @@

// Create a new Request object.
/** @type {RequestInit} */
const requestOptions = {

@@ -182,0 +183,0 @@ headers: new Headers(request.headers),

@@ -31,41 +31,75 @@

/**
* Request class
* @implements {globalThis.Request}
*
* @typedef {Object} RequestState
* @property {string} method
* @property {RequestRedirect} redirect
* @property {globalThis.Headers} headers
* @property {URL} parsedURL
* @property {AbortSignal|null} signal
*
* @typedef {Object} RequestExtraOptions
* @property {number} [follow]
* @property {boolean} [compress]
* @property {number} [size]
* @property {number} [counter]
* @property {Agent} [agent]
* @property {number} [highWaterMark]
* @property {boolean} [insecureHTTPParser]
*
* @typedef {((url:URL) => import('http').Agent) | import('http').Agent} Agent
*
* @typedef {Object} RequestOptions
* @property {string} [method]
* @property {ReadableStream<Uint8Array>|null} [body]
* @property {globalThis.Headers} [headers]
* @property {RequestRedirect} [redirect]
*
*/
export default class Request extends Body {
/**
* @param {string|Request} input Url or Request instance
* @param {RequestInit} init Custom options
* @param {string|Request|URL} info Url or Request instance
* @param {RequestInit & RequestExtraOptions} init Custom options
*/
constructor(input, init = {}) {
constructor(info, init = {}) {
let parsedURL;
/** @type {RequestOptions & RequestExtraOptions} */
let settings
// Normalize input and force URL to be encoded as UTF-8 (https://github.com/node-fetch/node-fetch/issues/245)
if (isRequest(input)) {
parsedURL = new URL(input.url);
if (isRequest(info)) {
parsedURL = new URL(info.url);
settings = (info)
} else {
parsedURL = new URL(input);
input = {};
parsedURL = new URL(info);
settings = {};
}
let method = init.method || input.method || 'GET';
let method = init.method || settings.method || 'GET';
method = method.toUpperCase();
const inputBody = init.body != null
? init.body
: (isRequest(info) && info.body !== null)
? clone(info)
: null;
// eslint-disable-next-line no-eq-null, eqeqeq
if (((init.body != null || isRequest(input)) && input.body !== null) &&
(method === 'GET' || method === 'HEAD')) {
if (inputBody != null && (method === 'GET' || method === 'HEAD')) {
throw new TypeError('Request with GET/HEAD method cannot have body');
}
const inputBody = init.body ?
init.body :
(isRequest(input) && input.body !== null ?
clone(input) :
null);
super(inputBody, {
size: init.size || input.size || 0
size: init.size || settings.size || 0
});
const input = settings
const headers = new Headers(init.headers || input.headers || {});
const headers = /** @type {globalThis.Headers} */
(new Headers(init.headers || input.headers || {}));

@@ -79,8 +113,7 @@ if (inputBody !== null && !headers.has('Content-Type')) {

let signal = isRequest(input) ?
input.signal :
null;
if ('signal' in init) {
signal = init.signal;
}
let signal = 'signal' in init
? init.signal
: isRequest(input)
? input.signal
: null;

@@ -92,2 +125,3 @@ // eslint-disable-next-line no-eq-null, eqeqeq

/** @type {RequestState} */
this[INTERNALS] = {

@@ -98,14 +132,63 @@ method,

parsedURL,
signal
signal: signal || null
};
/** @type {boolean} */
this.keepalive
// Node-fetch-only options
/** @type {number} */
this.follow = init.follow === undefined ? (input.follow === undefined ? 20 : input.follow) : init.follow;
/** @type {boolean} */
this.compress = init.compress === undefined ? (input.compress === undefined ? true : input.compress) : init.compress;
/** @type {number} */
this.counter = init.counter || input.counter || 0;
/** @type {Agent|undefined} */
this.agent = init.agent || input.agent;
/** @type {number} */
this.highWaterMark = init.highWaterMark || input.highWaterMark || 16384;
/** @type {boolean} */
this.insecureHTTPParser = init.insecureHTTPParser || input.insecureHTTPParser || false;
}
/**
* @type {RequestCache}
*/
get cache() {
return "default"
}
/**
* @type {RequestCredentials}
*/
get credentials() {
return "same-origin"
}
/**
* @type {RequestDestination}
*/
get destination() {
return ""
}
get integrity() {
return ""
}
/** @type {RequestMode} */
get mode() {
return "cors"
}
/** @type {string} */
get referrer() {
return ""
}
/** @type {ReferrerPolicy} */
get referrerPolicy() {
return ""
}
get method() {

@@ -116,3 +199,3 @@ return this[INTERNALS].method;

/**
* @type {URL}
* @type {string}
*/

@@ -123,2 +206,5 @@ get url() {

/**
* @type {globalThis.Headers}
*/
get headers() {

@@ -132,3 +218,7 @@ return this[INTERNALS].headers;

/**
* @returns {AbortSignal}
*/
get signal() {
// @ts-ignore
return this[INTERNALS].signal;

@@ -140,3 +230,3 @@ }

*
* @return Request
* @return {globalThis.Request}
*/

@@ -165,3 +255,3 @@ clone() {

*
* @param {Request} request - A Request instance
* @param {Request & Record<INTERNALS, RequestState>} request - A Request instance
*/

@@ -228,5 +318,7 @@ export const getNodeRequestOptions = request => {

search: parsedURL.search,
// @ts-ignore - it does not has a query
query: parsedURL.query,
href: parsedURL.href,
method: request.method,
// @ts-ignore - not sure what this supposed to do
headers: headers[Symbol.for('nodejs.util.inspect.custom')](),

@@ -233,0 +325,0 @@ insecureHTTPParser: request.insecureHTTPParser,

@@ -15,8 +15,16 @@ /**

* Response class
*
* @param Stream body Readable stream
* @param Object opts Response options
* @return Void
*
* @typedef {Object} Ext
* @property {number} [size]
* @property {string} [url]
* @property {number} [counter]
* @property {number} [highWaterMark]
*
* @implements {globalThis.Response}
*/
export default class Response extends Body {
/**
* @param {BodyInit|import('stream').Stream|null} [body] - Readable stream
* @param {ResponseInit & Ext} [options] - Response options
*/
constructor(body = null, options = {}) {

@@ -40,3 +48,3 @@ super(body, options);

headers,
counter: options.counter,
counter: options.counter || 0,
highWaterMark: options.highWaterMark

@@ -46,2 +54,9 @@ };

/**
* @type {ResponseType}
*/
get type() {
return "default"
}
get url() {

@@ -81,6 +96,6 @@ return this[INTERNALS].url || '';

*
* @return Response
* @returns {Response}
*/
clone() {
return new Response(clone(this, this.highWaterMark), {
return new Response(clone(this), {
url: this.url,

@@ -90,4 +105,2 @@ status: this.status,

headers: this.headers,
ok: this.ok,
redirected: this.redirected,
size: this.size

@@ -94,0 +107,0 @@ });

import {randomBytes} from 'crypto';
import { iterateMultipart } from '@ssttevee/multipart-parser'
import {isBlob} from './is.js';

@@ -28,4 +28,5 @@

if (isBlob(field)) {
header += `; filename="${field.name}"${carriage}`;
header += `Content-Type: ${field.type || 'application/octet-stream'}`;
const { name = 'blob', type } = /** @type {Blob & {name?:string}} */ (field);
header += `; filename="${name}"${carriage}`;
header += `Content-Type: ${type || 'application/octet-stream'}`;
}

@@ -84,1 +85,23 @@

}
/**
* @param {Body & {headers?:Headers}} source
*/
export const toFormData = async ({ body, headers }) => {
const contentType = headers?.get('Content-Type') || ''
const [type, boundary] = contentType.split(/\s*;\s*boundary=/)
if (type === 'multipart/form-data' && boundary != null && body != null) {
const form = new FormData()
const parts = iterateMultipart(body, boundary)
for await (const { name, data, filename, contentType } of parts) {
if (filename) {
form.append(name, new File([data], filename, { type: contentType }))
} else {
form.append(name, new TextDecoder().decode(data), filename)
}
}
return form
} else {
throw new TypeError('Could not parse content as FormData.')
}
}

@@ -0,1 +1,5 @@

/**
* @param {URL} parsedURL
* @returns {string}
*/
export const getSearch = parsedURL => {

@@ -2,0 +6,0 @@ if (parsedURL.search) {

@@ -1,2 +0,2 @@

import Stream from 'stream';
import Stream from "stream";

@@ -15,16 +15,16 @@ /**

*
* @param {*} obj
* @param {any} object
* @return {obj is URLSearchParams}
*/
export const isURLSearchParameters = object => {
export const isURLSearchParameters = (object) => {
return (
typeof object === 'object' &&
typeof object.append === 'function' &&
typeof object.delete === 'function' &&
typeof object.get === 'function' &&
typeof object.getAll === 'function' &&
typeof object.has === 'function' &&
typeof object.set === 'function' &&
typeof object.sort === 'function' &&
object[NAME] === 'URLSearchParams'
typeof object === "object" &&
typeof object.append === "function" &&
typeof object.delete === "function" &&
typeof object.get === "function" &&
typeof object.getAll === "function" &&
typeof object.has === "function" &&
typeof object.set === "function" &&
typeof object.sort === "function" &&
object[NAME] === "URLSearchParams"
);

@@ -39,9 +39,9 @@ };

*/
export const isBlob = object => {
export const isBlob = (object) => {
return (
typeof object === 'object' &&
typeof object.arrayBuffer === 'function' &&
typeof object.type === 'string' &&
typeof object.stream === 'function' &&
typeof object.constructor === 'function' &&
typeof object === "object" &&
typeof object.arrayBuffer === "function" &&
typeof object.type === "string" &&
typeof object.stream === "function" &&
typeof object.constructor === "function" &&
/^(Blob|File)$/.test(object[NAME])

@@ -59,13 +59,13 @@ );

return (
typeof object === 'object' &&
typeof object.append === 'function' &&
typeof object.set === 'function' &&
typeof object.get === 'function' &&
typeof object.getAll === 'function' &&
typeof object.delete === 'function' &&
typeof object.keys === 'function' &&
typeof object.values === 'function' &&
typeof object.entries === 'function' &&
typeof object.constructor === 'function' &&
object[NAME] === 'FormData'
typeof object === "object" &&
typeof object.append === "function" &&
typeof object.set === "function" &&
typeof object.get === "function" &&
typeof object.getAll === "function" &&
typeof object.delete === "function" &&
typeof object.keys === "function" &&
typeof object.values === "function" &&
typeof object.entries === "function" &&
typeof object.constructor === "function" &&
object[NAME] === "FormData"
);

@@ -80,8 +80,8 @@ }

*/
export const isMultipartFormDataStream = value => {
export const isMultipartFormDataStream = (value) => {
return (
value instanceof Stream &&
typeof value.getBoundary === 'function' &&
typeof value.hasKnownLength === 'function' &&
typeof value.getLengthSync === 'function'
value instanceof Stream === true &&
typeof value.getBoundary === "function" &&
typeof value.hasKnownLength === "function" &&
typeof value.getLengthSync === "function"
);

@@ -93,11 +93,9 @@ };

*
* @param {*} obj
* @param {any} object
* @return {obj is AbortSignal}
*/
export const isAbortSignal = object => {
export const isAbortSignal = (object) => {
return (
typeof object === 'object' && (
object[NAME] === 'AbortSignal' ||
object[NAME] === 'EventTarget'
)
typeof object === "object" &&
(object[NAME] === "AbortSignal" || object[NAME] === "EventTarget")
);

@@ -112,9 +110,16 @@ };

*/
export const isReadableStream = value => {
export const isReadableStream = (value) => {
return (
typeof value === 'object' &&
typeof value.getReader === 'function' &&
typeof value.cancel === 'function' &&
typeof value.tee === 'function'
typeof value === "object" &&
typeof value.getReader === "function" &&
typeof value.cancel === "function" &&
typeof value.tee === "function"
);
};
/**
*
* @param {any} value
* @returns {value is Iterable<unknown>}
*/
export const isIterable = (value) => value && Symbol.iterator in value;

Sorry, the diff of this file is too big to display