SafeFn
Running a SafeFn

Return type

The return type of a SafeFn is a ResultAsync<Value, Error> when using .run() or the useServerAction() hook, or an ActionResult<Value,Error> when calling the function returned by .createAction() directly.

ActionResult is a simple Result type that is used as a NeverThrow Result can not be serialized and sent over the network. The useServerAction() hook transforms this ActionResult back into a ResultAsync for you.

Value

The output type of your output schema if you defined one, otherwise inferred from your handler() or safeHandler() return type.

Error

A union that can contain the following errors:

Input parsing

When using .run()

type E = {
  code: "INPUT_PARSING";
  cause: z.ZodError<T>;
};

when using .createAction() full errors are stripped as classes can not be sent via a Server Action and stack traces should not be shipped to the client

type E = {
  code: "INPUT_PARSING";
  cause: {
    formattedError: z.ZodFormattedError<T>;
    flattenedError: z.typeToFlattenedError<T, string>;
  };
};

With T as the input of your input schema (this differs from output when using things like .transform()). Only applicable if an input schema is defined. If you defined a parent, its input type will be merged to create one single input error type.

Handler

The possible Err returns or yield* s of your handler() or safeHandler() function. As an arbitrary example:

function OnlyAbove() {
  let random = Math.random();
  if (random > 0.5) {
    return err({ code: "UNCAUGHT_ERROR" } as const);
  }
  return ok(random);
}
 
const fn1 = createSafeFn().handler(() => {
  const res = MaybeFail();
  if (res.isErr()) {
    return res;
  }
  return ok(res * 10);
});
 
const fn2 = createSafeFn().safeHandler(async function* () {
  const res = yield* OnlyAbove();
  return ok(res * 10);
});

both of these SafeFns will have an inferred handler error type of

type E = {
  code: "UNCAUGHT_ERROR";
};

Uncaught error handler

The inferred error type from your catch() error handler. If you didn't set one, the default will be used:

type E = {
  code: "UNCAUGHT_ERROR";
  cause: "An uncaught error occurred. You can implement a custom error handler by using `catch()`";
};

Output parsing

When using .run()

type E = {
  code: "OUTPUT_PARSING";
  cause: z.ZodError<T>;
};

when using .createAction() full errors are stripped as classes can not be sent via a Server Action and stack traces should not be shipped to the client

type E = {
  code: "OUTPUT_PARSING";
  cause: {
    formattedError: z.ZodFormattedError<T>;
    flattenedError: z.typeToFlattenedError<T, string>;
  };
};

With T as the input of your output schema (this differs from output when using things like .transform()). Only applicable if an output schema is defined. If you defined a parent, its output type will be merged to create one single output error type.

Parent

If you set a parent, its error type will be included in the union. These are built in the same way as the child errors above.

Example

Given the following arbitrary example:

function maybeFail() {
  if (Math.random() > 0.5) {
    return err({
      code: "TOO_LOW!",
    } as const);
  }
  return ok("hello");
}
 
const parent = createSafeFn()
  .input(
    z.object({
      parentIn: z.string(),
    }),
  )
  .output(
    z.object({
      parentOut: z.string(),
    }),
  )
  .safeHandler(async function* (args) {
    const res = yield* maybeFail().safeUnwrap();
    return ok({
      parentOut: `${res} ${args.input.parentIn}`,
    });
  });
 
const child = createSafeFn()
  .use(parent)
  .input(
    z.object({
      childIn: z.string(),
    }),
  )
  .output(
    z.object({
      childOut: z.string(),
    }),
  )
  .handler(async (args) => {
    return ok({
      childOut: `${args.ctx.parentOut} ${args.input.childIn}`,
    });
  })
  .catch((e) => {
    return err({
      code: "Woops!",
    });
  });

The return type of child.run() will be:

type Res = ResultAsync<
  {
    childOut: string;
  },
  // Merged error from parent and child
  | {
      code: "INPUT_PARSING";
      cause: z.ZodError<{
        parentIn: string;
        childIn: string;
      }>;
    }
  // Yielded error in parent
  | {
      code: "TOO_LOW!";
    }
  // Parent did not specify a catch handler, so default is used
  | {
      code: "UNCAUGHT_ERROR";
      cause: "An uncaught error occurred. You can implement a custom error handler by using `catch()`";
    }
  // Specified in child catch handler
  | {
      code: "Woops!";
    }
  // Output parsing error
  | {
      code: "OUTPUT_PARSING";
      cause: z.ZodError<{
        parentOut: string;
        childOut: string;
      }>;
    }
>;

The result of calling child.createAction() through the useServerAction() hook will be:

type Res = ResultAsync<
  {
    childOut: string;
  },
  // Merged error from parent and child
  | {
      code: "INPUT_PARSING";
      cause: {
        formattedError: z.ZodFormattedError<{
          parentIn: string;
          childIn: string;
        }>;
        flattenedError: z.typeToFlattenedError<
          {
            parentIn: string;
            childIn: string;
          },
          string
        >;
      };
    }
  // Yielded error in parent
  | {
      code: "TOO_LOW!";
    }
  // Parent did not specify a catch handler, so default is used
  | {
      code: "UNCAUGHT_ERROR";
      cause: "An uncaught error occurred. You can implement a custom error handler by using `catch()`";
    }
  // Specified in child catch handler
  | {
      code: "Woops!";
    }
  // Output parsing error
  | {
      code: "OUTPUT_PARSING";
      cause: {
        formattedError: z.ZodFormattedError<{
          parentOut: string;
          childOut: string;
        }>;
        flattenedError: z.typeToFlattenedError<
          {
            parentOut: string;
            childOut: string;
          },
          string
        >;
      };
    }
>;

when calling the returned function from .createAction() directly, the Value and Error types will be identical to the ones above, only wrapped in an ActionResult

On this page