--- status: draft last_updated: 2026-05-08 --- # Worker Event Target **Import**: `@alkdev/pubsub/event-target-worker` **Peer dep**: none (Web Worker / Node worker_threads are standard) **Status**: Not yet implemented. Needs R&D on Node vs Web Worker API differences. Enables `createPubSub` to work across Worker boundaries. Two factory functions: one for the main thread side, one for the worker thread side. ## API ```ts // Main thread side — wraps a Worker instance function createWorkerHostEventTarget( worker: Worker, ): TypedEventTarget; // Worker thread side — wraps parent message port function createWorkerThreadEventTarget(): TypedEventTarget; ``` The naming convention: `Host` is the side that owns the `Worker` object (typically the main thread). `Thread` is the side that runs inside the worker (accessing `self.onmessage` / `parentPort`). ## Protocol Worker messages use the `EventEnvelope` format over `postMessage`: ```json { "type": "call.responded", "id": "uuid-123", "payload": { "output": 42 } } ``` ### Host → Worker Thread ```ts // Host side (createWorkerHostEventTarget) dispatchEvent(event) { this.worker.postMessage(event.detail); // event.detail is the EventEnvelope return true; } ``` ### Worker Thread → Host ```ts // Worker thread side (createWorkerThreadEventTarget) dispatchEvent(event) { globalThis.postMessage(event.detail); return true; } ``` ### Receiving on Host Side ```ts // Host side (createWorkerHostEventTarget) this.worker.onmessage = (msg) => { const envelope = msg.data; const topic = `${envelope.type}:${envelope.id}`; const event = new CustomEvent(topic, { detail: envelope }); // dispatch to listeners }; ### Receiving on Worker Thread Side ```ts // Worker thread side (createWorkerThreadEventTarget) globalThis.onmessage = (msg) => { const envelope = msg.data; const topic = `${envelope.type}:${envelope.id}`; const event = new CustomEvent(topic, { detail: envelope }); // dispatch to listeners }; ``` ## Key Properties - **Bidirectional** — both sides can publish and subscribe - **Per-worker** — each worker gets its own event target on the main thread side - **Structured clone** — Web Workers use structured clone for serialization, but the JSON-serializable `EventEnvelope` ensures cross-platform compatibility - **No native deps** — works in any environment with Worker support ## Open Questions / R&D Needed ### Node vs Web Worker API The APIs differ significantly: | Feature | Web Worker | Node `worker_threads` | |---------|-----------|----------------------| | Create | `new Worker(url)` | `new Worker(path)` | | Send | `worker.postMessage(msg)` | `worker.postMessage(msg)` | | Receive | `worker.onmessage` | `worker.on('message')` | | Worker send | `self.postMessage(msg)` | `parentPort.postMessage(msg)` | | Worker receive | `self.onmessage` | `parentPort.on('message')` | | Transfer | `postMessage(msg, [transfer])` | `postMessage(msg, [transferList])` | | `MessagePort` | No built-in | Yes — `MessageChannel` for direct ports | Options: 1. **Two adapters** — `event-target-web-worker` and `event-target-node-worker` 2. **One adapter with runtime detection** — detect environment and use appropriate API 3. **One adapter abstracting both** — wrap the differences behind a common interface Recommendation: Start with a single adapter that targets Web Workers (browser + Deno + Bun all support this API). Add Node `worker_threads` support later if needed, potentially with a `MessagePort`-based approach for direct channels. ### Worker Pool Pattern The original sandbox implementation used a worker pool pattern. A `WorkerPoolManager` would: 1. Maintain a pool of workers 2. Assign tasks to available workers 3. Collect results and fan out to subscribers This is **not** part of the `WorkerEventTarget` — it's a downstream concern for `@alkdev/operations`. The event target just wraps a single `postMessage`/`onmessage` channel. Pool management belongs higher. ### Transferable Objects Web Workers support `Transferable` objects (ArrayBuffers, etc.) for zero-copy transfer. The current `EventEnvelope` is JSON, which gets structured-cloned. If large payloads need zero-copy transfer, the envelope could support a `Transferable` field, but this adds complexity and is not needed for the initial implementation. ## Relationship to Downstream Workers can subscribe to events and publish results through the event target, allowing `@alkdev/operations` to dispatch work to worker threads via the same pubsub transport. The correlation (`id` field in the envelope) connects request to response. ## Test Plan 1. **Main → Worker send** — dispatchEvent from main posts message to worker 2. **Worker → Main send** — dispatchEvent from worker posts message to main 3. **Bidirectional** — both sides can subscribe and publish 4. **Topic scoping** — type:id topics correctly formed 5. **Envelope round-trip** — full envelope survives serialization 6. **Worker termination** — cleanup when worker exits