6.7 KiB
OpenSpec Change: enterprise-browser-web-workers
- Change ID: enterprise-browser-web-workers
- Package:
@suaveplan/web-workers - Category: browser
- Target path:
browser/platform/web-workers - Status: IN PROGRESS
- Date: 2026-04-24
- Reference spec:
/mnt/s/web/suaveplan/enterprise/web-workers.md
Summary
Deliver @suaveplan/web-workers on the genesis branch at enterprise quality. The package provides WorkerManager and MessageBus. Genesis ships the two advertised-but-missing features: SharedWorkerManager (separate SharedWorker lifecycle + per-port wiring) and WorkerPool (FIFO task queue, round-robin dispatch, min/max idle, health checks, backpressure). Also adds: AbortSignal cancellation on sendRequest/MessageBus.request, transfer-list support, @suaveplan/events runtime wiring for WorkerManagerEventMap/MessageBusEventMap, circuit breaker, streaming RPC, defineWorker() Vite helper, and OTel metrics.
Current-state audit (genesis)
Fresh build — does not exist on genesis yet.
Available workspace deps on genesis
| Package | Role |
|---|---|
@suaveplan/events (workspace:^) |
Runtime event emission for WorkerManagerEventMap |
@suaveplan/error (workspace:^) |
Typed worker errors |
@suaveplan/types (workspace:^) |
LoggerLike |
@suaveplan/logger (workspace:^) |
Structured logging |
@suaveplan/schema-registry (workspace:^) |
Worker message schema |
zod (catalog:forms) |
WorkerMessage Zod schema |
@opentelemetry/api (catalog:observability) |
Message counters, RPC latency |
tslib (catalog:build) |
Runtime helpers |
Dev: vitest, @vitest/coverage-istanbul, happy-dom, @suaveplan/testing.
Preserved public API surface
class WorkerManager {
constructor(factory?: WorkerFactory)
create(name: string, url: string | URL, options?: WorkerConfig): Worker
postMessage(name: string, type: string, payload: unknown, options?: { transfer?: Transferable[] }): void
sendRequest<T>(name: string, type: string, payload: unknown, options?: RequestOptions): Promise<T>
terminate(name: string): void
terminateAll(): void
has(name: string): boolean
getStatus(name: string): WorkerStatus
getWorkerNames(): string[]
onError(name: string, cb: (e: ErrorEvent) => void): () => void
}
class SharedWorkerManager { // new
constructor(factory?: SharedWorkerFactory)
connect(name: string, url: string | URL): MessagePort
disconnect(name: string): void
sendRequest<T>(name: string, type: string, payload: unknown, options?: RequestOptions): Promise<T>
}
class WorkerPool { // new — implements WorkerPoolConfig
constructor(config: WorkerPoolConfig)
submit<T>(task: WorkerTask<T>): Promise<T>
resize(min: number, max: number): void
drain(): Promise<void>
terminate(): void
getMetrics(): PoolMetrics
}
class MessageBus {
subscribe<T>(topic: string, handler: MessageHandler<T>): () => void
subscribeOnce<T>(topic: string, handler: MessageHandler<T>): void
publish<T>(topic: string, payload: T): void
broadcast<T>(payload: T): void
request<T, R>(topic: string, payload: T, options?: RequestOptions): Promise<R>
registerHandler<T, R>(topic: string, handler: (payload: T) => R | Promise<R>): () => void
getSubscriberCount(topic: string): number
getTopics(): string[]
}
// Vite helper
function defineWorker(url: string, options?: WorkerOptions): Worker
Subpath exports: ".", "./worker-manager", "./shared-worker-manager", "./worker-pool", "./message-bus", "./helpers/vite-worker".
New additions for genesis
1. SharedWorkerManager
src/shared-worker-manager/ — SharedWorkerManager extends BaseWorkerManager; connects via new SharedWorker(url); manages one MessagePort per named connection.
2. WorkerPool
src/worker-pool/ — WorkerPool implementing WorkerPoolConfig; FIFO task queue, round-robin / least-busy dispatch, minIdle/maxIdle, health checks, per-task timeouts, circuit breaker at pool level.
3. AbortSignal + transfer-list
sendRequest and MessageBus.request accept AbortSignal in RequestOptions. postMessage accepts { transfer?: Transferable[] } for zero-copy handoffs.
4. @suaveplan/events runtime wiring
WorkerManagerEventMap and MessageBusEventMap are emitted at runtime via an injected EventBus (no longer types-only).
5. Streaming RPC
sendStream<T>(name, type, payload): AsyncIterable<T> in WorkerManager for partial-result pushes without manual correlation.
6. Circuit breaker
src/worker-manager/circuit-breaker.ts — opens after N consecutive timeouts; half-opens after cooldown; resets on success.
7. Backpressure ceiling
maxPendingRequests on WorkerConfig; sendRequest rejects with WORKER_QUEUE_FULL when exceeded.
8. Error codes
WORKER_NOT_FOUND, WORKER_QUEUE_FULL, WORKER_REQUEST_TIMEOUT, WORKER_CIRCUIT_OPEN.
9. OTel metrics
Counter workers.messages_sent_total, counter workers.messages_received_total, histogram workers.rpc_latency_ms, gauge workers.pool_size, gauge workers.pending_requests.
File layout
browser/platform/web-workers/
package.json
tsconfig.json
vitest.config.ts
src/
index.ts
error-codes.ts
core/
base-manager/
base-manager.ts
base-manager.md
index.ts
worker-manager/
worker-manager/
worker-manager.ts
worker-manager.test.ts
worker-manager.md
index.ts
circuit-breaker/
circuit-breaker.ts
circuit-breaker.test.ts
circuit-breaker.md
index.ts
shared-worker-manager/
shared-worker-manager/
shared-worker-manager.ts
shared-worker-manager.test.ts
shared-worker-manager.md
index.ts
worker-pool/
worker-pool/
worker-pool.ts
worker-pool.test.ts
worker-pool.md
worker-pool.types.ts
index.ts
message-bus/
message-bus/
message-bus.ts
message-bus.test.ts
message-bus.md
index.ts
streaming/
stream/
stream.ts
stream.test.ts
stream.md
index.ts
helpers/
vite-worker/
vite-worker.ts
vite-worker.test.ts
vite-worker.md
index.ts
types/
web-workers.types.ts
web-workers.types.md
constants/
index.ts
tests/
integration.test.ts
Impact
Downstream: any Suaveplan package using Web Worker orchestration.
Breaking changes: None — all new APIs are additive; sendRequest gains optional AbortSignal param.
Prerequisites: @suaveplan/events, @suaveplan/error, @suaveplan/logger on Verdaccio.