next-safe-action
Version:
Type safe and validated Server Actions in your Next.js project.
1 lines • 78 kB
Source Map (JSON)
{"version":3,"file":"index.mjs","names":["#args"],"sources":["../src/deep-merge.ts","../src/standard-schema.ts","../src/utils.ts","../src/validation-errors.ts","../src/action-builder.ts","../src/safe-action-client.ts","../src/middleware.ts","../src/index.ts"],"sourcesContent":["/*!\n * This file is a deep-merge implementation adapted from `deepmerge-ts`\n * (https://github.com/RebeccaStevens/deepmerge-ts). Portions of the code below, in\n * particular the plain-record detection (`isRecord`), key collection (`getKeys`) and the\n * recursive record merge with its `__proto__` prototype-pollution guard, are derived from\n * that project and remain under its original license, reproduced in full below.\n *\n * BSD 3-Clause License\n *\n * Copyright (c) 2021, Rebecca Stevens\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice, this\n * list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n * this list of conditions and the following disclaimer in the documentation\n * and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the copyright holder nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\n * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\n * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n/**\n * Internal deep-merge utility.\n *\n * Inlined from, and trimmed down to only what this library needs of, `deepmerge-ts`, so\n * that next-safe-action ships with zero runtime dependencies and is not exposed to\n * supply-chain attacks targeting third-party packages.\n *\n * The library only ever calls `deepmerge(a, b)` with two plain middleware context objects,\n * so this reproduces deepmerge-ts's *default* merge behavior for that case:\n * - plain records (objects) are merged recursively;\n * - arrays are concatenated;\n * - Sets are unioned and Maps are combined (later entries win on key conflict);\n * - any other or mismatched values: the last (rightmost) value wins.\n */\n\n// `Object.prototype.toString` tags that mark a value as a plain record.\nconst validRecordToStringValues = [\"[object Object]\", \"[object Module]\"];\n\n/**\n * Whether the given value is a plain record, as opposed to an array, Set, Map, class\n * instance, or other exotic object. Mirrors deepmerge-ts's record detection.\n */\nfunction isRecord(value: unknown): value is Record<PropertyKey, unknown> {\n\tif (typeof value !== \"object\" || value === null) {\n\t\treturn false;\n\t}\n\n\tif (!validRecordToStringValues.includes(Object.prototype.toString.call(value))) {\n\t\treturn false;\n\t}\n\n\tconst { constructor } = value as { constructor?: { prototype?: unknown } };\n\n\t// Objects with a null prototype (e.g. `Object.create(null)`) have no constructor.\n\tif (constructor === undefined) {\n\t\treturn true;\n\t}\n\n\tconst prototype = constructor.prototype;\n\n\tif (\n\t\tprototype === null ||\n\t\ttypeof prototype !== \"object\" ||\n\t\t!validRecordToStringValues.includes(Object.prototype.toString.call(prototype))\n\t) {\n\t\treturn false;\n\t}\n\n\t// A genuine plain object's prototype owns the standard Object.prototype methods.\n\tif (!Object.hasOwn(prototype, \"isPrototypeOf\")) {\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\n/**\n * The union of both objects' own enumerable string keys and own symbol keys, in\n * first-then-second insertion order.\n */\nfunction getKeys(a: object, b: object): Set<PropertyKey> {\n\tconst keys = new Set<PropertyKey>();\n\n\tfor (const object of [a, b]) {\n\t\tfor (const key of [...Object.keys(object), ...Object.getOwnPropertySymbols(object)]) {\n\t\t\tkeys.add(key);\n\t\t}\n\t}\n\n\treturn keys;\n}\n\n/**\n * Whether `object` has `property` as an own enumerable property.\n */\nfunction objectHasProperty(object: object, property: PropertyKey): boolean {\n\treturn Object.prototype.propertyIsEnumerable.call(object, property);\n}\n\n/**\n * Merge two values according to the default strategy described at the top of this file.\n */\nfunction mergeValues(a: unknown, b: unknown): unknown {\n\tif (isRecord(a) && isRecord(b)) {\n\t\treturn mergeRecords(a, b);\n\t}\n\n\tif (Array.isArray(a) && Array.isArray(b)) {\n\t\treturn [...a, ...b];\n\t}\n\n\tif (a instanceof Set && b instanceof Set) {\n\t\treturn new Set([...a, ...b]);\n\t}\n\n\tif (a instanceof Map && b instanceof Map) {\n\t\treturn new Map([...a, ...b]);\n\t}\n\n\t// Mismatched types, primitives, or any other value: the last one wins.\n\treturn b;\n}\n\n/**\n * Recursively merge two plain records into a fresh object.\n */\nfunction mergeRecords(a: Record<PropertyKey, unknown>, b: Record<PropertyKey, unknown>): Record<PropertyKey, unknown> {\n\tconst result: Record<PropertyKey, unknown> = {};\n\n\tfor (const key of getKeys(a, b)) {\n\t\tconst inA = objectHasProperty(a, key);\n\t\tconst inB = objectHasProperty(b, key);\n\n\t\t// The key is non-enumerable on both objects: nothing to merge.\n\t\tif (!inA && !inB) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tlet value: unknown;\n\t\tif (inA && inB) {\n\t\t\tvalue = mergeValues(a[key], b[key]);\n\t\t} else if (inB) {\n\t\t\tvalue = b[key];\n\t\t} else {\n\t\t\tvalue = a[key];\n\t\t}\n\n\t\t// Assigning to `__proto__` via `result[key] = value` would mutate the prototype\n\t\t// instead of creating an own property, so guard against prototype pollution.\n\t\tif (key === \"__proto__\") {\n\t\t\tObject.defineProperty(result, key, {\n\t\t\t\tvalue,\n\t\t\t\tconfigurable: true,\n\t\t\t\tenumerable: true,\n\t\t\t\twritable: true,\n\t\t\t});\n\t\t} else {\n\t\t\tresult[key] = value;\n\t\t}\n\t}\n\n\treturn result;\n}\n\n/**\n * Deeply merge the given objects, with the last value winning on conflict. The library\n * always calls this with exactly two middleware context objects.\n */\nexport function deepmerge(...objects: ReadonlyArray<object>): object {\n\tlet result: unknown = objects[0] ?? {};\n\n\tfor (let index = 1; index < objects.length; index++) {\n\t\tresult = mergeValues(result, objects[index]);\n\t}\n\n\treturn result as object;\n}\n","/** The Standard Schema interface. */\nexport interface StandardSchemaV1<Input = unknown, Output = Input> {\n\t/** The Standard Schema properties. */\n\treadonly \"~standard\": StandardSchemaV1.Props<Input, Output>;\n}\n\n// oxlint-disable-next-line typescript/no-namespace\nexport declare namespace StandardSchemaV1 {\n\t/** The Standard Schema properties interface. */\n\texport interface Props<Input = unknown, Output = Input> {\n\t\t/** The version number of the standard. */\n\t\treadonly version: 1;\n\t\t/** The vendor name of the schema library. */\n\t\treadonly vendor: string;\n\t\t/** Validates unknown input values. */\n\t\treadonly validate: (value: unknown) => Result<Output> | Promise<Result<Output>>;\n\t\t/** Inferred types associated with the schema. */\n\t\treadonly types?: Types<Input, Output> | undefined;\n\t}\n\n\t/** The result interface of the validate function. */\n\texport type Result<Output> = SuccessResult<Output> | FailureResult;\n\n\t/** The result interface if validation succeeds. */\n\texport interface SuccessResult<Output> {\n\t\t/** The typed output value. */\n\t\treadonly value: Output;\n\t\t/** The non-existent issues. */\n\t\treadonly issues?: undefined;\n\t}\n\n\t/** The result interface if validation fails. */\n\texport interface FailureResult {\n\t\t/** The issues of failed validation. */\n\t\treadonly issues: ReadonlyArray<Issue>;\n\t}\n\n\t/** The issue interface of the failure output. */\n\texport interface Issue {\n\t\t/** The error message of the issue. */\n\t\treadonly message: string;\n\t\t/** The path of the issue, if any. */\n\t\treadonly path?: ReadonlyArray<PropertyKey | PathSegment> | undefined;\n\t}\n\n\t/** The path segment interface of the issue. */\n\texport interface PathSegment {\n\t\t/** The key representing a path segment. */\n\t\treadonly key: PropertyKey;\n\t}\n\n\t/** The Standard Schema types interface. */\n\texport interface Types<Input = unknown, Output = Input> {\n\t\t/** The input type of the schema. */\n\t\treadonly input: Input;\n\t\t/** The output type of the schema. */\n\t\treadonly output: Output;\n\t}\n\n\t/** Infers the input type of a Standard Schema. */\n\texport type InferInput<Schema extends StandardSchemaV1> = NonNullable<Schema[\"~standard\"][\"types\"]>[\"input\"];\n\n\t/** Infers the output type of a Standard Schema. */\n\texport type InferOutput<Schema extends StandardSchemaV1> = NonNullable<Schema[\"~standard\"][\"types\"]>[\"output\"];\n}\n\n// custom helpers\n\n/** Infer the input type of an array of Standard Schemas. */\nexport type InferInputArray<Schemas extends readonly StandardSchemaV1[]> = {\n\t[K in keyof Schemas]: StandardSchemaV1.InferInput<Schemas[K]>;\n};\n\n/** Infer the output type of an array of Standard Schemas. */\nexport type InferOutputArray<Schemas extends readonly StandardSchemaV1[]> = {\n\t[K in keyof Schemas]: StandardSchemaV1.InferOutput<Schemas[K]>;\n};\n\n/** Infer the input type of a Standard Schema, or a default type if the schema is undefined. */\nexport type InferInputOrDefault<MaybeSchema, Default> = MaybeSchema extends StandardSchemaV1\n\t? StandardSchemaV1.InferInput<MaybeSchema>\n\t: Default;\n\n/** Infer the output type of a Standard Schema, or a default type if the schema is undefined. */\nexport type InferOutputOrDefault<MaybeSchema, Default> = MaybeSchema extends StandardSchemaV1\n\t? StandardSchemaV1.InferOutput<MaybeSchema>\n\t: Default;\n\nexport function isStandardSchema(value: unknown): value is StandardSchemaV1 {\n\tif (value === null || (typeof value !== \"object\" && typeof value !== \"function\")) {\n\t\treturn false;\n\t}\n\n\tconst standardProps = (value as { [\"~standard\"]?: unknown })[\"~standard\"];\n\n\tif (standardProps === null || typeof standardProps !== \"object\") {\n\t\treturn false;\n\t}\n\n\treturn (\n\t\t(standardProps as { version?: unknown }).version === 1 &&\n\t\ttypeof (standardProps as { validate?: unknown }).validate === \"function\"\n\t);\n}\n\nexport async function standardParse<Output>(schema: StandardSchemaV1<unknown, Output>, value: unknown) {\n\treturn schema[\"~standard\"].validate(value);\n}\n","export const DEFAULT_SERVER_ERROR_MESSAGE = \"Something went wrong while executing the operation.\";\n\n/**\n * Checks if passed argument is an instance of Error.\n */\nexport const isError = (error: unknown): error is Error => error instanceof Error;\n\n/**\n * Checks what the winning boolean value is from a series of values, from lowest to highest priority.\n * `null` and `undefined` values are skipped.\n */\nexport const winningBoolean = (...args: (boolean | undefined | null)[]) => {\n\treturn args.reduce((acc, v) => (typeof v === \"boolean\" ? v : acc), false) as boolean;\n};\n","/* oxlint-disable typescript/no-unsafe-member-access, typescript/no-unsafe-assignment */\nimport type { StandardSchemaV1 } from \"./standard-schema\";\nimport type { FlattenedValidationErrors, IssueWithUnionErrors, ValidationErrors } from \"./validation-errors.types\";\n\nconst getKey = (segment: PropertyKey | StandardSchemaV1.PathSegment) =>\n\ttypeof segment === \"object\" ? segment.key : segment;\n\nconst getIssueMessage = (issue: IssueWithUnionErrors): string[] => {\n\tif (issue.unionErrors) {\n\t\treturn issue.unionErrors.flatMap((u) => u.issues.map((i) => i.message));\n\t}\n\treturn [issue.message];\n};\n\n// This function is used internally to build the validation errors object from a list of validation issues.\nexport const buildValidationErrors = <Schema extends StandardSchemaV1 | undefined>(\n\tissues: readonly IssueWithUnionErrors[]\n) => {\n\t// Using `any` because validation errors are dynamically-shaped nested objects\n\t// built from schema paths with PropertyKey keys (string | number | symbol).\n\tconst ve: any = {};\n\n\tfor (const issue of issues) {\n\t\tconst { path } = issue;\n\n\t\t// When path is undefined or empty, set root errors.\n\t\tif (!path || path.length === 0) {\n\t\t\tconst issueMessages = getIssueMessage(issue);\n\t\t\tve._errors = ve._errors ? [...ve._errors, ...issueMessages] : [...issueMessages];\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Reference to errors object.\n\t\tlet ref = ve;\n\n\t\t// Set object for the path, if it doesn't exist.\n\t\tfor (let i = 0; i < path.length - 1; i++) {\n\t\t\tconst k = getKey(path[i]!);\n\n\t\t\tif (!ref[k]) {\n\t\t\t\tref[k] = {};\n\t\t\t}\n\n\t\t\tref = ref[k];\n\t\t}\n\n\t\t// Key is always the last element of the path.\n\t\tconst key = getKey(path[path.length - 1]!);\n\n\t\tconst issueMessage = getIssueMessage(issue);\n\n\t\t// Set error for the current path. If `_errors` array exists, add the message to it.\n\t\tconst existing = ref[key] ? structuredClone(ref[key]) : {};\n\t\tref[key] = existing._errors\n\t\t\t? { ...existing, _errors: [...existing._errors, ...issueMessage] }\n\t\t\t: { ...existing, _errors: [...issueMessage] };\n\t}\n\n\treturn ve as ValidationErrors<Schema>;\n};\n\n// This class is internally used to throw validation errors in action's server code function, using\n// `returnValidationErrors`.\nexport class ActionServerValidationError<Schema extends StandardSchemaV1> extends Error {\n\tpublic validationErrors: ValidationErrors<Schema>;\n\tconstructor(validationErrors: ValidationErrors<Schema>) {\n\t\tsuper(\"Server Action server validation error(s) occurred\");\n\t\tthis.validationErrors = validationErrors;\n\t}\n}\n\n// This class is internally used to throw validation errors in action's server code function, using\n// `returnValidationErrors`.\nexport class ActionValidationError<ShapedErrors> extends Error {\n\tpublic validationErrors: ShapedErrors;\n\tconstructor(validationErrors: ShapedErrors, overriddenErrorMessage?: string) {\n\t\tsuper(overriddenErrorMessage ?? \"Server Action validation error(s) occurred\");\n\t\tthis.validationErrors = validationErrors;\n\t}\n}\n\n// This class is internally used to throw validation errors in action's server code function, using\n// `returnValidationErrors`.\nexport class ActionBindArgsValidationError extends Error {\n\tpublic validationErrors: unknown[];\n\tconstructor(validationErrors: unknown[]) {\n\t\tsuper(\"Server Action bind args validation error(s) occurred\");\n\t\tthis.validationErrors = validationErrors;\n\t}\n}\n\n/**\n * Return custom validation errors to the client from the action's server code function.\n * Code declared after this function invocation will not be executed.\n * @param schema Input schema\n * @param validationErrors Validation errors object\n *\n * {@link https://next-safe-action.dev/docs/define-actions/validation-errors#returnvalidationerrors See docs for more information}\n */\nexport function returnValidationErrors<\n\tSchema extends StandardSchemaV1 | (() => Promise<StandardSchemaV1>),\n\tAS extends StandardSchemaV1 = Schema extends () => Promise<StandardSchemaV1> ? Awaited<ReturnType<Schema>> : Schema, // actual schema\n>(schema: Schema, validationErrors: ValidationErrors<AS>): never {\n\tthrow new ActionServerValidationError<AS>(validationErrors);\n}\n\n/**\n * Default validation errors format.\n * Emulation of `zod`'s [`format`](https://zod.dev/ERROR_HANDLING?id=formatting-errors) function.\n */\nexport function formatValidationErrors<VE extends ValidationErrors<any>>(validationErrors: VE) {\n\treturn validationErrors;\n}\n\n/**\n * Transform default formatted validation errors into flattened structure.\n * `formErrors` contains global errors, and `fieldErrors` contains errors for each field,\n * one level deep. It discards errors for nested fields.\n * Emulation of `zod`'s [`flatten`](https://zod.dev/ERROR_HANDLING?id=flattening-errors) function.\n * @param {ValidationErrors} [validationErrors] Validation errors object\n *\n * {@link https://next-safe-action.dev/docs/define-actions/validation-errors#flattenvalidationerrorsutility-function See docs for more information}\n */\nexport function flattenValidationErrors<VE extends ValidationErrors<any>>(validationErrors: VE) {\n\tconst flattened: FlattenedValidationErrors<VE> = {\n\t\tformErrors: [],\n\t\tfieldErrors: {},\n\t};\n\n\tfor (const [key, value] of Object.entries<string[] | { _errors: string[] }>(validationErrors ?? {})) {\n\t\tif (key === \"_errors\" && Array.isArray(value)) {\n\t\t\tflattened.formErrors = [...value];\n\t\t} else {\n\t\t\tif (\"_errors\" in value) {\n\t\t\t\tflattened.fieldErrors[key as keyof Omit<VE, \"_errors\">] = [...value._errors];\n\t\t\t}\n\t\t}\n\t}\n\n\treturn flattened;\n}\n\n/**\n * This error is thrown when an action metadata is invalid, i.e. when there's a mismatch between the\n * type of the metadata schema returned from `defineMetadataSchema` and the actual data passed.\n */\nexport class ActionMetadataValidationError<MDS extends StandardSchemaV1 | undefined> extends Error {\n\tpublic validationErrors: ValidationErrors<MDS>;\n\n\tconstructor(validationErrors: ValidationErrors<MDS>) {\n\t\tsuper(\"Invalid metadata input. Please be sure to pass metadata via `metadata` method before defining the action.\");\n\t\tthis.name = \"ActionMetadataError\";\n\t\tthis.validationErrors = validationErrors;\n\t}\n}\n\n/**\n * This error is thrown when an action's data (output) is invalid, i.e. when there's a mismatch between the\n * type of the data schema passed to `dataSchema` method and the actual return of the action.\n */\nexport class ActionOutputDataValidationError<DS extends StandardSchemaV1 | undefined> extends Error {\n\tpublic validationErrors: ValidationErrors<DS>;\n\n\tconstructor(validationErrors: ValidationErrors<DS>) {\n\t\tsuper(\n\t\t\t\"Invalid action data (output). Please be sure to return data following the shape of the schema passed to `dataSchema` method.\"\n\t\t);\n\t\tthis.name = \"ActionOutputDataError\";\n\t\tthis.validationErrors = validationErrors;\n\t}\n}\n","import type {} from \"zod\";\nimport { deepmerge } from \"./deep-merge\";\nimport type {\n\tValidationErrorsFormat,\n\tMiddlewareResult,\n\tSafeActionClientArgs,\n\tSafeActionFn,\n\tSafeActionResult,\n\tActionCallbacks,\n\tSafeStateActionFn,\n\tServerCodeFn,\n\tStatefulServerCodeFn,\n} from \"./index.types\";\nimport { FrameworkErrorHandler } from \"./next/errors\";\nimport type {\n\tInferInputArray,\n\tInferInputOrDefault,\n\tInferOutputArray,\n\tInferOutputOrDefault,\n\tStandardSchemaV1,\n} from \"./standard-schema\";\nimport { standardParse } from \"./standard-schema\";\nimport { DEFAULT_SERVER_ERROR_MESSAGE, isError, winningBoolean } from \"./utils\";\nimport {\n\tActionBindArgsValidationError,\n\tActionMetadataValidationError,\n\tActionOutputDataValidationError,\n\tActionServerValidationError,\n\tActionValidationError,\n\tbuildValidationErrors,\n} from \"./validation-errors\";\nimport type { ValidationErrors } from \"./validation-errors.types\";\n\nexport function actionBuilder<\n\tServerError,\n\tErrorsFormat extends ValidationErrorsFormat | undefined, // override default validation errors shape\n\tMetadataSchema extends StandardSchemaV1 | undefined = undefined,\n\tMetadata = InferOutputOrDefault<MetadataSchema, undefined>, // metadata type (inferred from metadata schema)\n\tCtx extends object = {},\n\tInputSchemaFn extends ((clientInput?: unknown) => Promise<StandardSchemaV1>) | undefined = undefined, // input schema function\n\tInputSchema extends StandardSchemaV1 | undefined = InputSchemaFn extends Function\n\t\t? Awaited<ReturnType<InputSchemaFn>>\n\t\t: undefined, // input schema\n\tOutputSchema extends StandardSchemaV1 | undefined = undefined, // output schema\n\tconst BindArgsSchemas extends readonly StandardSchemaV1[] = [],\n\tShapedErrors = undefined,\n\tThrowsValidationErrors extends boolean = false,\n\tHasValidatedMiddleware extends boolean = false,\n\tPreValidationCtx extends object = Ctx,\n>(\n\targs: SafeActionClientArgs<\n\t\tServerError,\n\t\tErrorsFormat,\n\t\tMetadataSchema,\n\t\tMetadata,\n\t\ttrue,\n\t\tCtx,\n\t\tInputSchemaFn,\n\t\tInputSchema,\n\t\tOutputSchema,\n\t\tBindArgsSchemas,\n\t\tShapedErrors,\n\t\tThrowsValidationErrors,\n\t\tHasValidatedMiddleware,\n\t\tPreValidationCtx\n\t>\n) {\n\tconst bindArgsSchemas = args.bindArgsSchemas ?? [];\n\n\t// ─── Validate metadata schema ────────────────────────────────────────\n\n\tasync function validateMetadata() {\n\t\tif (!args.metadataSchema) return;\n\n\t\tconst parsedMd = await standardParse(args.metadataSchema, args.metadata);\n\n\t\tif (parsedMd.issues) {\n\t\t\tthrow new ActionMetadataValidationError<MetadataSchema>(buildValidationErrors(parsedMd.issues));\n\t\t}\n\t}\n\n\t// ─── Validate bind args and main input ───────────────────────────────\n\t// Returns parsed inputs on success, or null if validation errors were set on middlewareResult.\n\n\tasync function validateInputs(\n\t\tmainClientInput: InferInputOrDefault<InputSchema, undefined>,\n\t\tbindArgsClientInputs: InferInputArray<BindArgsSchemas>,\n\t\tcurrentCtx: object,\n\t\tmiddlewareResult: MiddlewareResult<ServerError, object>\n\t): Promise<{ parsedMainInput: unknown; parsedBindArgsInputs: unknown[] } | null> {\n\t\tconst parsedBindArgsResults = await Promise.all(\n\t\t\tbindArgsSchemas.map((schema, i) => standardParse(schema, bindArgsClientInputs[i]))\n\t\t);\n\n\t\tconst parsedMainInputResult =\n\t\t\ttypeof args.inputSchemaFn === \"undefined\"\n\t\t\t\t? ({ value: undefined } as const satisfies StandardSchemaV1.Result<undefined>)\n\t\t\t\t: await standardParse(await args.inputSchemaFn(mainClientInput), mainClientInput);\n\n\t\t// Process bind args validation results.\n\t\tlet hasBindValidationErrors = false;\n\t\tconst bindArgsValidationErrors = Array(bindArgsSchemas.length).fill({});\n\t\tconst parsedBindArgsInputs: unknown[] = [];\n\n\t\tfor (let i = 0; i < parsedBindArgsResults.length; i++) {\n\t\t\tconst parsedInput = parsedBindArgsResults[i]!;\n\n\t\t\tif (!parsedInput.issues) {\n\t\t\t\tparsedBindArgsInputs.push(parsedInput.value);\n\t\t\t} else {\n\t\t\t\tbindArgsValidationErrors[i] = buildValidationErrors<BindArgsSchemas[number]>(parsedInput.issues);\n\t\t\t\thasBindValidationErrors = true;\n\t\t\t}\n\t\t}\n\n\t\t// Process main input validation result.\n\t\tlet parsedMainInput: unknown = undefined;\n\n\t\tif (!parsedMainInputResult.issues) {\n\t\t\tparsedMainInput = parsedMainInputResult.value;\n\t\t} else {\n\t\t\tconst validationErrors = buildValidationErrors<InputSchema>(parsedMainInputResult.issues);\n\n\t\t\tmiddlewareResult.validationErrors = await Promise.resolve(\n\t\t\t\targs.handleValidationErrorsShape(validationErrors, {\n\t\t\t\t\tclientInput: mainClientInput,\n\t\t\t\t\tbindArgsClientInputs,\n\t\t\t\t\tctx: currentCtx as Ctx,\n\t\t\t\t\tmetadata: args.metadata,\n\t\t\t\t})\n\t\t\t);\n\t\t}\n\n\t\t// Bind args errors are thrown (caught by the middleware stack's error handler).\n\t\tif (hasBindValidationErrors) {\n\t\t\tthrow new ActionBindArgsValidationError(bindArgsValidationErrors);\n\t\t}\n\n\t\t// Main input validation errors cause early return (no server code execution).\n\t\tif (middlewareResult.validationErrors) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn { parsedMainInput, parsedBindArgsInputs };\n\t}\n\n\t// ─── Run server code with output validation (no input validation) ────\n\n\tasync function runServerCode(\n\t\tserverCodeFn:\n\t\t\t| ServerCodeFn<Metadata, Ctx, InputSchema, BindArgsSchemas, any>\n\t\t\t| StatefulServerCodeFn<ServerError, Metadata, Ctx, InputSchema, BindArgsSchemas, ShapedErrors, any>,\n\t\tparsedMainInput: unknown,\n\t\tparsedBindArgsInputs: unknown[],\n\t\tmainClientInput: InferInputOrDefault<InputSchema, undefined>,\n\t\tbindArgsClientInputs: InferInputArray<BindArgsSchemas>,\n\t\tcurrentCtx: object,\n\t\tmiddlewareResult: MiddlewareResult<ServerError, object>,\n\t\tframeworkErrorHandler: FrameworkErrorHandler,\n\t\twithState: boolean,\n\t\tprevResult: SafeActionResult<ServerError, InputSchema, ShapedErrors, any>\n\t) {\n\t\t// Build server code function arguments.\n\t\tconst serverCodeArgs: unknown[] = [\n\t\t\t{\n\t\t\t\tparsedInput: parsedMainInput as InferOutputOrDefault<InputSchema, undefined>,\n\t\t\t\tbindArgsParsedInputs: parsedBindArgsInputs as InferOutputArray<BindArgsSchemas>,\n\t\t\t\tclientInput: mainClientInput,\n\t\t\t\tbindArgsClientInputs,\n\t\t\t\tctx: currentCtx as Ctx,\n\t\t\t\tmetadata: args.metadata,\n\t\t\t},\n\t\t];\n\n\t\tif (withState) {\n\t\t\tserverCodeArgs.push({ prevResult: structuredClone(prevResult) });\n\t\t}\n\n\t\tconst data = await (serverCodeFn as (...a: unknown[]) => Promise<unknown>)(...serverCodeArgs).catch((e) =>\n\t\t\tframeworkErrorHandler.handleError(e)\n\t\t);\n\n\t\t// Validate output schema if provided.\n\t\tif (typeof args.outputSchema !== \"undefined\" && !frameworkErrorHandler.error) {\n\t\t\tconst parsedData = await standardParse(args.outputSchema, data);\n\n\t\t\tif (parsedData.issues) {\n\t\t\t\tthrow new ActionOutputDataValidationError<OutputSchema>(buildValidationErrors(parsedData.issues));\n\t\t\t}\n\t\t}\n\n\t\t// Update middleware result based on execution outcome.\n\t\tif (frameworkErrorHandler.error) {\n\t\t\tmiddlewareResult.success = false;\n\t\t\tmiddlewareResult.navigationKind = FrameworkErrorHandler.getNavigationKind(frameworkErrorHandler.error);\n\t\t} else {\n\t\t\tmiddlewareResult.success = true;\n\t\t\tmiddlewareResult.data = data;\n\t\t}\n\n\t\tmiddlewareResult.parsedInput = parsedMainInput;\n\t\tmiddlewareResult.bindArgsParsedInputs = parsedBindArgsInputs;\n\t}\n\n\t// ─── Handle errors from middleware/action execution ──────────────────\n\n\tasync function handleExecutionError(\n\t\te: unknown,\n\t\tmainClientInput: InferInputOrDefault<InputSchema, undefined>,\n\t\tbindArgsClientInputs: InferInputArray<BindArgsSchemas>,\n\t\tcurrentCtx: object,\n\t\tmiddlewareResult: MiddlewareResult<ServerError, object>,\n\t\tserverErrorHandled: { value: boolean }\n\t) {\n\t\t// ActionServerValidationError: treat as if schema validation failed.\n\t\t// This check must come before the serverErrorHandled guard so middleware catch blocks\n\t\t// using `returnValidationErrors` work even when handleServerError is configured to rethrow.\n\t\tif (e instanceof ActionServerValidationError) {\n\t\t\tconst ve = e.validationErrors as ValidationErrors<InputSchema>;\n\n\t\t\tmiddlewareResult.validationErrors = await Promise.resolve(\n\t\t\t\targs.handleValidationErrorsShape(ve, {\n\t\t\t\t\tclientInput: mainClientInput,\n\t\t\t\t\tbindArgsClientInputs,\n\t\t\t\t\tctx: currentCtx as Ctx,\n\t\t\t\t\tmetadata: args.metadata,\n\t\t\t\t})\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\t// Only handle server errors once. If already handled, rethrow to bubble up.\n\t\tif (serverErrorHandled.value) {\n\t\t\tthrow e;\n\t\t}\n\t\tserverErrorHandled.value = true;\n\n\t\tconst error = isError(e) ? e : new Error(DEFAULT_SERVER_ERROR_MESSAGE);\n\t\tconst returnedError = await Promise.resolve(\n\t\t\targs.handleServerError(error, {\n\t\t\t\tclientInput: mainClientInput as unknown, // pass raw client input\n\t\t\t\tbindArgsClientInputs: bindArgsClientInputs as unknown[],\n\t\t\t\tctx: currentCtx,\n\t\t\t\tmetadata: args.metadata as InferOutputOrDefault<MetadataSchema, undefined>,\n\t\t\t})\n\t\t);\n\n\t\tmiddlewareResult.serverError = returnedError;\n\t}\n\n\t// ─── Build action result and run callbacks ───────────────────────────\n\n\tasync function buildResultAndRunCallbacks<Data>(\n\t\tmiddlewareResult: MiddlewareResult<ServerError, object>,\n\t\tframeworkErrorHandler: FrameworkErrorHandler,\n\t\tmainClientInput: InferInputOrDefault<InputSchema, undefined>,\n\t\tbindArgsClientInputs: InferInputArray<BindArgsSchemas>,\n\t\tcurrentCtx: object,\n\t\tutils?: ActionCallbacks<\n\t\t\tServerError,\n\t\t\tMetadata,\n\t\t\tCtx,\n\t\t\tInputSchema,\n\t\t\tBindArgsSchemas,\n\t\t\tShapedErrors,\n\t\t\tData,\n\t\t\tPreValidationCtx\n\t\t>\n\t): Promise<SafeActionResult<ServerError, InputSchema, ShapedErrors, Data>> {\n\t\tconst callbackPromises: (Promise<unknown> | undefined)[] = [];\n\n\t\t// If a navigation framework error occurred, run navigation callbacks then rethrow\n\t\t// so Next.js can process it.\n\t\tif (frameworkErrorHandler.error) {\n\t\t\tconst navigationKind = FrameworkErrorHandler.getNavigationKind(frameworkErrorHandler.error);\n\n\t\t\tcallbackPromises.push(\n\t\t\t\tutils?.onNavigation?.({\n\t\t\t\t\tmetadata: args.metadata,\n\t\t\t\t\tctx: currentCtx as unknown as PreValidationCtx & Partial<Ctx>,\n\t\t\t\t\tclientInput: mainClientInput,\n\t\t\t\t\tbindArgsClientInputs,\n\t\t\t\t\tnavigationKind,\n\t\t\t\t})\n\t\t\t);\n\n\t\t\tcallbackPromises.push(\n\t\t\t\tutils?.onSettled?.({\n\t\t\t\t\tmetadata: args.metadata,\n\t\t\t\t\tctx: currentCtx as unknown as PreValidationCtx & Partial<Ctx>,\n\t\t\t\t\tclientInput: mainClientInput,\n\t\t\t\t\tbindArgsClientInputs,\n\t\t\t\t\tresult: {},\n\t\t\t\t\tnavigationKind,\n\t\t\t\t})\n\t\t\t);\n\n\t\t\tawait Promise.all(callbackPromises.filter((p) => typeof p !== \"undefined\"));\n\t\t\tthrow frameworkErrorHandler.error;\n\t\t}\n\n\t\t// Handle error throws first. `throwValidationErrors` has higher priority\n\t\t// since it's set at the action level and overrides the client setting.\n\t\t// `throwServerError` is gated on the absence of `validationErrors` so that\n\t\t// the advertised precedence (validationErrors > serverError > data) is\n\t\t// honored even when a compound state reaches this point — e.g. invalid\n\t\t// bind args (wrapped as `serverError`) combined with invalid main input\n\t\t// (`validationErrors`). In that case we must not throw the wrapped bind\n\t\t// args server error and lose the actionable field errors.\n\t\tif (typeof middlewareResult.validationErrors !== \"undefined\") {\n\t\t\tif (\n\t\t\t\twinningBoolean(\n\t\t\t\t\targs.throwValidationErrors,\n\t\t\t\t\ttypeof utils?.throwValidationErrors === \"undefined\" ? undefined : Boolean(utils.throwValidationErrors)\n\t\t\t\t)\n\t\t\t) {\n\t\t\t\tconst overrideErrorMessageFn =\n\t\t\t\t\ttypeof utils?.throwValidationErrors === \"object\" && utils?.throwValidationErrors.overrideErrorMessage\n\t\t\t\t\t\t? utils?.throwValidationErrors.overrideErrorMessage\n\t\t\t\t\t\t: undefined;\n\n\t\t\t\tthrow new ActionValidationError(\n\t\t\t\t\tmiddlewareResult.validationErrors as ShapedErrors,\n\t\t\t\t\tawait overrideErrorMessageFn?.(middlewareResult.validationErrors as ShapedErrors)\n\t\t\t\t);\n\t\t\t}\n\t\t} else if (typeof middlewareResult.serverError !== \"undefined\" && utils?.throwServerError) {\n\t\t\tthrow middlewareResult.serverError;\n\t\t}\n\n\t\t// The result is a discriminated union: exactly one of `validationErrors`,\n\t\t// `serverError`, or `data` is populated, or the result is idle `{}`.\n\t\t// In compound-error scenarios where the runtime could otherwise produce\n\t\t// multiple populated fields (e.g. `next()` called twice after the action\n\t\t// had already succeeded, or invalid bind args combined with invalid main\n\t\t// input), we apply a fixed precedence: validation errors beat server\n\t\t// errors beat success data. The higher-priority state fully describes\n\t\t// the outcome and lower-priority state is discarded.\n\t\tconst hasValidationError = typeof middlewareResult.validationErrors !== \"undefined\";\n\t\tconst hasServerError = typeof middlewareResult.serverError !== \"undefined\";\n\t\tconst treatAsSuccess = middlewareResult.success && !hasValidationError && !hasServerError;\n\n\t\tlet actionResult: SafeActionResult<ServerError, InputSchema, ShapedErrors, Data>;\n\n\t\tif (hasValidationError) {\n\t\t\tactionResult = { validationErrors: middlewareResult.validationErrors as ShapedErrors };\n\t\t} else if (hasServerError) {\n\t\t\tactionResult = { serverError: middlewareResult.serverError as ServerError };\n\t\t} else if (treatAsSuccess && typeof middlewareResult.data !== \"undefined\") {\n\t\t\tactionResult = { data: middlewareResult.data as Data };\n\t\t} else {\n\t\t\tactionResult = {};\n\t\t}\n\n\t\tif (treatAsSuccess) {\n\t\t\tcallbackPromises.push(\n\t\t\t\tutils?.onSuccess?.({\n\t\t\t\t\tmetadata: args.metadata,\n\t\t\t\t\tctx: currentCtx as Ctx,\n\t\t\t\t\tdata: middlewareResult.data as Data,\n\t\t\t\t\tclientInput: mainClientInput,\n\t\t\t\t\tbindArgsClientInputs,\n\t\t\t\t\tparsedInput: middlewareResult.parsedInput as InferOutputOrDefault<InputSchema, undefined>,\n\t\t\t\t\tbindArgsParsedInputs: middlewareResult.bindArgsParsedInputs as InferOutputArray<BindArgsSchemas>,\n\t\t\t\t})\n\t\t\t);\n\t\t} else {\n\t\t\tcallbackPromises.push(\n\t\t\t\tutils?.onError?.({\n\t\t\t\t\tmetadata: args.metadata,\n\t\t\t\t\tctx: currentCtx as unknown as PreValidationCtx & Partial<Ctx>,\n\t\t\t\t\tclientInput: mainClientInput,\n\t\t\t\t\tbindArgsClientInputs,\n\t\t\t\t\terror: actionResult,\n\t\t\t\t})\n\t\t\t);\n\t\t}\n\n\t\t// onSettled, if provided, is always executed.\n\t\tcallbackPromises.push(\n\t\t\tutils?.onSettled?.({\n\t\t\t\tmetadata: args.metadata,\n\t\t\t\tctx: currentCtx as unknown as PreValidationCtx & Partial<Ctx>,\n\t\t\t\tclientInput: mainClientInput,\n\t\t\t\tbindArgsClientInputs,\n\t\t\t\tresult: actionResult,\n\t\t\t})\n\t\t);\n\n\t\tawait Promise.all(callbackPromises.filter((p) => typeof p !== \"undefined\"));\n\n\t\treturn actionResult;\n\t}\n\n\t// ─── Action builder ──────────────────────────────────────────────────\n\n\tfunction buildAction({ withState }: { withState: false }): {\n\t\taction: <Data extends InferOutputOrDefault<OutputSchema, any>>(\n\t\t\tserverCodeFn: ServerCodeFn<Metadata, Ctx, InputSchema, BindArgsSchemas, Data>,\n\t\t\tutils?: ActionCallbacks<\n\t\t\t\tServerError,\n\t\t\t\tMetadata,\n\t\t\t\tCtx,\n\t\t\t\tInputSchema,\n\t\t\t\tBindArgsSchemas,\n\t\t\t\tShapedErrors,\n\t\t\t\tData,\n\t\t\t\tPreValidationCtx\n\t\t\t>\n\t\t) => SafeActionFn<ServerError, InputSchema, BindArgsSchemas, ShapedErrors, Data>;\n\t};\n\tfunction buildAction({ withState }: { withState: true }): {\n\t\taction: <Data extends InferOutputOrDefault<OutputSchema, any>>(\n\t\t\tserverCodeFn: StatefulServerCodeFn<ServerError, Metadata, Ctx, InputSchema, BindArgsSchemas, ShapedErrors, Data>,\n\t\t\tutils?: ActionCallbacks<\n\t\t\t\tServerError,\n\t\t\t\tMetadata,\n\t\t\t\tCtx,\n\t\t\t\tInputSchema,\n\t\t\t\tBindArgsSchemas,\n\t\t\t\tShapedErrors,\n\t\t\t\tData,\n\t\t\t\tPreValidationCtx\n\t\t\t>\n\t\t) => SafeStateActionFn<ServerError, InputSchema, BindArgsSchemas, ShapedErrors, Data>;\n\t};\n\tfunction buildAction({ withState }: { withState: boolean }) {\n\t\treturn {\n\t\t\taction: <Data extends InferOutputOrDefault<OutputSchema, any>>(\n\t\t\t\tserverCodeFn:\n\t\t\t\t\t| ServerCodeFn<Metadata, Ctx, InputSchema, BindArgsSchemas, Data>\n\t\t\t\t\t| StatefulServerCodeFn<ServerError, Metadata, Ctx, InputSchema, BindArgsSchemas, ShapedErrors, Data>,\n\t\t\t\tutils?: ActionCallbacks<\n\t\t\t\t\tServerError,\n\t\t\t\t\tMetadata,\n\t\t\t\t\tCtx,\n\t\t\t\t\tInputSchema,\n\t\t\t\t\tBindArgsSchemas,\n\t\t\t\t\tShapedErrors,\n\t\t\t\t\tData,\n\t\t\t\t\tPreValidationCtx\n\t\t\t\t>\n\t\t\t) => {\n\t\t\t\treturn async (...clientInputs: unknown[]) => {\n\t\t\t\t\tlet currentCtx: object = {};\n\t\t\t\t\tconst middlewareResult: MiddlewareResult<ServerError, object> = { success: false };\n\t\t\t\t\ttype PrevResult = SafeActionResult<ServerError, InputSchema, ShapedErrors, Data>;\n\t\t\t\t\tlet prevResult: PrevResult = {};\n\t\t\t\t\tconst frameworkErrorHandler = new FrameworkErrorHandler();\n\t\t\t\t\tconst serverErrorHandled = { value: false };\n\t\t\t\t\tlet chainCompleted = false;\n\n\t\t\t\t\t// Extract prevResult for stateful actions.\n\t\t\t\t\tif (withState) {\n\t\t\t\t\t\tprevResult = clientInputs.splice(bindArgsSchemas.length, 1)[0] as PrevResult;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Extract structured inputs based on schema definitions rather than iterating over\n\t\t\t\t\t// clientInputs, so that excess arguments from external callers are silently ignored.\n\t\t\t\t\tconst mainClientInput = clientInputs[bindArgsSchemas.length] as InferInputOrDefault<InputSchema, undefined>;\n\t\t\t\t\tconst bindArgsClientInputs = clientInputs.slice(\n\t\t\t\t\t\t0,\n\t\t\t\t\t\tbindArgsSchemas.length\n\t\t\t\t\t) as InferInputArray<BindArgsSchemas>;\n\n\t\t\t\t\t// Validate metadata once, before running the middleware stack.\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait validateMetadata();\n\t\t\t\t\t} catch (e: unknown) {\n\t\t\t\t\t\tawait handleExecutionError(\n\t\t\t\t\t\t\te,\n\t\t\t\t\t\t\tmainClientInput,\n\t\t\t\t\t\t\tbindArgsClientInputs,\n\t\t\t\t\t\t\tcurrentCtx,\n\t\t\t\t\t\t\tmiddlewareResult,\n\t\t\t\t\t\t\tserverErrorHandled\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\treturn buildResultAndRunCallbacks<Data>(\n\t\t\t\t\t\t\tmiddlewareResult,\n\t\t\t\t\t\t\tframeworkErrorHandler,\n\t\t\t\t\t\t\tmainClientInput,\n\t\t\t\t\t\t\tbindArgsClientInputs,\n\t\t\t\t\t\t\tcurrentCtx,\n\t\t\t\t\t\t\tutils\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\t// ─── Validated middleware stack (post-validation) ─────────\n\n\t\t\t\t\tconst executeValidatedMiddlewareStack = async (\n\t\t\t\t\t\tidx: number,\n\t\t\t\t\t\tparsedMainInput: unknown,\n\t\t\t\t\t\tparsedBindArgsInputs: unknown[]\n\t\t\t\t\t) => {\n\t\t\t\t\t\tif (frameworkErrorHandler.error) return;\n\n\t\t\t\t\t\tconst validatedMiddlewareFn = args.validatedMiddlewareFns[idx];\n\t\t\t\t\t\tmiddlewareResult.ctx = currentCtx;\n\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tif (validatedMiddlewareFn) {\n\t\t\t\t\t\t\t\tlet nextCalled = false;\n\n\t\t\t\t\t\t\t\tawait validatedMiddlewareFn({\n\t\t\t\t\t\t\t\t\tparsedInput: parsedMainInput,\n\t\t\t\t\t\t\t\t\tclientInput: mainClientInput,\n\t\t\t\t\t\t\t\t\tbindArgsParsedInputs: parsedBindArgsInputs as readonly unknown[],\n\t\t\t\t\t\t\t\t\tbindArgsClientInputs: bindArgsClientInputs as readonly unknown[],\n\t\t\t\t\t\t\t\t\tctx: currentCtx,\n\t\t\t\t\t\t\t\t\tmetadata: args.metadata,\n\t\t\t\t\t\t\t\t\tnext: async (nextOpts) => {\n\t\t\t\t\t\t\t\t\t\tif (chainCompleted) {\n\t\t\t\t\t\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t\t\t\t\t\t\"next() called after the middleware chain has already completed. Do not store and call next() asynchronously after the action has returned.\"\n\t\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tif (nextCalled) {\n\t\t\t\t\t\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t\t\t\t\t\t\"next() called multiple times in middleware. Each middleware must call next() at most once.\"\n\t\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tnextCalled = true;\n\n\t\t\t\t\t\t\t\t\t\tcurrentCtx = deepmerge(currentCtx, nextOpts?.ctx ?? {});\n\t\t\t\t\t\t\t\t\t\tawait executeValidatedMiddlewareStack(idx + 1, parsedMainInput, parsedBindArgsInputs);\n\t\t\t\t\t\t\t\t\t\treturn middlewareResult;\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t}).catch((e) => {\n\t\t\t\t\t\t\t\t\tframeworkErrorHandler.handleError(e);\n\t\t\t\t\t\t\t\t\tif (frameworkErrorHandler.error) {\n\t\t\t\t\t\t\t\t\t\tmiddlewareResult.success = false;\n\t\t\t\t\t\t\t\t\t\tmiddlewareResult.navigationKind = FrameworkErrorHandler.getNavigationKind(\n\t\t\t\t\t\t\t\t\t\t\tframeworkErrorHandler.error\n\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t// Terminal case: execute server code (input already validated).\n\t\t\t\t\t\t\t\tawait runServerCode(\n\t\t\t\t\t\t\t\t\tserverCodeFn,\n\t\t\t\t\t\t\t\t\tparsedMainInput,\n\t\t\t\t\t\t\t\t\tparsedBindArgsInputs,\n\t\t\t\t\t\t\t\t\tmainClientInput,\n\t\t\t\t\t\t\t\t\tbindArgsClientInputs,\n\t\t\t\t\t\t\t\t\tcurrentCtx,\n\t\t\t\t\t\t\t\t\tmiddlewareResult,\n\t\t\t\t\t\t\t\t\tframeworkErrorHandler,\n\t\t\t\t\t\t\t\t\twithState,\n\t\t\t\t\t\t\t\t\tprevResult\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch (e: unknown) {\n\t\t\t\t\t\t\tawait handleExecutionError(\n\t\t\t\t\t\t\t\te,\n\t\t\t\t\t\t\t\tmainClientInput,\n\t\t\t\t\t\t\t\tbindArgsClientInputs,\n\t\t\t\t\t\t\t\tcurrentCtx,\n\t\t\t\t\t\t\t\tmiddlewareResult,\n\t\t\t\t\t\t\t\tserverErrorHandled\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\n\t\t\t\t\t// ─── Pre-validation middleware stack ──────────────────────\n\n\t\t\t\t\tconst executeMiddlewareStack = async (idx = 0) => {\n\t\t\t\t\t\tif (frameworkErrorHandler.error) return;\n\n\t\t\t\t\t\tconst middlewareFn = args.middlewareFns[idx];\n\t\t\t\t\t\tmiddlewareResult.ctx = currentCtx;\n\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tif (middlewareFn) {\n\t\t\t\t\t\t\t\tlet nextCalled = false;\n\n\t\t\t\t\t\t\t\tawait middlewareFn({\n\t\t\t\t\t\t\t\t\tclientInput: mainClientInput as unknown, // pass raw client input\n\t\t\t\t\t\t\t\t\tbindArgsClientInputs: bindArgsClientInputs as unknown[],\n\t\t\t\t\t\t\t\t\tctx: currentCtx,\n\t\t\t\t\t\t\t\t\tmetadata: args.metadata,\n\t\t\t\t\t\t\t\t\tnext: async (nextOpts) => {\n\t\t\t\t\t\t\t\t\t\tif (chainCompleted) {\n\t\t\t\t\t\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t\t\t\t\t\t\"next() called after the middleware chain has already completed. Do not store and call next() asynchronously after the action has returned.\"\n\t\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tif (nextCalled) {\n\t\t\t\t\t\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t\t\t\t\t\t\"next() called multiple times in middleware. Each middleware must call next() at most once.\"\n\t\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tnextCalled = true;\n\n\t\t\t\t\t\t\t\t\t\tcurrentCtx = deepmerge(currentCtx, nextOpts?.ctx ?? {});\n\t\t\t\t\t\t\t\t\t\tawait executeMiddlewareStack(idx + 1);\n\t\t\t\t\t\t\t\t\t\treturn middlewareResult;\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t}).catch((e) => {\n\t\t\t\t\t\t\t\t\tframeworkErrorHandler.handleError(e);\n\t\t\t\t\t\t\t\t\tif (frameworkErrorHandler.error) {\n\t\t\t\t\t\t\t\t\t\tmiddlewareResult.success = false;\n\t\t\t\t\t\t\t\t\t\tmiddlewareResult.navigationKind = FrameworkErrorHandler.getNavigationKind(\n\t\t\t\t\t\t\t\t\t\t\tframeworkErrorHandler.error\n\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t// Terminal case: validate inputs, then run validated middleware + server code.\n\t\t\t\t\t\t\t\tconst validated = await validateInputs(\n\t\t\t\t\t\t\t\t\tmainClientInput,\n\t\t\t\t\t\t\t\t\tbindArgsClientInputs,\n\t\t\t\t\t\t\t\t\tcurrentCtx,\n\t\t\t\t\t\t\t\t\tmiddlewareResult\n\t\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\t\t// Validation errors were set, skip server code execution.\n\t\t\t\t\t\t\t\tif (!validated) return;\n\n\t\t\t\t\t\t\t\tconst { parsedMainInput, parsedBindArgsInputs } = validated;\n\n\t\t\t\t\t\t\t\t// Run the validated middleware stack (terminates at server code).\n\t\t\t\t\t\t\t\tawait executeValidatedMiddlewareStack(0, parsedMainInput, parsedBindArgsInputs);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch (e: unknown) {\n\t\t\t\t\t\t\tawait handleExecutionError(\n\t\t\t\t\t\t\t\te,\n\t\t\t\t\t\t\t\tmainClientInput,\n\t\t\t\t\t\t\t\tbindArgsClientInputs,\n\t\t\t\t\t\t\t\tcurrentCtx,\n\t\t\t\t\t\t\t\tmiddlewareResult,\n\t\t\t\t\t\t\t\tserverErrorHandled\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\n\t\t\t\t\t// Execute middleware chain + action function.\n\t\t\t\t\tawait executeMiddlewareStack();\n\t\t\t\t\tchainCompleted = true;\n\n\t\t\t\t\treturn buildResultAndRunCallbacks<Data>(\n\t\t\t\t\t\tmiddlewareResult,\n\t\t\t\t\t\tframeworkErrorHandler,\n\t\t\t\t\t\tmainClientInput,\n\t\t\t\t\t\tbindArgsClientInputs,\n\t\t\t\t\t\tcurrentCtx,\n\t\t\t\t\t\tutils\n\t\t\t\t\t);\n\t\t\t\t};\n\t\t\t},\n\t\t};\n\t}\n\n\treturn {\n\t\t/**\n\t\t * Define the action.\n\t\t * @param serverCodeFn Code that will be executed on the **server side**\n\t\t *\n\t\t * {@link https://next-safe-action.dev/docs/define-actions/instance-methods#action--stateaction See docs for more information}\n\t\t */\n\t\taction: buildAction({ withState: false }).action,\n\n\t\t/**\n\t\t * Define the stateful action. To be used with the [`useStateAction`](https://next-safe-action.dev/docs/execute-actions/hooks/usestateaction) hook.\n\t\t * @param serverCodeFn Code that will be executed on the **server side**\n\t\t *\n\t\t * {@link https://next-safe-action.dev/docs/define-actions/instance-methods#action--stateaction See docs for more information}\n\t\t */\n\t\tstateAction: buildAction({ withState: true }).action,\n\t};\n}\n","import { actionBuilder } from \"./action-builder\";\nimport type {\n\tValidationErrorsFormat,\n\tEffectiveThrows,\n\tInputSchemaFactoryFn,\n\tMaybeBrandThrows,\n\tMiddlewareFn,\n\tValidatedMiddlewareFn,\n\tSafeActionClientArgs,\n\tSafeActionFn,\n\tSafeStateActionFn,\n\tActionCallbacks,\n\tServerCodeFn,\n\tStatefulServerCodeFn,\n} from \"./index.types\";\nimport type {\n\tInferInputOrDefault,\n\tInferInputArray,\n\tInferOutputOrDefault,\n\tInferOutputArray,\n\tStandardSchemaV1,\n} from \"./standard-schema\";\nimport { isStandardSchema } from \"./standard-schema\";\nimport type {\n\tFlattenedValidationErrors,\n\tHandleValidationErrorsShapeFn,\n\tValidationErrors,\n} from \"./validation-errors.types\";\n\nexport class SafeActionClient<\n\tServerError,\n\tErrorsFormat extends ValidationErrorsFormat | undefined, // override default validation errors shape\n\tMetadataSchema extends StandardSchemaV1 | undefined = undefined,\n\tMetadata = InferOutputOrDefault<MetadataSchema, undefined>, // metadata type (inferred from metadata schema)\n\tHasMetadata extends boolean = MetadataSchema extends undefined ? true : false,\n\tCtx extends object = {},\n\tInputSchemaFn extends ((clientInput?: unknown) => Promise<StandardSchemaV1>) | undefined = undefined, // input schema function\n\tInputSchema extends StandardSchemaV1 | undefined = InputSchemaFn extends Function\n\t\t? Awaited<ReturnType<InputSchemaFn>>\n\t\t: undefined, // input schema\n\tOutputSchema extends StandardSchemaV1 | undefined = undefined, // output schema\n\tconst BindArgsSchemas extends readonly StandardSchemaV1[] = [],\n\tShapedErrors = undefined,\n\tThrowsValidationErrors extends boolean = false,\n\tHasValidatedMiddleware extends boolean = false,\n\tPreValidationCtx extends object = Ctx,\n> {\n\treadonly #args: SafeActionClientArgs<\n\t\tServerError,\n\t\tErrorsFormat,\n\t\tMetadataSchema,\n\t\tMetadata,\n\t\tHasMetadata,\n\t\tCtx,\n\t\tInputSchemaFn,\n\t\tInputSchema,\n\t\tOutputSchema,\n\t\tBindArgsSchemas,\n\t\tShapedErrors,\n\t\tThrowsValidationErrors,\n\t\tHasValidatedMiddleware,\n\t\tPreValidationCtx\n\t>;\n\n\tconstructor(\n\t\targs: SafeActionClientArgs<\n\t\t\tServerError,\n\t\t\tErrorsFormat,\n\t\t\tMetadataSchema,\n\t\t\tMetadata,\n\t\t\tHasMetadata,\n\t\t\tCtx,\n\t\t\tInputSchemaFn,\n\t\t\tInputSchema,\n\t\t\tOutputSchema,\n\t\t\tBindArgsSchemas,\n\t\t\tShapedErrors,\n\t\t\tThrowsValidationErrors,\n\t\t\tHasValidatedMiddleware,\n\t\t\tPreValidationCtx\n\t\t>\n\t) {\n\t\tthis.#args = args;\n\t}\n\n\t/**\n\t * Use a middleware function. Middleware added via `use()` always runs **before** input validation.\n\t * Cannot be called after `useValidated()`.\n\t * @param middlewareFn Middleware function\n\t *\n\t * {@link https://next-safe-action.dev/docs/define-actions/instance-methods#use See docs for more information}\n\t */\n\tuse<NextCtx extends object>(\n\t\tthis: HasValidatedMiddleware extends false\n\t\t\t? SafeActionClient<\n\t\t\t\t\tServerError,\n\t\t\t\t\tErrorsFormat,\n\t\t\t\t\tMetadataSchema,\n\t\t\t\t\tMetadata,\n\t\t\t\t\tHasMetadata,\n\t\t\t\t\tCtx,\n\t\t\t\t\tInputSchemaFn,\n\t\t\t\t\tInputSchema,\n\t\t\t\t\tOutputSchema,\n\t\t\t\t\tBindArgsSchemas,\n\t\t\t\t\tShapedErrors,\n\t\t\t\t\tThrowsValidationErrors,\n\t\t\t\t\tHasValidatedMiddleware,\n\t\t\t\t\tPreValidationCtx\n\t\t\t\t>\n\t\t\t: never,\n\t\tmiddlewareFn: MiddlewareFn<ServerError, Metadata, Ctx, Ctx & NextCtx>\n\t) {\n\t\tif (this.#args.hasValidatedMiddleware) {\n\t\t\tthrow new Error(\"use() cannot be called after useValidated(). Move all use() calls before useValidated().\");\n\t\t}\n\n\t\treturn new SafeActionClient({\n\t\t\t...this.#args,\n\t\t\tmiddlewareFns: [...this.#args.middlewareFns, middlewareFn],\n\t\t\tctxType: {} as Ctx & NextCtx,\n\t\t\tpreValidationCtxType: {} as PreValidationCtx & NextCtx,\n\t\t});\n\t}\n\n\t/**\n\t * Use a validated middleware function. Middleware added via `useValidated()` runs **after** input\n\t * validation and receives typed `parsedInput` and `bindArgsParsedInputs`. Follows the onion model:\n\t * code before `next()` runs pre-action, code after `next()` runs post-action with access to the result.\n\t *\n\t * Requires `inputSchema()` or `bindArgsSchemas()` to be called before. After calling `useValidated()`,\n\t * `inputSchema()`, `bindArgsSchemas()`, and `use()` can no longer be called.\n\t *\n\t * @example\n\t * ```ts\n\t * authClient\n\t * .inputSchema(z.object({ postId: z.string() }))\n\t * .useValidated(async ({ parsedInput, ctx, next }) => {\n\t * const post = await db.post.findUnique({ where: { id: parsedInput.postId } });\n\t * if (post?.authorId !== ctx.user.id) throw new Error(\"Forbidden\");\n\t * return next({ ctx: { post } });\n\t * })\n\t * ```\n\t *\n\t * @param middlewareFn Validated middleware function\n\t *\n\t * {@link https://next-safe-action.dev/docs/define-actions/instance-methods#usevalidated See docs for more information}\n\t */\