Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,61 @@
# `main-side`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Mounts a main-process `@suaveplan/events` `EventBus` on a `MessagePort` via the `@suaveplan/api-worker-rpc` server so worker-tier plugins can publish to and subscribe from the same shared bus. The host owns the `EventBus`; this module installs the RPC handlers and lifecycle.
Three RPC methods are exposed: `emit`, `emitAsync`, and the streaming `subscribe`. Subscriptions return an `AsyncIterable<unknown>` that yields one chunk per main-side event until the worker cancels (or `mode: "once"` ends after the first delivery).
## Features
- Wires `emit` / `emitAsync` / `subscribe` over the canonical worker-RPC protocol — no bespoke wire format.
- Per-subscription `highWaterMark` cap with `EVENTS_PROXY.BACKPRESSURE_EXCEEDED` rejection when the worker stops draining.
- Payloads are Zod-validated and structured-clone-probed on encode; unsafe payloads surface as `EVENTS_PROXY.UNSERIALISABLE_PAYLOAD` BEFORE they touch the port.
- `EventsProxyError` instances are serialised onto the wire by `api-worker-rpc`'s `errorToEnvelope` so the worker side reconstructs typed errors.
- `dispose()` shuts the underlying `WorkerRpcServer` down; in-flight `subscribe` streams emit `stream-end` via the goodbye flow.
## Usage: Basic
```ts
import { EventBus } from "@suaveplan/events";
import { createEventBusProxyServer } from "@suaveplan/api-events-worker-proxy/main-side";
import { createInProcessPortPair } from "@suaveplan/api-worker-rpc";
const bus = new EventBus<{ "billing.invoice.created": { id: string } }>();
const { clientPort, serverPort, close } = createInProcessPortPair();
const server = createEventBusProxyServer(serverPort, bus);
// Pass `clientPort` to a worker; tear down with `server.dispose()` + close().
```
## Usage: Advanced
```ts
import { EventBus } from "@suaveplan/events";
import { createEventBusProxyServer } from "@suaveplan/api-events-worker-proxy/main-side";
import { createInProcessPortPair } from "@suaveplan/api-worker-rpc";
type Map = { "drive.chunk.delivered": { chunkId: string } };
const bus = new EventBus<Map>();
const { clientPort, serverPort } = createInProcessPortPair();
const server = createEventBusProxyServer(serverPort, bus);
// In-process subscribers keep receiving events as usual.
bus.on("drive.chunk.delivered", ({ chunkId }) =>
console.log("main-side observed", chunkId)
);
process.on("beforeExit", () => server.dispose());
```
## API Reference
- `createEventBusProxyServer<TMap>(port: PortLike, bus: EventBus<TMap>): EventBusProxyServer`.
- `EventBusProxyServer` — `{ dispose(reason?: string): void; readonly activeSubscriptionCount: number; }`.
## Implementation Notes
The handlers object passed to `serveWorkerRpc` declares three methods. `emit` and `emitAsync` parse positional params (`[topic, payload, traceId?, timeoutMs?]`), decode the wire envelope with `decodeEnvelope`, and forward to `bus.emit` / `bus.emitAsync`. The streaming `subscribe` handler builds an `AsyncIterable<unknown>` whose body subscribes to the bus, enqueues payloads on each event, and yields them on each iteration. Backpressure is enforced by checking `queue.length` against the worker-supplied high-water mark; over-budget events surface `BACKPRESSURE_EXCEEDED` and end the stream. Cancellation is wired through `AbortSignal`: the server's `cancel` frame triggers the per-call signal, which the iterator observes via `signal.aborted` plus a `wake()` callback so the pending `await` resolves immediately. The `unsubscribe` happens in the iterator's `finally` block so it runs on both normal completion and cancellation.