Relocate the ~6k-file docs tree from the repo root into plugins/document-navigator/docs/ so the plugin is self-contained and portable. docs/superpowers/ (specs and plans) stays at the repo root. Moved via cp+rm rather than git mv because directory rename() fails with EPERM on this WSL /mnt DrvFs mount; git rename-detection pairs the identical-content files at commit time. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@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 — Wires caller-side
AbortSignalinstances across theMessagePortboundary so an in-flight RPC can be cancelled cooper…- abort-forwarding — Wires caller-side
AbortSignalinstances across theMessagePortboundary so an in-flight RPC can be cancelled cooper…
- abort-forwarding — Wires caller-side
- backpressure — Bounds memory growth on a busy RPC channel by exposing two primitives: the logical
CallBudgetfor outstanding in-flig…- backpressure — Bounds memory growth on a busy RPC channel by exposing two primitives: the logical
CallBudgetfor outstanding in-flig…
- backpressure — Bounds memory growth on a busy RPC channel by exposing two primitives: the logical
- client — Caller-side binding for the worker-RPC protocol.
createRawWorkerRpcClientreturns a low-level object exposing `.call(…- client — Caller-side binding for the worker-RPC protocol.
createRawWorkerRpcClientreturns a low-level object exposing `.call(… - cloneable-guard — Walks a value (typically an RPC argument list or return value) and throws
WORKER_RPC.NOT_CLONEABLEat the call site i…
- client — Caller-side binding for the worker-RPC protocol.
- correlation — Provides the monotonic correlation-id generator and the in-memory pending-call registry that backs every outstanding RP…
- correlation — Provides the monotonic correlation-id generator and the in-memory pending-call registry that backs every outstanding RP…
- error-codes — Defines the stable
WORKER_RPC.*code registry and theWorkerRpcErrorclass. Every public failure path in `@suavepla…- error-codes — Defines the stable
WORKER_RPC.*code registry and theWorkerRpcErrorclass. Every public failure path in `@suavepla…
- error-codes — Defines the stable
- error-reconstruction — Translates thrown values into wire-safe
ErrorEnvelopes and back into typedErrorinstances on the receiving side. `…- error-reconstruction — Translates thrown values into wire-safe
ErrorEnvelopes and back into typedErrorinstances on the receiving side. `…
- error-reconstruction — Translates thrown values into wire-safe
- protocol — Defines the wire-format of every message that crosses a
MessagePortbetween anapi-hostand an isolated/sandboxed p…- protocol — Defines the wire-format of every message that crosses a
MessagePortbetween anapi-hostand an isolated/sandboxed p…
- protocol — Defines the wire-format of every message that crosses a
- server — Worker-side binding for the RPC protocol.
serveWorkerRpc(port, handlers)mounts a handlers object on aMessagePort,…- server — Worker-side binding for the RPC protocol.
serveWorkerRpc(port, handlers)mounts a handlers object on aMessagePort,…
- server — Worker-side binding for the RPC protocol.
- streaming — Caller-side primitives for consuming streaming RPC methods. The server publishes a
stream-chunkframe per yielded val…- streaming — Caller-side primitives for consuming streaming RPC methods. The server publishes a
stream-chunkframe per yielded val…
- streaming — Caller-side primitives for consuming streaming RPC methods. The server publishes a
- testing — Reusable conformance harness and an in-process paired-port factory used by every Stage-3 worker-proxy package's test su…
- conformance — Builds a paired client/server harness over an in-process
MessageChannelfor conformance tests. The harness is intende… - testing — Reusable conformance harness and an in-process paired-port factory used by every Stage-3 worker-proxy package's test su…
- conformance — Builds a paired client/server harness over an in-process
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
bun add @suaveplan/api-worker-rpc
Quick start
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<Calc>(clientPort);
console.log(await calc.add(2, 3)); // 5
calc.close();
server.close();
close();
Modules
protocol— Zod-validated wire frames (request,response,cancel,progress,stream-open,stream-chunk,stream-end,stream-ack,hello,goodbye) plusparseFrameand theErrorEnvelopetype.correlation— monotonic id generator +PendingRegistry(orphan detection, reuse detection, timeout eviction).client—createWorkerRpcClient<T>typed proxy andcreateRawWorkerRpcClientlow-level binding.client/cloneable-guard— pre-send structured-clone safety walk.server—serveWorkerRpc(port, handlers)mounts handler maps with automatic streaming and cancel-via-AbortController.streaming— caller-sidestreamRequestreturning anAsyncIterable+StreamDispatcherfor inbound-frame routing.abort-forwarding—attachAbortSignal,createRemoteAbort, and the platform-independentAbortErrorclass.backpressure—CallBudget(logical) andBoundedQueue(physical) with three policies.error-reconstruction—errorToEnvelope/envelopeToErrorwith depth-bounded cause chains.error-codes— theWORKER_RPC_CODESregistry andWorkerRpcErrorclass.testing—createInProcessPortPairrunWorkerRpcConformanceharness.
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