Retries

Sometimes its useful to have some retry functionality in your actions in case something goes wrong and an error is throwing in your action.

Retries on Actions

To configure retying on your server action, you can add the retry method:

actions.ts
"use server"

import { createServerAction } from "zsa"
import z from "zod"

export const retryExample = createServerAction()
    .retry({ 
        maxAttempts: 3
    }) 
    .handler(async () => {
        // throw an error 50% of the time
        if (Math.random() > 0.5) {
            throw new Error("Random error")
        }
        return "Success!"
    })

In this example, this server action will attempt to execute up to 3 times. It will only retry the action if an uncaught error is thrown. If the error still persists after the 3 attempts, then the server action will throw the error to the top level.

Additionally, you can add a delay between retries. This can be useful for things such as rate limit errors, where you may want to wait a certain amount of time before executing the server action again.

actions.ts
"use server"

import { createServerAction } from "zsa"
import z from "zod"

export const retryWithDelay = createServerAction()
    .retry({
        maxAttempts: 3,
        delay: 1000
    })
    .handler(async () => {
        //throw an error 50% of the time
        if (Math.random() > 0.5) {
            throw new Error("Random error")
        }
        return "Success!"
    })

In this example, the server action will wait 1 second between each retry.

Sometimes, you dont want to wait a fixed amount of time depending on certain conitions. In this case, you can set the delay to be a function that takes in a currentAttempt and the err that was throw.

actions.ts
"use server"

import { createServerAction } from "zsa"
import z from "zod"

export const retryWithProgressiveDelay = createServerAction()
    .retry({
        maxAttempts: 3,
        delay: (currentAttempt, err) => { 
            return 1000 * currentAttempt 
        }, 
    })
    .handler(async () => {
        //throw an error 50% of the time
        if (Math.random() > 0.5) {
            throw new Error("Random error")
        }
        return "Success!"
    })

In this example, the delay waits 1 second on the first attempt, 2 seconds on the second attempt, and 3 seconds on the third attempt.

Retries on Procedures

You can also set retires on procedures if you want a set of actions to inherit this retry functionality.

actions.ts
"use server"

import { createServerAction } from "zsa"
import z from "zod"

export const retryProcedure = createServerActionProcedure()
    .retry({
        maxAttempts: 3
    })
    .handler(async () => {
        if (Math.random() > 0.5) {
            throw new Error("Random error")
        }
        return {
            user: {
                id: "123",
                email: "test@example.com"
            }
        }
    })

export const exampleAction = retryProcedure
    .createServerAction()
    .handler(async ({ctx}) => {
        return ctx
    })

By adding retry onto a procedure, exampleAction and all other actions made from this procedure will be retried according to maxAttempts before an error throw to the top level.

If a retry is set on a procedure AND a separate retry is set on an action that inherits that procedure, then the retry on the action will override the retry on the procedure.

If a timeout is set on an action or a procedure that utilizes a retry, then the timeout takes precidence over the timeout. This means that if a timeout runs out of time, then the action will error out with a ZSAError (with a code of "TIMEOUT")