# `client` _(no submodules)_ ## Purpose Caller-side binding for the worker-RPC protocol. `createRawWorkerRpcClient` returns a low-level object exposing `.call(method, params, opts)` which returns a `Promise`; `createWorkerRpcClient` wraps that in a typed `Proxy` so plugin authors invoke remote methods as if they were local async methods on a typed handle. The client handles every cross-cutting concern: monotonic correlation-id allocation, request/response matching, optional `AbortSignal` forwarding via cancel frames, optional per-call timeout, budget-enforced backpressure, outbound queue management, structured-clone safety pre-checks, and inbound frame validation with version negotiation. ## Features - Typed `Proxy` so `await client.method(arg)` works without a generated stub. - `AbortSignal` per call; aborting sends a `cancel` frame and rejects the pending promise with an `AbortError`. - Per-call timeout via `opts.timeoutMs` or `defaultTimeoutMs`. - `callBudget` cap (default `1024`) — synchronous-throw exhaustion with `WORKER_RPC.BACKPRESSURE`. - Pre-send `assertCloneable` walk rejecting functions, class instances, and symbols at the call site instead of letting the platform throw a `DataCloneError` later. - Inbound frames validated via the Zod protocol schema; invalid payloads and version mismatches are surfaced through the `onProtocolError` observer (never crash the listener). - `onStreamFrame` hook to multiplex stream frames into the streaming layer. ## Usage: Basic ```ts import { createWorkerRpcClient } from "@suaveplan/api-worker-rpc/client"; interface Calc { add(a: number, b: number): number; } const calc = createWorkerRpcClient(workerPort); const sum = await calc.add(2, 3); calc.close(); ``` ## Usage: Advanced ```ts import { createRawWorkerRpcClient } from "@suaveplan/api-worker-rpc/client"; const client = createRawWorkerRpcClient(port, { callBudget: 256, defaultTimeoutMs: 5_000, onProtocolError: (err) => logger.warn({ err }, "rpc protocol error"), }); const ac = new AbortController(); setTimeout(() => ac.abort(), 200); try { const result = await client.call("longRunning", [42], { signal: ac.signal }); } catch (err) { if (err instanceof Error && err.name === "AbortError") { // caller cancelled } } client.close("shutdown"); ``` ## API Reference - `createRawWorkerRpcClient(port, opts?): WorkerRpcClient` — low-level binding. - `createWorkerRpcClient(port, opts?): WorkerProxyClient` — typed proxy whose `__raw__` field exposes the underlying client and whose `close(reason?)` tears it down. - `interface WorkerRpcClient { call, send, cancel, nextId, registry, inFlight, close }`. - `interface WorkerRpcClientOptions { idGenerator?, callBudget?, outboundQueue?, defaultTimeoutMs?, onStreamFrame?, onProtocolError? }`. - `assertCloneable(value, path?): void` — re-exported from `./cloneable-guard`. ## Implementation Notes - The proxy filters reserved keys (`__raw__`, `close`) so they do NOT become remote methods. - `call` acquires a budget slot synchronously before allocating the id so exhaustion fails at the call site, not later in the microtask queue. - The inbound listener catches `parseFrame` errors and routes them to `onProtocolError` rather than throwing — a malformed frame should never crash the host loop. - Cancel frames sent during abort do not require an ack from the server (best-effort cooperative cancel).