UNPKG

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
"use strict"; 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; }