Skip to content

tRPC v12 isn’t on npm yet. The @trpc12/* packages on this site can’t be installed until it’s released. Use v11 today

tRPC

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: BuildProcedure<InputDef<RootDef<Context, {}>, ZodObject<{
            id: ZodString;
        }, $strip>>, "query", {
            output: Post;
            errors: TrpcError<"NOT_FOUND", {
                id: string;
            }>;
            services: never;
            unmapped: never;
        }>;
        list: BuildProcedure<RootDef<Context, {}>, "query", {
            output: Post[];
            errors: never;
            services: never;
            unmapped: never;
        }>;
        create: BuildProcedure<InputDef<With<RootDef<Context, {}>, {
            ctx: Context;
            ctxAdded: {
                ...;
            };
            inputOut: Unset;
            errors: TrpcError<...>;
            shorts: never;
        }>, ZodObject<...>>, "mutation", {
            ...;
        }>;
    };
}, {}, undefined, LinksTypes<...>>
client
=
createTrpcClient<RouterType<{
    post: {
        byId: BuildProcedure<InputDef<RootDef<Context, {}>, ZodObject<{
            id: ZodString;
        }, $strip>>, "query", {
            output: Post;
            errors: TrpcError<"NOT_FOUND", {
                id: string;
            }>;
            services: never;
            unmapped: never;
        }>;
        list: BuildProcedure<RootDef<Context, {}>, "query", {
            output: Post[];
            errors: never;
            services: never;
            unmapped: never;
        }>;
        create: BuildProcedure<InputDef<With<RootDef<Context, {}>, {
            ctx: Context;
            ctxAdded: {
                ...;
            };
            inputOut: Unset;
            errors: TrpcError<...>;
            shorts: never;
        }>, ZodObject<...>>, "mutation", {
            ...;
        }>;
    };
}>, readonly [...]>(opts: TypedClientOptions<...>): TrpcClient<...>
createTrpcClient
({
TypedClientOptions<RouterType<{ post: { byId: BuildProcedure<InputDef<RootDef<Context, {}>, ZodObject<{ id: ZodString; }, $strip>>, "query", { output: Post; errors: TrpcError<...>; services: never; unmapped: never; }>; list: BuildProcedure<...>; create: BuildProcedure<...>; }; }>, readonly [...]>.router: RouterType<{
    post: {
        byId: BuildProcedure<InputDef<RootDef<Context, {}>, ZodObject<{
            id: ZodString;
        }, $strip>>, "query", {
            output: Post;
            errors: TrpcError<"NOT_FOUND", {
                id: string;
            }>;
            services: never;
            unmapped: never;
        }>;
        list: BuildProcedure<RootDef<Context, {}>, "query", {
            output: Post[];
            errors: never;
            services: never;
            unmapped: never;
        }>;
        create: BuildProcedure<InputDef<With<RootDef<Context, {}>, {
            ctx: Context;
            ctxAdded: {
                ...;
            };
            inputOut: Unset;
            errors: TrpcError<...>;
            shorts: never;
        }>, ZodObject<...>>, "mutation", {
            ...;
        }>;
    };
}>
router
:
routerType<{
    post: {
        byId: BuildProcedure<InputDef<RootDef<Context, {}>, ZodObject<{
            id: ZodString;
        }, $strip>>, "query", {
            output: Post;
            errors: TrpcError<"NOT_FOUND", {
                id: string;
            }>;
            services: never;
            unmapped: never;
        }>;
        list: BuildProcedure<RootDef<Context, {}>, "query", {
            output: Post[];
            errors: never;
            services: never;
            unmapped: never;
        }>;
        create: BuildProcedure<InputDef<With<RootDef<Context, {}>, {
            ctx: Context;
            ctxAdded: {
                ...;
            };
            inputOut: Unset;
            errors: TrpcError<...>;
            shorts: never;
        }>, ZodObject<...>>, "mutation", {
            ...;
        }>;
    };
}>(): RouterType<...>
routerType
<
type AppRouter = {
    post: {
        byId: BuildProcedure<InputDef<RootDef<Context, {}>, ZodObject<{
            id: ZodString;
        }, $strip>>, "query", {
            output: Post;
            errors: TrpcError<"NOT_FOUND", {
                id: string;
            }>;
            services: never;
            unmapped: never;
        }>;
        list: BuildProcedure<RootDef<Context, {}>, "query", {
            output: Post[];
            errors: never;
            services: never;
            unmapped: never;
        }>;
        create: BuildProcedure<InputDef<With<RootDef<Context, {}>, {
            ctx: Context;
            ctxAdded: {
                ...;
            };
            inputOut: Unset;
            errors: TrpcError<...>;
            shorts: never;
        }>, ZodObject<...>>, "mutation", {
            ...;
        }>;
    };
}
AppRouter
>(),
TypedClientOptions<RouterType<{ post: { byId: BuildProcedure<InputDef<RootDef<Context, {}>, ZodObject<{ id: ZodString; }, $strip>>, "query", { output: Post; errors: TrpcError<...>; services: never; unmapped: never; }>; list: BuildProcedure<...>; create: BuildProcedure<...>; }; }>, 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 check op.type yourself before retrying mutations.
  • delayMs(attempt, error): how long to wait before a retry, 2 ** attempt * 100 by 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 rate limit fails with TOO_MANY_REQUESTS and 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.