retryLink
Retry failed calls, by default queries that failed outside their typed errors, with backoff.
On this page
retryLink() retries a failed call by running the rest of the chain again. By default it retries queries only, on errors outside the procedure's typed union (network failures, 5xx), up to three times, waiting 200, 400 and 800 ms. It never retries an aborted call.
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 httpLink<TOpts extends HttpLinkOptions>(opts: TOpts): TrpcLink<HttpLinkDecl<TOpts>>httpLink,
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<{
form: JsonForm;
provides: "response";
}, any>]
links: [function retryLink(opts?: RetryLinkOptions): TrpcLinkretryLink(), httpLink<{
url: string;
}>(opts: {
url: string;
}): TrpcLink<{
form: JsonForm;
provides: "response";
}, any>
httpLink({ url: stringurl: 'http://localhost:3000/trpc' })],
});retries: how many times to retry, 3 by default.retry({ op, error, attempt }): whether to retry this failure. It replaces the default rule, so checkop.typeyourself before retrying mutations.delayMs(attempt, error): how long to wait before a retry,2 ** attempt * 100by default.
Put it before the terminating link, and after dedupeLink, so a shared query is retried once for all its callers.
Waiting out a rate limit
delayMs gets the error, so a retry can wait as long as the server asks. A server that rate-limits can fail with TOO_MANY_REQUESTS and the wait in data.retryAfter, in milliseconds. Links see every error untyped, so data is any here:
function retryLink(opts?: RetryLinkOptions): TrpcLinkretryLink({
RetryLinkOptions.retry?: ((opts: {
op: Operation;
error: AnyTrpcError;
attempt: number;
}) => boolean) | undefined
retry: (opts: {
op: Operation;
error: AnyTrpcError;
attempt: number;
}
opts) => opts: {
op: Operation;
error: AnyTrpcError;
attempt: number;
}
opts.error: AnyTrpcErrorerror.TrpcError<string, any>.code: stringcode === 'TOO_MANY_REQUESTS',
RetryLinkOptions.delayMs?: ((attempt: number, error: AnyTrpcError) => number) | undefineddelayMs: (attempt: numberattempt, error: AnyTrpcErrorerror) =>
error: AnyTrpcErrorerror.TrpcError<string, any>.code: stringcode === 'TOO_MANY_REQUESTS'
? error: AnyTrpcErrorerror.TrpcError<string, any>.data: anydata.retryAfter
: 2 ** attempt: numberattempt * 100,
});This retry replaces the default rule, so only rate-limited calls are retried, mutations included.