Fetch Client
Create a typed Fetch client to call your routes over HTTP.
initClient() creates a typed Fetch client from a runtime contract. When the
routes are implemented in the server, you need to Generate a Contract first.
import { initClient } from "@rest-rpc/core";
import { api } from "./client-contract";
const client = initClient(api, {
baseUrl: "https://api.example.com",
});
The client has the same route tree shape as the contract.
Call Routes
The route declaration determines the request and
result shapes. Flat input stays flat, and segmented input uses params,
query, headers, and body.
const todo = await client.todos.create({ title: "Write docs" });
const response = await client.todos.get({ params: { id: todo.id } });
Plain output routes return decoded data directly. Status response routes return
a status-discriminated union with body and the Fetch Headers object as
headers. Declared response headers are also exposed through responseHeaders.
if (response.status === 200) {
console.log(response.body.title);
} else if (response.status === 404) {
console.log(response.body.code);
}
Declared non-2xx responses are returned as envelopes. Undeclared statuses throw
an HttpError. Network failures and aborted requests reject
with the error from fetch. See the Route Builder for
choosing result forms and declaring expected outcomes.
Error Handling
An HttpError is thrown when a response does not match the route contract:
- The status is not declared by the route. A plain output route only accepts its successful status.
- The response content type is not one the route declares.
- Response validation is enabled and fails. This can happen with a 2xx status.
Its status is the received HTTP status, and its body is the decoded,
unvalidated response body. Validation issues are available through cause.
import { HttpError } from "@rest-rpc/core";
try {
await client.todos.get({ params: { id: "todo_1" } });
} catch (error) {
if (error instanceof HttpError) {
console.log(error.status, error.body);
} else {
throw error;
}
}
Consume Streams
A streaming route returns an async iterable of SseEvent<T>
values. Application data is available on event.data:
export type SseEvent<T> = {
data: T;
id?: string;
event?: string;
retry?: number;
};
for await (const event of await client.todos.events()) {
console.log(event.data.message);
console.log(event.id, event.event, event.retry);
}
For a status response stream, narrow the response before iterating its body:
const response = await client.todos.events();
if (response.status === 200) {
for await (const event of response.body) {
console.log(event.data.message);
}
}
The initial request and later stream iteration can fail independently. Pass an
AbortSignal in the per-call options to cancel both the request and stream:
const controller = new AbortController();
const events = await client.todos.events(undefined, {
signal: controller.signal,
});
controller.abort();
Reconnecting
The client reads SSE events over Fetch but is not a replacement for the
browser EventSource API. The id, event, and retry fields are exposed as
metadata only: the client does not reconnect automatically, send the last event
ID, or filter events by name.
To resume a stream, send the last received event ID in the last-event-id
header. The server receives it as
lastEventId.
let lastEventId: string | undefined;
async function connect() {
const events = await client.todos.events(undefined, {
additionalHeaders: {
"last-event-id": lastEventId,
},
});
for await (const event of events) {
lastEventId = event.id ?? lastEventId;
handleTodoEvent(event.data);
}
}
Client Options
const client = initClient(api, {
baseUrl: "https://api.example.com",
globalHeaders: {
authorization: () => `Bearer ${readToken()}`,
},
timeoutMs: 10_000,
fetchOptions: {
credentials: "include",
},
});
baseUrlstring
Base URL used to build route requests.
stringfetch?FetchLike
Custom fetch-compatible function used to send requests.
FetchLikefetchOptions?ApiClientFetchOptions
Default fetch options merged into every request. Excludes `signal`, which can only be passed per call.
ApiClientFetchOptionsbodyCodecs?readonly BodyCodec<Response>[]
Custom body codecs that take precedence over the defaults. See Serialization and Codecs.
readonly BodyCodec<Response>[]globalHeaders?ClientHeaders
Headers added to every request. Each value may be static or provided for each request.
ClientHeaderstimeoutMs?number
Aborts a request when fetch does not return in time.
numbervalidateResponses?boolean
Validates response bodies, declared response headers, and stream event data against the contract's schemas.
booleanfalseSee Serialization and Codecs for the default body codecs
and how to override them with bodyCodecs.
Global Headers
globalHeaders adds headers to every request. Values can be static or provided
by synchronous or asynchronous functions. A provider is useful for values that
can change during the lifetime of the client, such as an access token.
const client = initClient(api, {
baseUrl: "https://api.example.com",
globalHeaders: {
authorization: () => `Bearer ${readToken()}`,
"x-client-version": "2026-09",
},
});
A global header with a guaranteed value makes a matching declared route header
optional in client calls. A provider that can return undefined does not.
Routes that combine .input() with .headers()
only accept the input in client calls, so their declared headers must be
supplied through globalHeaders.
Custom Fetch
If you need to wrap the fetch client with custom logic, pass a fetch function.
const client = initClient(api, {
baseUrl: "https://api.example.com",
fetch: async (url, init) => {
const startedAt = performance.now();
try {
return await fetch(url, init);
} finally {
console.log("API request took", performance.now() - startedAt);
}
},
});
Per-Call Options
Pass fetch options as the second argument. They override matching defaults in
fetchOptions. Use signal for cancellation. timeoutMs still applies when a
signal is passed.
const response = await client.todos.get(
{
params: { id: "todo_1" },
headers: { "x-request-id": "req_1" },
},
{
cache: "no-store",
},
);
Use additionalHeaders for transport headers needed only by this call. Headers
declared by the route remain part of the typed request object.
const response = await client.todos.get(
{ params: { id: "todo_1" } },
{
additionalHeaders: {
"x-trace-id": traceId,
},
},
);
When a route declares a list of request content types, contentType selects
the one used to encode the body and is required:
await client.files.upload(bytes, { contentType: "image/png" });
If you need to pass options to a route without request input, pass undefined for the request input.
const response = await client.health(undefined, {
cache: "no-store",
});
Response Validation
The client decodes response bodies without schema validation by default.
Enable validateResponses to validate decoded bodies, declared response
headers, and stream event data:
const client = initClient(api, {
baseUrl: "https://api.example.com",
validateResponses: true,
});
The client receives the schema output on success, and a failure throws an
HttpError. The decoded response is the server’s schema
output, but the client validates it as schema input. Schemas whose input and
output types differ, such as transforms, may fail on the client.
Type Helpers
Use the type helpers when a client request or result type needs to be reused, for example in your own wrapper functions.
import type {
type InferClientError<T extends RouteTree> = T extends {
readonly "~restrpc": infer TRoute extends RouteDeclaration;
} ? ClientErrorForDeclaration<TRoute> : { [K in keyof T]: T[K] extends RouteTree ? InferClientError<T[K]> : never; }
Infers the declared error responses a route's client call can return.InferClientError,
type InferClientRequest<T extends RouteTree, TGlobalHeaders extends ClientHeaders = Record<never, string>> = T extends {
readonly "~restrpc": infer TRoute extends RouteDeclaration;
} ? ClientRequestValue<ClientRequestForDeclaration<TRoute, GlobalHeaderKeys<TGlobalHeaders>>> : { [K in keyof T]: T[K] extends RouteTree ? InferClientRequest<T[K], TGlobalHeaders> : never; }
Infers the input passed to a generated client route call.InferClientRequest,
type InferClientResponse<T extends RouteTree> = T extends {
readonly "~restrpc": infer TRoute extends RouteDeclaration;
} ? ClientResponseForDeclaration<TRoute> : { [K in keyof T]: T[K] extends RouteTree ? InferClientResponse<T[K]> : never; }
Infers a route's client result.InferClientResponse,
type InferClientStreamData<T extends RouteTree> = T extends {
readonly "~restrpc": infer TRoute extends RouteDeclaration;
} ? ClientStreamDataForDeclaration<TRoute> : { [K in keyof T]: T[K] extends RouteTree ? InferClientStreamData<T[K]> : never; }
Infers the `data` of each event received from a streaming route.InferClientStreamData,
type InferClientSuccess<T extends RouteTree> = T extends {
readonly "~restrpc": infer TRoute extends RouteDeclaration;
} ? ClientSuccessForDeclaration<TRoute> : { [K in keyof T]: T[K] extends RouteTree ? InferClientSuccess<T[K]> : never; }
Infers the successful result of a route's client call.InferClientSuccess,
} from "@rest-rpc/core";
import { const route: ServerRouteBuilder<ExpressHandlerFields, DefaultContext>Entry point for declaring routes with handlers for Express.route } from "@rest-rpc/express";
import import zz from "zod";
const const update: HandlerImplementation<{
readonly kind: "http";
readonly method: "PATCH";
readonly path: "/todos/:id";
request: {
body: readonly [z.ZodObject<{
title: z.ZodString;
}, z.core.$strip>];
params: readonly [z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>];
contentType: "application/json";
};
responses: {};
readonly input: "segments";
}, ExpressHandlerFields, DefaultContext, {
readonly id: string;
readonly title: string;
}, {
readonly input: "segments";
readonly method: "PATCH";
readonly path: "/todos/:id";
request: {
body: readonly [z.ZodObject<{
title: z.ZodString;
}, z.core.$strip>];
params: readonly [z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>];
contentType: "application/json";
};
readonly kind: "http";
responses: {
...;
};
output: "output";
}>
update = const route: ServerRouteBuilder<ExpressHandlerFields, DefaultContext>Entry point for declaring routes with handlers for Express.route
.patch<"/todos/:id", never>(this: BuilderReceiver<BuilderPath, never>, path?: "/todos/:id" | undefined): RouteBuilderView<WithHttpRoute<DerivedState, "PATCH">, ServerBuilderExtension<ExpressHandlerFields, DefaultContext>, "/todos/:id", never>patch("/todos/:id")
.params<z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>, "/todos/:id", never>(this: BuilderReceiver<"/todos/:id", never>, schema: z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>): RouteBuilderView<WithRequest<WithHttpRoute<DerivedState, "PATCH">, "params", z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>>, ServerBuilderExtension<ExpressHandlerFields, DefaultContext>, "/todos/:id", never>
Declares path parameters.params(import zz.function object<{
id: z.ZodString;
}>(shape?: {
id: z.ZodString;
} | undefined, params?: string | {
error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueInvalidType<unknown> | z.core.$ZodIssueUnrecognizedKeys>> | undefined;
message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>
object({ id: z.ZodStringid: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string() }))
.body<z.ZodObject<{
title: z.ZodString;
}, z.core.$strip>, undefined, "/todos/:id", never>(this: BuilderReceiver<"/todos/:id", never>, schema: z.ZodObject<{
title: z.ZodString;
}, z.core.$strip>, options?: undefined): RouteBuilderView<WithContentType<WithRequest<WithRequest<WithHttpRoute<DerivedState, "PATCH">, "params", z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>>, "body", z.ZodObject<{
title: z.ZodString;
}, z.core.$strip>>, "application/json">, ServerBuilderExtension<...>, "/todos/:id", never>
Declares a request body and its HTTP metadata.body(import zz.function object<{
title: z.ZodString;
}>(shape?: {
title: z.ZodString;
} | undefined, params?: string | {
error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueInvalidType<unknown> | z.core.$ZodIssueUnrecognizedKeys>> | undefined;
message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
title: z.ZodString;
}, z.core.$strip>
object({ title: z.ZodStringtitle: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string() }))
.handler<{
readonly id: string;
readonly title: string;
}>(handler: HandlerFor<{
readonly kind: "http";
readonly method: "PATCH";
readonly path: "/todos/:id";
request: {
body: readonly [z.ZodObject<{
title: z.ZodString;
}, z.core.$strip>];
params: readonly [z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>];
contentType: "application/json";
};
responses: {};
readonly input: "segments";
}, ExpressHandlerFields, DefaultContext, {
readonly id: string;
readonly title: string;
}>): HandlerImplementation<{
readonly kind: "http";
readonly method: "PATCH";
readonly path: "/todos/:id";
request: {
body: readonly [z.ZodObject<{
title: z.ZodString;
}, z.core.$strip>];
params: readonly [z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>];
contentType: "application/json";
};
responses: {};
readonly input: "segments";
}, ExpressHandlerFields, DefaultContext, {
readonly id: string;
readonly title: string;
}, {
...;
}>
handler(({ params: {
id: string;
}
params, body: {
title: string;
}
body }) => ({ id: stringid: params: {
id: string;
}
params.id: stringid, title: stringtitle: body: {
title: string;
}
body.title: stringtitle }));
const const getFirst: HandlerImplementation<{
readonly kind: "procedure";
readonly method: "POST";
readonly path: undefined;
request?: never | undefined;
responses: {};
}, ExpressHandlerFields, DefaultContext, {
readonly status: 200;
readonly body: {
readonly id: "todo_1";
readonly title: "Write docs";
readonly code?: undefined;
};
} | {
readonly status: 404;
readonly body: {
readonly code: "TODO_NOT_FOUND";
readonly id?: undefined;
readonly title?: undefined;
};
}, {
readonly method: "POST";
readonly path: undefined;
request?: never | undefined;
readonly kind: "procedure";
responses: InferredResponses<...>;
output: "response";
}>
getFirst = const route: ServerRouteBuilder<ExpressHandlerFields, DefaultContext>Entry point for declaring routes with handlers for Express.route.handler<{
readonly status: 200;
readonly body: {
readonly id: "todo_1";
readonly title: "Write docs";
readonly code?: undefined;
};
} | {
readonly status: 404;
readonly body: {
readonly code: "TODO_NOT_FOUND";
readonly id?: undefined;
readonly title?: undefined;
};
}>(handler: HandlerFor<{
readonly kind: "procedure";
readonly method: "POST";
readonly path: undefined;
request?: never | undefined;
responses: {};
}, ExpressHandlerFields, DefaultContext, {
readonly status: 200;
readonly body: {
readonly id: "todo_1";
readonly title: "Write docs";
readonly code?: undefined;
};
} | {
readonly status: 404;
readonly body: {
readonly code: "TODO_NOT_FOUND";
readonly id?: undefined;
readonly title?: undefined;
};
}>): HandlerImplementation<...>
handler(() => {
if (var Math: MathAn intrinsic object that provides basic mathematics functionality and constants.Math.Math.random(): numberReturns a pseudorandom number between 0 and 1.random() < 0.5) {
return { status: 200status: 200, body: {
readonly id: "todo_1";
readonly title: "Write docs";
readonly code?: undefined;
}
body: { id: "todo_1"id: "todo_1", title: "Write docs"title: "Write docs" } };
}
return { status: 404status: 404, body: {
readonly code: "TODO_NOT_FOUND";
readonly id?: undefined;
readonly title?: undefined;
}
body: { code: "TODO_NOT_FOUND"code: "TODO_NOT_FOUND" as type const = "TODO_NOT_FOUND"const } };
});
type type UpdateTodoRequest = {
body: {
title: string;
};
params: {
id: string;
};
}
UpdateTodoRequest = type InferClientRequest<T extends RouteTree, TGlobalHeaders extends ClientHeaders = Record<never, string>> = T extends {
readonly "~restrpc": infer TRoute extends RouteDeclaration;
} ? ClientRequestValue<ClientRequestForDeclaration<TRoute, GlobalHeaderKeys<TGlobalHeaders>>> : { [K in keyof T]: T[K] extends RouteTree ? InferClientRequest<T[K], TGlobalHeaders> : never; }
Infers the input passed to a generated client route call.InferClientRequest<typeof const update: HandlerImplementation<{
readonly kind: "http";
readonly method: "PATCH";
readonly path: "/todos/:id";
request: {
body: readonly [z.ZodObject<{
title: z.ZodString;
}, z.core.$strip>];
params: readonly [z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>];
contentType: "application/json";
};
responses: {};
readonly input: "segments";
}, ExpressHandlerFields, DefaultContext, {
readonly id: string;
readonly title: string;
}, {
readonly input: "segments";
readonly method: "PATCH";
readonly path: "/todos/:id";
request: {
body: readonly [z.ZodObject<{
title: z.ZodString;
}, z.core.$strip>];
params: readonly [z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>];
contentType: "application/json";
};
readonly kind: "http";
responses: {
...;
};
output: "output";
}>
update>;
type type GetFirstTodoResponse = {
status: 200;
body: {
readonly id: "todo_1";
readonly title: "Write docs";
readonly code?: undefined;
};
headers: Headers;
} | {
status: 404;
body: {
readonly code: "TODO_NOT_FOUND";
readonly id?: undefined;
readonly title?: undefined;
};
headers: Headers;
}
GetFirstTodoResponse = type InferClientResponse<T extends RouteTree> = T extends {
readonly "~restrpc": infer TRoute extends RouteDeclaration;
} ? ClientResponseForDeclaration<TRoute> : { [K in keyof T]: T[K] extends RouteTree ? InferClientResponse<T[K]> : never; }
Infers a route's client result.InferClientResponse<typeof const getFirst: HandlerImplementation<{
readonly kind: "procedure";
readonly method: "POST";
readonly path: undefined;
request?: never | undefined;
responses: {};
}, ExpressHandlerFields, DefaultContext, {
readonly status: 200;
readonly body: {
readonly id: "todo_1";
readonly title: "Write docs";
readonly code?: undefined;
};
} | {
readonly status: 404;
readonly body: {
readonly code: "TODO_NOT_FOUND";
readonly id?: undefined;
readonly title?: undefined;
};
}, {
readonly method: "POST";
readonly path: undefined;
request?: never | undefined;
readonly kind: "procedure";
responses: InferredResponses<...>;
output: "response";
}>
getFirst>;
type type GetFirstTodoSuccess = {
status: 200;
body: {
readonly id: "todo_1";
readonly title: "Write docs";
readonly code?: undefined;
};
headers: Headers;
}
GetFirstTodoSuccess = type InferClientSuccess<T extends RouteTree> = T extends {
readonly "~restrpc": infer TRoute extends RouteDeclaration;
} ? ClientSuccessForDeclaration<TRoute> : { [K in keyof T]: T[K] extends RouteTree ? InferClientSuccess<T[K]> : never; }
Infers the successful result of a route's client call.InferClientSuccess<typeof const getFirst: HandlerImplementation<{
readonly kind: "procedure";
readonly method: "POST";
readonly path: undefined;
request?: never | undefined;
responses: {};
}, ExpressHandlerFields, DefaultContext, {
readonly status: 200;
readonly body: {
readonly id: "todo_1";
readonly title: "Write docs";
readonly code?: undefined;
};
} | {
readonly status: 404;
readonly body: {
readonly code: "TODO_NOT_FOUND";
readonly id?: undefined;
readonly title?: undefined;
};
}, {
readonly method: "POST";
readonly path: undefined;
request?: never | undefined;
readonly kind: "procedure";
responses: InferredResponses<...>;
output: "response";
}>
getFirst>;
type type GetFirstTodoError = {
status: 404;
body: {
readonly code: "TODO_NOT_FOUND";
readonly id?: undefined;
readonly title?: undefined;
};
headers: Headers;
}
GetFirstTodoError = type InferClientError<T extends RouteTree> = T extends {
readonly "~restrpc": infer TRoute extends RouteDeclaration;
} ? ClientErrorForDeclaration<TRoute> : { [K in keyof T]: T[K] extends RouteTree ? InferClientError<T[K]> : never; }
Infers the declared error responses a route's client call can return.InferClientError<typeof const getFirst: HandlerImplementation<{
readonly kind: "procedure";
readonly method: "POST";
readonly path: undefined;
request?: never | undefined;
responses: {};
}, ExpressHandlerFields, DefaultContext, {
readonly status: 200;
readonly body: {
readonly id: "todo_1";
readonly title: "Write docs";
readonly code?: undefined;
};
} | {
readonly status: 404;
readonly body: {
readonly code: "TODO_NOT_FOUND";
readonly id?: undefined;
readonly title?: undefined;
};
}, {
readonly method: "POST";
readonly path: undefined;
request?: never | undefined;
readonly kind: "procedure";
responses: InferredResponses<...>;
output: "response";
}>
getFirst>;
const const events: HandlerImplementation<{
readonly kind: "http";
readonly method: "GET";
readonly path: "/todos/events";
request?: never | undefined;
responses: {
200: {
kind: "stream";
body: z.ZodObject<{
id: z.ZodString;
message: z.ZodString;
}, z.core.$strip>;
};
};
readonly output: "output";
}, ExpressHandlerFields, DefaultContext, AsyncGenerator<{
id: string;
message: string;
}, void, unknown>, {
readonly kind: "http";
readonly method: "GET";
readonly path: "/todos/events";
request?: never | undefined;
responses: {
200: {
kind: "stream";
body: z.ZodObject<{
id: z.ZodString;
message: z.ZodString;
}, z.core.$strip>;
};
};
readonly output: "output";
}>
events = const route: ServerRouteBuilder<ExpressHandlerFields, DefaultContext>Entry point for declaring routes with handlers for Express.route
.get<"/todos/events", never>(this: BuilderReceiver<BuilderPath, never>, path?: "/todos/events" | undefined): RouteBuilderView<WithHttpRoute<DerivedState, "GET">, ServerBuilderExtension<ExpressHandlerFields, DefaultContext>, "/todos/events", never>get("/todos/events")
.streamOutput<z.ZodObject<{
id: z.ZodString;
message: z.ZodString;
}, z.core.$strip>, "/todos/events", never>(this: BuilderReceiver<"/todos/events", never>, schema: z.ZodObject<{
id: z.ZodString;
message: z.ZodString;
}, z.core.$strip>): RouteBuilderView<WithPlainOutput<UseMethod<WithHttpRoute<DerivedState, "GET">, "output">, {
kind: "stream";
body: z.ZodObject<{
id: z.ZodString;
message: z.ZodString;
}, z.core.$strip>;
}>, ServerBuilderExtension<ExpressHandlerFields, DefaultContext>, "/todos/events", never>
Declares a streaming plain output.streamOutput(import zz.function object<{
id: z.ZodString;
message: z.ZodString;
}>(shape?: {
id: z.ZodString;
message: z.ZodString;
} | undefined, params?: string | {
error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueInvalidType<unknown> | z.core.$ZodIssueUnrecognizedKeys>> | undefined;
message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
id: z.ZodString;
message: z.ZodString;
}, z.core.$strip>
object({ id: z.ZodStringid: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string(), message: z.ZodStringmessage: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string() }))
.handler<AsyncGenerator<{
id: string;
message: string;
}, void, unknown>>(handler: HandlerFor<{
readonly kind: "http";
readonly method: "GET";
readonly path: "/todos/events";
request?: never | undefined;
responses: {
200: {
kind: "stream";
body: z.ZodObject<{
id: z.ZodString;
message: z.ZodString;
}, z.core.$strip>;
};
};
readonly output: "output";
}, ExpressHandlerFields, DefaultContext, AsyncGenerator<{
id: string;
message: string;
}, void, unknown>>): HandlerImplementation<{
readonly kind: "http";
readonly method: "GET";
readonly path: "/todos/events";
request?: never | undefined;
responses: {
200: {
kind: "stream";
body: z.ZodObject<{
id: z.ZodString;
message: z.ZodString;
}, z.core.$strip>;
};
};
readonly output: "output";
}, ExpressHandlerFields, DefaultContext, AsyncGenerator<...>, {
readonly kind: "http";
readonly method: "GET";
readonly path: "/todos/events";
request?: never | undefined;
responses: {
200: {
kind: "stream";
body: z.ZodObject<{
id: z.ZodString;
message: z.ZodString;
}, z.core.$strip>;
};
};
readonly output: "output";
}>
handler(async function* () {
yield { id: stringid: "todo_1", message: stringmessage: "Created" };
});
type type TodoEvent = {
id: string;
message: string;
}
TodoEvent = type InferClientStreamData<T extends RouteTree> = T extends {
readonly "~restrpc": infer TRoute extends RouteDeclaration;
} ? ClientStreamDataForDeclaration<TRoute> : { [K in keyof T]: T[K] extends RouteTree ? InferClientStreamData<T[K]> : never; }
Infers the `data` of each event received from a streaming route.InferClientStreamData<typeof const events: HandlerImplementation<{
readonly kind: "http";
readonly method: "GET";
readonly path: "/todos/events";
request?: never | undefined;
responses: {
200: {
kind: "stream";
body: z.ZodObject<{
id: z.ZodString;
message: z.ZodString;
}, z.core.$strip>;
};
};
readonly output: "output";
}, ExpressHandlerFields, DefaultContext, AsyncGenerator<{
id: string;
message: string;
}, void, unknown>, {
readonly kind: "http";
readonly method: "GET";
readonly path: "/todos/events";
request?: never | undefined;
responses: {
200: {
kind: "stream";
body: z.ZodObject<{
id: z.ZodString;
message: z.ZodString;
}, z.core.$strip>;
};
};
readonly output: "output";
}>
events>;
| Type | Infers |
|---|---|
InferClientRequest<Route, GlobalHeaders> |
The request passed to the route call, or undefined for routes without input. |
InferClientResponse<Route> |
Every declared response, or the output of a .output() route. |
InferClientSuccess<Route> |
The declared 2xx responses, or the output of a .output() route. |
InferClientError<Route> |
The declared non-2xx responses. .output() routes infer never. |
InferClientStreamData<Route> |
The data of each event from a streaming route, without SseEvent. |
InferClientError contains only the responses the client returns. The
HttpError and Error values it throws are not included.
InferClientRequest accepts the type of your globalHeaders as a second
argument, so headers that globalHeaders always provides become optional:
import type { InferClientRequest } from "@rest-rpc/core";
import type { api } from "./client-contract";
const globalHeaders = { authorization: () => `Bearer ${getToken()}` };
type UpdateTodoRequest = InferClientRequest<
typeof api.todos.update,
typeof globalHeaders
>;
ApiClientFor<typeof api> is the type of the whole client created from a
contract.
Each helper also accepts a route tree and infers a matching tree of types, so one exported type covers every route:
import type { InferClientResponse } from "@rest-rpc/core";
import type { api } from "./client-contract";
export type ClientResponses = InferClientResponse<typeof api>;
type TodoResponse = ClientResponses["todos"]["get"];