UNPKG

next-safe-action

Version:

Type safe and validated Server Actions in your Next.js project.

733 lines (732 loc) 32.2 kB
import { t as FrameworkErrorHandler } from "./errors-DSpBUWAx.mjs"; //#region src/deep-merge.ts /*! * This file is a deep-merge implementation adapted from `deepmerge-ts` * (https://github.com/RebeccaStevens/deepmerge-ts). Portions of the code below, in * particular the plain-record detection (`isRecord`), key collection (`getKeys`) and the * recursive record merge with its `__proto__` prototype-pollution guard, are derived from * that project and remain under its original license, reproduced in full below. * * BSD 3-Clause License * * Copyright (c) 2021, Rebecca Stevens * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. Neither the name of the copyright holder nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * Internal deep-merge utility. * * Inlined from, and trimmed down to only what this library needs of, `deepmerge-ts`, so * that next-safe-action ships with zero runtime dependencies and is not exposed to * supply-chain attacks targeting third-party packages. * * The library only ever calls `deepmerge(a, b)` with two plain middleware context objects, * so this reproduces deepmerge-ts's *default* merge behavior for that case: * - plain records (objects) are merged recursively; * - arrays are concatenated; * - Sets are unioned and Maps are combined (later entries win on key conflict); * - any other or mismatched values: the last (rightmost) value wins. */ const validRecordToStringValues = ["[object Object]", "[object Module]"]; /** * Whether the given value is a plain record, as opposed to an array, Set, Map, class * instance, or other exotic object. Mirrors deepmerge-ts's record detection. */ function isRecord(value) { if (typeof value !== "object" || value === null) return false; if (!validRecordToStringValues.includes(Object.prototype.toString.call(value))) return false; const { constructor } = value; if (constructor === void 0) return true; const prototype = constructor.prototype; if (prototype === null || typeof prototype !== "object" || !validRecordToStringValues.includes(Object.prototype.toString.call(prototype))) return false; if (!Object.hasOwn(prototype, "isPrototypeOf")) return false; return true; } /** * The union of both objects' own enumerable string keys and own symbol keys, in * first-then-second insertion order. */ function getKeys(a, b) { const keys = /* @__PURE__ */ new Set(); for (const object of [a, b]) for (const key of [...Object.keys(object), ...Object.getOwnPropertySymbols(object)]) keys.add(key); return keys; } /** * Whether `object` has `property` as an own enumerable property. */ function objectHasProperty(object, property) { return Object.prototype.propertyIsEnumerable.call(object, property); } /** * Merge two values according to the default strategy described at the top of this file. */ function mergeValues(a, b) { if (isRecord(a) && isRecord(b)) return mergeRecords(a, b); if (Array.isArray(a) && Array.isArray(b)) return [...a, ...b]; if (a instanceof Set && b instanceof Set) return new Set([...a, ...b]); if (a instanceof Map && b instanceof Map) return new Map([...a, ...b]); return b; } /** * Recursively merge two plain records into a fresh object. */ function mergeRecords(a, b) { const result = {}; for (const key of getKeys(a, b)) { const inA = objectHasProperty(a, key); const inB = objectHasProperty(b, key); if (!inA && !inB) continue; let value; if (inA && inB) value = mergeValues(a[key], b[key]); else if (inB) value = b[key]; else value = a[key]; if (key === "__proto__") Object.defineProperty(result, key, { value, configurable: true, enumerable: true, writable: true }); else result[key] = value; } return result; } /** * Deeply merge the given objects, with the last value winning on conflict. The library * always calls this with exactly two middleware context objects. */ function deepmerge(...objects) { let result = objects[0] ?? {}; for (let index = 1; index < objects.length; index++) result = mergeValues(result, objects[index]); return result; } //#endregion //#region src/standard-schema.ts function isStandardSchema(value) { if (value === null || typeof value !== "object" && typeof value !== "function") return false; const standardProps = value["~standard"]; if (standardProps === null || typeof standardProps !== "object") return false; return standardProps.version === 1 && typeof standardProps.validate === "function"; } async function standardParse(schema, value) { return schema["~standard"].validate(value); } //#endregion //#region src/utils.ts const DEFAULT_SERVER_ERROR_MESSAGE = "Something went wrong while executing the operation."; /** * Checks if passed argument is an instance of Error. */ const isError = (error) => error instanceof Error; /** * Checks what the winning boolean value is from a series of values, from lowest to highest priority. * `null` and `undefined` values are skipped. */ const winningBoolean = (...args) => { return args.reduce((acc, v) => typeof v === "boolean" ? v : acc, false); }; //#endregion //#region src/validation-errors.ts const getKey = (segment) => typeof segment === "object" ? segment.key : segment; const getIssueMessage = (issue) => { if (issue.unionErrors) return issue.unionErrors.flatMap((u) => u.issues.map((i) => i.message)); return [issue.message]; }; const buildValidationErrors = (issues) => { const ve = {}; for (const issue of issues) { const { path } = issue; if (!path || path.length === 0) { const issueMessages = getIssueMessage(issue); ve._errors = ve._errors ? [...ve._errors, ...issueMessages] : [...issueMessages]; continue; } let ref = ve; for (let i = 0; i < path.length - 1; i++) { const k = getKey(path[i]); if (!ref[k]) ref[k] = {}; ref = ref[k]; } const key = getKey(path[path.length - 1]); const issueMessage = getIssueMessage(issue); const existing = ref[key] ? structuredClone(ref[key]) : {}; ref[key] = existing._errors ? { ...existing, _errors: [...existing._errors, ...issueMessage] } : { ...existing, _errors: [...issueMessage] }; } return ve; }; var ActionServerValidationError = class extends Error { validationErrors; constructor(validationErrors) { super("Server Action server validation error(s) occurred"); this.validationErrors = validationErrors; } }; var ActionValidationError = class extends Error { validationErrors; constructor(validationErrors, overriddenErrorMessage) { super(overriddenErrorMessage ?? "Server Action validation error(s) occurred"); this.validationErrors = validationErrors; } }; var ActionBindArgsValidationError = class extends Error { validationErrors; constructor(validationErrors) { super("Server Action bind args validation error(s) occurred"); this.validationErrors = validationErrors; } }; /** * Return custom validation errors to the client from the action's server code function. * Code declared after this function invocation will not be executed. * @param schema Input schema * @param validationErrors Validation errors object * * {@link https://next-safe-action.dev/docs/define-actions/validation-errors#returnvalidationerrors See docs for more information} */ function returnValidationErrors(schema, validationErrors) { throw new ActionServerValidationError(validationErrors); } /** * Default validation errors format. * Emulation of `zod`'s [`format`](https://zod.dev/ERROR_HANDLING?id=formatting-errors) function. */ function formatValidationErrors(validationErrors) { return validationErrors; } /** * Transform default formatted validation errors into flattened structure. * `formErrors` contains global errors, and `fieldErrors` contains errors for each field, * one level deep. It discards errors for nested fields. * Emulation of `zod`'s [`flatten`](https://zod.dev/ERROR_HANDLING?id=flattening-errors) function. * @param {ValidationErrors} [validationErrors] Validation errors object * * {@link https://next-safe-action.dev/docs/define-actions/validation-errors#flattenvalidationerrorsutility-function See docs for more information} */ function flattenValidationErrors(validationErrors) { const flattened = { formErrors: [], fieldErrors: {} }; for (const [key, value] of Object.entries(validationErrors ?? {})) if (key === "_errors" && Array.isArray(value)) flattened.formErrors = [...value]; else if ("_errors" in value) flattened.fieldErrors[key] = [...value._errors]; return flattened; } /** * This error is thrown when an action metadata is invalid, i.e. when there's a mismatch between the * type of the metadata schema returned from `defineMetadataSchema` and the actual data passed. */ var ActionMetadataValidationError = class extends Error { validationErrors; constructor(validationErrors) { super("Invalid metadata input. Please be sure to pass metadata via `metadata` method before defining the action."); this.name = "ActionMetadataError"; this.validationErrors = validationErrors; } }; /** * This error is thrown when an action's data (output) is invalid, i.e. when there's a mismatch between the * type of the data schema passed to `dataSchema` method and the actual return of the action. */ var ActionOutputDataValidationError = class extends Error { validationErrors; constructor(validationErrors) { super("Invalid action data (output). Please be sure to return data following the shape of the schema passed to `dataSchema` method."); this.name = "ActionOutputDataError"; this.validationErrors = validationErrors; } }; //#endregion //#region src/action-builder.ts function actionBuilder(args) { const bindArgsSchemas = args.bindArgsSchemas ?? []; async function validateMetadata() { if (!args.metadataSchema) return; const parsedMd = await standardParse(args.metadataSchema, args.metadata); if (parsedMd.issues) throw new ActionMetadataValidationError(buildValidationErrors(parsedMd.issues)); } async function validateInputs(mainClientInput, bindArgsClientInputs, currentCtx, middlewareResult) { const parsedBindArgsResults = await Promise.all(bindArgsSchemas.map((schema, i) => standardParse(schema, bindArgsClientInputs[i]))); const parsedMainInputResult = typeof args.inputSchemaFn === "undefined" ? { value: void 0 } : await standardParse(await args.inputSchemaFn(mainClientInput), mainClientInput); let hasBindValidationErrors = false; const bindArgsValidationErrors = Array(bindArgsSchemas.length).fill({}); const parsedBindArgsInputs = []; for (let i = 0; i < parsedBindArgsResults.length; i++) { const parsedInput = parsedBindArgsResults[i]; if (!parsedInput.issues) parsedBindArgsInputs.push(parsedInput.value); else { bindArgsValidationErrors[i] = buildValidationErrors(parsedInput.issues); hasBindValidationErrors = true; } } let parsedMainInput = void 0; if (!parsedMainInputResult.issues) parsedMainInput = parsedMainInputResult.value; else { const validationErrors = buildValidationErrors(parsedMainInputResult.issues); middlewareResult.validationErrors = await Promise.resolve(args.handleValidationErrorsShape(validationErrors, { clientInput: mainClientInput, bindArgsClientInputs, ctx: currentCtx, metadata: args.metadata })); } if (hasBindValidationErrors) throw new ActionBindArgsValidationError(bindArgsValidationErrors); if (middlewareResult.validationErrors) return null; return { parsedMainInput, parsedBindArgsInputs }; } async function runServerCode(serverCodeFn, parsedMainInput, parsedBindArgsInputs, mainClientInput, bindArgsClientInputs, currentCtx, middlewareResult, frameworkErrorHandler, withState, prevResult) { const serverCodeArgs = [{ parsedInput: parsedMainInput, bindArgsParsedInputs: parsedBindArgsInputs, clientInput: mainClientInput, bindArgsClientInputs, ctx: currentCtx, metadata: args.metadata }]; if (withState) serverCodeArgs.push({ prevResult: structuredClone(prevResult) }); const data = await serverCodeFn(...serverCodeArgs).catch((e) => frameworkErrorHandler.handleError(e)); if (typeof args.outputSchema !== "undefined" && !frameworkErrorHandler.error) { const parsedData = await standardParse(args.outputSchema, data); if (parsedData.issues) throw new ActionOutputDataValidationError(buildValidationErrors(parsedData.issues)); } if (frameworkErrorHandler.error) { middlewareResult.success = false; middlewareResult.navigationKind = FrameworkErrorHandler.getNavigationKind(frameworkErrorHandler.error); } else { middlewareResult.success = true; middlewareResult.data = data; } middlewareResult.parsedInput = parsedMainInput; middlewareResult.bindArgsParsedInputs = parsedBindArgsInputs; } async function handleExecutionError(e, mainClientInput, bindArgsClientInputs, currentCtx, middlewareResult, serverErrorHandled) { if (e instanceof ActionServerValidationError) { const ve = e.validationErrors; middlewareResult.validationErrors = await Promise.resolve(args.handleValidationErrorsShape(ve, { clientInput: mainClientInput, bindArgsClientInputs, ctx: currentCtx, metadata: args.metadata })); return; } if (serverErrorHandled.value) throw e; serverErrorHandled.value = true; const error = isError(e) ? e : new Error(DEFAULT_SERVER_ERROR_MESSAGE); middlewareResult.serverError = await Promise.resolve(args.handleServerError(error, { clientInput: mainClientInput, bindArgsClientInputs, ctx: currentCtx, metadata: args.metadata })); } async function buildResultAndRunCallbacks(middlewareResult, frameworkErrorHandler, mainClientInput, bindArgsClientInputs, currentCtx, utils) { const callbackPromises = []; if (frameworkErrorHandler.error) { const navigationKind = FrameworkErrorHandler.getNavigationKind(frameworkErrorHandler.error); callbackPromises.push(utils?.onNavigation?.({ metadata: args.metadata, ctx: currentCtx, clientInput: mainClientInput, bindArgsClientInputs, navigationKind })); callbackPromises.push(utils?.onSettled?.({ metadata: args.metadata, ctx: currentCtx, clientInput: mainClientInput, bindArgsClientInputs, result: {}, navigationKind })); await Promise.all(callbackPromises.filter((p) => typeof p !== "undefined")); throw frameworkErrorHandler.error; } if (typeof middlewareResult.validationErrors !== "undefined") { if (winningBoolean(args.throwValidationErrors, typeof utils?.throwValidationErrors === "undefined" ? void 0 : Boolean(utils.throwValidationErrors))) { const overrideErrorMessageFn = typeof utils?.throwValidationErrors === "object" && utils?.throwValidationErrors.overrideErrorMessage ? utils?.throwValidationErrors.overrideErrorMessage : void 0; throw new ActionValidationError(middlewareResult.validationErrors, await overrideErrorMessageFn?.(middlewareResult.validationErrors)); } } else if (typeof middlewareResult.serverError !== "undefined" && utils?.throwServerError) throw middlewareResult.serverError; const hasValidationError = typeof middlewareResult.validationErrors !== "undefined"; const hasServerError = typeof middlewareResult.serverError !== "undefined"; const treatAsSuccess = middlewareResult.success && !hasValidationError && !hasServerError; let actionResult; if (hasValidationError) actionResult = { validationErrors: middlewareResult.validationErrors }; else if (hasServerError) actionResult = { serverError: middlewareResult.serverError }; else if (treatAsSuccess && typeof middlewareResult.data !== "undefined") actionResult = { data: middlewareResult.data }; else actionResult = {}; if (treatAsSuccess) callbackPromises.push(utils?.onSuccess?.({ metadata: args.metadata, ctx: currentCtx, data: middlewareResult.data, clientInput: mainClientInput, bindArgsClientInputs, parsedInput: middlewareResult.parsedInput, bindArgsParsedInputs: middlewareResult.bindArgsParsedInputs })); else callbackPromises.push(utils?.onError?.({ metadata: args.metadata, ctx: currentCtx, clientInput: mainClientInput, bindArgsClientInputs, error: actionResult })); callbackPromises.push(utils?.onSettled?.({ metadata: args.metadata, ctx: currentCtx, clientInput: mainClientInput, bindArgsClientInputs, result: actionResult })); await Promise.all(callbackPromises.filter((p) => typeof p !== "undefined")); return actionResult; } function buildAction({ withState }) { return { action: (serverCodeFn, utils) => { return async (...clientInputs) => { let currentCtx = {}; const middlewareResult = { success: false }; let prevResult = {}; const frameworkErrorHandler = new FrameworkErrorHandler(); const serverErrorHandled = { value: false }; let chainCompleted = false; if (withState) prevResult = clientInputs.splice(bindArgsSchemas.length, 1)[0]; const mainClientInput = clientInputs[bindArgsSchemas.length]; const bindArgsClientInputs = clientInputs.slice(0, bindArgsSchemas.length); try { await validateMetadata(); } catch (e) { await handleExecutionError(e, mainClientInput, bindArgsClientInputs, currentCtx, middlewareResult, serverErrorHandled); return buildResultAndRunCallbacks(middlewareResult, frameworkErrorHandler, mainClientInput, bindArgsClientInputs, currentCtx, utils); } const executeValidatedMiddlewareStack = async (idx, parsedMainInput, parsedBindArgsInputs) => { if (frameworkErrorHandler.error) return; const validatedMiddlewareFn = args.validatedMiddlewareFns[idx]; middlewareResult.ctx = currentCtx; try { if (validatedMiddlewareFn) { let nextCalled = false; await validatedMiddlewareFn({ parsedInput: parsedMainInput, clientInput: mainClientInput, bindArgsParsedInputs: parsedBindArgsInputs, bindArgsClientInputs, ctx: currentCtx, metadata: args.metadata, next: async (nextOpts) => { if (chainCompleted) throw new Error("next() called after the middleware chain has already completed. Do not store and call next() asynchronously after the action has returned."); if (nextCalled) throw new Error("next() called multiple times in middleware. Each middleware must call next() at most once."); nextCalled = true; currentCtx = deepmerge(currentCtx, nextOpts?.ctx ?? {}); await executeValidatedMiddlewareStack(idx + 1, parsedMainInput, parsedBindArgsInputs); return middlewareResult; } }).catch((e) => { frameworkErrorHandler.handleError(e); if (frameworkErrorHandler.error) { middlewareResult.success = false; middlewareResult.navigationKind = FrameworkErrorHandler.getNavigationKind(frameworkErrorHandler.error); } }); } else await runServerCode(serverCodeFn, parsedMainInput, parsedBindArgsInputs, mainClientInput, bindArgsClientInputs, currentCtx, middlewareResult, frameworkErrorHandler, withState, prevResult); } catch (e) { await handleExecutionError(e, mainClientInput, bindArgsClientInputs, currentCtx, middlewareResult, serverErrorHandled); } }; const executeMiddlewareStack = async (idx = 0) => { if (frameworkErrorHandler.error) return; const middlewareFn = args.middlewareFns[idx]; middlewareResult.ctx = currentCtx; try { if (middlewareFn) { let nextCalled = false; await middlewareFn({ clientInput: mainClientInput, bindArgsClientInputs, ctx: currentCtx, metadata: args.metadata, next: async (nextOpts) => { if (chainCompleted) throw new Error("next() called after the middleware chain has already completed. Do not store and call next() asynchronously after the action has returned."); if (nextCalled) throw new Error("next() called multiple times in middleware. Each middleware must call next() at most once."); nextCalled = true; currentCtx = deepmerge(currentCtx, nextOpts?.ctx ?? {}); await executeMiddlewareStack(idx + 1); return middlewareResult; } }).catch((e) => { frameworkErrorHandler.handleError(e); if (frameworkErrorHandler.error) { middlewareResult.success = false; middlewareResult.navigationKind = FrameworkErrorHandler.getNavigationKind(frameworkErrorHandler.error); } }); } else { const validated = await validateInputs(mainClientInput, bindArgsClientInputs, currentCtx, middlewareResult); if (!validated) return; const { parsedMainInput, parsedBindArgsInputs } = validated; await executeValidatedMiddlewareStack(0, parsedMainInput, parsedBindArgsInputs); } } catch (e) { await handleExecutionError(e, mainClientInput, bindArgsClientInputs, currentCtx, middlewareResult, serverErrorHandled); } }; await executeMiddlewareStack(); chainCompleted = true; return buildResultAndRunCallbacks(middlewareResult, frameworkErrorHandler, mainClientInput, bindArgsClientInputs, currentCtx, utils); }; } }; } return { /** * Define the action. * @param serverCodeFn Code that will be executed on the **server side** * * {@link https://next-safe-action.dev/docs/define-actions/instance-methods#action--stateaction See docs for more information} */ action: buildAction({ withState: false }).action, /** * Define the stateful action. To be used with the [`useStateAction`](https://next-safe-action.dev/docs/execute-actions/hooks/usestateaction) hook. * @param serverCodeFn Code that will be executed on the **server side** * * {@link https://next-safe-action.dev/docs/define-actions/instance-methods#action--stateaction See docs for more information} */ stateAction: buildAction({ withState: true }).action }; } //#endregion //#region src/safe-action-client.ts var SafeActionClient = class SafeActionClient { #args; constructor(args) { this.#args = args; } /** * Use a middleware function. Middleware added via `use()` always runs **before** input validation. * Cannot be called after `useValidated()`. * @param middlewareFn Middleware function * * {@link https://next-safe-action.dev/docs/define-actions/instance-methods#use See docs for more information} */ use(middlewareFn) { if (this.#args.hasValidatedMiddleware) throw new Error("use() cannot be called after useValidated(). Move all use() calls before useValidated()."); return new SafeActionClient({ ...this.#args, middlewareFns: [...this.#args.middlewareFns, middlewareFn], ctxType: {}, preValidationCtxType: {} }); } /** * Use a validated middleware function. Middleware added via `useValidated()` runs **after** input * validation and receives typed `parsedInput` and `bindArgsParsedInputs`. Follows the onion model: * code before `next()` runs pre-action, code after `next()` runs post-action with access to the result. * * Requires `inputSchema()` or `bindArgsSchemas()` to be called before. After calling `useValidated()`, * `inputSchema()`, `bindArgsSchemas()`, and `use()` can no longer be called. * * @example * ```ts * authClient * .inputSchema(z.object({ postId: z.string() })) * .useValidated(async ({ parsedInput, ctx, next }) => { * const post = await db.post.findUnique({ where: { id: parsedInput.postId } }); * if (post?.authorId !== ctx.user.id) throw new Error("Forbidden"); * return next({ ctx: { post } }); * }) * ``` * * @param middlewareFn Validated middleware function * * {@link https://next-safe-action.dev/docs/define-actions/instance-methods#usevalidated See docs for more information} */ useValidated(middlewareFn) { return new SafeActionClient({ ...this.#args, validatedMiddlewareFns: [...this.#args.validatedMiddlewareFns, middlewareFn], ctxType: {}, preValidationCtxType: {}, hasValidatedMiddleware: true, handleValidationErrorsShape: this.#args.handleValidationErrorsShape }); } /** * Define metadata for the action. * @param data Metadata with the same type as the return value of the [`defineMetadataSchema`](https://next-safe-action.dev/docs/define-actions/create-the-client#definemetadataschema) optional initialization function * * {@link https://next-safe-action.dev/docs/define-actions/instance-methods#metadata See docs for more information} */ metadata(data) { return new SafeActionClient({ ...this.#args, metadata: data, metadataProvided: true }); } /** * Define the input validation schema for the action. * Cannot be called after `useValidated()`. * @param inputSchema Input validation schema * @param utils Optional utils object * * {@link https://next-safe-action.dev/docs/define-actions/create-the-client#inputschema See docs for more information} */ inputSchema(inputSchema, utils) { const isDirectStandardSchema = isStandardSchema(inputSchema); const isInputSchemaFactoryFn = !isDirectStandardSchema && typeof inputSchema === "function" && Object.prototype.toString.call(inputSchema) === "[object AsyncFunction]"; if (!isDirectStandardSchema && typeof inputSchema === "function" && !isInputSchemaFactoryFn) throw new TypeError("`inputSchema()` received a function that is not a Standard Schema validator. Pass a Standard Schema validator (`~standard.validate`) directly, or use an async function to build/extend the schema."); return new SafeActionClient({ ...this.#args, inputSchemaFn: isInputSchemaFactoryFn ? async (clientInput) => { return inputSchema(await this.#args.inputSchemaFn?.(clientInput), { clientInput }); } : async () => inputSchema, handleValidationErrorsShape: utils?.handleValidationErrorsShape ?? this.#args.handleValidationErrorsShape }); } /** * @deprecated Alias for `inputSchema` method. Use that instead. */ schema = this.inputSchema; /** * Define the bind args input validation schema for the action. * Cannot be called after `useValidated()`. * @param bindArgsSchemas Bind args input validation schemas * * {@link https://next-safe-action.dev/docs/define-actions/instance-methods#bindargsschemas See docs for more information} */ bindArgsSchemas(bindArgsSchemas) { return new SafeActionClient({ ...this.#args, bindArgsSchemas, handleValidationErrorsShape: this.#args.handleValidationErrorsShape }); } /** * Define the output data validation schema for the action. * @param schema Output data validation schema * * {@link https://next-safe-action.dev/docs/define-actions/create-the-client#outputschema See docs for more information} */ outputSchema(dataSchema) { return new SafeActionClient({ ...this.#args, outputSchema: dataSchema }); } /** * Define the action. * @param serverCodeFn Code that will be executed on the **server side** * @param [cb] Optional callbacks that will be called after action execution, on the server. * * {@link https://next-safe-action.dev/docs/define-actions/instance-methods#action--stateaction See docs for more information} */ action(serverCodeFn, utils) { return actionBuilder(this.#args).action(serverCodeFn, utils); } /** * Define the stateful action. * To be used with the [`useStateAction`](https://next-safe-action.dev/docs/execute-actions/hooks/usestateaction) hook. * @param serverCodeFn Code that will be executed on the **server side** * @param [cb] Optional callbacks that will be called after action execution, on the server. * * {@link https://next-safe-action.dev/docs/define-actions/instance-methods#action--stateaction See docs for more information} */ stateAction(serverCodeFn, utils) { return actionBuilder(this.#args).stateAction(serverCodeFn, utils); } }; //#endregion //#region src/middleware.ts /** * Creates a standalone middleware function. It accepts a generic object with optional `serverError`, `ctx` and `metadata` * properties, if you need one or all of them to be typed. The type for each property that is passed as generic is the * **minimum** shape required to define the middleware function, but it can also be larger than that. * * {@link https://next-safe-action.dev/docs/define-actions/middleware#create-standalone-middleware See docs for more information} */ const createMiddleware = () => { return { define: (middlewareFn) => middlewareFn }; }; /** * Creates a standalone validated middleware function. It accepts a generic object with optional `serverError`, `ctx`, * `metadata`, `parsedInput`, `clientInput`, `bindArgsParsedInputs`, and `bindArgsClientInputs` properties, if you need * one or all of them to be typed. The type for each property that is passed as generic is the **minimum** shape required * to define the validated middleware function, but it can also be larger than that. * * Validated middleware runs after input validation and receives typed parsed inputs. * * {@link https://next-safe-action.dev/docs/define-actions/middleware#create-standalone-validated-middleware See docs for more information} */ const createValidatedMiddleware = () => { return { define: (middlewareFn) => middlewareFn }; }; //#endregion //#region src/index.ts /** * Detect Next.js navigation/framework errors (redirect, notFound, forbidden, unauthorized, etc.) * that must be re-thrown to let the framework handle them. */ function isNavigationError(error) { return FrameworkErrorHandler.isNavigationError(error); } /** * Create a new safe action client. * Note: this client only works with Zod as the validation library. * @param createOpts Initialization options * * {@link https://next-safe-action.dev/docs/define-actions/create-the-client#initialization-options See docs for more information} */ const createSafeActionClient = (createOpts) => { return new SafeActionClient({ middlewareFns: [async ({ next }) => next({ ctx: {} })], validatedMiddlewareFns: [], handleServerError: createOpts?.handleServerError || ((e) => { console.error("Action error:", e.message); return "Something went wrong while executing the operation."; }), inputSchemaFn: void 0, bindArgsSchemas: [], outputSchema: void 0, ctxType: {}, preValidationCtxType: {}, metadataSchema: createOpts?.defineMetadataSchema?.() ?? void 0, metadata: void 0, defaultValidationErrorsShape: createOpts?.defaultValidationErrorsShape ?? "formatted", throwValidationErrors: createOpts?.throwValidationErrors ?? false, handleValidationErrorsShape: async (ve) => createOpts?.defaultValidationErrorsShape === "flattened" ? flattenValidationErrors(ve) : formatValidationErrors(ve) }); }; //#endregion export { ActionBindArgsValidationError, ActionMetadataValidationError, ActionOutputDataValidationError, ActionValidationError, DEFAULT_SERVER_ERROR_MESSAGE, createMiddleware, createSafeActionClient, createValidatedMiddleware, flattenValidationErrors, formatValidationErrors, isNavigationError, returnValidationErrors }; //# sourceMappingURL=index.mjs.map