# @suaveplan/api-worker-rpc - MessagePort-based RPC protocol for worker-isolated API plugins. Provides Zod-validated wire frames, monotonic correlation ids, AbortSignal forwarding, streaming with backpressure, structured-clone safety guards, SuaveplanError reconstruction across the boundary, and paired client/server bindings. Universal; Stage-3 worker-proxy packages depend on this. MessagePort-based RPC protocol for worker-isolated API plugins. Provides the shared protocol — Zod-validated wire frames, monotonic correlation ids, AbortSignal forwarding, streaming with backpressure, structured- clone safety guards, SuaveplanError reconstruction across the boundary, and paired client/server bindings — that every Stage-3 worker-proxy package builds on. The package is universal: it depends only on a structural `PortLike` shape that matches both Node/Bun's `worker_threads` `MessagePort` and the Web platform's `MessagePort`. No platform globals are imported at module load. ## Documentation tree - [abort-forwarding](./src/abort-forwarding/abort-forwarding.md) — Wires caller-side `AbortSignal` instances across the `MessagePort` boundary so an in-flight RPC can be cancelled cooper… - [abort-forwarding](./src/abort-forwarding/abort-forwarding.md) — Wires caller-side `AbortSignal` instances across the `MessagePort` boundary so an in-flight RPC can be cancelled cooper… - [backpressure](./src/backpressure/backpressure.md) — Bounds memory growth on a busy RPC channel by exposing two primitives: the logical `CallBudget` for outstanding in-flig… - [backpressure](./src/backpressure/backpressure.md) — Bounds memory growth on a busy RPC channel by exposing two primitives: the logical `CallBudget` for outstanding in-flig… - [client](./src/client/client.md) — Caller-side binding for the worker-RPC protocol. `createRawWorkerRpcClient` returns a low-level object exposing `.call(… - [client](./src/client/client.md) — Caller-side binding for the worker-RPC protocol. `createRawWorkerRpcClient` returns a low-level object exposing `.call(… - [cloneable-guard](./src/client/cloneable-guard.md) — Walks a value (typically an RPC argument list or return value) and throws `WORKER_RPC.NOT_CLONEABLE` at the call site i… - [correlation](./src/correlation/correlation.md) — Provides the monotonic correlation-id generator and the in-memory pending-call registry that backs every outstanding RP… - [correlation](./src/correlation/correlation.md) — Provides the monotonic correlation-id generator and the in-memory pending-call registry that backs every outstanding RP… - [error-codes](./src/error-codes/error-codes.md) — Defines the stable `WORKER_RPC.*` code registry and the `WorkerRpcError` class. Every public failure path in `@suavepla… - [error-codes](./src/error-codes/error-codes.md) — Defines the stable `WORKER_RPC.*` code registry and the `WorkerRpcError` class. Every public failure path in `@suavepla… - [error-reconstruction](./src/error-reconstruction/error-reconstruction.md) — Translates thrown values into wire-safe `ErrorEnvelope`s and back into typed `Error` instances on the receiving side. `… - [error-reconstruction](./src/error-reconstruction/error-reconstruction.md) — Translates thrown values into wire-safe `ErrorEnvelope`s and back into typed `Error` instances on the receiving side. `… - [protocol](./src/protocol/protocol.md) — Defines the wire-format of every message that crosses a `MessagePort` between an `api-host` and an isolated/sandboxed p… - [protocol](./src/protocol/protocol.md) — Defines the wire-format of every message that crosses a `MessagePort` between an `api-host` and an isolated/sandboxed p… - [server](./src/server/server.md) — Worker-side binding for the RPC protocol. `serveWorkerRpc(port, handlers)` mounts a handlers object on a `MessagePort`,… - [server](./src/server/server.md) — Worker-side binding for the RPC protocol. `serveWorkerRpc(port, handlers)` mounts a handlers object on a `MessagePort`,… - [streaming](./src/streaming/streaming.md) — Caller-side primitives for consuming streaming RPC methods. The server publishes a `stream-chunk` frame per yielded val… - [streaming](./src/streaming/streaming.md) — Caller-side primitives for consuming streaming RPC methods. The server publishes a `stream-chunk` frame per yielded val… - [testing](./src/testing/testing.md) — Reusable conformance harness and an in-process paired-port factory used by every Stage-3 worker-proxy package's test su… - [conformance](./src/testing/conformance.md) — Builds a paired client/server harness over an in-process `MessageChannel` for conformance tests. The harness is intende… - [testing](./src/testing/testing.md) — Reusable conformance harness and an in-process paired-port factory used by every Stage-3 worker-proxy package's test su… ## Why Per the api-plugin system design (§2.2 isolation tiers, §5.2a worker boundary), every capability adapter ships in two surfaces: a direct in-process binding for `trusted` plugins and a `MessagePort`- marshalled binding for `isolated` / `sandboxed` plugins. Hand-rolling that marshalling once per capability is how plugin frameworks accrete subtly-different RPC layers that diverge on cancellation, streaming, and error reconstruction. `api-worker-rpc` is the single shared protocol every worker-proxy package builds on; getting it right once means every capability — `db`, `events`, `secrets`, `jobs`, `logger`, future ones — gets cancellation, backpressure, streaming, and error reconstruction for free. ## Install ```bash bun add @suaveplan/api-worker-rpc ``` ## Quick start ```ts import { createWorkerRpcClient } from "@suaveplan/api-worker-rpc"; import { serveWorkerRpc } from "@suaveplan/api-worker-rpc"; import { createInProcessPortPair } from "@suaveplan/api-worker-rpc/testing"; interface Calc { add(a: number, b: number): number; } const { clientPort, serverPort, close } = createInProcessPortPair(); const server = serveWorkerRpc(serverPort, { add: ([a, b]) => (a as number) + (b as number), }); const calc = createWorkerRpcClient(clientPort); console.log(await calc.add(2, 3)); // 5 calc.close(); server.close(); close(); ``` ## Modules - [`protocol`](./src/protocol/protocol.md) — Zod-validated wire frames (`request`, `response`, `cancel`, `progress`, `stream-open`, `stream-chunk`, `stream-end`, `stream-ack`, `hello`, `goodbye`) plus `parseFrame` and the `ErrorEnvelope` type. - [`correlation`](./src/correlation/correlation.md) — monotonic id generator + `PendingRegistry` (orphan detection, reuse detection, timeout eviction). - [`client`](./src/client/client.md) — `createWorkerRpcClient` typed proxy and `createRawWorkerRpcClient` low-level binding. - [`client/cloneable-guard`](./src/client/cloneable-guard.md) — pre-send structured-clone safety walk. - [`server`](./src/server/server.md) — `serveWorkerRpc(port, handlers)` mounts handler maps with automatic streaming and cancel-via-`AbortController`. - [`streaming`](./src/streaming/streaming.md) — caller-side `streamRequest` returning an `AsyncIterable` + `StreamDispatcher` for inbound-frame routing. - [`abort-forwarding`](./src/abort-forwarding/abort-forwarding.md) — `attachAbortSignal`, `createRemoteAbort`, and the platform-independent `AbortError` class. - [`backpressure`](./src/backpressure/backpressure.md) — `CallBudget` (logical) and `BoundedQueue` (physical) with three policies. - [`error-reconstruction`](./src/error-reconstruction/error-reconstruction.md) — `errorToEnvelope` / `envelopeToError` with depth-bounded cause chains. - [`error-codes`](./src/error-codes/error-codes.md) — the `WORKER_RPC_CODES` registry and `WorkerRpcError` class. - [`testing`](./src/testing/testing.md) — `createInProcessPortPair` + `runWorkerRpcConformance` harness. ## Error codes Every code in `WORKER_RPC_CODES` is reachable from at least one production path: | Code | Raised from | | --------------------------------- | ------------------------------------------- | | `WORKER_RPC.PORT_CLOSED` | `client.close()` reject; post-close send | | `WORKER_RPC.REQUEST_TIMEOUT` | per-call timeout eviction | | `WORKER_RPC.RESPONSE_TIMEOUT` | (reserved for response-only timeout policy) | | `WORKER_RPC.INVALID_FRAME` | malformed inbound frame | | `WORKER_RPC.UNKNOWN_METHOD` | server dispatch of missing handler | | `WORKER_RPC.CORRELATION_ID_REUSED`| double-register on same id | | `WORKER_RPC.STREAM_BACKPRESSURE_EXCEEDED` | reserved for stream-explicit overflow | | `WORKER_RPC.HANDLER_THREW` | server-side stream handler not iterable | | `WORKER_RPC.CANCELLED` | registry cancel | | `WORKER_RPC.VERSION_MISMATCH` | inbound `v !== PROTOCOL_VERSION` | | `WORKER_RPC.ORPHAN_RESPONSE` | `settle()` on unknown id | | `WORKER_RPC.NOT_CLONEABLE` | `assertCloneable` pre-send walk | | `WORKER_RPC.REMOTE_THROW` | wrapping foreign throws on the wire | | `WORKER_RPC.BACKPRESSURE` | `CallBudget` or fail-mode `BoundedQueue` | ## Verification - 100% test coverage gated in CI (`bunx turbo test:coverage --filter=@suaveplan/api-worker-rpc`). - Full lint+typecheck+test+build gate per package rules. ## License MIT