@whatwg-node/node-fetch
Version:
Fetch API implementation for Node
225 lines (224 loc) • 7.43 kB
JavaScript
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;
}