node/lib/internal/webstreams/readablestream.js
atlowChemi a9528e87b9 stream: use internal addAbortListener
PR-URL: https://github.com/nodejs/node/pull/52081
Refs: https://github.com/nodejs/node/pull/48596
Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
2024-03-16 09:08:59 +00:00

3396 lines
93 KiB
JavaScript

'use strict';
const {
ArrayBuffer,
ArrayBufferPrototypeGetByteLength,
ArrayBufferPrototypeSlice,
ArrayPrototypePush,
ArrayPrototypeShift,
DataView,
FunctionPrototypeBind,
FunctionPrototypeCall,
MathMin,
NumberIsInteger,
ObjectDefineProperties,
ObjectSetPrototypeOf,
Promise,
PromisePrototypeThen,
PromiseResolve,
PromiseReject,
SafePromiseAll,
Symbol,
SymbolAsyncIterator,
SymbolDispose,
SymbolToStringTag,
TypedArrayPrototypeGetLength,
Uint8Array,
} = primordials;
const {
AbortError,
codes: {
ERR_ILLEGAL_CONSTRUCTOR,
ERR_INVALID_ARG_VALUE,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_STATE,
ERR_INVALID_THIS,
ERR_OUT_OF_RANGE,
},
} = require('internal/errors');
const {
DOMException,
} = internalBinding('messaging');
const {
isArrayBufferView,
isDataView,
} = require('internal/util/types');
const {
createDeferredPromise,
customInspectSymbol: kInspect,
isArrayBufferDetached,
kEmptyObject,
kEnumerableProperty,
SideEffectFreeRegExpPrototypeSymbolReplace,
} = require('internal/util');
const {
validateAbortSignal,
validateBuffer,
validateObject,
kValidateObjectAllowObjects,
kValidateObjectAllowObjectsAndNull,
} = require('internal/validators');
const {
MessageChannel,
} = require('internal/worker/io');
const {
kDeserialize,
kTransfer,
kTransferList,
markTransferMode,
} = require('internal/worker/js_transferable');
const {
queueMicrotask,
} = require('internal/process/task_queues');
const {
kIsDisturbed,
kIsErrored,
kIsReadable,
kIsClosedPromise,
kControllerErrorFunction,
} = require('internal/streams/utils');
const { structuredClone } = internalBinding('messaging');
const {
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,
} = require('internal/webstreams/util');
const {
WritableStreamDefaultWriter,
isWritableStream,
isWritableStreamLocked,
isWritableStreamDefaultController,
isWritableStreamDefaultWriter,
writableStreamAbort,
writableStreamCloseQueuedOrInFlight,
writableStreamDefaultWriterCloseWithErrorPropagation,
writableStreamDefaultWriterRelease,
writableStreamDefaultWriterWrite,
} = require('internal/webstreams/writablestream');
const { Buffer } = require('buffer');
const assert = require('internal/assert');
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 PromiseReject(new ERR_INVALID_THIS('ReadableStream'));
if (isReadableStreamLocked(this)) {
return PromiseReject(
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 PromiseReject(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 PromiseResolve({ done: true, value: undefined });
if (reader[kState].stream === undefined) {
return PromiseReject(
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 ObjectSetPrototypeOf({
// 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 = PromiseResolve();
started = true;
}
state.current = state.current !== undefined ?
PromisePrototypeThen(state.current, nextSteps, nextSteps) :
nextSteps();
return state.current;
},
return(error) {
return state.current ?
PromisePrototypeThen(
state.current,
() => returnSteps(error),
() => returnSteps(error)) :
returnSteps(error);
},
[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);
}
}
ObjectDefineProperties(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,
[SymbolToStringTag]: getNonWritablePropertyDescriptor(ReadableStream.name),
});
ObjectDefineProperties(ReadableStream, {
from: kEnumerableProperty,
});
function InternalTransferredReadableStream() {
markTransferMode(this, false, true);
this[kType] = 'ReadableStream';
this[kState] = createReadableStreamState();
this[kIsClosedPromise] = createDeferredPromise();
}
ObjectSetPrototypeOf(InternalTransferredReadableStream.prototype, ReadableStream.prototype);
ObjectSetPrototypeOf(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 = ArrayBufferViewGetByteLength(view);
const viewBuffer = ArrayBufferViewGetBuffer(view);
const viewBufferByteLength = ArrayBufferPrototypeGetByteLength(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,
});
}
}
ObjectDefineProperties(ReadableStreamBYOBRequest.prototype, {
view: kEnumerableProperty,
respond: kEnumerableProperty,
respondWithNewView: kEnumerableProperty,
[SymbolToStringTag]: 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 PromiseReject(new ERR_INVALID_THIS('ReadableStreamDefaultReader'));
if (this[kState].stream === undefined) {
return PromiseReject(
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 PromiseReject(new ERR_INVALID_THIS('ReadableStreamDefaultReader'));
return this[kState].close.promise;
}
/**
* @param {any} [reason]
* @returns {Promise<void>}
*/
cancel(reason = undefined) {
if (!isReadableStreamDefaultReader(this))
return PromiseReject(new ERR_INVALID_THIS('ReadableStreamDefaultReader'));
if (this[kState].stream === undefined) {
return PromiseReject(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,
});
}
}
ObjectDefineProperties(ReadableStreamDefaultReader.prototype, {
closed: kEnumerableProperty,
read: kEnumerableProperty,
releaseLock: kEnumerableProperty,
cancel: kEnumerableProperty,
[SymbolToStringTag]: 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 = ArrayBufferViewGetByteLength(view);
const viewBuffer = ArrayBufferViewGetBuffer(view);
const viewBufferByteLength = ArrayBufferPrototypeGetByteLength(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 (!NumberIsInteger(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 PromiseReject(new ERR_INVALID_THIS('ReadableStreamBYOBReader'));
return this[kState].close.promise;
}
/**
* @param {any} [reason]
* @returns {Promise<void>}
*/
cancel(reason = undefined) {
if (!isReadableStreamBYOBReader(this))
return PromiseReject(new ERR_INVALID_THIS('ReadableStreamBYOBReader'));
if (this[kState].stream === undefined) {
return PromiseReject(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,
});
}
}
ObjectDefineProperties(ReadableStreamBYOBReader.prototype, {
closed: kEnumerableProperty,
read: kEnumerableProperty,
releaseLock: kEnumerableProperty,
cancel: kEnumerableProperty,
[SymbolToStringTag]: 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], { });
}
}
ObjectDefineProperties(ReadableStreamDefaultController.prototype, {
desiredSize: kEnumerableProperty,
close: kEnumerableProperty,
enqueue: kEnumerableProperty,
error: kEnumerableProperty,
[SymbolToStringTag]: 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 = ArrayBufferViewGetByteLength(chunk);
const chunkBuffer = ArrayBufferViewGetBuffer(chunk);
const chunkBufferByteLength = ArrayBufferPrototypeGetByteLength(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], { });
}
}
ObjectDefineProperties(ReadableByteStreamController.prototype, {
byobRequest: kEnumerableProperty,
desiredSize: kEnumerableProperty,
close: kEnumerableProperty,
enqueue: kEnumerableProperty,
error: kEnumerableProperty,
[SymbolToStringTag]: 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);
}
ObjectSetPrototypeOf(InternalReadableStream.prototype, ReadableStream.prototype);
ObjectSetPrototypeOf(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);
}
ObjectSetPrototypeOf(InternalReadableByteStream.prototype, ReadableStream.prototype);
ObjectSetPrototypeOf(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 = PromiseResolve(nextResult);
return PromisePrototypeThen(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 PromiseResolve();
}
const returnResult = FunctionPrototypeCall(returnMethod, iterator, reason);
const returnPromise = PromiseResolve(returnResult);
return PromisePrototypeThen(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 PromiseReject(error);
}
source[kState].disturbed = true;
let shuttingDown = false;
if (signal !== undefined) {
try {
validateAbortSignal(signal, 'options.signal');
} catch (error) {
return PromiseReject(error);
}
}
const promise = createDeferredPromise();
const state = {
currentWrite: PromiseResolve(),
};
// 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?.[SymbolDispose]();
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)) {
PromisePrototypeThen(
waitForCurrentWrite(),
complete,
(error) => finalize(true, error));
return;
}
complete();
function complete() {
PromisePrototypeThen(
action(),
() => finalize(rejected, originalError),
(error) => finalize(true, error));
}
}
function shutdown(rejected, error) {
if (shuttingDown) return;
shuttingDown = true;
if (dest[kState].state === 'writable' &&
!writableStreamCloseQueuedOrInFlight(dest)) {
PromisePrototypeThen(
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) {
ArrayPrototypePush(
actions,
() => {
if (dest[kState].state === 'writable')
return writableStreamAbort(dest, error);
return PromiseResolve();
});
}
if (!preventCancel) {
ArrayPrototypePush(
actions,
() => {
if (source[kState].state === 'readable')
return readableStreamCancel(source, error);
return PromiseResolve();
});
}
shutdownWithAnAction(
() => SafePromiseAll(actions, (action) => action()),
true,
error);
}
function watchErrored(stream, promise, action) {
if (stream[kState].state === 'errored')
action(stream[kState].storedError);
else
PromisePrototypeThen(promise, undefined, action);
}
function watchClosed(stream, promise, action) {
if (stream[kState].state === 'closed')
action();
else
PromisePrototypeThen(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 ??= require('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);
PromisePrototypeThen(
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) {
PromisePrototypeThen(
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].controller,
error,
);
cancelDeferred.resolve(readableStreamCancel(stream, error));
return;
}
}
if (!canceled1) {
readableByteStreamControllerEnqueue(
branch1[kState].controller,
chunk1,
);
}
if (!canceled2) {
readableByteStreamControllerEnqueue(
branch2[kState].controller,
chunk2,
);
}
reading = false;
if (readAgainForBranch1) {
pull1Algorithm();
} else if (readAgainForBranch2) {
pull2Algorithm();
}
});
},
[kClose]() {
reading = false;
if (!canceled1) {
readableByteStreamControllerClose(branch1[kState].controller);
}
if (!canceled2) {
readableByteStreamControllerClose(branch2[kState].controller);
}
if (branch1[kState].controller[kState].pendingPullIntos.length > 0) {
readableByteStreamControllerRespond(branch1[kState].controller, 0);
}
if (branch2[kState].controller[kState].pendingPullIntos.length > 0) {
readableByteStreamControllerRespond(branch2[kState].controller, 0);
}
if (!canceled1 || !canceled2) {
cancelDeferred.resolve();
}
},
[kError]() {
reading = false;
},
};
readableStreamDefaultReaderRead(reader, readRequest);
}
function pullWithBYOBReader(view, forBranch2) {
if (isReadableStreamDefaultReader(reader)) {
readableStreamDefaultReaderRelease(reader);
reader = new ReadableStreamBYOBReader(stream);
forwardReaderError(reader);
}
const byobBranch = forBranch2 === true ? branch2 : branch1;
const otherBranch = forBranch2 === false ? branch2 : branch1;
const readIntoRequest = {
[kChunk](chunk) {
queueMicrotask(() => {
readAgainForBranch1 = false;
readAgainForBranch2 = false;
const byobCanceled = forBranch2 === true ? canceled2 : canceled1;
const otherCanceled = forBranch2 === false ? canceled2 : canceled1;
if (!otherCanceled) {
let clonedChunk;
try {
clonedChunk = cloneAsUint8Array(chunk);
} catch (error) {
readableByteStreamControllerError(
byobBranch[kState].controller,
error,
);
readableByteStreamControllerError(
otherBranch[kState].controller,
error,
);
cancelDeferred.resolve(readableStreamCancel(stream, error));
return;
}
if (!byobCanceled) {
readableByteStreamControllerRespondWithNewView(
byobBranch[kState].controller,
chunk,
);
}
readableByteStreamControllerEnqueue(
otherBranch[kState].controller,
clonedChunk,
);
} else if (!byobCanceled) {
readableByteStreamControllerRespondWithNewView(
byobBranch[kState].controller,
chunk,
);
}
reading = false;
if (readAgainForBranch1) {
pull1Algorithm();
} else if (readAgainForBranch2) {
pull2Algorithm();
}
});
},
[kClose](chunk) {
reading = false;
const byobCanceled = forBranch2 === true ? canceled2 : canceled1;
const otherCanceled = forBranch2 === false ? canceled2 : canceled1;
if (!byobCanceled) {
readableByteStreamControllerClose(byobBranch[kState].controller);
}
if (!otherCanceled) {
readableByteStreamControllerClose(otherBranch[kState].controller);
}
if (chunk !== undefined) {
if (!byobCanceled) {
readableByteStreamControllerRespondWithNewView(
byobBranch[kState].controller,
chunk,
);
}
if (
!otherCanceled &&
otherBranch[kState].controller[kState].pendingPullIntos.length > 0
) {
readableByteStreamControllerRespond(
otherBranch[kState].controller,
0,
);
}
}
if (!byobCanceled || !otherCanceled) {
cancelDeferred.resolve();
}
},
[kError]() {
reading = false;
},
};
readableStreamBYOBReaderRead(reader, view, 1, readIntoRequest);
}
function pull1Algorithm() {
if (reading) {
readAgainForBranch1 = true;
return PromiseResolve();
}
reading = true;
const byobRequest = branch1[kState].controller.byobRequest;
if (byobRequest === null) {
pullWithDefaultReader();
} else {
pullWithBYOBReader(byobRequest[kState].view, false);
}
return PromiseResolve();
}
function pull2Algorithm() {
if (reading) {
readAgainForBranch2 = true;
return PromiseResolve();
}
reading = true;
const byobRequest = branch2[kState].controller.byobRequest;
if (byobRequest === null) {
pullWithDefaultReader();
} else {
pullWithBYOBReader(byobRequest[kState].view, true);
}
return PromiseResolve();
}
function cancel1Algorithm(reason) {
canceled1 = true;
reason1 = reason;
if (canceled2) {
cancelDeferred.resolve(readableStreamCancel(stream, [reason1, reason2]));
}
return cancelDeferred.promise;
}
function cancel2Algorithm(reason) {
canceled2 = true;
reason2 = reason;
if (canceled1) {
cancelDeferred.resolve(readableStreamCancel(stream, [reason1, reason2]));
}
return cancelDeferred.promise;
}
branch1 =
createReadableByteStream(nonOpStart, pull1Algorithm, cancel1Algorithm);
branch2 =
createReadableByteStream(nonOpStart, pull2Algorithm, cancel2Algorithm);
forwardReaderError(reader);
return [branch1, branch2];
}
function readableByteStreamControllerConvertPullIntoDescriptor(desc) {
const {
buffer,
bytesFilled,
byteLength,
byteOffset,
ctor,
elementSize,
} = desc;
if (bytesFilled > byteLength)
throw new ERR_INVALID_STATE.RangeError('The buffer size is invalid');
assert(!(bytesFilled % elementSize));
const transferredBuffer = transferArrayBuffer(buffer);
if (ctor === Buffer) {
return Buffer.from(transferredBuffer, byteOffset, bytesFilled / elementSize);
}
return new ctor(transferredBuffer, byteOffset, bytesFilled / elementSize);
}
function isReadableStreamLocked(stream) {
return stream[kState].reader !== undefined;
}
function readableStreamCancel(stream, reason) {
stream[kState].disturbed = true;
switch (stream[kState].state) {
case 'closed':
return PromiseResolve();
case 'errored':
return PromiseReject(stream[kState].storedError);
}
readableStreamClose(stream);
const {
reader,
} = stream[kState];
if (reader !== undefined && readableStreamHasBYOBReader(stream)) {
for (let n = 0; n < reader[kState].readIntoRequests.length; n++)
reader[kState].readIntoRequests[n][kClose]();
reader[kState].readIntoRequests = [];
}
return PromisePrototypeThen(
stream[kState].controller[kCancel](reason),
() => {});
}
function readableStreamClose(stream) {
assert(stream[kState].state === 'readable');
stream[kState].state = 'closed';
stream[kIsClosedPromise].resolve();
const {
reader,
} = stream[kState];
if (reader === undefined)
return;
reader[kState].close.resolve();
if (readableStreamHasDefaultReader(stream)) {
for (let n = 0; n < reader[kState].readRequests.length; n++)
reader[kState].readRequests[n][kClose]();
reader[kState].readRequests = [];
}
}
function readableStreamError(stream, error) {
assert(stream[kState].state === 'readable');
stream[kState].state = 'errored';
stream[kState].storedError = error;
stream[kIsClosedPromise].reject(error);
setPromiseHandled(stream[kIsClosedPromise].promise);
const {
reader,
} = stream[kState];
if (reader === undefined)
return;
reader[kState].close.reject(error);
setPromiseHandled(reader[kState].close.promise);
if (readableStreamHasDefaultReader(stream)) {
for (let n = 0; n < reader[kState].readRequests.length; n++)
reader[kState].readRequests[n][kError](error);
reader[kState].readRequests = [];
} else {
assert(readableStreamHasBYOBReader(stream));
for (let n = 0; n < reader[kState].readIntoRequests.length; n++)
reader[kState].readIntoRequests[n][kError](error);
reader[kState].readIntoRequests = [];
}
}
function readableStreamHasDefaultReader(stream) {
const {
reader,
} = stream[kState];
if (reader === undefined)
return false;
return reader[kState] !== undefined &&
reader[kType] === 'ReadableStreamDefaultReader';
}
function readableStreamGetNumReadRequests(stream) {
assert(readableStreamHasDefaultReader(stream));
return stream[kState].reader[kState].readRequests.length;
}
function readableStreamHasBYOBReader(stream) {
const {
reader,
} = stream[kState];
if (reader === undefined)
return false;
return reader[kState] !== undefined &&
reader[kType] === 'ReadableStreamBYOBReader';
}
function readableStreamGetNumReadIntoRequests(stream) {
assert(readableStreamHasBYOBReader(stream));
return stream[kState].reader[kState].readIntoRequests.length;
}
function readableStreamFulfillReadRequest(stream, chunk, done) {
assert(readableStreamHasDefaultReader(stream));
const {
reader,
} = stream[kState];
assert(reader[kState].readRequests.length);
const readRequest = ArrayPrototypeShift(reader[kState].readRequests);
// TODO(@jasnell): It's not clear under what exact conditions done
// will be true here. The spec requires this check but none of the
// WPT's or other tests trigger it. Will need to investigate how to
// get coverage for this.
if (done)
readRequest[kClose]();
else
readRequest[kChunk](chunk);
}
function readableStreamFulfillReadIntoRequest(stream, chunk, done) {
assert(readableStreamHasBYOBReader(stream));
const {
reader,
} = stream[kState];
assert(reader[kState].readIntoRequests.length);
const readIntoRequest = ArrayPrototypeShift(reader[kState].readIntoRequests);
if (done)
readIntoRequest[kClose](chunk);
else
readIntoRequest[kChunk](chunk);
}
function readableStreamAddReadRequest(stream, readRequest) {
assert(readableStreamHasDefaultReader(stream));
assert(stream[kState].state === 'readable');
ArrayPrototypePush(stream[kState].reader[kState].readRequests, readRequest);
}
function readableStreamAddReadIntoRequest(stream, readIntoRequest) {
assert(readableStreamHasBYOBReader(stream));
assert(stream[kState].state !== 'errored');
ArrayPrototypePush(
stream[kState].reader[kState].readIntoRequests,
readIntoRequest);
}
function readableStreamReaderGenericCancel(reader, reason) {
const {
stream,
} = reader[kState];
assert(stream !== undefined);
return readableStreamCancel(stream, reason);
}
function readableStreamReaderGenericInitialize(reader, stream) {
reader[kState].stream = stream;
stream[kState].reader = reader;
switch (stream[kState].state) {
case 'readable':
reader[kState].close = createDeferredPromise();
break;
case 'closed':
reader[kState].close = {
promise: PromiseResolve(),
resolve: undefined,
reject: undefined,
};
break;
case 'errored':
reader[kState].close = {
promise: PromiseReject(stream[kState].storedError),
resolve: undefined,
reject: undefined,
};
setPromiseHandled(reader[kState].close.promise);
break;
}
}
function readableStreamDefaultReaderRelease(reader) {
readableStreamReaderGenericRelease(reader);
readableStreamDefaultReaderErrorReadRequests(
reader,
lazyReadableReleasingError(),
);
}
function readableStreamDefaultReaderErrorReadRequests(reader, e) {
for (let n = 0; n < reader[kState].readRequests.length; ++n) {
reader[kState].readRequests[n][kError](e);
}
reader[kState].readRequests = [];
}
function readableStreamBYOBReaderRelease(reader) {
readableStreamReaderGenericRelease(reader);
readableStreamBYOBReaderErrorReadIntoRequests(
reader,
lazyReadableReleasingError(),
);
}
function readableStreamBYOBReaderErrorReadIntoRequests(reader, e) {
for (let n = 0; n < reader[kState].readIntoRequests.length; ++n) {
reader[kState].readIntoRequests[n][kError](e);
}
reader[kState].readIntoRequests = [];
}
function readableStreamReaderGenericRelease(reader) {
const {
stream,
} = reader[kState];
assert(stream !== undefined);
assert(stream[kState].reader === reader);
const releasedStateError = lazyReadableReleasedError();
if (stream[kState].state === 'readable') {
reader[kState].close.reject?.(releasedStateError);
} else {
reader[kState].close = {
promise: PromiseReject(releasedStateError),
resolve: undefined,
reject: undefined,
};
}
setPromiseHandled(reader[kState].close.promise);
stream[kState].controller[kRelease]();
stream[kState].reader = undefined;
reader[kState].stream = undefined;
}
function readableStreamBYOBReaderRead(reader, view, min, readIntoRequest) {
const {
stream,
} = reader[kState];
assert(stream !== undefined);
stream[kState].disturbed = true;
if (stream[kState].state === 'errored') {
readIntoRequest[kError](stream[kState].storedError);
return;
}
readableByteStreamControllerPullInto(
stream[kState].controller,
view,
min,
readIntoRequest);
}
function readableStreamDefaultReaderRead(reader, readRequest) {
const {
stream,
} = reader[kState];
assert(stream !== undefined);
stream[kState].disturbed = true;
switch (stream[kState].state) {
case 'closed':
readRequest[kClose]();
break;
case 'errored':
readRequest[kError](stream[kState].storedError);
break;
case 'readable':
stream[kState].controller[kPull](readRequest);
}
}
function setupReadableStreamBYOBReader(reader, stream) {
if (isReadableStreamLocked(stream))
throw new ERR_INVALID_STATE.TypeError('ReadableStream is locked');
const {
controller,
} = stream[kState];
if (!isReadableByteStreamController(controller))
throw new ERR_INVALID_ARG_VALUE('stream', stream, 'must be a byte stream');
readableStreamReaderGenericInitialize(reader, stream);
reader[kState].readIntoRequests = [];
}
function setupReadableStreamDefaultReader(reader, stream) {
if (isReadableStreamLocked(stream))
throw new ERR_INVALID_STATE.TypeError('ReadableStream is locked');
readableStreamReaderGenericInitialize(reader, stream);
reader[kState].readRequests = [];
}
function readableStreamDefaultControllerClose(controller) {
if (!readableStreamDefaultControllerCanCloseOrEnqueue(controller))
return;
controller[kState].closeRequested = true;
if (!controller[kState].queue.length) {
readableStreamDefaultControllerClearAlgorithms(controller);
readableStreamClose(controller[kState].stream);
}
}
function readableStreamDefaultControllerEnqueue(controller, chunk) {
if (!readableStreamDefaultControllerCanCloseOrEnqueue(controller))
return;
const {
stream,
} = controller[kState];
if (isReadableStreamLocked(stream) &&
readableStreamGetNumReadRequests(stream)) {
readableStreamFulfillReadRequest(stream, chunk, false);
} else {
try {
const chunkSize =
FunctionPrototypeCall(
controller[kState].sizeAlgorithm,
undefined,
chunk);
enqueueValueWithSize(controller, chunk, chunkSize);
} catch (error) {
readableStreamDefaultControllerError(controller, error);
throw error;
}
}
readableStreamDefaultControllerCallPullIfNeeded(controller);
}
function readableStreamDefaultControllerHasBackpressure(controller) {
return !readableStreamDefaultControllerShouldCallPull(controller);
}
function readableStreamDefaultControllerCanCloseOrEnqueue(controller) {
const {
stream,
} = controller[kState];
return !controller[kState].closeRequested &&
stream[kState].state === 'readable';
}
function readableStreamDefaultControllerGetDesiredSize(controller) {
const {
stream,
highWaterMark,
queueTotalSize,
} = controller[kState];
switch (stream[kState].state) {
case 'errored': return null;
case 'closed': return 0;
default:
return highWaterMark - queueTotalSize;
}
}
function readableStreamDefaultControllerShouldCallPull(controller) {
const {
stream,
} = controller[kState];
if (!readableStreamDefaultControllerCanCloseOrEnqueue(controller) ||
!controller[kState].started)
return false;
if (isReadableStreamLocked(stream) &&
readableStreamGetNumReadRequests(stream)) {
return true;
}
const desiredSize = readableStreamDefaultControllerGetDesiredSize(controller);
assert(desiredSize !== null);
return desiredSize > 0;
}
function readableStreamDefaultControllerCallPullIfNeeded(controller) {
if (!readableStreamDefaultControllerShouldCallPull(controller))
return;
if (controller[kState].pulling) {
controller[kState].pullAgain = true;
return;
}
assert(!controller[kState].pullAgain);
controller[kState].pulling = true;
PromisePrototypeThen(
controller[kState].pullAlgorithm(controller),
() => {
controller[kState].pulling = false;
if (controller[kState].pullAgain) {
controller[kState].pullAgain = false;
readableStreamDefaultControllerCallPullIfNeeded(controller);
}
},
(error) => readableStreamDefaultControllerError(controller, error));
}
function readableStreamDefaultControllerClearAlgorithms(controller) {
controller[kState].pullAlgorithm = undefined;
controller[kState].cancelAlgorithm = undefined;
controller[kState].sizeAlgorithm = undefined;
}
function readableStreamDefaultControllerError(controller, error) {
const {
stream,
} = controller[kState];
if (stream[kState].state === 'readable') {
resetQueue(controller);
readableStreamDefaultControllerClearAlgorithms(controller);
readableStreamError(stream, error);
}
}
function readableStreamDefaultControllerCancelSteps(controller, reason) {
resetQueue(controller);
const result = controller[kState].cancelAlgorithm(reason);
readableStreamDefaultControllerClearAlgorithms(controller);
return result;
}
function readableStreamDefaultControllerPullSteps(controller, readRequest) {
const {
stream,
queue,
} = controller[kState];
if (queue.length) {
const chunk = dequeueValue(controller);
if (controller[kState].closeRequested && !queue.length) {
readableStreamDefaultControllerClearAlgorithms(controller);
readableStreamClose(stream);
} else {
readableStreamDefaultControllerCallPullIfNeeded(controller);
}
readRequest[kChunk](chunk);
return;
}
readableStreamAddReadRequest(stream, readRequest);
readableStreamDefaultControllerCallPullIfNeeded(controller);
}
function setupReadableStreamDefaultController(
stream,
controller,
startAlgorithm,
pullAlgorithm,
cancelAlgorithm,
highWaterMark,
sizeAlgorithm) {
assert(stream[kState].controller === undefined);
controller[kState] = {
cancelAlgorithm,
closeRequested: false,
highWaterMark,
pullAgain: false,
pullAlgorithm,
pulling: false,
queue: [],
queueTotalSize: 0,
started: false,
sizeAlgorithm,
stream,
};
stream[kState].controller = controller;
stream[kControllerErrorFunction] = FunctionPrototypeBind(controller.error, controller);
const startResult = startAlgorithm();
PromisePrototypeThen(
new Promise((r) => r(startResult)),
() => {
controller[kState].started = true;
assert(!controller[kState].pulling);
assert(!controller[kState].pullAgain);
readableStreamDefaultControllerCallPullIfNeeded(controller);
},
(error) => readableStreamDefaultControllerError(controller, error));
}
function setupReadableStreamDefaultControllerFromSource(
stream,
source,
highWaterMark,
sizeAlgorithm) {
const controller = new ReadableStreamDefaultController(kSkipThrow);
const start = source?.start;
const pull = source?.pull;
const cancel = source?.cancel;
const startAlgorithm = start ?
FunctionPrototypeBind(start, source, controller) :
nonOpStart;
const pullAlgorithm = pull ?
createPromiseCallback('source.pull', pull, source) :
nonOpPull;
const cancelAlgorithm = cancel ?
createPromiseCallback('source.cancel', cancel, source) :
nonOpCancel;
setupReadableStreamDefaultController(
stream,
controller,
startAlgorithm,
pullAlgorithm,
cancelAlgorithm,
highWaterMark,
sizeAlgorithm);
}
function readableByteStreamControllerClose(controller) {
const {
closeRequested,
pendingPullIntos,
queueTotalSize,
stream,
} = controller[kState];
if (closeRequested || stream[kState].state !== 'readable')
return;
if (queueTotalSize) {
controller[kState].closeRequested = true;
return;
}
if (pendingPullIntos.length) {
const firstPendingPullInto = pendingPullIntos[0];
if (firstPendingPullInto.bytesFilled % firstPendingPullInto.elementSize !== 0) {
const error = new ERR_INVALID_STATE.TypeError('Partial read');
readableByteStreamControllerError(controller, error);
throw error;
}
}
readableByteStreamControllerClearAlgorithms(controller);
readableStreamClose(stream);
}
function readableByteStreamControllerCommitPullIntoDescriptor(stream, desc) {
assert(stream[kState].state !== 'errored');
assert(desc.type !== 'none');
let done = false;
if (stream[kState].state === 'closed') {
assert(desc.bytesFilled % desc.elementSize === 0);
done = true;
}
const filledView =
readableByteStreamControllerConvertPullIntoDescriptor(desc);
if (desc.type === 'default') {
readableStreamFulfillReadRequest(stream, filledView, done);
} else {
assert(desc.type === 'byob');
readableStreamFulfillReadIntoRequest(stream, filledView, done);
}
}
function readableByteStreamControllerInvalidateBYOBRequest(controller) {
if (controller[kState].byobRequest === null)
return;
controller[kState].byobRequest[kState].controller = undefined;
controller[kState].byobRequest[kState].view = null;
controller[kState].byobRequest = null;
}
function readableByteStreamControllerClearAlgorithms(controller) {
controller[kState].pullAlgorithm = undefined;
controller[kState].cancelAlgorithm = undefined;
}
function readableByteStreamControllerClearPendingPullIntos(controller) {
readableByteStreamControllerInvalidateBYOBRequest(controller);
controller[kState].pendingPullIntos = [];
}
function readableByteStreamControllerGetDesiredSize(controller) {
const {
stream,
highWaterMark,
queueTotalSize,
} = controller[kState];
switch (stream[kState].state) {
case 'errored': return null;
case 'closed': return 0;
default: return highWaterMark - queueTotalSize;
}
}
function readableByteStreamControllerShouldCallPull(controller) {
const {
stream,
} = controller[kState];
if (stream[kState].state !== 'readable' ||
controller[kState].closeRequested ||
!controller[kState].started) {
return false;
}
if (readableStreamHasDefaultReader(stream) &&
readableStreamGetNumReadRequests(stream) > 0) {
return true;
}
if (readableStreamHasBYOBReader(stream) &&
readableStreamGetNumReadIntoRequests(stream) > 0) {
return true;
}
const desiredSize = readableByteStreamControllerGetDesiredSize(controller);
assert(desiredSize !== null);
return desiredSize > 0;
}
function readableByteStreamControllerHandleQueueDrain(controller) {
const {
closeRequested,
queueTotalSize,
stream,
} = controller[kState];
assert(stream[kState].state === 'readable');
if (!queueTotalSize && closeRequested) {
readableByteStreamControllerClearAlgorithms(controller);
readableStreamClose(stream);
return;
}
readableByteStreamControllerCallPullIfNeeded(controller);
}
function readableByteStreamControllerPullInto(
controller,
view,
min,
readIntoRequest) {
const {
closeRequested,
stream,
pendingPullIntos,
} = controller[kState];
let elementSize = 1;
let ctor = DataView;
if (isArrayBufferView(view) && !isDataView(view)) {
elementSize = view.constructor.BYTES_PER_ELEMENT;
ctor = view.constructor;
}
const minimumFill = min * elementSize;
assert(minimumFill >= elementSize && minimumFill <= view.byteLength);
assert(minimumFill % elementSize === 0);
const buffer = ArrayBufferViewGetBuffer(view);
const byteOffset = ArrayBufferViewGetByteOffset(view);
const byteLength = ArrayBufferViewGetByteLength(view);
const bufferByteLength = ArrayBufferPrototypeGetByteLength(buffer);
let transferredBuffer;
try {
transferredBuffer = transferArrayBuffer(buffer);
} catch (error) {
readIntoRequest[kError](error);
return;
}
const desc = {
buffer: transferredBuffer,
bufferByteLength,
byteOffset,
byteLength,
bytesFilled: 0,
minimumFill,
elementSize,
ctor,
type: 'byob',
};
if (pendingPullIntos.length) {
ArrayPrototypePush(pendingPullIntos, desc);
readableStreamAddReadIntoRequest(stream, readIntoRequest);
return;
}
if (stream[kState].state === 'closed') {
const emptyView = new ctor(desc.buffer, byteOffset, 0);
readIntoRequest[kClose](emptyView);
return;
}
if (controller[kState].queueTotalSize) {
if (readableByteStreamControllerFillPullIntoDescriptorFromQueue(
controller,
desc)) {
const filledView =
readableByteStreamControllerConvertPullIntoDescriptor(desc);
readableByteStreamControllerHandleQueueDrain(controller);
readIntoRequest[kChunk](filledView);
return;
}
if (closeRequested) {
const error = new ERR_INVALID_STATE.TypeError('ReadableStream closed');
readableByteStreamControllerError(controller, error);
readIntoRequest[kError](error);
return;
}
}
ArrayPrototypePush(pendingPullIntos, desc);
readableStreamAddReadIntoRequest(stream, readIntoRequest);
readableByteStreamControllerCallPullIfNeeded(controller);
}
function readableByteStreamControllerRespondInternal(controller, bytesWritten) {
const {
stream,
pendingPullIntos,
} = controller[kState];
const desc = pendingPullIntos[0];
readableByteStreamControllerInvalidateBYOBRequest(controller);
if (stream[kState].state === 'closed') {
if (bytesWritten)
throw new ERR_INVALID_STATE.TypeError(
'Controller is closed but view is not zero-length');
readableByteStreamControllerRespondInClosedState(controller, desc);
} else {
assert(stream[kState].state === 'readable');
if (!bytesWritten)
throw new ERR_INVALID_STATE.TypeError('View cannot be zero-length');
readableByteStreamControllerRespondInReadableState(
controller,
bytesWritten,
desc);
}
readableByteStreamControllerCallPullIfNeeded(controller);
}
function readableByteStreamControllerRespond(controller, bytesWritten) {
const {
pendingPullIntos,
stream,
} = controller[kState];
assert(pendingPullIntos.length);
const desc = pendingPullIntos[0];
if (stream[kState].state === 'closed') {
if (bytesWritten !== 0)
throw new ERR_INVALID_ARG_VALUE('bytesWritten', bytesWritten);
} else {
assert(stream[kState].state === 'readable');
if (!bytesWritten)
throw new ERR_INVALID_ARG_VALUE('bytesWritten', bytesWritten);
if ((desc.bytesFilled + bytesWritten) > desc.byteLength)
throw new ERR_INVALID_ARG_VALUE.RangeError('bytesWritten', bytesWritten);
}
desc.buffer = transferArrayBuffer(desc.buffer);
readableByteStreamControllerRespondInternal(controller, bytesWritten);
}
function readableByteStreamControllerRespondInClosedState(controller, desc) {
assert(desc.bytesFilled % desc.elementSize === 0);
if (desc.type === 'none') {
readableByteStreamControllerShiftPendingPullInto(controller);
}
const {
stream,
} = controller[kState];
if (readableStreamHasBYOBReader(stream)) {
while (readableStreamGetNumReadIntoRequests(stream) > 0) {
readableByteStreamControllerCommitPullIntoDescriptor(
stream,
readableByteStreamControllerShiftPendingPullInto(controller));
}
}
}
function readableByteStreamControllerFillHeadPullIntoDescriptor(
controller,
size,
desc) {
const {
pendingPullIntos,
byobRequest,
} = controller[kState];
assert(!pendingPullIntos.length || pendingPullIntos[0] === desc);
assert(byobRequest === null);
desc.bytesFilled += size;
}
function readableByteStreamControllerEnqueue(controller, chunk) {
const {
closeRequested,
pendingPullIntos,
queue,
stream,
} = controller[kState];
const buffer = ArrayBufferViewGetBuffer(chunk);
const byteOffset = ArrayBufferViewGetByteOffset(chunk);
const byteLength = ArrayBufferViewGetByteLength(chunk);
if (closeRequested || stream[kState].state !== 'readable')
return;
const transferredBuffer = transferArrayBuffer(buffer);
if (pendingPullIntos.length) {
const firstPendingPullInto = pendingPullIntos[0];
if (isArrayBufferDetached(firstPendingPullInto.buffer)) {
throw new ERR_INVALID_STATE.TypeError(
'Destination ArrayBuffer is detached',
);
}
readableByteStreamControllerInvalidateBYOBRequest(controller);
firstPendingPullInto.buffer = transferArrayBuffer(
firstPendingPullInto.buffer,
);
if (firstPendingPullInto.type === 'none') {
readableByteStreamControllerEnqueueDetachedPullIntoToQueue(
controller,
firstPendingPullInto,
);
}
}
if (readableStreamHasDefaultReader(stream)) {
readableByteStreamControllerProcessReadRequestsUsingQueue(controller);
if (!readableStreamGetNumReadRequests(stream)) {
readableByteStreamControllerEnqueueChunkToQueue(
controller,
transferredBuffer,
byteOffset,
byteLength);
} else {
assert(!queue.length);
if (pendingPullIntos.length) {
assert(pendingPullIntos[0].type === 'default');
readableByteStreamControllerShiftPendingPullInto(controller);
}
const transferredView =
new Uint8Array(transferredBuffer, byteOffset, byteLength);
readableStreamFulfillReadRequest(stream, transferredView, false);
}
} else if (readableStreamHasBYOBReader(stream)) {
readableByteStreamControllerEnqueueChunkToQueue(
controller,
transferredBuffer,
byteOffset,
byteLength);
readableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(
controller);
} else {
assert(!isReadableStreamLocked(stream));
readableByteStreamControllerEnqueueChunkToQueue(
controller,
transferredBuffer,
byteOffset,
byteLength);
}
readableByteStreamControllerCallPullIfNeeded(controller);
}
function readableByteStreamControllerEnqueueClonedChunkToQueue(
controller,
buffer,
byteOffset,
byteLength,
) {
let cloneResult;
try {
cloneResult = ArrayBufferPrototypeSlice(
buffer,
byteOffset,
byteOffset + byteLength,
);
} catch (error) {
readableByteStreamControllerError(controller, error);
throw error;
}
readableByteStreamControllerEnqueueChunkToQueue(
controller,
cloneResult,
0,
byteLength,
);
}
function readableByteStreamControllerEnqueueChunkToQueue(
controller,
buffer,
byteOffset,
byteLength) {
ArrayPrototypePush(
controller[kState].queue,
{
buffer,
byteOffset,
byteLength,
});
controller[kState].queueTotalSize += byteLength;
}
function readableByteStreamControllerEnqueueDetachedPullIntoToQueue(
controller,
desc,
) {
const {
buffer,
byteOffset,
bytesFilled,
type,
} = desc;
assert(type === 'none');
if (bytesFilled > 0) {
readableByteStreamControllerEnqueueClonedChunkToQueue(
controller,
buffer,
byteOffset,
bytesFilled,
);
}
readableByteStreamControllerShiftPendingPullInto(controller);
}
function readableByteStreamControllerFillPullIntoDescriptorFromQueue(
controller,
desc) {
const {
buffer,
byteLength,
byteOffset,
bytesFilled,
minimumFill,
elementSize,
} = desc;
const maxBytesToCopy = MathMin(
controller[kState].queueTotalSize,
byteLength - bytesFilled);
const maxBytesFilled = bytesFilled + maxBytesToCopy;
const maxAlignedBytes = maxBytesFilled - (maxBytesFilled % elementSize);
let totalBytesToCopyRemaining = maxBytesToCopy;
let ready = false;
assert(bytesFilled < minimumFill);
if (maxAlignedBytes >= minimumFill) {
totalBytesToCopyRemaining = maxAlignedBytes - bytesFilled;
ready = true;
}
const {
queue,
} = controller[kState];
while (totalBytesToCopyRemaining) {
const headOfQueue = queue[0];
const bytesToCopy = MathMin(
totalBytesToCopyRemaining,
headOfQueue.byteLength);
const destStart = byteOffset + desc.bytesFilled;
const arrayBufferByteLength = ArrayBufferPrototypeGetByteLength(buffer);
if (arrayBufferByteLength - destStart < bytesToCopy) {
throw new ERR_INVALID_STATE.RangeError(
'view ArrayBuffer size is invalid');
}
assert(arrayBufferByteLength - destStart >= bytesToCopy);
copyArrayBuffer(
buffer,
destStart,
headOfQueue.buffer,
headOfQueue.byteOffset,
bytesToCopy);
if (headOfQueue.byteLength === bytesToCopy) {
ArrayPrototypeShift(queue);
} else {
headOfQueue.byteOffset += bytesToCopy;
headOfQueue.byteLength -= bytesToCopy;
}
controller[kState].queueTotalSize -= bytesToCopy;
readableByteStreamControllerFillHeadPullIntoDescriptor(
controller,
bytesToCopy,
desc);
totalBytesToCopyRemaining -= bytesToCopy;
}
if (!ready) {
assert(!controller[kState].queueTotalSize);
assert(desc.bytesFilled > 0);
assert(desc.bytesFilled < minimumFill);
}
return ready;
}
function readableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(
controller) {
const {
closeRequested,
pendingPullIntos,
stream,
} = controller[kState];
assert(!closeRequested);
while (pendingPullIntos.length) {
if (!controller[kState].queueTotalSize)
return;
const desc = pendingPullIntos[0];
if (readableByteStreamControllerFillPullIntoDescriptorFromQueue(
controller,
desc)) {
readableByteStreamControllerShiftPendingPullInto(controller);
readableByteStreamControllerCommitPullIntoDescriptor(stream, desc);
}
}
}
function readableByteStreamControllerRespondInReadableState(
controller,
bytesWritten,
desc) {
const {
buffer,
bytesFilled,
byteLength,
type,
} = desc;
if (bytesFilled + bytesWritten > byteLength)
throw new ERR_INVALID_STATE.RangeError('The buffer size is invalid');
readableByteStreamControllerFillHeadPullIntoDescriptor(
controller,
bytesWritten,
desc);
if (type === 'none') {
readableByteStreamControllerEnqueueDetachedPullIntoToQueue(
controller,
desc,
);
readableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(
controller,
);
return;
}
if (desc.bytesFilled < desc.minimumFill)
return;
readableByteStreamControllerShiftPendingPullInto(controller);
const remainderSize = desc.bytesFilled % desc.elementSize;
if (remainderSize) {
const end = desc.byteOffset + desc.bytesFilled;
const start = end - remainderSize;
const remainder =
ArrayBufferPrototypeSlice(
buffer,
start,
end);
readableByteStreamControllerEnqueueChunkToQueue(
controller,
remainder,
0,
ArrayBufferPrototypeGetByteLength(remainder));
}
desc.bytesFilled -= remainderSize;
readableByteStreamControllerCommitPullIntoDescriptor(
controller[kState].stream,
desc);
readableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(controller);
}
function readableByteStreamControllerRespondWithNewView(controller, view) {
const {
stream,
pendingPullIntos,
} = controller[kState];
assert(pendingPullIntos.length);
const desc = pendingPullIntos[0];
assert(stream[kState].state !== 'errored');
const viewByteLength = ArrayBufferViewGetByteLength(view);
const viewByteOffset = ArrayBufferViewGetByteOffset(view);
const viewBuffer = ArrayBufferViewGetBuffer(view);
const viewBufferByteLength = ArrayBufferPrototypeGetByteLength(viewBuffer);
if (stream[kState].state === 'closed') {
if (viewByteLength !== 0)
throw new ERR_INVALID_STATE.TypeError('View is not zero-length');
} else {
assert(stream[kState].state === 'readable');
if (viewByteLength === 0)
throw new ERR_INVALID_STATE.TypeError('View is zero-length');
}
const {
byteOffset,
byteLength,
bytesFilled,
bufferByteLength,
} = desc;
if (byteOffset + bytesFilled !== viewByteOffset)
throw new ERR_INVALID_ARG_VALUE.RangeError('view', view);
if (bytesFilled + viewByteLength > byteLength)
throw new ERR_INVALID_ARG_VALUE.RangeError('view', view);
if (bufferByteLength !== viewBufferByteLength)
throw new ERR_INVALID_ARG_VALUE.RangeError('view', view);
desc.buffer = transferArrayBuffer(viewBuffer);
readableByteStreamControllerRespondInternal(controller, viewByteLength);
}
function readableByteStreamControllerShiftPendingPullInto(controller) {
assert(controller[kState].byobRequest === null);
return ArrayPrototypeShift(controller[kState].pendingPullIntos);
}
function readableByteStreamControllerCallPullIfNeeded(controller) {
if (!readableByteStreamControllerShouldCallPull(controller))
return;
if (controller[kState].pulling) {
controller[kState].pullAgain = true;
return;
}
assert(!controller[kState].pullAgain);
controller[kState].pulling = true;
PromisePrototypeThen(
controller[kState].pullAlgorithm(controller),
() => {
controller[kState].pulling = false;
if (controller[kState].pullAgain) {
controller[kState].pullAgain = false;
readableByteStreamControllerCallPullIfNeeded(controller);
}
},
(error) => readableByteStreamControllerError(controller, error));
}
function readableByteStreamControllerError(controller, error) {
const {
stream,
} = controller[kState];
if (stream[kState].state !== 'readable')
return;
readableByteStreamControllerClearPendingPullIntos(controller);
resetQueue(controller);
readableByteStreamControllerClearAlgorithms(controller);
readableStreamError(stream, error);
}
function readableByteStreamControllerCancelSteps(controller, reason) {
readableByteStreamControllerClearPendingPullIntos(controller);
resetQueue(controller);
const result = controller[kState].cancelAlgorithm(reason);
readableByteStreamControllerClearAlgorithms(controller);
return result;
}
function readableByteStreamControllerFillReadRequestFromQueue(controller, readRequest) {
const {
queue,
queueTotalSize,
} = controller[kState];
assert(queueTotalSize > 0);
const {
buffer,
byteOffset,
byteLength,
} = ArrayPrototypeShift(queue);
controller[kState].queueTotalSize -= byteLength;
readableByteStreamControllerHandleQueueDrain(controller);
const view = new Uint8Array(buffer, byteOffset, byteLength);
readRequest[kChunk](view);
}
function readableByteStreamControllerProcessReadRequestsUsingQueue(controller) {
const {
stream,
queueTotalSize,
} = controller[kState];
const { reader } = stream[kState];
assert(isReadableStreamDefaultReader(reader));
while (reader[kState].readRequests.length > 0) {
if (queueTotalSize === 0) {
return;
}
readableByteStreamControllerFillReadRequestFromQueue(
controller,
ArrayPrototypeShift(reader[kState].readRequests),
);
}
}
function readableByteStreamControllerPullSteps(controller, readRequest) {
const {
pendingPullIntos,
queueTotalSize,
stream,
} = controller[kState];
assert(readableStreamHasDefaultReader(stream));
if (queueTotalSize) {
assert(!readableStreamGetNumReadRequests(stream));
readableByteStreamControllerFillReadRequestFromQueue(
controller,
readRequest,
);
return;
}
const {
autoAllocateChunkSize,
} = controller[kState];
if (autoAllocateChunkSize !== undefined) {
try {
const buffer = new ArrayBuffer(autoAllocateChunkSize);
ArrayPrototypePush(
pendingPullIntos,
{
buffer,
bufferByteLength: autoAllocateChunkSize,
byteOffset: 0,
byteLength: autoAllocateChunkSize,
bytesFilled: 0,
minimumFill: 1,
elementSize: 1,
ctor: Uint8Array,
type: 'default',
});
} catch (error) {
readRequest[kError](error);
return;
}
}
readableStreamAddReadRequest(stream, readRequest);
readableByteStreamControllerCallPullIfNeeded(controller);
}
function setupReadableByteStreamController(
stream,
controller,
startAlgorithm,
pullAlgorithm,
cancelAlgorithm,
highWaterMark,
autoAllocateChunkSize) {
assert(stream[kState].controller === undefined);
if (autoAllocateChunkSize !== undefined) {
assert(NumberIsInteger(autoAllocateChunkSize));
assert(autoAllocateChunkSize > 0);
}
controller[kState] = {
byobRequest: null,
closeRequested: false,
pullAgain: false,
pulling: false,
started: false,
stream,
queue: [],
queueTotalSize: 0,
highWaterMark,
pullAlgorithm,
cancelAlgorithm,
autoAllocateChunkSize,
pendingPullIntos: [],
};
stream[kState].controller = controller;
const startResult = startAlgorithm();
PromisePrototypeThen(
new Promise((r) => r(startResult)),
() => {
controller[kState].started = true;
assert(!controller[kState].pulling);
assert(!controller[kState].pullAgain);
readableByteStreamControllerCallPullIfNeeded(controller);
},
(error) => readableByteStreamControllerError(controller, error));
}
function setupReadableByteStreamControllerFromSource(
stream,
source,
highWaterMark) {
const controller = new ReadableByteStreamController(kSkipThrow);
const start = source?.start;
const pull = source?.pull;
const cancel = source?.cancel;
const autoAllocateChunkSize = source?.autoAllocateChunkSize;
const startAlgorithm = start ?
FunctionPrototypeBind(start, source, controller) :
nonOpStart;
const pullAlgorithm = pull ?
createPromiseCallback('source.pull', pull, source, controller) :
nonOpPull;
const cancelAlgorithm = cancel ?
createPromiseCallback('source.cancel', cancel, source) :
nonOpCancel;
if (autoAllocateChunkSize === 0) {
throw new ERR_INVALID_ARG_VALUE(
'source.autoAllocateChunkSize',
autoAllocateChunkSize);
}
setupReadableByteStreamController(
stream,
controller,
startAlgorithm,
pullAlgorithm,
cancelAlgorithm,
highWaterMark,
autoAllocateChunkSize);
}
module.exports = {
ReadableStream,
ReadableStreamDefaultReader,
ReadableStreamBYOBReader,
ReadableStreamBYOBRequest,
ReadableByteStreamController,
ReadableStreamDefaultController,
TransferredReadableStream,
// Exported Brand Checks
isReadableStream,
isReadableByteStreamController,
isReadableStreamBYOBRequest,
isReadableStreamDefaultReader,
isReadableStreamBYOBReader,
isWritableStreamDefaultWriter,
isWritableStreamDefaultController,
readableStreamPipeTo,
readableStreamTee,
readableByteStreamControllerConvertPullIntoDescriptor,
isReadableStreamLocked,
readableStreamCancel,
readableStreamClose,
readableStreamError,
readableStreamHasDefaultReader,
readableStreamGetNumReadRequests,
readableStreamHasBYOBReader,
readableStreamGetNumReadIntoRequests,
readableStreamFulfillReadRequest,
readableStreamFulfillReadIntoRequest,
readableStreamAddReadRequest,
readableStreamAddReadIntoRequest,
readableStreamReaderGenericCancel,
readableStreamReaderGenericInitialize,
readableStreamReaderGenericRelease,
readableStreamBYOBReaderRead,
readableStreamDefaultReaderRead,
setupReadableStreamBYOBReader,
setupReadableStreamDefaultReader,
readableStreamDefaultControllerClose,
readableStreamDefaultControllerEnqueue,
readableStreamDefaultControllerHasBackpressure,
readableStreamDefaultControllerCanCloseOrEnqueue,
readableStreamDefaultControllerGetDesiredSize,
readableStreamDefaultControllerShouldCallPull,
readableStreamDefaultControllerCallPullIfNeeded,
readableStreamDefaultControllerClearAlgorithms,
readableStreamDefaultControllerError,
readableStreamDefaultControllerCancelSteps,
readableStreamDefaultControllerPullSteps,
setupReadableStreamDefaultController,
setupReadableStreamDefaultControllerFromSource,
readableByteStreamControllerClose,
readableByteStreamControllerCommitPullIntoDescriptor,
readableByteStreamControllerInvalidateBYOBRequest,
readableByteStreamControllerClearAlgorithms,
readableByteStreamControllerClearPendingPullIntos,
readableByteStreamControllerGetDesiredSize,
readableByteStreamControllerShouldCallPull,
readableByteStreamControllerHandleQueueDrain,
readableByteStreamControllerPullInto,
readableByteStreamControllerRespondInternal,
readableByteStreamControllerRespond,
readableByteStreamControllerRespondInClosedState,
readableByteStreamControllerFillHeadPullIntoDescriptor,
readableByteStreamControllerEnqueue,
readableByteStreamControllerEnqueueChunkToQueue,
readableByteStreamControllerFillPullIntoDescriptorFromQueue,
readableByteStreamControllerProcessPullIntoDescriptorsUsingQueue,
readableByteStreamControllerRespondInReadableState,
readableByteStreamControllerRespondWithNewView,
readableByteStreamControllerShiftPendingPullInto,
readableByteStreamControllerCallPullIfNeeded,
readableByteStreamControllerError,
readableByteStreamControllerCancelSteps,
readableByteStreamControllerPullSteps,
setupReadableByteStreamController,
setupReadableByteStreamControllerFromSource,
createReadableStream,
createReadableByteStream,
};