@whatwg-node/node-fetch
Version:
Fetch API implementation for Node
99 lines (98 loc) • 3.88 kB
JavaScript
import { once } from 'node:events';
function isHeadersInstance(obj) {
return obj?.forEach != null;
}
export function getHeadersObj(headers) {
if (headers == null || !isHeadersInstance(headers)) {
return headers;
}
// @ts-expect-error - `headersInit` is not a public property
if (headers.headersInit && !headers._map && !isHeadersInstance(headers.headersInit)) {
// @ts-expect-error - `headersInit` is not a public property
return headers.headersInit;
}
return Object.fromEntries(headers.entries());
}
export function defaultHeadersSerializer(headers, onContentLength) {
const headerArray = [];
headers.forEach((value, key) => {
if (onContentLength && key === 'content-length') {
onContentLength(value);
}
headerArray.push(`${key}: ${value}`);
});
return headerArray;
}
export { fakePromise } from '@whatwg-node/promise-helpers';
export function isArrayBufferView(obj) {
return obj != null && obj.buffer != null && obj.byteLength != null && obj.byteOffset != null;
}
export function isNodeReadable(obj) {
return obj != null && obj.pipe != null;
}
export function isIterable(value) {
return value?.[Symbol.iterator] != null;
}
export function shouldRedirect(status) {
return status === 301 || status === 302 || status === 303 || status === 307 || status === 308;
}
export function pipeThrough({ src, dest, signal, onError, }) {
if (onError) {
// listen for errors on the destination stream if necessary. if the readable
// stream (src) emits an error, the writable destination (dest) will be
// destroyed with that error (see below)
dest.once('error', onError);
}
src.once('error', (e) => {
// if the readable stream (src) emits an error during pipe, the writable
// destination (dest) is not closed automatically. that needs to be
// done manually. the readable stream is closed when error is emitted,
// so only the writable destination needs to be destroyed
dest.destroy(e);
});
dest.once('close', () => {
// if the writable destination (dest) is closed, the readable stream (src)
// is not closed automatically. that needs to be done manually
if (!src.destroyed) {
src.destroy();
}
});
if (signal) {
// this is faster than `import('node:signal').addAbortSignal(signal, src)`
const srcRef = new WeakRef(src);
const signalRef = new WeakRef(signal);
function cleanup() {
signalRef.deref()?.removeEventListener('abort', onAbort);
srcRef.deref()?.removeListener('end', cleanup);
srcRef.deref()?.removeListener('error', cleanup);
srcRef.deref()?.removeListener('close', cleanup);
}
function onAbort() {
srcRef.deref()?.destroy(new AbortError());
cleanup();
}
signal.addEventListener('abort', onAbort, { once: true });
// this is faster than `import('node:signal').finished(src, cleanup)`
src.once('end', cleanup);
src.once('error', cleanup);
src.once('close', cleanup);
}
src.pipe(dest, { end: true /* already default */ });
}
export function endStream(stream) {
// @ts-expect-error Avoid arguments adaptor trampoline https://v8.dev/blog/adaptor-frame
return stream.end(null, null, null);
}
export function safeWrite(chunk, stream) {
const result = stream.write(chunk);
if (!result) {
return once(stream, 'drain');
}
}
// https://github.com/nodejs/node/blob/f692878dec6354c0a82241f224906981861bc840/lib/internal/errors.js#L961-L973
class AbortError extends Error {
constructor(message = 'The operation was aborted', options = undefined) {
super(message, options);
this.name = 'AbortError';
}
}