web-streams-extensions
Version:
A comprehensive collection of helper methods for WebStreams with built-in backpressure support, inspired by ReactiveExtensions
169 lines (168 loc) • 6.29 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.onStream = onStream;
exports._resetWorker = _resetWorker;
const router_js_1 = require("./router.cjs");
const streams_js_1 = require("./streams.cjs");
const protocol_js_1 = require("./protocol.cjs");
const transferables_js_1 = require("./transferables.cjs");
// Worker-side message router that extends base router
class WorkerMessageRouter extends router_js_1.MessageRouter {
postMessage;
transferHandler;
constructor(postMessage, getTransferables) {
super();
this.postMessage = postMessage;
this.transferHandler = (0, transferables_js_1.createTransferHandler)(getTransferables || transferables_js_1.defaultGetTransferables);
this.setupHandlers();
}
setupHandlers() {
this.on('id-request', (msg) => this.handleIdRequest(msg));
this.on('stream-request', (msg) => this.handleStreamRequest(msg));
this.on('data-to-worker', (msg) => this.handleDataToWorker(msg));
this.on('cancel', (msg) => this.handleCancel(msg));
}
handleIdRequest(msg) {
// Generate a new stream ID and send it back
const streamId = this.generateId();
this.postMessage({
type: 'id-response',
requestId: msg.requestId,
streamId
});
}
handleStreamRequest(msg) {
const streamInfo = this.registerStream(msg.id, msg.name);
this.updateState(msg.id, protocol_js_1.StreamState.Requested);
// Track whether accept() or reject() has been called
let responded = false;
// Create stream request object
const request = {
name: msg.name,
accept: (options) => {
if (responded) {
throw new Error('Cannot call accept() after stream has been accepted or rejected');
}
responded = true;
this.updateState(msg.id, protocol_js_1.StreamState.Accepted);
// Send accept message
this.postMessage({
id: msg.id,
type: 'stream-accept'
});
// Use stream-specific getTransferables if provided, otherwise use global
const streamGetTransferables = options?.getTransferables ||
((value) => this.getTransferables(value));
// Create worker streams with the stream-specific getTransferables
const readableWrapper = new streams_js_1.WorkerReadableStream(msg.id, (msg) => {
this.postMessage(msg, msg.transferList);
});
const writableWrapper = new streams_js_1.WorkerWritableStream(msg.id, (msg) => {
this.postMessage(msg, msg.transferList);
}, streamGetTransferables);
// Store stream wrappers for data routing
streamInfo.readableWrapper = readableWrapper;
streamInfo.writableWrapper = writableWrapper;
streamInfo.cleanup = () => {
// Cleanup logic if needed
};
this.updateState(msg.id, protocol_js_1.StreamState.Active);
return {
readable: readableWrapper.createStream(),
writable: writableWrapper.createStream(),
};
},
reject: (reason) => {
if (responded) {
throw new Error('Cannot call reject() after stream has been accepted or rejected');
}
responded = true;
this.updateState(msg.id, protocol_js_1.StreamState.Rejected);
// Send reject message
this.postMessage({
id: msg.id,
type: 'stream-reject',
reason
});
this.closeStream(msg.id);
}
};
// Trigger user handler
if (workerStreamHandler) {
try {
workerStreamHandler(request);
}
catch (error) {
request.reject(error.message || 'Stream handler error');
}
}
else {
request.reject('No stream handler registered');
}
}
handleDataToWorker(msg) {
const streamInfo = this.getStream(msg.id);
if (!streamInfo) {
return;
}
// Route data to the readable stream wrapper
const readableWrapper = streamInfo.readableWrapper;
if (readableWrapper) {
readableWrapper.handleData(msg);
}
}
handleCancel(msg) {
this.closeStream(msg.id);
}
sendError(id, error) {
this.postMessage({
id,
type: 'error',
error: error.message || error
});
}
// Get transferables for a value
getTransferables(value) {
return this.transferHandler.getTransferables(value);
}
}
// Global router instance for worker
let workerRouter = null;
let workerStreamHandler = null;
// Initialize worker message handling
function initializeWorker(getTransferables) {
if (workerRouter)
return; // Already initialized
workerRouter = new WorkerMessageRouter((msg, transferList) => {
if (typeof self !== 'undefined' && self.postMessage) {
if (transferList && transferList.length > 0) {
self.postMessage(msg, transferList);
}
else {
self.postMessage(msg);
}
}
}, getTransferables);
// Listen for messages from main thread
if (typeof self !== 'undefined') {
self.onmessage = (event) => {
const msg = event.data;
if (msg && typeof msg === 'object' && 'type' in msg) {
workerRouter.route(msg);
}
};
}
}
// Public API: Register stream handler
function onStream(handler, getTransferables) {
workerStreamHandler = handler;
initializeWorker(getTransferables);
}
// Cleanup function for testing
function _resetWorker() {
if (workerRouter) {
workerRouter.cleanup();
}
workerRouter = null;
workerStreamHandler = null;
}