nstdlib-nightly
Version:
Node.js standard library converted to runtime-agnostic ES modules.
665 lines (589 loc) • 18.8 kB
JavaScript
// Source: https://github.com/nodejs/node/blob/65eff1eb/lib/internal/webstreams/transformstream.js
import { codes as __codes__ } from "nstdlib/lib/internal/errors";
import { DOMException } from "nstdlib/stub/binding/messaging";
import {
createDeferredPromise,
customInspectSymbol as kInspect,
kEmptyObject,
kEnumerableProperty,
} from "nstdlib/lib/internal/util";
import {
validateObject,
kValidateObjectAllowObjects,
kValidateObjectAllowObjectsAndNull,
} from "nstdlib/lib/internal/validators";
import {
kDeserialize,
kTransfer,
kTransferList,
markTransferMode,
} from "nstdlib/lib/internal/worker/js_transferable";
import {
createPromiseCallback,
customInspect,
extractHighWaterMark,
extractSizeAlgorithm,
isBrandCheck,
nonOpFlush,
kType,
kState,
nonOpCancel,
} from "nstdlib/lib/internal/webstreams/util";
import {
createReadableStream,
readableStreamDefaultControllerCanCloseOrEnqueue,
readableStreamDefaultControllerClose,
readableStreamDefaultControllerEnqueue,
readableStreamDefaultControllerError,
readableStreamDefaultControllerGetDesiredSize,
readableStreamDefaultControllerHasBackpressure,
} from "nstdlib/lib/internal/webstreams/readablestream";
import {
createWritableStream,
writableStreamDefaultControllerErrorIfNeeded,
} from "nstdlib/lib/internal/webstreams/writablestream";
import * as assert from "nstdlib/lib/internal/assert";
const {
ERR_ILLEGAL_CONSTRUCTOR,
ERR_INVALID_ARG_VALUE,
ERR_INVALID_STATE,
ERR_INVALID_THIS,
} = __codes__;
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(
Function.prototype.call.call(
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;
}
}
Object.defineProperties(TransformStream.prototype, {
readable: kEnumerableProperty,
writable: kEnumerableProperty,
[Symbol.toStringTag]: 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,
};
}
Object.setPrototypeOf(
InternalTransferredTransformStream.prototype,
TransformStream.prototype,
);
Object.setPrototypeOf(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,
});
}
}
Object.defineProperties(TransformStreamDefaultController.prototype, {
desiredSize: kEnumerableProperty,
enqueue: kEnumerableProperty,
error: kEnumerableProperty,
terminate: kEnumerableProperty,
[Symbol.toStringTag]: 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 Promise.prototype.then.call(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);
Promise.prototype.then.call(
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);
Promise.prototype.then.call(
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);
Promise.prototype.then.call(
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;
}
export { TransformStream };
export { TransformStreamDefaultController };
export { TransferredTransformStream };
export { isTransformStream };
export { isTransformStreamDefaultController };