Links
A client's chain of links, the built-in ones, and the order they go in.
On this page
A client's links is a chain: each link gets the operation and the rest of the chain. The links in the middle route, retry, share or log operations; the last one, the terminating link, carries them to a server or runs them in process.
import {
function createTrpcClient<TSource, const TLinks extends ReadonlyArray<TrpcLink<any, RouterOf<TSource>>>>(opts: TypedClientOptions<TSource, TLinks> & DuplicateSwitchCheck<TLinks> & UnmetNeedCheck<TLinks>): TrpcClient<RouterOf<TSource>, LinksContext<TLinks>, undefined, LinksTypes<TLinks>>createTrpcClient,
function dedupeLink(opts?: DedupeLinkOptions): TrpcLink<{
context: DedupeContext;
}>
dedupeLink,
function httpLink<TOpts extends HttpLinkOptions>(opts: TOpts): TrpcLink<HttpLinkDecl<TOpts>>httpLink,
function loggerLink(opts?: {
enabled?: (op: Operation) => boolean;
log?: (event: LoggerEvent) => void;
}): TrpcLink
loggerLink,
function retryLink(opts?: RetryLinkOptions): TrpcLinkretryLink,
function routerType<TRouter>(): RouterType<TRouter>routerType,
} from '@trpc12/trpc/client';
const const client: TrpcClient<{
post: {
byId: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: {
id: string;
};
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}> | SchemaValidationError<ZodObject<{
id: ZodString;
}, $strip>>;
services: never;
}>;
list: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: undefined;
output: Post[];
errors: never;
services: never;
}>;
create: Procedure<{
type: "mutation";
ctx: Context;
meta: {};
input: {
title: string;
};
output: Post;
errors: TrpcError<...> | SchemaValidationError<...>;
services: never;
}>;
};
}, {
...;
}, undefined, LinksTypes<...>>
client = createTrpcClient<RouterType<{
post: {
byId: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: {
id: string;
};
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}> | SchemaValidationError<ZodObject<{
id: ZodString;
}, $strip>>;
services: never;
}>;
list: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: undefined;
output: Post[];
errors: never;
services: never;
}>;
create: Procedure<{
type: "mutation";
ctx: Context;
meta: {};
input: {
title: string;
};
output: Post;
errors: TrpcError<...> | SchemaValidationError<...>;
services: never;
}>;
};
}>, readonly [...]>(opts: TypedClientOptions<...>): TrpcClient<...>
createTrpcClient({
TypedClientOptions<RouterType<{ post: { byId: Procedure<{ type: "query"; ctx: Context; meta: {}; input: { id: string; }; output: Post; errors: TrpcError<"NOT_FOUND", { id: string; }> | SchemaValidationError<ZodObject<{ id: ZodString; }, $strip>>; services: never; }>; list: Procedure<...>; create: Procedure<...>; }; }>, readonly [...]>.router: RouterType<{
post: {
byId: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: {
id: string;
};
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}> | SchemaValidationError<ZodObject<{
id: ZodString;
}, $strip>>;
services: never;
}>;
list: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: undefined;
output: Post[];
errors: never;
services: never;
}>;
create: Procedure<{
type: "mutation";
ctx: Context;
meta: {};
input: {
title: string;
};
output: Post;
errors: TrpcError<...> | SchemaValidationError<...>;
services: never;
}>;
};
}>
router: routerType<{
post: {
byId: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: {
id: string;
};
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}> | SchemaValidationError<ZodObject<{
id: ZodString;
}, $strip>>;
services: never;
}>;
list: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: undefined;
output: Post[];
errors: never;
services: never;
}>;
create: Procedure<{
type: "mutation";
ctx: Context;
meta: {};
input: {
title: string;
};
output: Post;
errors: TrpcError<...> | SchemaValidationError<...>;
services: never;
}>;
};
}>(): RouterType<...>
routerType<type AppRouter = {
post: {
byId: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: {
id: string;
};
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}> | SchemaValidationError<ZodObject<{
id: ZodString;
}, $strip>>;
services: never;
}>;
list: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: undefined;
output: Post[];
errors: never;
services: never;
}>;
create: Procedure<{
type: "mutation";
ctx: Context;
meta: {};
input: {
title: string;
};
output: Post;
errors: TrpcError<...> | SchemaValidationError<...>;
services: never;
}>;
};
}
AppRouter>(),
TypedClientOptions<RouterType<{ post: { byId: Procedure<{ type: "query"; ctx: Context; meta: {}; input: { id: string; }; output: Post; errors: TrpcError<"NOT_FOUND", { id: string; }> | SchemaValidationError<ZodObject<{ id: ZodString; }, $strip>>; services: never; }>; list: Procedure<...>; create: Procedure<...>; }; }>, readonly [...]>.links: readonly [TrpcLink<{}, any>, TrpcLink<{
context: DedupeContext;
}, any>, TrpcLink<{}, any>, TrpcLink<HttpLinkDecl<{
url: string;
batch: true;
}>, any>]
links: [
function loggerLink(opts?: {
enabled?: (op: Operation) => boolean;
log?: (event: LoggerEvent) => void;
}): TrpcLink
loggerLink({ enabled?: ((op: Operation) => boolean) | undefinedenabled: () => var process: NodeJS.Processprocess.NodeJS.Process.env: NodeJS.ProcessEnvenv.string | undefinedNODE_ENV === 'development' }),
function dedupeLink(opts?: DedupeLinkOptions): TrpcLink<{
context: DedupeContext;
}>
dedupeLink(),
function retryLink(opts?: RetryLinkOptions): TrpcLinkretryLink(),
httpLink<{
url: string;
batch: true;
}>(opts: {
url: string;
batch: true;
}): TrpcLink<HttpLinkDecl<{
url: string;
batch: true;
}>, any>
httpLink({ url: stringurl: 'http://localhost:3000/trpc', batch: truebatch: true }),
],
});An operation goes through the links in order and its results come back the other way. Here the logger sees each call once, and identical queries are shared before the retries, so a retried query is still one request for every caller.
Terminating links
| Link | Carries calls |
|---|---|
httpLink | Over HTTP: queries as GET, mutations as POST, subscriptions as SSE |
wsLink | Over one WebSocket |
localLink | To a router in the same process |
Links in the middle
| Link | Does |
|---|---|
splitLink | Picks one of two chains per call |
switchLink | Picks one of several named chains, and a call can force one |
retryLink | Retries failed calls |
dedupeLink | Shares identical queries in flight |
loggerLink | Logs every operation, its results and its errors |
Call options
A link declares the call options it reads (dedupe for dedupeLink, a switchLink's name), and a client made with router: types them on every call, next to the built-in signal, headers, init, batch and reconnect (The client).
link(({ op, next }) => stream) writes a link of your own, with the call options it reads, the errors it fails with and what it needs from the links after it (Custom links).
Pages under Links
- httpLinkQueries as GET, mutations as POST and subscriptions as SSE, with batching, streamed outputs and files.
- wsLinkEvery call over one WebSocket, with connection params, keep-alive pings and reconnects that resume subscriptions.
- splitLinkSend each call down one of two chains, picked from its path, type or input.
- switchLinkPick one of several named chains per call, and let a call force one with a typed call option.
- retryLinkRetry failed calls, by default queries that failed outside their typed errors, with backoff.
- dedupeLinkShare one request between identical queries in flight.
- loggerLinkLog every operation, its results and its errors, to the console or a function of your own.
- localLinkCall a router in the same process, through the client and its other links.
- Custom linksWrite a link with link(), and declare the call options it reads, the errors it fails with and what it needs.