API reference

yatta/rpc

Serve a route table, so the server and the client read one definition.

21 exported symbols and 7 members, read from src/types/rpc.ts.

Construct

clientFor

function

Builds a typed client for a served route table.

The companion to : same table, other side of the wire. Prefer this over calling `createClient` on a table of served routes, because the returned type is the one whose schemas actually resolve.

clientFor<const R extends Record<string, RouteDef>>(routes: R, options?: ClientOptions): ClientFor<R>
tsts
const api = clientFor(routes, { baseUrl: "/api" });const user = await api.getUser({ params: { id: "42" } });

fail

function

Throws a `HttpError` with a readable message, for use inside a handler.

fail(status: number, message: string): never

serve

function

Builds a router that serves a route table.

serve<const R extends Record<string, RouteDef>>(routes: R, options?: ServeOptions): Router
tsts
const routes = {  getUser: serverRoute(    { method: "get", path: "/users/:id", params: UserId, response: User },    async ({ params }) => db.users.findById(params.id),  ),}; const api = serve(routes, { prefix: "/api" });const client = createClient(routes, { baseUrl });

serverRoute

function

Declares a route with its handler.

serverRoute<const D extends RouteDef>(definition: D, handler: ServerRoute<D>["handler"]): ServerRoute<D>

Types

ServeOptions

interface
interface ServeOptions

6 members

  • prefixproperty
    prefix?: string

    Prefix every path is mounted under, e.g. "/api".

  • validateResponsesproperty
    validateResponses?: boolean

    Validate a handler's return value against the route's `response` schema.

    Off by default because it costs a validation per request. Worth turning on in development: it is the only thing that catches a handler drifting from the shape its clients are typed against.

  • handlersproperty
    handlers?: Record<string, (input: any, ctx: Context<never>) => HandlerResult | Promise<HandlerResult>>

    Handlers, keyed by route name.

    Kept separate from the route table on purpose. A table with handlers attached cannot be imported by a browser without dragging the server code — `db`, the auth module, whatever — into the client bundle. Split this way, the contract file holds only paths and schemas, both sides import it, and the handlers stay on the server. A route may instead carry its own handler; one attached inline wins over one given here.

  • middlewareproperty
    middleware?: Array<(ctx: Context<never>, next: () => Promise<Response>) => Promise<Response>>

    Extra middleware, run before every route.

  • corsproperty
    cors?: Parameters<Router["cors"]>[0]
  • rateLimitproperty
    rateLimit?: { max: number; windowMs: number; key?: (ctx: Context<never>) => string; }

    Rate limiting applied to every route.

ServerRoute

interface

A route as the server sees it: the definition plus the handler.

`handler` is typed against the route's own validators, so a handler cannot read a body field the schema does not declare — the error appears where it is made rather than at runtime.

interface ServerRoute<D extends RouteDef>

1 member

  • handlerproperty
    handler: (input: ServerInput<D>, ctx: Context<never>) => HandlerResult | Promise<HandlerResult>

ClientFor

type

The client type for a served route table.

Takes the same table, so a handler and its client method are two views of one definition rather than two things written twice. Takes the same shape of table as : routes with or without inline handlers. Neither side is widened to `RouteDef` first — doing that made every validator `StandardSchemaV1<any> | undefined`, so `Infer` gave `unknown`, every request body became `unknown` and every response became `any`. The client then accepted any argument and claimed to know nothing, while looking fully typed. A route with an inline handler is unwrapped to its bare definition, and one without is passed through untouched. Both branches are needed: passing a served route through unresolved left every method signature deferred, and unwrapping with a `: RouteDef` fallback widened the schemas away.

type ClientFor<R extends Record<string, RouteDef>> = Client<{ [K in keyof R]: R[K] extends ServerRoute<infer D> ? D : R[K]; }>

PathParamsOf

type

Path parameters, as plain strings.

type PathParamsOf<Path extends string> = Path extends `${infer _}:${infer Param}/${infer Rest}` ? { [K in Param | keyof PathParamsOf<`/${Rest}`>]: string; } : Path extends `${infer _}:${infer Param}` ? { [K in Param]: string; } : {}

ServerInput

type

Everything a handler is given: validated params, query and body.

type ServerInput<D extends RouteDef> = { params: D extends { params: infer P; } ? unknown extends P ? Record<string, string> : Infer<P> : D extends { path: infer Path extends string; } ? PathParamsOf<Path> : Record<string, string>; query: D extends { query: infer Q; } ? unknown extends Q ? Record<string, unknown> : Infer<Q> : Record<string, unknown>; body: D extends { body: infer B; } ? unknown extends B ? unknown : Infer<B> : undefined; ctx: Context<never>; }

Other

buildPath

unknown
buildPath

Client

unknown

Context

unknown

createClient

unknown
createClient

defineRoutes

unknown
defineRoutes

HandlerResult

unknown

HttpMethod

unknown

Infer

unknown

MethodFor

unknown

route

unknown
route

RouteDef

unknown

StandardSchemaV1

unknown
Tip
Most of the types above are inferred. You rarely import AuthConfig or JobPayload — declaring your schema once is enough for the rest to follow. See Typed keys.