Files
pubsub/docs/architecture/event-targets/worker.md
glm-5.1 371dabc20d Add per-adapter architecture docs in event-targets/ directory
- Create docs/architecture/event-targets/ with individual specs:
  in-process, redis, websocket-client, websocket-server,
  worker, iroh-spoke, iroh-hub
- Update event-targets.md to serve as index with topology model
  (symmetric vs fan-out) and adapter status table
- Update architecture.md index to reference new directory
2026-05-07 14:49:50 +00:00

4.6 KiB

status, last_updated
status last_updated
draft 2026-05-07

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

// Main thread — wraps a Worker instance
function createWorkerEventTarget<TEvent extends TypedEvent>(
  worker: Worker,
): TypedEventTarget<TEvent>;

// Worker thread — wraps parent message port
function createMainThreadEventTarget<TEvent extends TypedEvent>(): TypedEventTarget<TEvent>;

Protocol

Worker messages use the EventEnvelope format over postMessage:

{ "type": "call.responded", "id": "uuid-123", "payload": { "output": 42 } }

Main Thread → Worker

dispatchEvent(event) {
  this.worker.postMessage(event.detail);
  // event.detail is the EventEnvelope
  return true;
}

Worker → Main Thread

dispatchEvent(event) {
  globalThis.postMessage(event.detail);
  return true;
}

Receiving

// Main thread side
this.worker.onmessage = (msg) => {
  const envelope = msg.data;
  const topic = `${envelope.type}:${envelope.id}`;
  const event = new CustomEvent(topic, { detail: envelope });
  // dispatch to listeners
};

// Worker thread side
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 adaptersevent-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