UNPKG

nstdlib-nightly

Version:

Node.js standard library converted to runtime-agnostic ES modules.

298 lines (269 loc) 8.09 kB
// Source: https://github.com/nodejs/node/blob/65eff1eb/lib/internal/webstreams/transfer.js import { kState, setPromiseHandled, } from "nstdlib/lib/internal/webstreams/util"; import { DOMException } from "nstdlib/stub/binding/messaging"; import { ReadableStream, readableStreamDefaultControllerEnqueue, readableStreamDefaultControllerClose, readableStreamDefaultControllerError, readableStreamPipeTo, } from "nstdlib/lib/internal/webstreams/readablestream"; import { WritableStream, writableStreamDefaultControllerErrorIfNeeded, } from "nstdlib/lib/internal/webstreams/writablestream"; import { createDeferredPromise } from "nstdlib/lib/internal/util"; import * as assert from "nstdlib/lib/internal/assert"; import { markTransferMode, kClone, kDeserialize, } from "nstdlib/lib/internal/worker/js_transferable"; // This class is a bit of a hack. The Node.js implementation of // DOMException is not transferable/cloneable. This provides us // with a variant that is. Unfortunately, it means playing around // a bit with the message, name, and code properties and the // prototype. We can revisit this if DOMException is ever made // properly cloneable. class CloneableDOMException extends DOMException { constructor(message, name) { super(message, name); markTransferMode(this, true, false); this[kDeserialize]({ message: this.message, name: this.name, code: this.code, }); } [kClone]() { return { data: { message: this.message, name: this.name, code: this.code, }, deserializeInfo: "internal/webstreams/transfer:InternalCloneableDOMException", }; } [kDeserialize]({ message, name, code }) { Object.defineProperties(this, { message: { __proto__: null, configurable: true, enumerable: true, get() { return message; }, }, name: { __proto__: null, configurable: true, enumerable: true, get() { return name; }, }, code: { __proto__: null, configurable: true, enumerable: true, get() { return code; }, }, }); } } function InternalCloneableDOMException() { return Reflect.construct(CloneableDOMException, [], DOMException); } InternalCloneableDOMException[kDeserialize] = () => {}; class CrossRealmTransformReadableSource { constructor(port, unref) { this[kState] = { port, controller: undefined, unref, }; port.onmessage = ({ data }) => { const { controller } = this[kState]; const { type, value } = data; switch (type) { case "chunk": readableStreamDefaultControllerEnqueue(controller, value); break; case "close": readableStreamDefaultControllerClose(controller); port.close(); break; case "error": readableStreamDefaultControllerError(controller, value); port.close(); break; } }; port.onmessageerror = () => { const error = new CloneableDOMException( "Internal transferred ReadableStream error", "DataCloneError", ); port.postMessage({ type: "error", value: error }); readableStreamDefaultControllerError(this[kState].controller, error); port.close(); }; port.unref(); } start(controller) { this[kState].controller = controller; } async pull() { if (this[kState].unref) { this[kState].unref = false; this[kState].port.ref(); } this[kState].port.postMessage({ type: "pull" }); } async cancel(reason) { try { this[kState].port.postMessage({ type: "error", value: reason }); } catch (error) { if (error instanceof DOMException) { // eslint-disable-next-line no-ex-assign error = new CloneableDOMException(error.message, error.name); } this[kState].port.postMessage({ type: "error", value: error }); throw error; } finally { this[kState].port.close(); } } } class CrossRealmTransformWritableSink { constructor(port, unref) { this[kState] = { port, controller: undefined, backpressurePromise: createDeferredPromise(), unref, }; port.onmessage = ({ data }) => { assert(typeof data === "object"); const { type, value } = { ...data }; assert(typeof type === "string"); switch (type) { case "pull": if (this[kState].backpressurePromise !== undefined) this[kState].backpressurePromise.resolve?.(); this[kState].backpressurePromise = undefined; break; case "error": writableStreamDefaultControllerErrorIfNeeded( this[kState].controller, value, ); if (this[kState].backpressurePromise !== undefined) this[kState].backpressurePromise.resolve?.(); this[kState].backpressurePromise = undefined; break; } }; port.onmessageerror = () => { const error = new CloneableDOMException( "Internal transferred ReadableStream error", "DataCloneError", ); port.postMessage({ type: "error", value: error }); writableStreamDefaultControllerErrorIfNeeded( this[kState].controller, error, ); port.close(); }; port.unref(); } start(controller) { this[kState].controller = controller; } async write(chunk) { if (this[kState].unref) { this[kState].unref = false; this[kState].port.ref(); } if (this[kState].backpressurePromise === undefined) { this[kState].backpressurePromise = { promise: Promise.resolve(), resolve: undefined, reject: undefined, }; } await this[kState].backpressurePromise.promise; this[kState].backpressurePromise = createDeferredPromise(); try { this[kState].port.postMessage({ type: "chunk", value: chunk }); } catch (error) { if (error instanceof DOMException) { // eslint-disable-next-line no-ex-assign error = new CloneableDOMException(error.message, error.name); } this[kState].port.postMessage({ type: "error", value: error }); this[kState].port.close(); throw error; } } close() { this[kState].port.postMessage({ type: "close" }); this[kState].port.close(); } abort(reason) { try { this[kState].port.postMessage({ type: "error", value: reason }); } catch (error) { if (error instanceof DOMException) { // eslint-disable-next-line no-ex-assign error = new CloneableDOMException(error.message, error.name); } this[kState].port.postMessage({ type: "error", value: error }); throw error; } finally { this[kState].port.close(); } } } function newCrossRealmReadableStream(writable, port) { // MessagePort should always be unref. // There is a problem with the process not terminating. // https://github.com/nodejs/node/issues/44985 const readable = new ReadableStream( new CrossRealmTransformReadableSource(port, false), ); const promise = readableStreamPipeTo(readable, writable, false, false, false); setPromiseHandled(promise); return { readable, promise, }; } function newCrossRealmWritableSink(readable, port) { // MessagePort should always be unref. // There is a problem with the process not terminating. // https://github.com/nodejs/node/issues/44985 const writable = new WritableStream( new CrossRealmTransformWritableSink(port, false), ); const promise = readableStreamPipeTo(readable, writable, false, false, false); setPromiseHandled(promise); return { writable, promise, }; } export { newCrossRealmReadableStream }; export { newCrossRealmWritableSink }; export { CrossRealmTransformWritableSink }; export { CrossRealmTransformReadableSource }; export { CloneableDOMException }; export { InternalCloneableDOMException };