Retry Link
On this page
retryLink is a link that allows you to retry failed operations in your tRPC client. It provides a customizable way to handle transient errors, such as network failures or server errors, by automatically retrying the failed requests based on specified conditions.
Usage
You can import and add the retryLink to the links array when creating your tRPC client. This link can be placed before or after other links in your setup, depending on your requirements.
import { function createTRPCClient<TRouter extends AnyRouter>(opts: CreateTRPCClientOptions<TRouter>): TRPCClient<TRouter>createTRPCClient, function httpBatchLink<TRouter extends AnyRouter>(opts: HTTPBatchLinkOptions<TRouter["_def"]["_config"]["$types"]>): TRPCLink<TRouter>httpBatchLink, function retryLink<TInferrable extends InferrableClientTypes>(opts: RetryLinkOptions<TInferrable>): TRPCLink<TInferrable>retryLink } from '@trpc/client';
import type { type AppRouter = Router<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{}>> & DecorateCreateRouterOptions<{}>
AppRouter } from './server';
const const client: TRPCClient<BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{}>>>
client = createTRPCClient<BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{}>>>(opts: CreateTRPCClientOptions<BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{}>>>): TRPCClient<BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<...>>>
createTRPCClient<type AppRouter = Router<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{}>> & DecorateCreateRouterOptions<{}>
AppRouter>({
links: TRPCLink<BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{}>>>[]
links: [
retryLink<BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{}>>>(opts: RetryLinkOptions<BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{}>>>): TRPCLink<BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{}>>>
retryLink({
RetryLinkOptions<BuiltRouter<{ ctx: object; meta: object; errorShape: DefaultErrorShape; transformer: false; }, DecorateCreateRouterOptions<{}>>>.retry: (opts: RetryFnOptions<BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{}>>>) => boolean
retry(opts: RetryFnOptions<BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{}>>>
opts) {
if (
opts: RetryFnOptions<BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{}>>>
opts.RetryFnOptions<BuiltRouter<{ ctx: object; meta: object; errorShape: DefaultErrorShape; transformer: false; }, DecorateCreateRouterOptions<{}>>>.error: TRPCClientError<BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{}>>>
error.TRPCClientError<BuiltRouter<{ ctx: object; meta: object; errorShape: DefaultErrorShape; transformer: false; }, DecorateCreateRouterOptions<{}>>>.data: Maybe<DefaultErrorData>data &&
opts: RetryFnOptions<BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{}>>>
opts.RetryFnOptions<BuiltRouter<{ ctx: object; meta: object; errorShape: DefaultErrorShape; transformer: false; }, DecorateCreateRouterOptions<{}>>>.error: TRPCClientError<BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{}>>>
error.TRPCClientError<BuiltRouter<{ ctx: object; meta: object; errorShape: DefaultErrorShape; transformer: false; }, DecorateCreateRouterOptions<{}>>>.data: DefaultErrorDatadata.code: "PARSE_ERROR" | "BAD_REQUEST" | "INTERNAL_SERVER_ERROR" | "NOT_IMPLEMENTED" | "BAD_GATEWAY" | "SERVICE_UNAVAILABLE" | "GATEWAY_TIMEOUT" | "UNAUTHORIZED" | "PAYMENT_REQUIRED" | "FORBIDDEN" | "NOT_FOUND" | "METHOD_NOT_SUPPORTED" | "TIMEOUT" | "CONFLICT" | "PRECONDITION_FAILED" | "PAYLOAD_TOO_LARGE" | "UNSUPPORTED_MEDIA_TYPE" | "UNPROCESSABLE_CONTENT" | "PRECONDITION_REQUIRED" | "TOO_MANY_REQUESTS" | "CLIENT_CLOSED_REQUEST"code !== 'INTERNAL_SERVER_ERROR'
) {
// Don't retry on non-500s
return false;
}
if (opts: RetryFnOptions<BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{}>>>
opts.RetryFnOptions<BuiltRouter<{ ctx: object; meta: object; errorShape: DefaultErrorShape; transformer: false; }, DecorateCreateRouterOptions<{}>>>.op: Operationop.type: "query" | "mutation" | "subscription"type !== 'query') {
// Only retry queries
return false;
}
// Retry up to 3 times
return opts: RetryFnOptions<BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{}>>>
opts.RetryFnOptions<BuiltRouter<{ ctx: object; meta: object; errorShape: DefaultErrorShape; transformer: false; }, DecorateCreateRouterOptions<{}>>>.attempts: numberattempts <= 3;
},
// Double every attempt, with max of 30 seconds (starting at 1 second)
RetryLinkOptions<BuiltRouter<{ ctx: object; meta: object; errorShape: DefaultErrorShape; transformer: false; }, DecorateCreateRouterOptions<{}>>>.retryDelayMs?: ((attempt: number) => number) | undefinedretryDelayMs: (attemptIndex: numberattemptIndex) => var Math: MathMath.Math.min(...values: number[]): numbermin(1000 * 2 ** attemptIndex: numberattemptIndex, 30000),
}),
httpBatchLink<BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{}>>>(opts: HTTPBatchLinkOptions<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>): TRPCLink<BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{}>>>
httpBatchLink({
url: string | URLurl: 'http://localhost:3000',
}),
],
});In the example above, we add the retryLink before the httpBatchLink. The retry function is required and defines when to retry. In this example, it will:
- Retry the request if the error is a
TRPCClientErrorwith a status code of 500 or if we couldn't get a valid tRPC error. - Retry the request up to 3 times.
Options
interface interface RetryLinkOptions<TInferrable extends unknown>RetryLinkOptions<function (type parameter) TInferrable in RetryLinkOptions<TInferrable extends unknown>TInferrable extends type InferrableClientTypes = anyInferrableClientTypes> {
/**
* The retry function
*/
RetryLinkOptions<TInferrable extends unknown>.retry: (opts: RetryFnOptions<TInferrable>) => booleanretry: (opts: RetryFnOptions<TInferrable>opts: interface RetryFnOptions<TInferrable extends unknown>RetryFnOptions<function (type parameter) TInferrable in RetryLinkOptions<TInferrable extends unknown>TInferrable>) => boolean;
/**
* The delay between retries in ms (defaults to 0)
*/
RetryLinkOptions<TInferrable extends unknown>.retryDelayMs?: ((attempt: number) => number) | undefinedretryDelayMs?: (attempt: numberattempt: number) => number;
}
interface interface RetryFnOptions<TInferrable extends unknown>RetryFnOptions<function (type parameter) TInferrable in RetryFnOptions<TInferrable extends unknown>TInferrable extends type InferrableClientTypes = anyInferrableClientTypes> {
/**
* The operation that failed
*/
RetryFnOptions<TInferrable extends unknown>.op: Operationop: type Operation = {
id: number;
type: string;
input: unknown;
path: string;
}
Operation;
/**
* The error that occurred
*/
RetryFnOptions<TInferrable extends unknown>.error: TRPCClientError<TInferrable>error: type TRPCClientError<T> = Error & {
data: any;
}
TRPCClientError<function (type parameter) TInferrable in RetryFnOptions<TInferrable extends unknown>TInferrable>;
/**
* The number of attempts that have been made (including the first call)
*/
RetryFnOptions<TInferrable extends unknown>.attempts: numberattempts: number;
}Handling tracked() events
When using retryLink with subscriptions that use tracked(), the link will automatically include the last known event ID when retrying. This ensures that when a subscription reconnects, it can resume from where it left off without missing any events.
For example, if you're using Server-sent Events (SSE) with httpSubscriptionLink, the retryLink will automatically handle reconnecting with the last event ID when errors like 401 Unauthorized occur.