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

127 lines
4.6 KiB
Markdown

---
status: draft
last_updated: 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
```ts
// 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`:
```json
{ "type": "call.responded", "id": "uuid-123", "payload": { "output": 42 } }
```
### Main Thread → Worker
```ts
dispatchEvent(event) {
this.worker.postMessage(event.detail);
// event.detail is the EventEnvelope
return true;
}
```
### Worker → Main Thread
```ts
dispatchEvent(event) {
globalThis.postMessage(event.detail);
return true;
}
```
### Receiving
```ts
// 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 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