import * as z4 from 'zod/v4/core'; import { $ZodError, $ZodIssue, $ZodType } from 'zod/v4/core'; interface ServerActionError { message: string; code?: string; /** First field with an error, as a dotted path such as `address.city`. */ field?: string; /** Messages per field, keyed by dotted path (`items.0.name`). Absent when no field failed. */ fields?: Record; /** Messages that belong to the whole input, such as a failed cross-field refinement. */ formErrors?: string[]; statusCode?: number; shouldRedirect?: boolean; redirectTo?: string; } type RedirectConfig = { url: string; replace?: boolean; delay?: number; }; /** Where to go after a successful action: a same-origin path or a config object. */ type RedirectTarget = string | RedirectConfig; /** * A redirect after success: a fixed target, or a callback that computes it * from the action result (it may be async; returning `undefined` means no * redirect). Only same-origin paths starting with "/" are followed. */ type RedirectSpec = RedirectTarget | ((result: Result) => RedirectTarget | undefined | Promise); /** Options for `.action()` and `.formAction()`. */ interface ActionOptions { /** * Redirect after success, overriding `.redirect()`. The callback receives * the action result, typed. Prefer this form in `"use server"` files: on * Next.js 15 a non-async callback passed directly to `.redirect()` does not * compile ("Server Actions must be async functions"). */ redirect?: RedirectSpec; } type ServerActionResponse = { success: true; data: T; redirect?: string | RedirectConfig; } | { success: false; error: ServerActionError; }; type ServerAction = TInput extends void ? () => Promise> : (input: TInput) => Promise>; /** * What a hook's `execute` resolves to. * * - `success`: `data` holds the result, even when it is `undefined`. * - `error`: `serverError` holds the action's error (`validationErrors` its * field messages), or `fetchError` the message of a network failure. * - `navigation`: the action navigated away (`redirect()`, `notFound()`…). */ interface ActionResult { status: "success" | "error" | "navigation"; data?: TOutput; serverError?: ServerActionError; validationErrors?: Record; fetchError?: string; } /** * Shows toast messages for the hooks. Sonner's `toast` (and most toast * libraries') satisfies it as-is: ``. */ interface ToastAdapter { success(message: string): unknown; error(message: string): unknown; } type ExtractSuccessData = T extends ServerActionResponse ? U : never; type IsErrorResponse = T extends { success: false; error: ServerActionError; } ? true : false; /** Receives the library's own diagnostics. `console` satisfies it. */ interface ActionLogger { error(...args: unknown[]): void; warn(...args: unknown[]): void; } /** Where errors that mean "not signed in" send the user. */ interface AuthRedirectOptions { /** * `code` values returned by `toServerActionError()`, or legacy `error.type` * values, that trigger the redirect. Default: `["AUTHENTICATION_ERROR"]`. */ codes?: readonly string[]; /** Destination path, for example `"/login"`. */ to: string; } /** Options accepted by `createActionClient()`. */ interface ActionClientOptions { /** * Adds `shouldRedirect` and `redirectTo` to errors converted through * `toServerActionError()` whose code matches, so the hooks navigate to the * sign-in page. Values set by the error itself are kept. `false` disables it. * * Default: `{ codes: ["AUTHENTICATION_ERROR"], to: "/login" }`. */ authRedirect?: AuthRedirectOptions | false; /** * Where the library reports, in every environment, the errors nobody * handled, output validation failures, a throwing `.onError()` handler * (`error`) and throwing lifecycle hooks (`warn`). Expected failures such * as input validation or middleware rejections are never logged. * * Default: `console`. Pass `false` to silence the library. */ logger?: ActionLogger | false; /** * Keys whose values lifecycle hooks and `.onError()` see as * `"[REDACTED]"`, in `input`, `rawInput`, `output`, the `data` of * `result` and the error handler's `parsedInput`/`rawInput`. The handler * always receives the real values. A string matches the whole key, * ignoring case; a RegExp is tested against the key. The list replaces the * defaults; `false` disables redaction. * * Default: password, token, secret, API key, JWT, bearer, authorization, * cookie, `*hash`, credit card, CVV and SSN keys (see `DEFAULT_REDACT_KEYS`). */ redact?: false | readonly (string | RegExp)[]; } /** * Options controlling how a lifecycle hook is executed. */ interface HookOptions { /** * When true, the hook starts after the action has resolved and is not * awaited: none of its code delays the response. Errors inside it are * reported through the client's logger but never surface to the caller. * * On serverless platforms the function may stop once the response is * sent. For work that must complete there, keep the hook awaited and * schedule the slow part with `after()` from `next/server`, which * Server Functions support. * * Default: `false` (the hook is awaited). */ fireAndForget?: boolean; } /** * Fired after input validation and before the `.use()` middleware run. * `context` holds only what `.useBeforeValidation()` middleware added. * `input` is the validated input (redacted), `undefined` without a schema; * `rawInput` is what the client sent (redacted). */ type OnStartHook = (params: { input: Input; rawInput: unknown; context: Partial; name?: string; }) => void | Promise; /** * Fired after the handler resolves successfully and output validation passes. * The middleware chain has completed, so `context` is the full accumulated * `Context` type. `duration` is the elapsed time in ms since the start. * `input`, `rawInput` and `output` are redacted copies. */ type OnSuccessHook = (params: { input: Input; rawInput: unknown; context: Context; name?: string; output: Output; duration: number; }) => void | Promise; /** * Fired when the action fails, either because a middleware returned an error, * input/output validation failed, or the handler threw. NOT fired for Next.js * navigation errors (redirect, notFound), which are preserved as-is. * * `input` is the validated input, or `undefined` when the failure happened * before validation succeeded; `rawInput` is always what the client sent. * Both are redacted. Use `rawInput` to log attempts that failed validation. * * Because failures can occur BEFORE the middleware chain has finished * populating the context (e.g. input validation, an early middleware * rejection), `context` is typed as `Partial`. Fields may or may * not be present; always null-check before use. */ type OnFailureHook = (params: { input: Input | undefined; rawInput: unknown; /** May be empty or partially populated depending on where the failure occurred. */ context: Partial; name?: string; error: unknown; duration: number; }) => void | Promise; /** * Fired after the action resolves, regardless of success or failure. * Not fired when the action re-throws a Next.js navigation error. * * Like `onFailure`, `context` is typed as `Partial` and `input` may * be `undefined`. The `data` of a successful `result` is redacted. */ type OnCompleteHook = (params: { input: Input | undefined; rawInput: unknown; context: Partial; name?: string; result: ServerActionResponse; duration: number; }) => void | Promise; type CustomErrorHandler = (error: unknown) => ServerActionError | undefined; type ResolvedAuthRedirect = { codes: readonly string[]; to: string; } | false; /** * Format a `ZodError` as a user-facing validation error. The pipeline applies * it to input validation failures only; use it in `.onError()` to expose * other validation errors on purpose. */ declare function zodErrorToServerActionError(error: $ZodError): ServerActionError; /** * Convert an error into a failed `ServerActionResponse`. A `ZodError` passed * here is treated as a validation error meant for the user. Unhandled errors * are reported to `console.error`. * * Next.js control flow (`redirect()`, `notFound()`, prerendering bailouts…) * is rethrown, so calling this inside your own `catch` never swallows it. */ declare function handleServerActionError(error: unknown, customHandler?: CustomErrorHandler): { success: false; error: ServerActionError; }; declare function isErrorResponse(response: ServerActionResponse): response is { success: false; error: ServerActionError; }; /** * Raised when an action's result does not match its `.outputSchema()`. * * This is a server-side bug, never a user mistake: the client receives a * generic `OUTPUT_VALIDATION_ERROR` without field details, while `onError` * and `onFailure` receive this error with the original Zod error as `cause`. */ /** * A form action was called without FormData, most often because a * `.formAction()` was passed where a regular `.action()` is expected (for * example to `useFormAction`). Answered with `INVALID_FORM_DATA` (400). */ declare class InvalidFormDataError extends Error { constructor(); } declare class OutputValidationError extends Error { readonly issues: $ZodIssue[]; constructor(zodError: $ZodError); } /** * Whether `value` is a same-origin path that is safe to navigate to: it * starts with a single "/" and, once parsed the way a browser parses it, * still points to the current origin. Rejects absolute and * protocol-relative URLs, backslash tricks ("/\\evil.example") and control * characters. */ declare function isSafeRedirectPath(value: unknown): value is string; /** Placeholder that replaces the value of a sensitive key. */ declare const REDACTED = "[REDACTED]"; /** * A key to hide: a string matches the whole key, ignoring case; a RegExp is * tested against the key. */ type RedactMatcher = string | RegExp; /** * Keys hidden from lifecycle hooks and `.onError()` by default. Built from * the audit sanitizers of real consumers. Short names such as `ssn` match the * whole key only, so keys like `className` are left alone. */ declare const DEFAULT_REDACT_KEYS: readonly RedactMatcher[]; /** * What a middleware returns: the context it adds (merged into the context * accumulated so far), or an error that stops the action. */ type MiddlewareResult = { context: NextContext; error?: undefined; } | { error: ServerActionError; context?: undefined; }; /** * A server action middleware. * * @template Context - The context the middleware receives (what earlier middleware produced). * @template Input - The input it receives: the raw input before validation, the validated one after. * @template NextContext - The context it returns. Only the keys it adds are needed: * the result is merged into `Context`. */ type ServerActionMiddleware = (params: { context: Context; input: Input; }) => Promise> | MiddlewareResult; /** * Declare a middleware's types explicitly, for middleware built by a factory * or shared between clients. * * Inline middleware passed to `.use()` does not need it: its `context` is * already typed and the context it returns is inferred. * * @template TOutputContext - The context the middleware returns (what it adds). * @template TInput - The input it reads. Defaults to `unknown`. * @template TContext - The context it requires from earlier middleware. * `.use()` rejects a client whose context does not provide it. Defaults to * `any`, which accepts every client but leaves `context` untyped. * * @example * ```typescript * interface AuthContext { userId: string; permissions: Set } * * const authMiddleware = createTypedMiddleware(async () => { * const session = await getSession(); * if (!session) return { error: { message: "Unauthorized", code: "AUTH_ERROR" } }; * return { context: { userId: session.userId, permissions: session.permissions } }; * }); * * // Requires AuthContext: using it on a client without authMiddleware is a type error. * const requirePermission = (permission: string) => * createTypedMiddleware<{}, unknown, AuthContext>(async ({ context }) => * context.permissions.has(permission) * ? { context: {} } * : { error: { message: "Forbidden", code: "FORBIDDEN" } }, * ); * * const adminClient = baseClient.use(authMiddleware).use(requirePermission("admin")); * ``` */ declare function createTypedMiddleware(middlewareFn: (params: { context: TContext; input: TInput; }) => Promise> | MiddlewareResult): ServerActionMiddleware; type HookEntry = { hook: H; options: HookOptions; }; /** Context given to `.onError()`: the input known when the error happened, redacted. */ interface ErrorHandlerContext { /** The validated input, or `undefined` when validation had not succeeded. */ parsedInput?: unknown; /** What the client sent. */ rawInput?: unknown; } type ActionErrorHandler = (error: unknown, context: ErrorHandlerContext) => ServerActionError | undefined; /** * A form action. React calls it with `(formData)` when it is passed to * `
` and with `(prevState, formData)` through `useActionState`. */ interface FormServerAction { (formData: FormData): Promise>; /** * The same call as seen by ``, which types its handler as * returning `void | Promise` and ignores the result. */ (formData: FormData): Promise; (prevState: ServerActionResponse, formData: FormData): Promise>; /** Type-only marker: useFormAction sends objects and rejects form actions. */ readonly "~formAction"?: true; } type InferZodOutput = T extends z4.$ZodType ? z4.output : never; type InferZodInput = T extends z4.$ZodType ? z4.input : never; /** What the client receives as `data`: the output schema's output, or what the handler returns. */ type ResultOf = OutputSchema extends z4.$ZodType ? z4.output : Data; /** Input seen after validation: the schema output, or `unknown` while no schema is attached yet. */ type ValidatedInput = InputSchema extends z4.$ZodType ? z4.output : unknown; type Prettify = { [K in keyof T]: T[K]; } & {}; type ContextOf = R extends { context: infer C; } ? (C extends object ? C : never) : never; /** The context a middleware adds: `{}` when it only ever returns errors. */ type AddedContext = [ContextOf] extends [never] ? {} : ContextOf; /** The context after a middleware: the keys it returns replace the ones they shadow. */ type MergeContext = Prettify & Added>; type AnyMiddleware = ServerActionMiddleware; type SafeActionClientArgs = { middlewareFns: AnyMiddleware[]; preValidationFns?: AnyMiddleware[]; inputSchema?: z4.$ZodType; outputSchema?: z4.$ZodType; onError?: ActionErrorHandler; redirectConfig?: RedirectSpec; actionName?: string; logger?: ActionLogger; authRedirect?: ResolvedAuthRedirect; redactKeys?: readonly RedactMatcher[]; onStartFns?: HookEntry>[]; onSuccessFns?: HookEntry>[]; onFailureFns?: HookEntry>[]; onCompleteFns?: HookEntry>[]; }; declare class ServerActionClient { #private; constructor(args?: SafeActionClientArgs); /** * Add a middleware that runs after input validation, in registration * order. It receives the context accumulated so far and the validated input * (`unknown` when no schema is attached yet), and returns either * `{ context }` with the keys it adds, merged into the context, or * `{ error }` to stop the action. */ use>(middleware: (params: { context: Context; input: ValidatedInput; }) => Promise | R): ServerActionClient>, InputSchema, OutputSchema>; /** * Add a middleware that runs before input validation, on the raw input the * client sent. Use it for checks that must happen first, such as * authentication: an anonymous caller is rejected before the schema can * tell it anything about the expected input. * * It must be registered before any `.use()` middleware, because it runs * before them and cannot depend on the context they add. */ useBeforeValidation>(middleware: (params: { context: Context; input: unknown; }) => Promise | R): ServerActionClient>, InputSchema, OutputSchema>; /** * Define the input validation schema for the action. * @param schema Input validation schema (Zod or Zod Mini) */ inputSchema(schema: IS): ServerActionClient; /** * Define the output validation schema for the action. * @param schema Output validation schema (Zod or Zod Mini) */ outputSchema(schema: OS): ServerActionClient; /** * Define a custom error handler for the action. * @param handler Error handler function */ onError(handler: ActionErrorHandler): this; /** * Define a redirect configuration for successful actions. * * Only same-origin paths starting with "/" are accepted: a static target * outside the app throws here, and a target returned by the callback that * is not safe is dropped and logged. Use `redirect()` from * `next/navigation` inside the handler to leave the app. * * The callback may be async. After `.outputSchema()` its `result` is typed * as the schema output; otherwise annotate it, or pass `{ redirect }` to * `.action()` to have it typed from the handler. * * @param config Redirect URL, config object, or function that returns redirect config based on result */ redirect : any>(config: RedirectSpec): this; /** * Attach a semantic name to the action. The name is passed to every * lifecycle hook (`onStart` / `onSuccess` / `onFailure` / `onComplete`) * and is useful for structured logging and audit trails. * * Calling `.name()` multiple times replaces the previous value. * * @param actionName Human-readable identifier (e.g. `"auth.login"`) */ name(actionName: string): this; /** * Register a hook fired after input validation and before the `.use()` * middleware chain runs. Useful for request-start logging. * * The hook is side-effect only: its return value is ignored and it cannot * mutate the action flow. Errors thrown inside the hook are reported * through the client's logger but never surface to the caller. * * Multiple hooks can be registered; they run in registration order. */ onStart(hook: OnStartHook : undefined>, options?: HookOptions): this; /** * Register a hook fired after the handler resolves successfully and * output validation passes (but before the response is returned to the * caller). Receives the validated `output` and the total `duration` in ms. * * Side-effect only; errors are reported through the client's logger. * Multiple hooks can be registered; they run in registration order. */ onSuccess : unknown>(hook: OnSuccessHook : undefined, Output>, options?: HookOptions): this; /** * Register a hook fired when the action fails for any reason: * input/output validation failure, middleware-returned error, or a * thrown exception inside the handler. * * NOT invoked for Next.js navigation errors (redirect, notFound), which * are re-thrown unchanged. * * Side-effect only; errors are reported through the client's logger. * Multiple hooks can be registered; they run in registration order. */ onFailure(hook: OnFailureHook : undefined>, options?: HookOptions): this; /** * Register a hook fired after the action resolves (success or failure), * with the final `result` object available. Not fired when the action * re-throws a Next.js navigation error. * * Side-effect only; errors are reported through the client's logger. * Multiple hooks can be registered; they run in registration order. */ onComplete : unknown>(hook: OnCompleteHook : undefined, Output>, options?: HookOptions): this; /** * Define the action. * @param serverCodeFn Code that will be executed on the server side * @param options `redirect` after success, typed from the result (overrides `.redirect()`) */ action(serverCodeFn: InputSchema extends undefined ? (context: Context) => Promise : InputSchema extends z4.$ZodType ? (input: InferZodOutput, context: Context) => Promise : (context: Context) => Promise, options?: ActionOptions>): ServerAction : void, ResultOf>; /** * Define a form action that accepts FormData. * @param serverCodeFn Code that will be executed on the server side * @param options `redirect` after success, typed from the result (overrides `.redirect()`) */ formAction(serverCodeFn: InputSchema extends undefined ? (context: Context) => Promise : InputSchema extends z4.$ZodType ? (input: InferZodOutput, context: Context) => Promise : (context: Context) => Promise, options?: ActionOptions>): FormServerAction>; } /** Any client, whatever its context and schemas: the constraint for helpers that decorate clients. */ type AnyServerActionClient = ServerActionClient; /** * Create a new action client. Every client derived from it through the * builder methods shares these options. */ declare function createActionClient(options?: ActionClientOptions): ServerActionClient<{}, undefined, undefined>; /** * Decode native form data into a plain object without guessing types. * * - `a.b` and `a[b]` build nested objects, `a[0]` builds arrays, `a[]` * appends, and a repeated name becomes an array. * - Values stay strings (or `File`); declare conversions in the schema. * - The empty part an unselected `` sends is dropped. * - Names reaching `__proto__`, `prototype` or `constructor` are ignored. * - Indexes keep their positions: rows removed from the middle of a list leave * empty slots, which a schema accepts with optional elements * (`z.array(row.optional())`). All arrays together may hold up to 10,000 * empty slots; an entry that would open more is ignored. * - With a schema, a single value is wrapped where the schema expects an array. */ declare function decodeFormData(formData: FormData, schema?: $ZodType): Record; /** Turn an empty or whitespace-only string into `null`; other values pass through. */ declare function emptyToNull(value: unknown): unknown; /** Turn an empty or whitespace-only string into `undefined`; other values pass through. */ declare function emptyToUndefined(value: unknown): unknown; export { type AnyServerActionClient as A, type OnFailureHook as B, type OnCompleteHook as C, DEFAULT_REDACT_KEYS as D, type ErrorHandlerContext as E, type FormServerAction as F, type HookOptions as H, InvalidFormDataError as I, type MiddlewareResult as M, OutputValidationError as O, REDACTED as R, ServerActionClient as S, type ToastAdapter as T, isSafeRedirectPath as a, type RedactMatcher as b, createActionClient as c, decodeFormData as d, emptyToNull as e, emptyToUndefined as f, createTypedMiddleware as g, handleServerActionError as h, isErrorResponse as i, type ServerActionMiddleware as j, type ServerActionError as k, type RedirectConfig as l, type RedirectTarget as m, type RedirectSpec as n, type ActionOptions as o, type ServerActionResponse as p, type ServerAction as q, type ActionResult as r, type ExtractSuccessData as s, type IsErrorResponse as t, type ActionLogger as u, type AuthRedirectOptions as v, type ActionClientOptions as w, type OnStartHook as x, type OnSuccessHook as y, zodErrorToServerActionError as z };