nstdlib-nightly
Version:
Node.js standard library converted to runtime-agnostic ES modules.
1,818 lines (1,608 loc) • 98.2 kB
JavaScript
// Source: https://github.com/nodejs/node/blob/65eff1eb/lib/internal/webstreams/readablestream.js
import { AbortError, codes as __codes__ } from "nstdlib/lib/internal/errors";
import { DOMException } from "nstdlib/stub/binding/messaging";
import { isArrayBufferView, isDataView } from "nstdlib/lib/internal/util/types";
import {
createDeferredPromise,
customInspectSymbol as kInspect,
isArrayBufferDetached,
kEmptyObject,
kEnumerableProperty,
SideEffectFreeRegExpPrototypeSymbolReplace,
} from "nstdlib/lib/internal/util";
import {
validateAbortSignal,
validateBuffer,
validateObject,
kValidateObjectAllowObjects,
kValidateObjectAllowObjectsAndNull,
} from "nstdlib/lib/internal/validators";
import { MessageChannel } from "nstdlib/lib/internal/worker/io";
import {
kDeserialize,
kTransfer,
kTransferList,
markTransferMode,
} from "nstdlib/lib/internal/worker/js_transferable";
import { queueMicrotask } from "nstdlib/lib/internal/process/task_queues";
import {
kIsDisturbed,
kIsErrored,
kIsReadable,
kIsClosedPromise,
kControllerErrorFunction,
} from "nstdlib/lib/internal/streams/utils";
import { structuredClone } from "nstdlib/stub/binding/messaging";
import {
ArrayBufferViewGetBuffer,
ArrayBufferViewGetByteLength,
ArrayBufferViewGetByteOffset,
AsyncIterator,
cloneAsUint8Array,
copyArrayBuffer,
createPromiseCallback,
customInspect,
dequeueValue,
enqueueValueWithSize,
extractHighWaterMark,
extractSizeAlgorithm,
lazyTransfer,
isViewedArrayBufferDetached,
isBrandCheck,
resetQueue,
setPromiseHandled,
transferArrayBuffer,
nonOpCancel,
nonOpPull,
nonOpStart,
getIterator,
iteratorNext,
kType,
kState,
} from "nstdlib/lib/internal/webstreams/util";
import {
WritableStreamDefaultWriter,
isWritableStream,
isWritableStreamLocked,
isWritableStreamDefaultController,
isWritableStreamDefaultWriter,
writableStreamAbort,
writableStreamCloseQueuedOrInFlight,
writableStreamDefaultWriterCloseWithErrorPropagation,
writableStreamDefaultWriterRelease,
writableStreamDefaultWriterWrite,
} from "nstdlib/lib/internal/webstreams/writablestream";
import { Buffer } from "nstdlib/lib/buffer";
import * as assert from "nstdlib/lib/internal/assert";
import * as __hoisted_internal_events_abort_listener__ from "nstdlib/lib/internal/events/abort_listener";
const {
ERR_ILLEGAL_CONSTRUCTOR,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_ARG_VALUE,
ERR_INVALID_STATE,
ERR_INVALID_THIS,
ERR_OUT_OF_RANGE,
} = __codes__;
const kCancel = Symbol("kCancel");
const kClose = Symbol("kClose");
const kChunk = Symbol("kChunk");
const kError = Symbol("kError");
const kPull = Symbol("kPull");
const kRelease = Symbol("kRelease");
const kSkipThrow = Symbol("kSkipThrow");
let releasedError;
let releasingError;
let addAbortListener;
const userModuleRegExp = /^ {4}at (?:[^/\\(]+ \()(?!node:(.+):\d+:\d+\)$).*/gm;
function lazyReadableReleasedError() {
if (releasedError) {
return releasedError;
}
releasedError = new ERR_INVALID_STATE.TypeError("Reader released");
// Avoid V8 leak and remove userland stackstrace
releasedError.stack = SideEffectFreeRegExpPrototypeSymbolReplace(
userModuleRegExp,
releasedError.stack,
"",
);
return releasedError;
}
function lazyReadableReleasingError() {
if (releasingError) {
return releasingError;
}
releasingError = new ERR_INVALID_STATE.TypeError("Releasing reader");
// Avoid V8 leak and remove userland stackstrace
releasingError.stack = SideEffectFreeRegExpPrototypeSymbolReplace(
userModuleRegExp,
releasingError.stack,
"",
);
return releasingError;
}
const getNonWritablePropertyDescriptor = (value) => {
return {
__proto__: null,
configurable: true,
value,
};
};
/**
* @typedef {import('../abort_controller').AbortSignal} AbortSignal
* @typedef {import('./queuingstrategies').QueuingStrategy} QueuingStrategy
* @typedef {import('./queuingstrategies').QueuingStrategySize
* } QueuingStrategySize
* @typedef {import('./writablestream').WritableStream} WritableStream
*/
/**
* @typedef {ReadableStreamDefaultController | ReadableByteStreamController
* } ReadableStreamController
*/
/**
* @typedef {ReadableStreamDefaultReader | ReadableStreamBYOBReader
* } ReadableStreamReader
*/
/**
* @callback UnderlyingSourceStartCallback
* @param {ReadableStreamController} controller
* @returns { any | Promise<void> }
*/
/**
* @callback UnderlyingSourcePullCallback
* @param {ReadableStreamController} controller
* @returns { Promise<void> }
*/
/**
* @callback UnderlyingSourceCancelCallback
* @param {any} reason
* @returns { Promise<void> }
*/
/**
* @typedef {{
* readable: ReadableStream,
* writable: WritableStream,
* }} ReadableWritablePair
*/
/**
* @typedef {{
* preventClose? : boolean,
* preventAbort? : boolean,
* preventCancel? : boolean,
* signal? : AbortSignal,
* }} StreamPipeOptions
*/
/**
* @typedef {{
* start? : UnderlyingSourceStartCallback,
* pull? : UnderlyingSourcePullCallback,
* cancel? : UnderlyingSourceCancelCallback,
* type? : "bytes",
* autoAllocateChunkSize? : number
* }} UnderlyingSource
*/
class ReadableStream {
[kType] = "ReadableStream";
/**
* @param {UnderlyingSource} [source]
* @param {QueuingStrategy} [strategy]
*/
constructor(source = kEmptyObject, strategy = kEmptyObject) {
markTransferMode(this, false, true);
validateObject(source, "source", kValidateObjectAllowObjects);
validateObject(strategy, "strategy", kValidateObjectAllowObjectsAndNull);
this[kState] = createReadableStreamState();
this[kIsClosedPromise] = createDeferredPromise();
this[kControllerErrorFunction] = () => {};
// The spec requires handling of the strategy first
// here. Specifically, if getting the size and
// highWaterMark from the strategy fail, that has
// to trigger a throw before getting the details
// from the source. So be sure to keep these in
// this order.
const size = strategy?.size;
const highWaterMark = strategy?.highWaterMark;
const type = source.type;
if (`${type}` === "bytes") {
if (size !== undefined)
throw new ERR_INVALID_ARG_VALUE.RangeError("strategy.size", size);
setupReadableByteStreamControllerFromSource(
this,
source,
extractHighWaterMark(highWaterMark, 0),
);
} else {
if (type !== undefined)
throw new ERR_INVALID_ARG_VALUE("source.type", type);
setupReadableStreamDefaultControllerFromSource(
this,
source,
extractHighWaterMark(highWaterMark, 1),
extractSizeAlgorithm(size),
);
}
}
get [kIsDisturbed]() {
return this[kState].disturbed;
}
get [kIsErrored]() {
return this[kState].state === "errored";
}
get [kIsReadable]() {
return this[kState].state === "readable";
}
/**
* @readonly
* @type {boolean}
*/
get locked() {
if (!isReadableStream(this)) throw new ERR_INVALID_THIS("ReadableStream");
return isReadableStreamLocked(this);
}
static from(iterable) {
return readableStreamFromIterable(iterable);
}
/**
* @param {any} [reason]
* @returns { Promise<void> }
*/
cancel(reason = undefined) {
if (!isReadableStream(this))
return Promise.reject(new ERR_INVALID_THIS("ReadableStream"));
if (isReadableStreamLocked(this)) {
return Promise.reject(
new ERR_INVALID_STATE.TypeError("ReadableStream is locked"),
);
}
return readableStreamCancel(this, reason);
}
/**
* @param {{
* mode? : "byob"
* }} [options]
* @returns {ReadableStreamReader}
*/
getReader(options = kEmptyObject) {
if (!isReadableStream(this)) throw new ERR_INVALID_THIS("ReadableStream");
validateObject(options, "options", kValidateObjectAllowObjectsAndNull);
const mode = options?.mode;
if (mode === undefined)
// eslint-disable-next-line no-use-before-define
return new ReadableStreamDefaultReader(this);
if (`${mode}` !== "byob")
throw new ERR_INVALID_ARG_VALUE("options.mode", mode);
// eslint-disable-next-line no-use-before-define
return new ReadableStreamBYOBReader(this);
}
/**
* @param {ReadableWritablePair} transform
* @param {StreamPipeOptions} [options]
* @returns {ReadableStream}
*/
pipeThrough(transform, options = kEmptyObject) {
if (!isReadableStream(this)) throw new ERR_INVALID_THIS("ReadableStream");
const readable = transform?.readable;
if (!isReadableStream(readable)) {
throw new ERR_INVALID_ARG_TYPE(
"transform.readable",
"ReadableStream",
readable,
);
}
const writable = transform?.writable;
if (!isWritableStream(writable)) {
throw new ERR_INVALID_ARG_TYPE(
"transform.writable",
"WritableStream",
writable,
);
}
// The web platform tests require that these be handled one at a
// time and in a specific order. options can be null or undefined.
validateObject(options, "options", kValidateObjectAllowObjectsAndNull);
const preventAbort = options?.preventAbort;
const preventCancel = options?.preventCancel;
const preventClose = options?.preventClose;
const signal = options?.signal;
if (signal !== undefined) {
validateAbortSignal(signal, "options.signal");
}
if (isReadableStreamLocked(this))
throw new ERR_INVALID_STATE.TypeError("The ReadableStream is locked");
if (isWritableStreamLocked(writable))
throw new ERR_INVALID_STATE.TypeError("The WritableStream is locked");
const promise = readableStreamPipeTo(
this,
writable,
!!preventClose,
!!preventAbort,
!!preventCancel,
signal,
);
setPromiseHandled(promise);
return readable;
}
/**
* @param {WritableStream} destination
* @param {StreamPipeOptions} [options]
* @returns {Promise<void>}
*/
pipeTo(destination, options = kEmptyObject) {
try {
if (!isReadableStream(this)) throw new ERR_INVALID_THIS("ReadableStream");
if (!isWritableStream(destination)) {
throw new ERR_INVALID_ARG_TYPE(
"transform.writable",
"WritableStream",
destination,
);
}
validateObject(options, "options", kValidateObjectAllowObjectsAndNull);
const preventAbort = options?.preventAbort;
const preventCancel = options?.preventCancel;
const preventClose = options?.preventClose;
const signal = options?.signal;
if (signal !== undefined) {
validateAbortSignal(signal, "options.signal");
}
if (isReadableStreamLocked(this))
throw new ERR_INVALID_STATE.TypeError("The ReadableStream is locked");
if (isWritableStreamLocked(destination))
throw new ERR_INVALID_STATE.TypeError("The WritableStream is locked");
return readableStreamPipeTo(
this,
destination,
!!preventClose,
!!preventAbort,
!!preventCancel,
signal,
);
} catch (error) {
return Promise.reject(error);
}
}
/**
* @returns {ReadableStream[]}
*/
tee() {
if (!isReadableStream(this)) throw new ERR_INVALID_THIS("ReadableStream");
return readableStreamTee(this, false);
}
/**
* @param {{
* preventCancel? : boolean,
* }} [options]
* @returns {AsyncIterable}
*/
values(options = kEmptyObject) {
if (!isReadableStream(this)) throw new ERR_INVALID_THIS("ReadableStream");
validateObject(options, "options", kValidateObjectAllowObjectsAndNull);
const preventCancel = !!options?.preventCancel;
// eslint-disable-next-line no-use-before-define
const reader = new ReadableStreamDefaultReader(this);
// No __proto__ here to avoid the performance hit.
const state = {
done: false,
current: undefined,
};
let started = false;
// The nextSteps function is not an async function in order
// to make it more efficient. Because nextSteps explicitly
// creates a Promise and returns it in the common case,
// making it an async function just causes two additional
// unnecessary Promise allocations to occur, which just add
// cost.
function nextSteps() {
if (state.done) return Promise.resolve({ done: true, value: undefined });
if (reader[kState].stream === undefined) {
return Promise.reject(
new ERR_INVALID_STATE.TypeError(
"The reader is not bound to a ReadableStream",
),
);
}
const promise = createDeferredPromise();
// eslint-disable-next-line no-use-before-define
readableStreamDefaultReaderRead(
reader,
new ReadableStreamAsyncIteratorReadRequest(reader, state, promise),
);
return promise.promise;
}
async function returnSteps(value) {
if (state.done) return { done: true, value }; // eslint-disable-line node-core/avoid-prototype-pollution
state.done = true;
if (reader[kState].stream === undefined) {
throw new ERR_INVALID_STATE.TypeError(
"The reader is not bound to a ReadableStream",
);
}
assert(!reader[kState].readRequests.length);
if (!preventCancel) {
const result = readableStreamReaderGenericCancel(reader, value);
readableStreamReaderGenericRelease(reader);
await result;
return { done: true, value }; // eslint-disable-line node-core/avoid-prototype-pollution
}
readableStreamReaderGenericRelease(reader);
return { done: true, value }; // eslint-disable-line node-core/avoid-prototype-pollution
}
// TODO(@jasnell): Explore whether an async generator
// can be used here instead of a custom iterator object.
return Object.setPrototypeOf(
{
// Changing either of these functions (next or return)
// to async functions causes a failure in the streams
// Web Platform Tests that check for use of a modified
// Promise.prototype.then. Since the await keyword
// uses Promise.prototype.then, it is open to prototype
// pollution, which causes the test to fail. The other
// await uses here do not trigger that failure because
// the test that fails does not trigger those code paths.
next() {
// If this is the first read, delay by one microtask
// to ensure that the controller has had an opportunity
// to properly start and perform the initial pull.
// TODO(@jasnell): The spec doesn't call this out so
// need to investigate if it's a bug in our impl or
// the spec.
if (!started) {
state.current = Promise.resolve();
started = true;
}
state.current =
state.current !== undefined
? Promise.prototype.then.call(state.current, nextSteps, nextSteps)
: nextSteps();
return state.current;
},
return(error) {
started = true;
state.current =
state.current !== undefined
? Promise.prototype.then.call(
state.current,
() => returnSteps(error),
() => returnSteps(error),
)
: returnSteps(error);
return state.current;
},
[SymbolAsyncIterator]() {
return this;
},
},
AsyncIterator,
);
}
[kInspect](depth, options) {
return customInspect(depth, options, this[kType], {
locked: this.locked,
state: this[kState].state,
supportsBYOB:
// eslint-disable-next-line no-use-before-define
this[kState].controller instanceof ReadableByteStreamController,
});
}
[kTransfer]() {
if (!isReadableStream(this)) throw new ERR_INVALID_THIS("ReadableStream");
if (this.locked) {
this[kState].transfer.port1?.close();
this[kState].transfer.port1 = undefined;
this[kState].transfer.port2 = undefined;
throw new DOMException(
"Cannot transfer a locked ReadableStream",
"DataCloneError",
);
}
const { writable, promise } = lazyTransfer().newCrossRealmWritableSink(
this,
this[kState].transfer.port1,
);
this[kState].transfer.writable = writable;
this[kState].transfer.promise = promise;
return {
data: { port: this[kState].transfer.port2 },
deserializeInfo:
"internal/webstreams/readablestream:TransferredReadableStream",
};
}
[kTransferList]() {
const { port1, port2 } = new MessageChannel();
this[kState].transfer.port1 = port1;
this[kState].transfer.port2 = port2;
return [port2];
}
[kDeserialize]({ port }) {
const transfer = lazyTransfer();
setupReadableStreamDefaultControllerFromSource(
this,
// The MessagePort is set to be referenced when reading.
// After two MessagePorts are closed, there is a problem with
// lingering promise not being properly resolved.
// https://github.com/nodejs/node/issues/51486
new transfer.CrossRealmTransformReadableSource(port, true),
0,
() => 1,
);
}
}
Object.defineProperties(ReadableStream.prototype, {
[SymbolAsyncIterator]: {
__proto__: null,
configurable: true,
enumerable: false,
writable: true,
value: ReadableStream.prototype.values,
},
locked: kEnumerableProperty,
cancel: kEnumerableProperty,
getReader: kEnumerableProperty,
pipeThrough: kEnumerableProperty,
pipeTo: kEnumerableProperty,
tee: kEnumerableProperty,
values: kEnumerableProperty,
[Symbol.toStringTag]: getNonWritablePropertyDescriptor(ReadableStream.name),
});
Object.defineProperties(ReadableStream, {
from: kEnumerableProperty,
});
function InternalTransferredReadableStream() {
markTransferMode(this, false, true);
this[kType] = "ReadableStream";
this[kState] = createReadableStreamState();
this[kIsClosedPromise] = createDeferredPromise();
}
Object.setPrototypeOf(
InternalTransferredReadableStream.prototype,
ReadableStream.prototype,
);
Object.setPrototypeOf(InternalTransferredReadableStream, ReadableStream);
function TransferredReadableStream() {
const stream = new InternalTransferredReadableStream();
stream.constructor = ReadableStream;
return stream;
}
TransferredReadableStream.prototype[kDeserialize] = () => {};
class ReadableStreamBYOBRequest {
[kType] = "ReadableStreamBYOBRequest";
constructor(skipThrowSymbol = undefined) {
if (skipThrowSymbol !== kSkipThrow) {
throw new ERR_ILLEGAL_CONSTRUCTOR();
}
}
/**
* @readonly
* @type {ArrayBufferView}
*/
get view() {
if (!isReadableStreamBYOBRequest(this))
throw new ERR_INVALID_THIS("ReadableStreamBYOBRequest");
return this[kState].view;
}
/**
* @param {number} bytesWritten
*/
respond(bytesWritten) {
if (!isReadableStreamBYOBRequest(this))
throw new ERR_INVALID_THIS("ReadableStreamBYOBRequest");
const { view, controller } = this[kState];
if (controller === undefined) {
throw new ERR_INVALID_STATE.TypeError(
"This BYOB request has been invalidated",
);
}
const viewByteLength = Object.getOwnPropertyDescriptor(
ArrayBufferView.prototype,
"byteLength",
).get(view);
const viewBuffer = Object.getOwnPropertyDescriptor(
ArrayBufferView.prototype,
"buffer",
).get(view);
const viewBufferByteLength = Object.getOwnPropertyDescriptor(
ArrayBufferView.prototype,
"byteLength",
).get(viewBuffer);
if (isArrayBufferDetached(viewBuffer)) {
throw new ERR_INVALID_STATE.TypeError("Viewed ArrayBuffer is detached");
}
assert(viewByteLength > 0);
assert(viewBufferByteLength > 0);
readableByteStreamControllerRespond(controller, bytesWritten);
}
/**
* @param {ArrayBufferView} view
*/
respondWithNewView(view) {
if (!isReadableStreamBYOBRequest(this))
throw new ERR_INVALID_THIS("ReadableStreamBYOBRequest");
const { controller } = this[kState];
if (controller === undefined) {
throw new ERR_INVALID_STATE.TypeError(
"This BYOB request has been invalidated",
);
}
validateBuffer(view, "view");
if (isViewedArrayBufferDetached(view)) {
throw new ERR_INVALID_STATE.TypeError("Viewed ArrayBuffer is detached");
}
readableByteStreamControllerRespondWithNewView(controller, view);
}
[kInspect](depth, options) {
return customInspect(depth, options, this[kType], {
view: this.view,
controller: this[kState].controller,
});
}
}
Object.defineProperties(ReadableStreamBYOBRequest.prototype, {
view: kEnumerableProperty,
respond: kEnumerableProperty,
respondWithNewView: kEnumerableProperty,
[Symbol.toStringTag]: getNonWritablePropertyDescriptor(
ReadableStreamBYOBRequest.name,
),
});
function createReadableStreamBYOBRequest(controller, view) {
const stream = new ReadableStreamBYOBRequest(kSkipThrow);
stream[kState] = {
controller,
view,
};
return stream;
}
class ReadableStreamAsyncIteratorReadRequest {
constructor(reader, state, promise) {
this.reader = reader;
this.state = state;
this.promise = promise;
}
[kChunk](chunk) {
this.state.current = undefined;
this.promise.resolve({ value: chunk, done: false });
}
[kClose]() {
this.state.current = undefined;
this.state.done = true;
readableStreamReaderGenericRelease(this.reader);
this.promise.resolve({ done: true, value: undefined });
}
[kError](error) {
this.state.current = undefined;
this.state.done = true;
readableStreamReaderGenericRelease(this.reader);
this.promise.reject(error);
}
}
class DefaultReadRequest {
constructor() {
this[kState] = createDeferredPromise();
}
[kChunk](value) {
this[kState].resolve?.({ value, done: false });
}
[kClose]() {
this[kState].resolve?.({ value: undefined, done: true });
}
[kError](error) {
this[kState].reject?.(error);
}
get promise() {
return this[kState].promise;
}
}
class ReadIntoRequest {
constructor() {
this[kState] = createDeferredPromise();
}
[kChunk](value) {
this[kState].resolve?.({ value, done: false });
}
[kClose](value) {
this[kState].resolve?.({ value, done: true });
}
[kError](error) {
this[kState].reject?.(error);
}
get promise() {
return this[kState].promise;
}
}
class ReadableStreamDefaultReader {
[kType] = "ReadableStreamDefaultReader";
/**
* @param {ReadableStream} stream
*/
constructor(stream) {
if (!isReadableStream(stream))
throw new ERR_INVALID_ARG_TYPE("stream", "ReadableStream", stream);
this[kState] = {
readRequests: [],
stream: undefined,
close: {
promise: undefined,
resolve: undefined,
reject: undefined,
},
};
setupReadableStreamDefaultReader(this, stream);
}
/**
* @returns {Promise<{
* value : any,
* done : boolean
* }>}
*/
read() {
if (!isReadableStreamDefaultReader(this))
return Promise.reject(
new ERR_INVALID_THIS("ReadableStreamDefaultReader"),
);
if (this[kState].stream === undefined) {
return Promise.reject(
new ERR_INVALID_STATE.TypeError(
"The reader is not attached to a stream",
),
);
}
const readRequest = new DefaultReadRequest();
readableStreamDefaultReaderRead(this, readRequest);
return readRequest.promise;
}
releaseLock() {
if (!isReadableStreamDefaultReader(this))
throw new ERR_INVALID_THIS("ReadableStreamDefaultReader");
if (this[kState].stream === undefined) return;
readableStreamDefaultReaderRelease(this);
}
/**
* @readonly
* @type {Promise<void>}
*/
get closed() {
if (!isReadableStreamDefaultReader(this))
return Promise.reject(
new ERR_INVALID_THIS("ReadableStreamDefaultReader"),
);
return this[kState].close.promise;
}
/**
* @param {any} [reason]
* @returns {Promise<void>}
*/
cancel(reason = undefined) {
if (!isReadableStreamDefaultReader(this))
return Promise.reject(
new ERR_INVALID_THIS("ReadableStreamDefaultReader"),
);
if (this[kState].stream === undefined) {
return Promise.reject(
new ERR_INVALID_STATE.TypeError(
"The reader is not attached to a stream",
),
);
}
return readableStreamReaderGenericCancel(this, reason);
}
[kInspect](depth, options) {
return customInspect(depth, options, this[kType], {
stream: this[kState].stream,
readRequests: this[kState].readRequests.length,
close: this[kState].close.promise,
});
}
}
Object.defineProperties(ReadableStreamDefaultReader.prototype, {
closed: kEnumerableProperty,
read: kEnumerableProperty,
releaseLock: kEnumerableProperty,
cancel: kEnumerableProperty,
[Symbol.toStringTag]: getNonWritablePropertyDescriptor(
ReadableStreamDefaultReader.name,
),
});
class ReadableStreamBYOBReader {
[kType] = "ReadableStreamBYOBReader";
/**
* @param {ReadableStream} stream
*/
constructor(stream) {
if (!isReadableStream(stream))
throw new ERR_INVALID_ARG_TYPE("stream", "ReadableStream", stream);
this[kState] = {
stream: undefined,
requestIntoRequests: [],
close: {
promise: undefined,
resolve: undefined,
reject: undefined,
},
};
setupReadableStreamBYOBReader(this, stream);
}
/**
* @param {ArrayBufferView} view
* @param {{
* min? : number
* }} [options]
* @returns {Promise<{
* value : ArrayBufferView,
* done : boolean,
* }>}
*/
async read(view, options = kEmptyObject) {
if (!isReadableStreamBYOBReader(this))
throw new ERR_INVALID_THIS("ReadableStreamBYOBReader");
if (!isArrayBufferView(view)) {
throw new ERR_INVALID_ARG_TYPE(
"view",
["Buffer", "TypedArray", "DataView"],
view,
);
}
validateObject(options, "options", kValidateObjectAllowObjectsAndNull);
const viewByteLength = Object.getOwnPropertyDescriptor(
ArrayBufferView.prototype,
"byteLength",
).get(view);
const viewBuffer = Object.getOwnPropertyDescriptor(
ArrayBufferView.prototype,
"buffer",
).get(view);
const viewBufferByteLength = Object.getOwnPropertyDescriptor(
ArrayBufferView.prototype,
"byteLength",
).get(viewBuffer);
if (viewByteLength === 0 || viewBufferByteLength === 0) {
throw new ERR_INVALID_STATE.TypeError(
"View or Viewed ArrayBuffer is zero-length or detached",
);
}
// Supposed to assert here that the view's buffer is not
// detached, but there's no API available to use to check that.
const min = options?.min ?? 1;
if (typeof min !== "number")
throw new ERR_INVALID_ARG_TYPE("options.min", "number", min);
if (!Number.isInteger(min))
throw new ERR_INVALID_ARG_VALUE("options.min", min, "must be an integer");
if (min <= 0)
throw new ERR_INVALID_ARG_VALUE(
"options.min",
min,
"must be greater than 0",
);
if (!isDataView(view)) {
if (min > TypedArrayPrototypeGetLength(view)) {
throw new ERR_OUT_OF_RANGE("options.min", "<= view.length", min);
}
} else if (min > viewByteLength) {
throw new ERR_OUT_OF_RANGE("options.min", "<= view.byteLength", min);
}
if (this[kState].stream === undefined) {
throw new ERR_INVALID_STATE.TypeError(
"The reader is not attached to a stream",
);
}
const readIntoRequest = new ReadIntoRequest();
readableStreamBYOBReaderRead(this, view, min, readIntoRequest);
return readIntoRequest.promise;
}
releaseLock() {
if (!isReadableStreamBYOBReader(this))
throw new ERR_INVALID_THIS("ReadableStreamBYOBReader");
if (this[kState].stream === undefined) return;
readableStreamBYOBReaderRelease(this);
}
/**
* @readonly
* @type {Promise<void>}
*/
get closed() {
if (!isReadableStreamBYOBReader(this))
return Promise.reject(new ERR_INVALID_THIS("ReadableStreamBYOBReader"));
return this[kState].close.promise;
}
/**
* @param {any} [reason]
* @returns {Promise<void>}
*/
cancel(reason = undefined) {
if (!isReadableStreamBYOBReader(this))
return Promise.reject(new ERR_INVALID_THIS("ReadableStreamBYOBReader"));
if (this[kState].stream === undefined) {
return Promise.reject(
new ERR_INVALID_STATE.TypeError(
"The reader is not attached to a stream",
),
);
}
return readableStreamReaderGenericCancel(this, reason);
}
[kInspect](depth, options) {
return customInspect(depth, options, this[kType], {
stream: this[kState].stream,
requestIntoRequests: this[kState].requestIntoRequests.length,
close: this[kState].close.promise,
});
}
}
Object.defineProperties(ReadableStreamBYOBReader.prototype, {
closed: kEnumerableProperty,
read: kEnumerableProperty,
releaseLock: kEnumerableProperty,
cancel: kEnumerableProperty,
[Symbol.toStringTag]: getNonWritablePropertyDescriptor(
ReadableStreamBYOBReader.name,
),
});
class ReadableStreamDefaultController {
[kType] = "ReadableStreamDefaultController";
[kState] = {};
constructor(skipThrowSymbol = undefined) {
if (skipThrowSymbol !== kSkipThrow) {
throw new ERR_ILLEGAL_CONSTRUCTOR();
}
}
/**
* @readonly
* @type {number}
*/
get desiredSize() {
return readableStreamDefaultControllerGetDesiredSize(this);
}
close() {
if (!readableStreamDefaultControllerCanCloseOrEnqueue(this))
throw new ERR_INVALID_STATE.TypeError("Controller is already closed");
readableStreamDefaultControllerClose(this);
}
/**
* @param {any} [chunk]
*/
enqueue(chunk = undefined) {
if (!readableStreamDefaultControllerCanCloseOrEnqueue(this))
throw new ERR_INVALID_STATE.TypeError("Controller is already closed");
readableStreamDefaultControllerEnqueue(this, chunk);
}
/**
* @param {any} [error]
*/
error(error = undefined) {
readableStreamDefaultControllerError(this, error);
}
[kCancel](reason) {
return readableStreamDefaultControllerCancelSteps(this, reason);
}
[kPull](readRequest) {
readableStreamDefaultControllerPullSteps(this, readRequest);
}
[kRelease]() {}
[kInspect](depth, options) {
return customInspect(depth, options, this[kType], {});
}
}
Object.defineProperties(ReadableStreamDefaultController.prototype, {
desiredSize: kEnumerableProperty,
close: kEnumerableProperty,
enqueue: kEnumerableProperty,
error: kEnumerableProperty,
[Symbol.toStringTag]: getNonWritablePropertyDescriptor(
ReadableStreamDefaultController.name,
),
});
class ReadableByteStreamController {
[kType] = "ReadableByteStreamController";
[kState] = {};
constructor(skipThrowSymbol = undefined) {
if (skipThrowSymbol !== kSkipThrow) {
throw new ERR_ILLEGAL_CONSTRUCTOR();
}
}
/**
* @readonly
* @type {ReadableStreamBYOBRequest}
*/
get byobRequest() {
if (!isReadableByteStreamController(this))
throw new ERR_INVALID_THIS("ReadableByteStreamController");
if (
this[kState].byobRequest === null &&
this[kState].pendingPullIntos.length
) {
const { buffer, byteOffset, bytesFilled, byteLength } =
this[kState].pendingPullIntos[0];
const view = new Uint8Array(
buffer,
byteOffset + bytesFilled,
byteLength - bytesFilled,
);
this[kState].byobRequest = createReadableStreamBYOBRequest(this, view);
}
return this[kState].byobRequest;
}
/**
* @readonly
* @type {number}
*/
get desiredSize() {
if (!isReadableByteStreamController(this))
throw new ERR_INVALID_THIS("ReadableByteStreamController");
return readableByteStreamControllerGetDesiredSize(this);
}
close() {
if (!isReadableByteStreamController(this))
throw new ERR_INVALID_THIS("ReadableByteStreamController");
if (this[kState].closeRequested)
throw new ERR_INVALID_STATE.TypeError("Controller is already closed");
if (this[kState].stream[kState].state !== "readable")
throw new ERR_INVALID_STATE.TypeError("ReadableStream is already closed");
readableByteStreamControllerClose(this);
}
/**
* @param {ArrayBufferView} chunk
*/
enqueue(chunk) {
if (!isReadableByteStreamController(this))
throw new ERR_INVALID_THIS("ReadableByteStreamController");
validateBuffer(chunk);
const chunkByteLength = Object.getOwnPropertyDescriptor(
ArrayBufferView.prototype,
"byteLength",
).get(chunk);
const chunkBuffer = Object.getOwnPropertyDescriptor(
ArrayBufferView.prototype,
"buffer",
).get(chunk);
const chunkBufferByteLength = Object.getOwnPropertyDescriptor(
ArrayBufferView.prototype,
"byteLength",
).get(chunkBuffer);
if (chunkByteLength === 0 || chunkBufferByteLength === 0) {
throw new ERR_INVALID_STATE.TypeError(
"chunk ArrayBuffer is zero-length or detached",
);
}
if (this[kState].closeRequested)
throw new ERR_INVALID_STATE.TypeError("Controller is already closed");
if (this[kState].stream[kState].state !== "readable")
throw new ERR_INVALID_STATE.TypeError("ReadableStream is already closed");
readableByteStreamControllerEnqueue(this, chunk);
}
/**
* @param {any} [error]
*/
error(error = undefined) {
if (!isReadableByteStreamController(this))
throw new ERR_INVALID_THIS("ReadableByteStreamController");
readableByteStreamControllerError(this, error);
}
[kCancel](reason) {
return readableByteStreamControllerCancelSteps(this, reason);
}
[kPull](readRequest) {
readableByteStreamControllerPullSteps(this, readRequest);
}
[kRelease]() {
const { pendingPullIntos } = this[kState];
if (pendingPullIntos.length > 0) {
const firstPendingPullInto = pendingPullIntos[0];
firstPendingPullInto.type = "none";
this[kState].pendingPullIntos = [firstPendingPullInto];
}
}
[kInspect](depth, options) {
return customInspect(depth, options, this[kType], {});
}
}
Object.defineProperties(ReadableByteStreamController.prototype, {
byobRequest: kEnumerableProperty,
desiredSize: kEnumerableProperty,
close: kEnumerableProperty,
enqueue: kEnumerableProperty,
error: kEnumerableProperty,
[Symbol.toStringTag]: getNonWritablePropertyDescriptor(
ReadableByteStreamController.name,
),
});
function InternalReadableStream(start, pull, cancel, highWaterMark, size) {
markTransferMode(this, false, true);
this[kType] = "ReadableStream";
this[kState] = createReadableStreamState();
this[kIsClosedPromise] = createDeferredPromise();
const controller = new ReadableStreamDefaultController(kSkipThrow);
setupReadableStreamDefaultController(
this,
controller,
start,
pull,
cancel,
highWaterMark,
size,
);
}
Object.setPrototypeOf(
InternalReadableStream.prototype,
ReadableStream.prototype,
);
Object.setPrototypeOf(InternalReadableStream, ReadableStream);
function createReadableStream(
start,
pull,
cancel,
highWaterMark = 1,
size = () => 1,
) {
const stream = new InternalReadableStream(
start,
pull,
cancel,
highWaterMark,
size,
);
// For spec compliance the InternalReadableStream must be a ReadableStream
stream.constructor = ReadableStream;
return stream;
}
function InternalReadableByteStream(start, pull, cancel) {
markTransferMode(this, false, true);
this[kType] = "ReadableStream";
this[kState] = createReadableStreamState();
this[kIsClosedPromise] = createDeferredPromise();
const controller = new ReadableByteStreamController(kSkipThrow);
setupReadableByteStreamController(
this,
controller,
start,
pull,
cancel,
0,
undefined,
);
}
Object.setPrototypeOf(
InternalReadableByteStream.prototype,
ReadableStream.prototype,
);
Object.setPrototypeOf(InternalReadableByteStream, ReadableStream);
function createReadableByteStream(start, pull, cancel) {
const stream = new InternalReadableByteStream(start, pull, cancel);
// For spec compliance the InternalReadableByteStream must be a ReadableStream
stream.constructor = ReadableStream;
return stream;
}
const isReadableStream = isBrandCheck("ReadableStream");
const isReadableByteStreamController = isBrandCheck(
"ReadableByteStreamController",
);
const isReadableStreamBYOBRequest = isBrandCheck("ReadableStreamBYOBRequest");
const isReadableStreamDefaultReader = isBrandCheck(
"ReadableStreamDefaultReader",
);
const isReadableStreamBYOBReader = isBrandCheck("ReadableStreamBYOBReader");
// ---- ReadableStream Implementation
function createReadableStreamState() {
return {
__proto__: null,
disturbed: false,
reader: undefined,
state: "readable",
storedError: undefined,
transfer: {
__proto__: null,
writable: undefined,
port1: undefined,
port2: undefined,
promise: undefined,
},
};
}
function readableStreamFromIterable(iterable) {
let stream;
const iteratorRecord = getIterator(iterable, "async");
const startAlgorithm = nonOpStart;
async function pullAlgorithm() {
const nextResult = iteratorNext(iteratorRecord);
const nextPromise = Promise.resolve(nextResult);
return Promise.prototype.then.call(nextPromise, (iterResult) => {
if (typeof iterResult !== "object" || iterResult === null) {
throw new ERR_INVALID_STATE.TypeError(
"The promise returned by the iterator.next() method must fulfill with an object",
);
}
if (iterResult.done) {
readableStreamDefaultControllerClose(stream[kState].controller);
} else {
readableStreamDefaultControllerEnqueue(
stream[kState].controller,
iterResult.value,
);
}
});
}
async function cancelAlgorithm(reason) {
const iterator = iteratorRecord.iterator;
const returnMethod = iterator.return;
if (returnMethod === undefined) {
return Promise.resolve();
}
const returnResult = Function.prototype.call.call(
returnMethod,
iterator,
reason,
);
const returnPromise = Promise.resolve(returnResult);
return Promise.prototype.then.call(returnPromise, (iterResult) => {
if (typeof iterResult !== "object" || iterResult === null) {
throw new ERR_INVALID_STATE.TypeError(
"The promise returned by the iterator.return() method must fulfill with an object",
);
}
return undefined;
});
}
stream = createReadableStream(
startAlgorithm,
pullAlgorithm,
cancelAlgorithm,
0,
);
return stream;
}
function readableStreamPipeTo(
source,
dest,
preventClose,
preventAbort,
preventCancel,
signal,
) {
let reader;
let writer;
let disposable;
// Both of these can throw synchronously. We want to capture
// the error and return a rejected promise instead.
try {
reader = new ReadableStreamDefaultReader(source);
writer = new WritableStreamDefaultWriter(dest);
} catch (error) {
return Promise.reject(error);
}
source[kState].disturbed = true;
let shuttingDown = false;
if (signal !== undefined) {
try {
validateAbortSignal(signal, "options.signal");
} catch (error) {
return Promise.reject(error);
}
}
const promise = createDeferredPromise();
const state = {
currentWrite: Promise.resolve(),
};
// The error here can be undefined. The rejected arg
// tells us that the promise must be rejected even
// when error is undefine.
function finalize(rejected, error) {
writableStreamDefaultWriterRelease(writer);
readableStreamReaderGenericRelease(reader);
if (signal !== undefined) disposable?.[Symbol.for("nodejs.dispose")]();
if (rejected) promise.reject(error);
else promise.resolve();
}
async function waitForCurrentWrite() {
const write = state.currentWrite;
await write;
if (write !== state.currentWrite) await waitForCurrentWrite();
}
function shutdownWithAnAction(action, rejected, originalError) {
if (shuttingDown) return;
shuttingDown = true;
if (
dest[kState].state === "writable" &&
!writableStreamCloseQueuedOrInFlight(dest)
) {
Promise.prototype.then.call(waitForCurrentWrite(), complete, (error) =>
finalize(true, error),
);
return;
}
complete();
function complete() {
Promise.prototype.then.call(
action(),
() => finalize(rejected, originalError),
(error) => finalize(true, error),
);
}
}
function shutdown(rejected, error) {
if (shuttingDown) return;
shuttingDown = true;
if (
dest[kState].state === "writable" &&
!writableStreamCloseQueuedOrInFlight(dest)
) {
Promise.prototype.then.call(
waitForCurrentWrite(),
() => finalize(rejected, error),
(error) => finalize(true, error),
);
return;
}
finalize(rejected, error);
}
function abortAlgorithm() {
let error;
if (signal.reason instanceof AbortError) {
// Cannot use the AbortError class here. It must be a DOMException.
error = new DOMException(signal.reason.message, "AbortError");
} else {
error = signal.reason;
}
const actions = [];
if (!preventAbort) {
Array.prototype.push.call(actions, () => {
if (dest[kState].state === "writable")
return writableStreamAbort(dest, error);
return Promise.resolve();
});
}
if (!preventCancel) {
Array.prototype.push.call(actions, () => {
if (source[kState].state === "readable")
return readableStreamCancel(source, error);
return Promise.resolve();
});
}
shutdownWithAnAction(
() => Promise.all(actions, (action) => action()),
true,
error,
);
}
function watchErrored(stream, promise, action) {
if (stream[kState].state === "errored") action(stream[kState].storedError);
else Promise.prototype.then.call(promise, undefined, action);
}
function watchClosed(stream, promise, action) {
if (stream[kState].state === "closed") action();
else Promise.prototype.then.call(promise, action, () => {});
}
async function step() {
if (shuttingDown) return true;
await writer[kState].ready.promise;
const promise = createDeferredPromise();
// eslint-disable-next-line no-use-before-define
readableStreamDefaultReaderRead(
reader,
new PipeToReadableStreamReadRequest(writer, state, promise),
);
return promise.promise;
}
async function run() {
// Run until step resolves as true
while (!(await step()));
}
if (signal !== undefined) {
if (signal.aborted) {
abortAlgorithm();
return promise.promise;
}
addAbortListener ??=
__hoisted_internal_events_abort_listener__.addAbortListener;
disposable = addAbortListener(signal, abortAlgorithm);
}
setPromiseHandled(run());
watchErrored(source, reader[kState].close.promise, (error) => {
if (!preventAbort) {
return shutdownWithAnAction(
() => writableStreamAbort(dest, error),
true,
error,
);
}
shutdown(true, error);
});
watchErrored(dest, writer[kState].close.promise, (error) => {
if (!preventCancel) {
return shutdownWithAnAction(
() => readableStreamCancel(source, error),
true,
error,
);
}
shutdown(true, error);
});
watchClosed(source, reader[kState].close.promise, () => {
if (!preventClose) {
return shutdownWithAnAction(() =>
writableStreamDefaultWriterCloseWithErrorPropagation(writer),
);
}
shutdown();
});
if (
writableStreamCloseQueuedOrInFlight(dest) ||
dest[kState].state === "closed"
) {
const error = new ERR_INVALID_STATE.TypeError(
"Destination WritableStream is closed",
);
if (!preventCancel) {
shutdownWithAnAction(
() => readableStreamCancel(source, error),
true,
error,
);
} else {
shutdown(true, error);
}
}
return promise.promise;
}
class PipeToReadableStreamReadRequest {
constructor(writer, state, promise) {
this.writer = writer;
this.state = state;
this.promise = promise;
}
[kChunk](chunk) {
this.state.currentWrite = writableStreamDefaultWriterWrite(
this.writer,
chunk,
);
setPromiseHandled(this.state.currentWrite);
this.promise.resolve(false);
}
[kClose]() {
this.promise.resolve(true);
}
[kError](error) {
this.promise.reject(error);
}
}
function readableStreamTee(stream, cloneForBranch2) {
if (isReadableByteStreamController(stream[kState].controller)) {
return readableByteStreamTee(stream);
}
return readableStreamDefaultTee(stream, cloneForBranch2);
}
function readableStreamDefaultTee(stream, cloneForBranch2) {
const reader = new ReadableStreamDefaultReader(stream);
let reading = false;
let canceled1 = false;
let canceled2 = false;
let reason1;
let reason2;
let branch1;
let branch2;
const cancelPromise = createDeferredPromise();
async function pullAlgorithm() {
if (reading) return;
reading = true;
const readRequest = {
[kChunk](value) {
queueMicrotask(() => {
reading = false;
const value1 = value;
let value2 = value;
if (!canceled2 && cloneForBranch2) {
value2 = structuredClone(value2);
}
if (!canceled1) {
readableStreamDefaultControllerEnqueue(
branch1[kState].controller,
value1,
);
}
if (!canceled2) {
readableStreamDefaultControllerEnqueue(
branch2[kState].controller,
value2,
);
}
});
},
[kClose]() {
// The `process.nextTick()` is not part of the spec.
// This approach was needed to avoid a race condition working with esm
// Further information, see: https://github.com/nodejs/node/issues/39758
process.nextTick(() => {
reading = false;
if (!canceled1)
readableStreamDefaultControllerClose(branch1[kState].controller);
if (!canceled2)
readableStreamDefaultControllerClose(branch2[kState].controller);
if (!canceled1 || !canceled2) cancelPromise.resolve();
});
},
[kError]() {
reading = false;
},
};
readableStreamDefaultReaderRead(reader, readRequest);
}
function cancel1Algorithm(reason) {
canceled1 = true;
reason1 = reason;
if (canceled2) {
const compositeReason = [reason1, reason2];
cancelPromise.resolve(readableStreamCancel(stream, compositeReason));
}
return cancelPromise.promise;
}
function cancel2Algorithm(reason) {
canceled2 = true;
reason2 = reason;
if (canceled1) {
const compositeReason = [reason1, reason2];
cancelPromise.resolve(readableStreamCancel(stream, compositeReason));
}
return cancelPromise.promise;
}
branch1 = createReadableStream(nonOpStart, pullAlgorithm, cancel1Algorithm);
branch2 = createReadableStream(nonOpStart, pullAlgorithm, cancel2Algorithm);
Promise.prototype.then.call(
reader[kState].close.promise,
undefined,
(error) => {
readableStreamDefaultControllerError(branch1[kState].controller, error);
readableStreamDefaultControllerError(branch2[kState].controller, error);
if (!canceled1 || !canceled2) cancelPromise.resolve();
},
);
return [branch1, branch2];
}
function readableByteStreamTee(stream) {
assert(isReadableStream(stream));
assert(isReadableByteStreamController(stream[kState].controller));
let reader = new ReadableStreamDefaultReader(stream);
let reading = false;
let readAgainForBranch1 = false;
let readAgainForBranch2 = false;
let canceled1 = false;
let canceled2 = false;
let reason1;
let reason2;
let branch1;
let branch2;
const cancelDeferred = createDeferredPromise();
function forwardReaderError(thisReader) {
Promise.prototype.then.call(
thisReader[kState].close.promise,
undefined,
(error) => {
if (thisReader !== reader) {
return;
}
readableStreamDefaultControllerError(branch1[kState].controller, error);
readableStreamDefaultControllerError(branch2[kState].controller, error);
if (!canceled1 || !canceled2) {
cancelDeferred.resolve();
}
},
);
}
function pullWithDefaultReader() {
if (isReadableStreamBYOBReader(reader)) {
readableStreamBYOBReaderRelease(reader);
reader = new ReadableStreamDefaultReader(stream);
forwardReaderError(reader);
}
const readRequest = {
[kChunk](chunk) {
queueMicrotask(() => {
readAgainForBranch1 = false;
readAgainForBranch2 = false;
const chunk1 = chunk;
let chunk2 = chunk;
if (!canceled1 && !canceled2) {
try {
chunk2 = cloneAsUint8Array(chunk);
} catch (error) {
readableByteStreamControllerError(
branch1[kState].controller,
error,
);
readableByteStreamControllerError(
branch2[kState]