first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,140 @@
# `base-manager`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`base-manager.ts` declares `BaseWorkerManager<TTransport>`, the abstract
class that hosts everything shared between the classic-Worker manager
(`WorkerManager`) and the SharedWorker manager (`SharedWorkerManager`).
Both concrete classes extend this base and only override
`createTransport`, `terminateTransport`, and `postToTransport` — the rest
of the lifecycle, RPC state, AbortSignal wiring, transfer-list validation,
backpressure ceiling, circuit-breaker integration, OTel emission, and
`EventBus` lifecycle events all live here.
Centralising this logic is what makes the contract testable in isolation
and identical across transports: the same `terminate-rejects-pending`
semantics, the same backpressure code, the same OTel counter increments
fire regardless of whether the underlying transport is a `Worker` or a
SharedWorker port.
## Features
- Per-name worker registry with `idle / busy / error / terminated` status.
- Pending-request map with timeout + AbortSignal + backpressure ceiling
(`maxPendingRequests`).
- Per-worker circuit breaker integration (`config.circuitBreaker`).
- Transfer-list validation (rejects non-array `transfer` with `INVALID_TRANSFER`).
- `EventBus<WorkerManagerEventMap>` emission on every lifecycle event.
- OTel counter increments on every send / receive / create / terminate.
- RPC latency histogram observation on every fulfilled request.
- Multi-handler `onError` registry.
## Usage: Basic Example
`BaseWorkerManager` is abstract — consumers always work with `WorkerManager`
or `SharedWorkerManager` directly. The shared API surface that
`BaseWorkerManager` exposes is therefore consumed via the subclasses:
```ts
import { WorkerManager } from "@suaveplan/web-workers";
const manager = new WorkerManager({ factory });
manager.create({ name: "hash", url: WORKER_URL });
const digest = await manager.sendRequest<string, string>(
"hash",
"sha256",
"hello"
);
```
## Usage: Advanced Example
Subclassing for a custom transport (e.g. a `MessageChannel`-backed in-process
worker):
```ts
import {
BaseWorkerManager,
type BaseManagerOptions,
type WorkerConfig,
type WorkerMessage,
} from "@suaveplan/web-workers";
interface ChannelTransport {
readonly port: MessagePort;
}
class MessageChannelManager extends BaseWorkerManager<ChannelTransport> {
constructor(opts: BaseManagerOptions) {
super(opts);
}
protected override createTransport(config: WorkerConfig): ChannelTransport {
const { port1, port2 } = new MessageChannel();
port1.onmessage = (event) => {
this.handleMessage(
config.name,
event.data as WorkerMessage
);
};
port1.start();
// hand `port2` to the worker side somehow (e.g. via postMessage)
return { port: port1 };
}
protected override terminateTransport(t: ChannelTransport): void {
t.port.close();
}
protected override postToTransport(
t: ChannelTransport,
message: unknown,
transfer: Transferable[] | undefined
): void {
if (transfer !== undefined && transfer.length > 0) {
t.port.postMessage(message, transfer);
} else {
t.port.postMessage(message);
}
}
}
```
## API Reference
- `BaseWorkerManager<TTransport>` — abstract class.
- `events: EventBus<WorkerManagerEventMap>` — lifecycle event bus.
- `create(config: WorkerConfig)` — throws `DUPLICATE_WORKER`.
- `terminate(name)` — throws `WORKER_NOT_FOUND`; rejects pending with
`WORKER_TERMINATED`.
- `terminateAll()` — iterates `terminate(name)` for each.
- `has(name): boolean`.
- `getStatus(name): WorkerStatus` — throws `WORKER_NOT_FOUND`.
- `getWorkerNames(): string[]`.
- `onError(handler: (name, error) => void): void`.
- `postMessage<T>(name, type, payload, options?)` — fire-and-forget;
validates transfer.
- `sendRequest<TReq, TRes>(name, type, payload, options?)` — request /
response with timeout, signal, transfer, backpressure, circuit breaker.
- Protected abstract methods: `createTransport`, `terminateTransport`,
`postToTransport`.
- Protected helpers: `handleMessage`, `handleError`, `getEntry`.
- `ManagedEntry<TTransport>` — exported internal entry type for subclass
visibility.
## Implementation Notes
The constructor is `protected` — only subclasses can instantiate. The
internal `PendingEntry` interface stores both the `timer` handle and the
`cleanupSignal` closure so both can be torn down in any of the three exit
paths (success, abort, terminate, timeout). The `entry.pending.delete(id)`
call happens unconditionally in both the timer + abort paths so a single
race where the abort fires after the timer body started running still
leaves the pending map in a consistent state. `dispatchRequest` is split
out of `sendRequest` to keep both methods under the 50-line soft limit and
to give `sendRequest` a clean place to do the synchronous pre-flight
(backpressure check, pre-aborted signal, transfer validation, breaker
probe). The status field flips `busy → idle` only when the pending count
hits zero, so concurrent in-flight requests don't prematurely reset the
status.