mirror of
https://github.com/nodejs/node.git
synced 2025-05-03 02:06:12 +00:00

PR-URL: https://github.com/nodejs/node/pull/50888 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Debadree Chatterjee <debadree333@gmail.com>
695 lines
18 KiB
JavaScript
695 lines
18 KiB
JavaScript
'use strict';
|
|
|
|
const {
|
|
FunctionPrototypeCall,
|
|
ObjectDefineProperties,
|
|
ObjectSetPrototypeOf,
|
|
PromisePrototypeThen,
|
|
SymbolToStringTag,
|
|
Symbol,
|
|
} = primordials;
|
|
|
|
const {
|
|
codes: {
|
|
ERR_ILLEGAL_CONSTRUCTOR,
|
|
ERR_INVALID_ARG_VALUE,
|
|
ERR_INVALID_STATE,
|
|
ERR_INVALID_THIS,
|
|
},
|
|
} = require('internal/errors');
|
|
|
|
const {
|
|
DOMException,
|
|
} = internalBinding('messaging');
|
|
|
|
const {
|
|
createDeferredPromise,
|
|
customInspectSymbol: kInspect,
|
|
kEmptyObject,
|
|
kEnumerableProperty,
|
|
} = require('internal/util');
|
|
|
|
const {
|
|
validateObject,
|
|
kValidateObjectAllowObjects,
|
|
kValidateObjectAllowObjectsAndNull,
|
|
} = require('internal/validators');
|
|
|
|
const {
|
|
kDeserialize,
|
|
kTransfer,
|
|
kTransferList,
|
|
markTransferMode,
|
|
} = require('internal/worker/js_transferable');
|
|
|
|
const {
|
|
createPromiseCallback,
|
|
customInspect,
|
|
extractHighWaterMark,
|
|
extractSizeAlgorithm,
|
|
isBrandCheck,
|
|
nonOpFlush,
|
|
kType,
|
|
kState,
|
|
nonOpCancel,
|
|
} = require('internal/webstreams/util');
|
|
|
|
const {
|
|
createReadableStream,
|
|
readableStreamDefaultControllerCanCloseOrEnqueue,
|
|
readableStreamDefaultControllerClose,
|
|
readableStreamDefaultControllerEnqueue,
|
|
readableStreamDefaultControllerError,
|
|
readableStreamDefaultControllerGetDesiredSize,
|
|
readableStreamDefaultControllerHasBackpressure,
|
|
} = require('internal/webstreams/readablestream');
|
|
|
|
const {
|
|
createWritableStream,
|
|
writableStreamDefaultControllerErrorIfNeeded,
|
|
} = require('internal/webstreams/writablestream');
|
|
|
|
const assert = require('internal/assert');
|
|
|
|
const kSkipThrow = Symbol('kSkipThrow');
|
|
|
|
const getNonWritablePropertyDescriptor = (value) => {
|
|
return {
|
|
__proto__: null,
|
|
configurable: true,
|
|
value,
|
|
};
|
|
};
|
|
|
|
/**
|
|
* @typedef {import('./queuingstrategies').QueuingStrategy
|
|
* } QueuingStrategy
|
|
* @typedef {import('./queuingstrategies').QueuingStrategySize
|
|
* } QueuingStrategySize
|
|
*/
|
|
|
|
/**
|
|
* @callback TransformerStartCallback
|
|
* @param {TransformStreamDefaultController} controller;
|
|
*/
|
|
|
|
/**
|
|
* @callback TransformerFlushCallback
|
|
* @param {TransformStreamDefaultController} controller;
|
|
* @returns {Promise<void>}
|
|
*/
|
|
|
|
/**
|
|
* @callback TransformerTransformCallback
|
|
* @param {any} chunk
|
|
* @param {TransformStreamDefaultController} controller
|
|
* @returns {Promise<void>}
|
|
*/
|
|
|
|
/**
|
|
* @typedef {{
|
|
* start? : TransformerStartCallback,
|
|
* transform? : TransformerTransformCallback,
|
|
* flush? : TransformerFlushCallback,
|
|
* readableType? : any,
|
|
* writableType? : any,
|
|
* }} Transformer
|
|
*/
|
|
|
|
class TransformStream {
|
|
[kType] = 'TransformStream';
|
|
|
|
/**
|
|
* @param {Transformer} [transformer]
|
|
* @param {QueuingStrategy} [writableStrategy]
|
|
* @param {QueuingStrategy} [readableStrategy]
|
|
*/
|
|
constructor(
|
|
transformer = kEmptyObject,
|
|
writableStrategy = kEmptyObject,
|
|
readableStrategy = kEmptyObject) {
|
|
markTransferMode(this, false, true);
|
|
validateObject(transformer, 'transformer', kValidateObjectAllowObjects);
|
|
validateObject(writableStrategy, 'writableStrategy', kValidateObjectAllowObjectsAndNull);
|
|
validateObject(readableStrategy, 'readableStrategy', kValidateObjectAllowObjectsAndNull);
|
|
const readableType = transformer?.readableType;
|
|
const writableType = transformer?.writableType;
|
|
const start = transformer?.start;
|
|
|
|
if (readableType !== undefined) {
|
|
throw new ERR_INVALID_ARG_VALUE.RangeError(
|
|
'transformer.readableType',
|
|
readableType);
|
|
}
|
|
if (writableType !== undefined) {
|
|
throw new ERR_INVALID_ARG_VALUE.RangeError(
|
|
'transformer.writableType',
|
|
writableType);
|
|
}
|
|
|
|
const readableHighWaterMark = readableStrategy?.highWaterMark;
|
|
const readableSize = readableStrategy?.size;
|
|
|
|
const writableHighWaterMark = writableStrategy?.highWaterMark;
|
|
const writableSize = writableStrategy?.size;
|
|
|
|
const actualReadableHighWaterMark =
|
|
extractHighWaterMark(readableHighWaterMark, 0);
|
|
const actualReadableSize = extractSizeAlgorithm(readableSize);
|
|
|
|
const actualWritableHighWaterMark =
|
|
extractHighWaterMark(writableHighWaterMark, 1);
|
|
const actualWritableSize = extractSizeAlgorithm(writableSize);
|
|
|
|
const startPromise = createDeferredPromise();
|
|
|
|
initializeTransformStream(
|
|
this,
|
|
startPromise,
|
|
actualWritableHighWaterMark,
|
|
actualWritableSize,
|
|
actualReadableHighWaterMark,
|
|
actualReadableSize);
|
|
|
|
setupTransformStreamDefaultControllerFromTransformer(this, transformer);
|
|
|
|
if (start !== undefined) {
|
|
startPromise.resolve(
|
|
FunctionPrototypeCall(
|
|
start,
|
|
transformer,
|
|
this[kState].controller));
|
|
} else {
|
|
startPromise.resolve();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @readonly
|
|
* @type {ReadableStream}
|
|
*/
|
|
get readable() {
|
|
if (!isTransformStream(this))
|
|
throw new ERR_INVALID_THIS('TransformStream');
|
|
return this[kState].readable;
|
|
}
|
|
|
|
/**
|
|
* @readonly
|
|
* @type {WritableStream}
|
|
*/
|
|
get writable() {
|
|
if (!isTransformStream(this))
|
|
throw new ERR_INVALID_THIS('TransformStream');
|
|
return this[kState].writable;
|
|
}
|
|
|
|
[kInspect](depth, options) {
|
|
return customInspect(depth, options, this[kType], {
|
|
readable: this.readable,
|
|
writable: this.writable,
|
|
backpressure: this[kState].backpressure,
|
|
});
|
|
}
|
|
|
|
[kTransfer]() {
|
|
if (!isTransformStream(this))
|
|
throw new ERR_INVALID_THIS('TransformStream');
|
|
const {
|
|
readable,
|
|
writable,
|
|
} = this[kState];
|
|
if (readable.locked) {
|
|
throw new DOMException(
|
|
'Cannot transfer a locked ReadableStream',
|
|
'DataCloneError');
|
|
}
|
|
if (writable.locked) {
|
|
throw new DOMException(
|
|
'Cannot transfer a locked WritableStream',
|
|
'DataCloneError');
|
|
}
|
|
return {
|
|
data: {
|
|
readable,
|
|
writable,
|
|
},
|
|
deserializeInfo:
|
|
'internal/webstreams/transformstream:TransferredTransformStream',
|
|
};
|
|
}
|
|
|
|
[kTransferList]() {
|
|
return [ this[kState].readable, this[kState].writable ];
|
|
}
|
|
|
|
[kDeserialize]({ readable, writable }) {
|
|
this[kState].readable = readable;
|
|
this[kState].writable = writable;
|
|
}
|
|
}
|
|
|
|
ObjectDefineProperties(TransformStream.prototype, {
|
|
readable: kEnumerableProperty,
|
|
writable: kEnumerableProperty,
|
|
[SymbolToStringTag]: getNonWritablePropertyDescriptor(TransformStream.name),
|
|
});
|
|
|
|
function InternalTransferredTransformStream() {
|
|
markTransferMode(this, false, true);
|
|
this[kType] = 'TransformStream';
|
|
this[kState] = {
|
|
__proto__: null,
|
|
readable: undefined,
|
|
writable: undefined,
|
|
backpressure: undefined,
|
|
backpressureChange: {
|
|
__proto__: null,
|
|
promise: undefined,
|
|
resolve: undefined,
|
|
reject: undefined,
|
|
},
|
|
controller: undefined,
|
|
};
|
|
}
|
|
|
|
ObjectSetPrototypeOf(InternalTransferredTransformStream.prototype, TransformStream.prototype);
|
|
ObjectSetPrototypeOf(InternalTransferredTransformStream, TransformStream);
|
|
|
|
function TransferredTransformStream() {
|
|
const stream = new InternalTransferredTransformStream();
|
|
|
|
stream.constructor = TransformStream;
|
|
|
|
return stream;
|
|
}
|
|
|
|
TransferredTransformStream.prototype[kDeserialize] = () => {};
|
|
|
|
class TransformStreamDefaultController {
|
|
[kType] = 'TransformStreamDefaultController';
|
|
|
|
constructor(skipThrowSymbol = undefined) {
|
|
if (skipThrowSymbol !== kSkipThrow) {
|
|
throw new ERR_ILLEGAL_CONSTRUCTOR();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @readonly
|
|
* @type {number}
|
|
*/
|
|
get desiredSize() {
|
|
if (!isTransformStreamDefaultController(this))
|
|
throw new ERR_INVALID_THIS('TransformStreamDefaultController');
|
|
const {
|
|
stream,
|
|
} = this[kState];
|
|
const {
|
|
readable,
|
|
} = stream[kState];
|
|
const {
|
|
controller: readableController,
|
|
} = readable[kState];
|
|
return readableStreamDefaultControllerGetDesiredSize(readableController);
|
|
}
|
|
|
|
/**
|
|
* @param {any} [chunk]
|
|
*/
|
|
enqueue(chunk = undefined) {
|
|
if (!isTransformStreamDefaultController(this))
|
|
throw new ERR_INVALID_THIS('TransformStreamDefaultController');
|
|
transformStreamDefaultControllerEnqueue(this, chunk);
|
|
}
|
|
|
|
/**
|
|
* @param {any} [reason]
|
|
*/
|
|
error(reason = undefined) {
|
|
if (!isTransformStreamDefaultController(this))
|
|
throw new ERR_INVALID_THIS('TransformStreamDefaultController');
|
|
transformStreamDefaultControllerError(this, reason);
|
|
}
|
|
|
|
terminate() {
|
|
if (!isTransformStreamDefaultController(this))
|
|
throw new ERR_INVALID_THIS('TransformStreamDefaultController');
|
|
transformStreamDefaultControllerTerminate(this);
|
|
}
|
|
|
|
[kInspect](depth, options) {
|
|
return customInspect(depth, options, this[kType], {
|
|
stream: this[kState].stream,
|
|
});
|
|
}
|
|
}
|
|
|
|
ObjectDefineProperties(TransformStreamDefaultController.prototype, {
|
|
desiredSize: kEnumerableProperty,
|
|
enqueue: kEnumerableProperty,
|
|
error: kEnumerableProperty,
|
|
terminate: kEnumerableProperty,
|
|
[SymbolToStringTag]: getNonWritablePropertyDescriptor(TransformStreamDefaultController.name),
|
|
});
|
|
|
|
const isTransformStream =
|
|
isBrandCheck('TransformStream');
|
|
const isTransformStreamDefaultController =
|
|
isBrandCheck('TransformStreamDefaultController');
|
|
|
|
async function defaultTransformAlgorithm(chunk, controller) {
|
|
transformStreamDefaultControllerEnqueue(controller, chunk);
|
|
}
|
|
|
|
function initializeTransformStream(
|
|
stream,
|
|
startPromise,
|
|
writableHighWaterMark,
|
|
writableSizeAlgorithm,
|
|
readableHighWaterMark,
|
|
readableSizeAlgorithm) {
|
|
|
|
const startAlgorithm = () => startPromise.promise;
|
|
|
|
const writable = createWritableStream(
|
|
startAlgorithm,
|
|
(chunk) => transformStreamDefaultSinkWriteAlgorithm(stream, chunk),
|
|
() => transformStreamDefaultSinkCloseAlgorithm(stream),
|
|
(reason) => transformStreamDefaultSinkAbortAlgorithm(stream, reason),
|
|
writableHighWaterMark,
|
|
writableSizeAlgorithm,
|
|
);
|
|
|
|
const readable = createReadableStream(
|
|
startAlgorithm,
|
|
() => transformStreamDefaultSourcePullAlgorithm(stream),
|
|
(reason) => transformStreamDefaultSourceCancelAlgorithm(stream, reason),
|
|
readableHighWaterMark,
|
|
readableSizeAlgorithm,
|
|
);
|
|
|
|
stream[kState] = {
|
|
__proto__: null,
|
|
readable,
|
|
writable,
|
|
controller: undefined,
|
|
backpressure: undefined,
|
|
backpressureChange: {
|
|
__proto__: null,
|
|
promise: undefined,
|
|
resolve: undefined,
|
|
reject: undefined,
|
|
},
|
|
};
|
|
|
|
transformStreamSetBackpressure(stream, true);
|
|
}
|
|
|
|
function transformStreamError(stream, error) {
|
|
const {
|
|
readable,
|
|
} = stream[kState];
|
|
const {
|
|
controller,
|
|
} = readable[kState];
|
|
readableStreamDefaultControllerError(controller, error);
|
|
transformStreamErrorWritableAndUnblockWrite(stream, error);
|
|
}
|
|
|
|
function transformStreamErrorWritableAndUnblockWrite(stream, error) {
|
|
const {
|
|
controller,
|
|
writable,
|
|
} = stream[kState];
|
|
transformStreamDefaultControllerClearAlgorithms(controller);
|
|
writableStreamDefaultControllerErrorIfNeeded(
|
|
writable[kState].controller,
|
|
error);
|
|
transformStreamUnblockWrite(stream);
|
|
}
|
|
|
|
function transformStreamUnblockWrite(stream) {
|
|
if (stream[kState].backpressure)
|
|
transformStreamSetBackpressure(stream, false);
|
|
}
|
|
|
|
function transformStreamSetBackpressure(stream, backpressure) {
|
|
assert(stream[kState].backpressure !== backpressure);
|
|
if (stream[kState].backpressureChange.promise !== undefined)
|
|
stream[kState].backpressureChange.resolve?.();
|
|
stream[kState].backpressureChange = createDeferredPromise();
|
|
stream[kState].backpressure = backpressure;
|
|
}
|
|
|
|
function setupTransformStreamDefaultController(
|
|
stream,
|
|
controller,
|
|
transformAlgorithm,
|
|
flushAlgorithm,
|
|
cancelAlgorithm) {
|
|
assert(isTransformStream(stream));
|
|
assert(stream[kState].controller === undefined);
|
|
controller[kState] = {
|
|
__proto__: null,
|
|
stream,
|
|
transformAlgorithm,
|
|
flushAlgorithm,
|
|
cancelAlgorithm,
|
|
};
|
|
stream[kState].controller = controller;
|
|
}
|
|
|
|
function setupTransformStreamDefaultControllerFromTransformer(
|
|
stream,
|
|
transformer) {
|
|
const controller = new TransformStreamDefaultController(kSkipThrow);
|
|
const transform = transformer?.transform;
|
|
const flush = transformer?.flush;
|
|
const cancel = transformer?.cancel;
|
|
const transformAlgorithm = transform ?
|
|
createPromiseCallback('transformer.transform', transform, transformer) :
|
|
defaultTransformAlgorithm;
|
|
const flushAlgorithm = flush ?
|
|
createPromiseCallback('transformer.flush', flush, transformer) :
|
|
nonOpFlush;
|
|
const cancelAlgorithm = cancel ?
|
|
createPromiseCallback('transformer.cancel', cancel, transformer) :
|
|
nonOpCancel;
|
|
|
|
setupTransformStreamDefaultController(
|
|
stream,
|
|
controller,
|
|
transformAlgorithm,
|
|
flushAlgorithm,
|
|
cancelAlgorithm);
|
|
}
|
|
|
|
function transformStreamDefaultControllerClearAlgorithms(controller) {
|
|
controller[kState].transformAlgorithm = undefined;
|
|
controller[kState].flushAlgorithm = undefined;
|
|
controller[kState].cancelAlgorithm = undefined;
|
|
}
|
|
|
|
function transformStreamDefaultControllerEnqueue(controller, chunk) {
|
|
const {
|
|
stream,
|
|
} = controller[kState];
|
|
const {
|
|
readable,
|
|
} = stream[kState];
|
|
const {
|
|
controller: readableController,
|
|
} = readable[kState];
|
|
if (!readableStreamDefaultControllerCanCloseOrEnqueue(readableController))
|
|
throw new ERR_INVALID_STATE.TypeError('Unable to enqueue');
|
|
try {
|
|
readableStreamDefaultControllerEnqueue(readableController, chunk);
|
|
} catch (error) {
|
|
transformStreamErrorWritableAndUnblockWrite(stream, error);
|
|
throw readable[kState].storedError;
|
|
}
|
|
const backpressure =
|
|
readableStreamDefaultControllerHasBackpressure(readableController);
|
|
if (backpressure !== stream[kState].backpressure) {
|
|
assert(backpressure);
|
|
transformStreamSetBackpressure(stream, true);
|
|
}
|
|
}
|
|
|
|
function transformStreamDefaultControllerError(controller, error) {
|
|
transformStreamError(controller[kState].stream, error);
|
|
}
|
|
|
|
async function transformStreamDefaultControllerPerformTransform(controller, chunk) {
|
|
try {
|
|
return await controller[kState].transformAlgorithm(chunk, controller);
|
|
} catch (error) {
|
|
transformStreamError(controller[kState].stream, error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
function transformStreamDefaultControllerTerminate(controller) {
|
|
const {
|
|
stream,
|
|
} = controller[kState];
|
|
const {
|
|
readable,
|
|
} = stream[kState];
|
|
assert(readable !== undefined);
|
|
const {
|
|
controller: readableController,
|
|
} = readable[kState];
|
|
readableStreamDefaultControllerClose(readableController);
|
|
transformStreamErrorWritableAndUnblockWrite(
|
|
stream,
|
|
new ERR_INVALID_STATE.TypeError('TransformStream has been terminated'));
|
|
}
|
|
|
|
function transformStreamDefaultSinkWriteAlgorithm(stream, chunk) {
|
|
const {
|
|
writable,
|
|
controller,
|
|
} = stream[kState];
|
|
assert(writable[kState].state === 'writable');
|
|
if (stream[kState].backpressure) {
|
|
const backpressureChange = stream[kState].backpressureChange.promise;
|
|
return PromisePrototypeThen(
|
|
backpressureChange,
|
|
() => {
|
|
const {
|
|
writable,
|
|
} = stream[kState];
|
|
if (writable[kState].state === 'erroring')
|
|
throw writable[kState].storedError;
|
|
assert(writable[kState].state === 'writable');
|
|
return transformStreamDefaultControllerPerformTransform(
|
|
controller,
|
|
chunk);
|
|
});
|
|
}
|
|
return transformStreamDefaultControllerPerformTransform(controller, chunk);
|
|
}
|
|
|
|
async function transformStreamDefaultSinkAbortAlgorithm(stream, reason) {
|
|
const {
|
|
controller,
|
|
readable,
|
|
} = stream[kState];
|
|
|
|
if (controller[kState].finishPromise !== undefined) {
|
|
return controller[kState].finishPromise;
|
|
}
|
|
|
|
const { promise, resolve, reject } = createDeferredPromise();
|
|
controller[kState].finishPromise = promise;
|
|
const cancelPromise = controller[kState].cancelAlgorithm(reason);
|
|
transformStreamDefaultControllerClearAlgorithms(controller);
|
|
|
|
PromisePrototypeThen(
|
|
cancelPromise,
|
|
() => {
|
|
if (readable[kState].state === 'errored')
|
|
reject(readable[kState].storedError);
|
|
else {
|
|
readableStreamDefaultControllerError(readable[kState].controller, reason);
|
|
resolve();
|
|
}
|
|
},
|
|
(error) => {
|
|
readableStreamDefaultControllerError(readable[kState].controller, error);
|
|
reject(error);
|
|
},
|
|
);
|
|
|
|
return controller[kState].finishPromise;
|
|
}
|
|
|
|
function transformStreamDefaultSinkCloseAlgorithm(stream) {
|
|
const {
|
|
readable,
|
|
controller,
|
|
} = stream[kState];
|
|
|
|
if (controller[kState].finishPromise !== undefined) {
|
|
return controller[kState].finishPromise;
|
|
}
|
|
const { promise, resolve, reject } = createDeferredPromise();
|
|
controller[kState].finishPromise = promise;
|
|
const flushPromise = controller[kState].flushAlgorithm(controller);
|
|
transformStreamDefaultControllerClearAlgorithms(controller);
|
|
PromisePrototypeThen(
|
|
flushPromise,
|
|
() => {
|
|
if (readable[kState].state === 'errored')
|
|
reject(readable[kState].storedError);
|
|
else {
|
|
readableStreamDefaultControllerClose(readable[kState].controller);
|
|
resolve();
|
|
}
|
|
},
|
|
(error) => {
|
|
readableStreamDefaultControllerError(readable[kState].controller, error);
|
|
reject(error);
|
|
});
|
|
return controller[kState].finishPromise;
|
|
}
|
|
|
|
function transformStreamDefaultSourcePullAlgorithm(stream) {
|
|
assert(stream[kState].backpressure);
|
|
assert(stream[kState].backpressureChange.promise !== undefined);
|
|
transformStreamSetBackpressure(stream, false);
|
|
return stream[kState].backpressureChange.promise;
|
|
}
|
|
|
|
function transformStreamDefaultSourceCancelAlgorithm(stream, reason) {
|
|
const {
|
|
controller,
|
|
writable,
|
|
} = stream[kState];
|
|
|
|
if (controller[kState].finishPromise !== undefined) {
|
|
return controller[kState].finishPromise;
|
|
}
|
|
|
|
const { promise, resolve, reject } = createDeferredPromise();
|
|
controller[kState].finishPromise = promise;
|
|
const cancelPromise = controller[kState].cancelAlgorithm(reason);
|
|
transformStreamDefaultControllerClearAlgorithms(controller);
|
|
|
|
PromisePrototypeThen(
|
|
cancelPromise,
|
|
() => {
|
|
if (writable[kState].state === 'errored')
|
|
reject(writable[kState].storedError);
|
|
else {
|
|
writableStreamDefaultControllerErrorIfNeeded(
|
|
writable[kState].controller,
|
|
reason);
|
|
transformStreamUnblockWrite(stream);
|
|
resolve();
|
|
}
|
|
},
|
|
(error) => {
|
|
writableStreamDefaultControllerErrorIfNeeded(
|
|
writable[kState].controller,
|
|
error);
|
|
transformStreamUnblockWrite(stream);
|
|
reject(error);
|
|
},
|
|
);
|
|
|
|
return controller[kState].finishPromise;
|
|
}
|
|
|
|
module.exports = {
|
|
TransformStream,
|
|
TransformStreamDefaultController,
|
|
TransferredTransformStream,
|
|
|
|
// Exported Brand Checks
|
|
isTransformStream,
|
|
isTransformStreamDefaultController,
|
|
};
|