Retry Plugin
Automatically retry failed oRPC requests with customizable strategies controlled through client context options.
Setup
import { RetryLinkPlugin, RetryLinkPluginContext } from '@orpc/client/plugins'
interface ClientContext extends RetryLinkPluginContext {}
const link = new RPCLink<ClientContext>({
plugins: [
new RetryLinkPlugin(),
],
})
Usage
By default, retries are disabled. To enable retries, set the retry count in the request context:
const const planets: {
id: number;
name: string;
description?: string | undefined;
}[]
planets = await const client: {
planet: {
list: ProcedureClient<RetryLinkPluginContext, ZodObject<{
limit: ZodOptional<ZodNumber>;
cursor: ZodDefault<ZodNumber>;
}, $strip>, ZodArray<ZodObject<{
id: ZodNumber;
name: ZodString;
description: ZodOptional<ZodString>;
}, $strip>>, object>;
find: ProcedureClient<RetryLinkPluginContext, ZodObject<{
id: ZodNumber;
}, $strip>, ZodObject<...>, object>;
create: ProcedureClient<...>;
};
}
client.planet: {
list: ProcedureClient<RetryLinkPluginContext, ZodObject<{
limit: ZodOptional<ZodNumber>;
cursor: ZodDefault<ZodNumber>;
}, $strip>, ZodArray<ZodObject<{
id: ZodNumber;
name: ZodString;
description: ZodOptional<ZodString>;
}, $strip>>, object>;
find: ProcedureClient<RetryLinkPluginContext, ZodObject<{
id: ZodNumber;
}, $strip>, ZodObject<...>, object>;
create: ProcedureClient<...>;
}
planet.list: Client
(input: {
limit?: number | undefined;
cursor?: number | undefined;
}, options?: FriendlyClientOptions<RetryLinkPluginContext> | undefined) => PromiseWithError<{
id: number;
name: string;
description?: string | undefined;
}[], Error>
list({ limit?: number | undefinedlimit: 10 }, {
context?: RetryLinkPluginContext | undefinedcontext: {
RetryLinkPluginContext.retry?: Value<Promisable<number>, [Omit<StandardLinkInterceptorOptions<RetryLinkPluginContext>, "next">]> | undefinedMaximum retry attempts before throwing.
Use `Number.POSITIVE_INFINITY` for infinite retries (e.g. for AsyncIteratorObject).retry: 3, // Maximum retry attempts
RetryLinkPluginContext.retryDelay?: Value<Promisable<number>, [RetryLinkPluginAttemptOptions<RetryLinkPluginContext>]> | undefinedDelay (in ms) before retrying.retryDelay: 2000, // Delay between retries in ms
RetryLinkPluginContext.shouldRetry?: Value<Promisable<boolean>, [RetryLinkPluginAttemptOptions<RetryLinkPluginContext>]> | undefinedDetermine whether to retry.shouldRetry: options: RetryLinkPluginAttemptOptions<RetryLinkPluginContext>options => true, // Determines whether to retry based on the error
RetryLinkPluginContext.onRetry?: ((options: RetryLinkPluginAttemptOptions<RetryLinkPluginContext>) => void | ((isSuccess: boolean) => void)) | undefinedHook called before each retry. Can return a cleanup callback.onRetry: (options: RetryLinkPluginAttemptOptions<RetryLinkPluginContext>options) => {
// Hook executed on each retry
return (isSuccess: booleanisSuccess) => {
// Execute after the retry is complete
}
},
}
})
Client Error Responses
By default, errors from a 4xx response other than 408 and 429 are not retried, since retrying would most likely fail the same way. To retry every error, set shouldRetry to true, or extend the default with isRetryableError:
import { ORPCError } from '@orpc/client'
import { isRetryableError } from '@orpc/client/plugins'
const planets = await client.planet.list({ limit: 10 }, {
context: {
retry: 3,
shouldRetry: ({ error }) => isRetryableError(error) || (error instanceof ORPCError && error.code === 'CONFLICT'),
}
})
Event Source Simulation
To replicate the behavior of EventSource for an AsyncIteratorObject, use the following configuration:
const streaming = await client.streaming('the input', {
context: {
retry: Number.POSITIVE_INFINITY,
}
})
for await (const message of streaming) {
console.log(message)
}
Learn More
For implementation details, see the source code.