UNPKG

@whatwg-node/node-fetch

Version:

Fetch API implementation for Node

99 lines (98 loc) 3.88 kB
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'; } }