Initial Commit
This commit is contained in:
207
openspec/changes/enterprise-browser-web-workers/proposal.md
Normal file
207
openspec/changes/enterprise-browser-web-workers/proposal.md
Normal file
@@ -0,0 +1,207 @@
|
||||
# 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
|
||||
|
||||
```ts
|
||||
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
|
||||
|
||||
```text
|
||||
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.
|
||||
120
openspec/changes/enterprise-browser-web-workers/tasks.md
Normal file
120
openspec/changes/enterprise-browser-web-workers/tasks.md
Normal file
@@ -0,0 +1,120 @@
|
||||
# Tasks: enterprise-browser-web-workers
|
||||
|
||||
**Change ID**: enterprise-browser-web-workers
|
||||
**Package**: `@suaveplan/web-workers`
|
||||
**Target path**: `browser/platform/web-workers`
|
||||
**Prerequisite packages on Verdaccio**: `@suaveplan/events`, `@suaveplan/error`, `@suaveplan/logger`
|
||||
|
||||
---
|
||||
|
||||
## 0. Pre-flight (non-negotiable)
|
||||
|
||||
- [ ] 0.1 Confirm clean working tree (`git status` reports nothing) and no in-flight worktrees with uncommitted work (`git worktree list` then `git status` in each)
|
||||
- [ ] 0.2 If any worktrees exist under `.claude/worktrees/`, account for each one: still-active agent, work-pending-merge, or stale-and-prune. Stale worktrees MUST be removed (`git worktree remove`) before opening a new one — code that has been lost twice on main is the failure mode this guards against.
|
||||
- [ ] 0.3 Confirm the package set in `MANIFEST.md` matches `find packages -maxdepth 3 -name package.json` count. Drift here means a prior change archived without regenerating the manifest.
|
||||
|
||||
## 1. Scaffold
|
||||
|
||||
- [ ] Create directory tree per `proposal.md`.
|
||||
- [ ] `package.json` v0.0.0, `"sideEffects": false`.
|
||||
- `dependencies`: `@suaveplan/events workspace:^`, `@suaveplan/error workspace:^`, `@suaveplan/types workspace:^`, `@suaveplan/logger workspace:^`, `@suaveplan/schema-registry workspace:^`, `zod catalog:forms`, `@opentelemetry/api catalog:observability`, `tslib catalog:build`
|
||||
- `devDependencies`: `vitest catalog:testing`, `@vitest/coverage-istanbul catalog:testing`, `happy-dom catalog:testing`, `@suaveplan/testing workspace:^`
|
||||
- Subpath exports: `"."`, `"./worker-manager"`, `"./shared-worker-manager"`, `"./worker-pool"`, `"./message-bus"`, `"./helpers/vite-worker"`
|
||||
- [ ] `tsconfig.json`, `vitest.config.ts` (pool: forks, happy-dom environment).
|
||||
|
||||
---
|
||||
|
||||
## 2. Error codes
|
||||
|
||||
- [ ] `WORKER_NOT_FOUND`, `WORKER_QUEUE_FULL`, `WORKER_REQUEST_TIMEOUT`, `WORKER_CIRCUIT_OPEN`.
|
||||
- [ ] `registerWebWorkerErrorCodes()` — call eagerly.
|
||||
|
||||
---
|
||||
|
||||
## 3. Types (`src/types/web-workers.types.ts`)
|
||||
|
||||
- [ ] `WorkerConfig`, `WorkerStatus`, `WorkerFactory`, `WorkerLike`, `WorkerMessage`, `WorkerPoolConfig`, `WorkerTask`, `PoolMetrics`, `MessageBusEventMap`, `WorkerManagerEventMap`, `RequestOptions`, `SharedWorkerFactory`.
|
||||
- [ ] Write `web-workers.types.md`.
|
||||
|
||||
---
|
||||
|
||||
## 4. `BaseWorkerManager` (`src/core/base-manager/`)
|
||||
|
||||
- [ ] `base-manager.ts` — shared correlation-ID map, timeout logic, event emission, `AbortSignal` wiring.
|
||||
- [ ] `WorkerManager` and `SharedWorkerManager` both extend this.
|
||||
- [ ] Write `base-manager.md`.
|
||||
|
||||
---
|
||||
|
||||
## 5. Circuit breaker (`src/worker-manager/circuit-breaker/`)
|
||||
|
||||
- [ ] `circuit-breaker.ts` — state machine (closed → open → half-open); injectable clock; `record(success)`, `isOpen()`, `reset()`.
|
||||
- [ ] Write `circuit-breaker.test.ts` (open on N failures, half-open after cooldown, reset on success). Write `circuit-breaker.md`.
|
||||
|
||||
---
|
||||
|
||||
## 6. `WorkerManager` (`src/worker-manager/worker-manager/`)
|
||||
|
||||
- [ ] `worker-manager.ts` — extends `BaseWorkerManager`; `create`, `postMessage` (with transfer list), `sendRequest` (with `AbortSignal` + backpressure ceiling), `terminate`, `terminateAll`, `has`, `getStatus`, `getWorkerNames`, `onError`.
|
||||
- [ ] Wire circuit breaker and OTel metrics.
|
||||
- [ ] Wire `@suaveplan/events` runtime emission.
|
||||
- [ ] Write `worker-manager.test.ts` (create, postMessage, sendRequest, abort, backpressure, circuit open). Write `worker-manager.md`.
|
||||
|
||||
---
|
||||
|
||||
## 7. `SharedWorkerManager` (`src/shared-worker-manager/shared-worker-manager/`)
|
||||
|
||||
- [ ] `shared-worker-manager.ts` — `new SharedWorker(url)`; one `MessagePort` per named connection; `connect`, `disconnect`, `sendRequest` (AbortSignal).
|
||||
- [ ] Write `shared-worker-manager.test.ts` (connect, message, disconnect). Write `shared-worker-manager.md`.
|
||||
|
||||
---
|
||||
|
||||
## 8. `WorkerPool` (`src/worker-pool/worker-pool/`)
|
||||
|
||||
- [ ] `worker-pool.ts` — FIFO task queue, round-robin dispatch, `minIdle`/`maxIdle`, health checks, per-task timeout, drain.
|
||||
- [ ] `getMetrics()` returning `PoolMetrics` (size, idle, pending, completed, failed).
|
||||
- [ ] Write `worker-pool.test.ts` (submit, saturation, backpressure, drain, health-check eviction). Write `worker-pool.md`.
|
||||
|
||||
---
|
||||
|
||||
## 9. `MessageBus` (`src/message-bus/message-bus/`)
|
||||
|
||||
- [ ] `message-bus.ts` — topic pub/sub, `subscribeOnce`, `broadcast`, `request` (AbortSignal), `registerHandler`, subscriber count, topic enumeration.
|
||||
- [ ] Wire `@suaveplan/events` runtime emission via `MessageBusEventMap`.
|
||||
- [ ] Write `message-bus.test.ts` (subscribe, publish, request/response, abort, broadcast). Write `message-bus.md`.
|
||||
|
||||
---
|
||||
|
||||
## 10. Streaming RPC (`src/streaming/stream/`)
|
||||
|
||||
- [ ] `stream.ts` — `sendStream<T>(manager, name, type, payload): AsyncIterable<T>` using correlation IDs and `MessagePort` transfer.
|
||||
- [ ] Write `stream.test.ts`. Write `stream.md`.
|
||||
|
||||
---
|
||||
|
||||
## 11. Vite worker helper (`src/helpers/vite-worker/`)
|
||||
|
||||
- [ ] `vite-worker.ts` — `defineWorker(url, options?)` using `new URL(url, import.meta.url)`; exports factory and type.
|
||||
- [ ] Write `vite-worker.test.ts`. Write `vite-worker.md`.
|
||||
|
||||
---
|
||||
|
||||
## 12. Integration test (`tests/integration.test.ts`)
|
||||
|
||||
- [ ] `WorkerManager` + `MessageBus` together: publish from worker, subscribe in main.
|
||||
- [ ] `WorkerPool` saturation test with 10 concurrent tasks and `maxIdle: 3`.
|
||||
- [ ] `AbortSignal` cancels in-flight `sendRequest`.
|
||||
- [ ] Transfer `ArrayBuffer` via transfer list (verify zero-copy).
|
||||
|
||||
---
|
||||
|
||||
## 13. Docs + verify
|
||||
|
||||
- [ ] Write `.md` per module (≥200 words, API reference, examples).
|
||||
- [ ] Write `README.md` with subpath table, pool guide, streaming RPC example, circuit-breaker reference.
|
||||
- [ ] `bunx turbo lint typecheck test --filter=@suaveplan/web-workers` — green.
|
||||
- [ ] `bunx turbo test:coverage` — 100%.
|
||||
- [ ] No `@ts-expect-error` in `src/` or tests. No `vi.stubGlobal`.
|
||||
- [ ] `SharedWorkerManager` and `WorkerPool` fully implemented (no stubs).
|
||||
- [ ] `bun scripts/publish.ts --package @suaveplan/web-workers`.
|
||||
- [ ] Archive to `openspec/archive/2026-04-24-enterprise-browser-web-workers/`.
|
||||
Reference in New Issue
Block a user