UNPKG

@whatwg-node/node-fetch

Version:

Fetch API implementation for Node

225 lines (224 loc) • 7.43 kB
import { Buffer } from 'node:buffer'; import { Readable } from 'node:stream'; import { fakePromise } from './utils.js'; function createController(desiredSize, readable) { let chunks = []; let _closed = false; let flushed = false; return { desiredSize, enqueue(chunk) { const buf = typeof chunk === 'string' ? Buffer.from(chunk) : chunk; if (!flushed) { chunks.push(buf); } else { readable.push(buf); } }, close() { if (chunks.length > 0) { this._flush(); } readable.push(null); _closed = true; }, error(error) { if (chunks.length > 0) { this._flush(); } readable.destroy(error); }, get _closed() { return _closed; }, _flush() { flushed = true; if (chunks.length > 0) { const concatenated = chunks.length > 1 ? Buffer.concat(chunks) : chunks[0]; readable.push(concatenated); chunks = []; } }, }; } function isNodeReadable(obj) { return obj?.read != null; } function isReadableStream(obj) { return obj?.getReader != null; } export class PonyfillReadableStream { readable; constructor(underlyingSource) { if (underlyingSource instanceof PonyfillReadableStream && underlyingSource.readable != null) { this.readable = underlyingSource.readable; } else if (isNodeReadable(underlyingSource)) { this.readable = underlyingSource; } else if (isReadableStream(underlyingSource)) { this.readable = Readable.fromWeb(underlyingSource); } else { let started = false; let ongoing = false; const readImpl = async (desiredSize) => { if (!started) { const controller = createController(desiredSize, this.readable); started = true; await underlyingSource?.start?.(controller); controller._flush(); if (controller._closed) { return; } } const controller = createController(desiredSize, this.readable); await underlyingSource?.pull?.(controller); controller._flush(); ongoing = false; }; this.readable = new Readable({ read(desiredSize) { if (ongoing) { return; } ongoing = true; return readImpl(desiredSize); }, destroy(err, callback) { if (underlyingSource?.cancel) { try { const res$ = underlyingSource.cancel(err); if (res$?.then) { return res$.then(() => { callback(null); }, err => { callback(err); }); } } catch (err) { callback(err); return; } } callback(null); }, }); } } cancel(reason) { this.readable.destroy(reason); return new Promise(resolve => this.readable.once('end', resolve)); } locked = false; getReader(_options) { const iterator = this.readable[Symbol.asyncIterator](); this.locked = true; return { read() { return iterator.next(); }, releaseLock: () => { if (iterator.return) { const retResult$ = iterator.return(); if (retResult$.then) { retResult$.then(() => { this.locked = false; }); return; } } this.locked = false; }, cancel: reason => { if (iterator.return) { const retResult$ = iterator.return(reason); if (retResult$.then) { return retResult$.then(() => { this.locked = false; }); } } this.locked = false; return fakePromise(); }, closed: new Promise((resolve, reject) => { this.readable.once('end', resolve); this.readable.once('error', reject); }), }; } [Symbol.asyncIterator]() { const iterator = this.readable[Symbol.asyncIterator](); return { [Symbol.asyncIterator]() { return this; }, next: () => iterator.next(), return: () => { if (!this.readable.destroyed) { this.readable.destroy(); } return iterator.return?.() || fakePromise({ done: true, value: undefined }); }, throw: (err) => { if (!this.readable.destroyed) { this.readable.destroy(err); } return iterator.throw?.(err) || fakePromise({ done: true, value: undefined }); }, }; } tee() { throw new Error('Not implemented'); } async pipeToWriter(writer) { try { for await (const chunk of this) { await writer.write(chunk); } await writer.close(); } catch (err) { await writer.abort(err); } } pipeTo(destination) { if (isPonyfillWritableStream(destination)) { return new Promise((resolve, reject) => { this.readable.pipe(destination.writable); destination.writable.once('finish', resolve); destination.writable.once('error', reject); }); } else { const writer = destination.getWriter(); return this.pipeToWriter(writer); } } pipeThrough({ writable, readable, }) { this.pipeTo(writable).catch(err => { this.readable.destroy(err); }); if (isPonyfillReadableStream(readable)) { readable.readable.once('error', err => this.readable.destroy(err)); readable.readable.once('finish', () => this.readable.push(null)); readable.readable.once('close', () => this.readable.push(null)); } return readable; } static [Symbol.hasInstance](instance) { return isReadableStream(instance); } static from(iterable) { return new PonyfillReadableStream(Readable.from(iterable)); } } function isPonyfillReadableStream(obj) { return obj?.readable != null; } function isPonyfillWritableStream(obj) { return obj?.writable != null; }