Skip to content

@insler/rpc/client

The caller side. Client.create(contract, transport, options?) builds a fully-typed client whose methods are derived from the contract; the wire it calls through is any ClientTransport — the in-memory transport or an adapter like NATS.

import { Client } from '@insler/rpc/client';
const accounts = Client.create(Accounts, clientTransport);
await accounts.getBalance({ accountId: 'a_1' }); // { balance: number }
  • Throw mode (default): a failed call throws ContractError — best ergonomics.
  • Result mode (errors: 'result'): calls return { ok, value } | { ok, error } with the error union typed per method — handle typed errors without try/catch.

Client.withContext(client, ctx) returns a scoped client with context pre-applied (works in both error modes):

const asUser = Client.withContext(accounts, { identity: { userId: 'u_1' } });
await asUser.getBalance({ accountId: 'a_1' });

options.middleware composes cross-cutting per-call logic (auth, logging, timing) via composeMiddleware; middleware executes in array order, outermost first. Coverage today is partial: unary and serverStream calls route through the chain, while clientStream and duplex still call the transport directly — do not assume uniform coverage on streaming interceptors yet.

  • @insler/rpc/client/testTestTransport for unit-testing client logic without a host.
  • @insler/rpc/client/dev — logging and timing middleware for development.

Let the contract infer method signatures — never hand-type them. Keep business validation on the host, not in client middleware. Streaming methods require a transport that implements the matching stream invocation; absence throws a clear error.