3.6 KiB
main-side
(no submodules)
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/subscribeover the canonical worker-RPC protocol — no bespoke wire format. - Per-subscription
highWaterMarkcap withEVENTS_PROXY.BACKPRESSURE_EXCEEDEDrejection when the worker stops draining. - Payloads are Zod-validated and structured-clone-probed on encode; unsafe payloads surface as
EVENTS_PROXY.UNSERIALISABLE_PAYLOADBEFORE they touch the port. EventsProxyErrorinstances are serialised onto the wire byapi-worker-rpc'serrorToEnvelopeso the worker side reconstructs typed errors.dispose()shuts the underlyingWorkerRpcServerdown; in-flightsubscribestreams emitstream-endvia the goodbye flow.
Usage: Basic
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
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.