From 10143bb58f25a6f45e65b42191212307cf4b8284 Mon Sep 17 00:00:00 2001 From: Edoardo Ranghieri Date: Tue, 16 Jul 2024 10:09:04 +0200 Subject: [PATCH] refactor: set up built-in multi validation system --- packages/next-safe-action/package.json | 5 ++++ .../next-safe-action/src/action-builder.ts | 2 +- packages/next-safe-action/src/hooks-utils.ts | 2 +- packages/next-safe-action/src/hooks.ts | 2 +- packages/next-safe-action/src/hooks.types.ts | 3 ++- packages/next-safe-action/src/index.ts | 2 +- packages/next-safe-action/src/index.types.ts | 3 ++- .../src/safe-action-client.ts | 2 +- .../next-safe-action/src/stateful-hooks.ts | 2 +- packages/next-safe-action/src/utils.ts | 2 +- packages/next-safe-action/src/utils.types.ts | 21 ---------------- .../src/validation-adapters/index.ts | 3 +++ .../src/validation-adapters/libs/zod.ts | 24 +++++++++++++++++++ .../src/validation-adapters/types.ts | 23 ++++++++++++++++++ .../next-safe-action/src/validation-errors.ts | 2 +- .../src/validation-errors.types.ts | 3 ++- 16 files changed, 69 insertions(+), 32 deletions(-) create mode 100644 packages/next-safe-action/src/validation-adapters/index.ts create mode 100644 packages/next-safe-action/src/validation-adapters/libs/zod.ts create mode 100644 packages/next-safe-action/src/validation-adapters/types.ts diff --git a/packages/next-safe-action/package.json b/packages/next-safe-action/package.json index cc0a04d2..335af90b 100644 --- a/packages/next-safe-action/package.json +++ b/packages/next-safe-action/package.json @@ -87,6 +87,11 @@ "react-dom": ">= 18.2.0", "zod": ">= 3.0.0" }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + }, "repository": { "type": "git", "url": "https://github.com/TheEdoRan/next-safe-action.git" diff --git a/packages/next-safe-action/src/action-builder.ts b/packages/next-safe-action/src/action-builder.ts index 22397b29..a348ba77 100644 --- a/packages/next-safe-action/src/action-builder.ts +++ b/packages/next-safe-action/src/action-builder.ts @@ -13,7 +13,7 @@ import type { StateServerCodeFn, } from "./index.types"; import { ActionMetadataError, DEFAULT_SERVER_ERROR_MESSAGE, isError, zodValidate } from "./utils"; -import type { Infer, InferArray, InferIn, InferInArray, Schema } from "./utils.types"; +import type { Infer, InferArray, InferIn, InferInArray, Schema } from "./validation-adapters"; import { ActionValidationError, buildValidationErrors } from "./validation-errors"; import type { BindArgsValidationErrors, diff --git a/packages/next-safe-action/src/hooks-utils.ts b/packages/next-safe-action/src/hooks-utils.ts index 0212f97a..25c83460 100644 --- a/packages/next-safe-action/src/hooks-utils.ts +++ b/packages/next-safe-action/src/hooks-utils.ts @@ -2,7 +2,7 @@ import * as React from "react"; import {} from "react/experimental"; import type {} from "zod"; import type { HookActionStatus, HookCallbacks, HookResult } from "./hooks.types"; -import type { InferIn, Schema } from "./utils.types"; +import type { InferIn, Schema } from "./validation-adapters"; export const getActionStatus = < ServerError, diff --git a/packages/next-safe-action/src/hooks.ts b/packages/next-safe-action/src/hooks.ts index ce7e0d6a..60f42743 100644 --- a/packages/next-safe-action/src/hooks.ts +++ b/packages/next-safe-action/src/hooks.ts @@ -9,7 +9,7 @@ import type {} from "zod"; import { getActionShorthandStatusObject, getActionStatus, useActionCallbacks } from "./hooks-utils"; import type { HookCallbacks, HookResult, HookSafeActionFn } from "./hooks.types"; import { isError } from "./utils"; -import type { InferIn, Schema } from "./utils.types"; +import type { InferIn, Schema } from "./validation-adapters"; // HOOKS diff --git a/packages/next-safe-action/src/hooks.types.ts b/packages/next-safe-action/src/hooks.types.ts index 111c4728..549c3a36 100644 --- a/packages/next-safe-action/src/hooks.types.ts +++ b/packages/next-safe-action/src/hooks.types.ts @@ -1,5 +1,6 @@ import type { SafeActionResult } from "./index.types"; -import type { InferIn, MaybePromise, Prettify, Schema } from "./utils.types"; +import type { MaybePromise, Prettify } from "./utils.types"; +import type { InferIn, Schema } from "./validation-adapters"; /** * Type of `result` object returned by `useAction`, `useOptimisticAction` and `useStateAction` hooks. diff --git a/packages/next-safe-action/src/index.ts b/packages/next-safe-action/src/index.ts index a8d7c492..21ec2b04 100644 --- a/packages/next-safe-action/src/index.ts +++ b/packages/next-safe-action/src/index.ts @@ -1,7 +1,7 @@ import type { DVES, SafeActionClientOpts } from "./index.types"; import { SafeActionClient } from "./safe-action-client"; import { DEFAULT_SERVER_ERROR_MESSAGE } from "./utils"; -import type { Infer, Schema } from "./utils.types"; +import type { Infer, Schema } from "./validation-adapters"; import { flattenBindArgsValidationErrors, flattenValidationErrors, diff --git a/packages/next-safe-action/src/index.types.ts b/packages/next-safe-action/src/index.types.ts index d1197bec..8d713740 100644 --- a/packages/next-safe-action/src/index.types.ts +++ b/packages/next-safe-action/src/index.types.ts @@ -1,4 +1,5 @@ -import type { Infer, InferArray, InferIn, InferInArray, MaybePromise, Prettify, Schema } from "./utils.types"; +import type { MaybePromise, Prettify } from "./utils.types"; +import type { Infer, InferArray, InferIn, InferInArray, Schema } from "./validation-adapters"; import type { BindArgsValidationErrors, ValidationErrors } from "./validation-errors.types"; /** diff --git a/packages/next-safe-action/src/safe-action-client.ts b/packages/next-safe-action/src/safe-action-client.ts index ec7f1ccd..1dcd2543 100644 --- a/packages/next-safe-action/src/safe-action-client.ts +++ b/packages/next-safe-action/src/safe-action-client.ts @@ -8,7 +8,7 @@ import type { ServerCodeFn, StateServerCodeFn, } from "./index.types"; -import type { Infer, Schema } from "./utils.types"; +import type { Infer, Schema } from "./validation-adapters"; import type { BindArgsValidationErrors, FlattenedBindArgsValidationErrors, diff --git a/packages/next-safe-action/src/stateful-hooks.ts b/packages/next-safe-action/src/stateful-hooks.ts index 360460f5..b020b9d5 100644 --- a/packages/next-safe-action/src/stateful-hooks.ts +++ b/packages/next-safe-action/src/stateful-hooks.ts @@ -6,7 +6,7 @@ import {} from "react/experimental"; import type {} from "zod"; import { getActionShorthandStatusObject, getActionStatus, useActionCallbacks } from "./hooks-utils"; import type { HookCallbacks, HookSafeStateActionFn } from "./hooks.types"; -import type { InferIn, Schema } from "./utils.types"; +import type { InferIn, Schema } from "./validation-adapters"; /** * Use the stateful action from a Client Component via hook. Used for actions defined with [`stateAction`](https://next-safe-action.dev/docs/safe-action-client/instance-methods#action--stateaction). * @param safeActionFn The action function diff --git a/packages/next-safe-action/src/utils.ts b/packages/next-safe-action/src/utils.ts index df2f6713..3071d5fa 100644 --- a/packages/next-safe-action/src/utils.ts +++ b/packages/next-safe-action/src/utils.ts @@ -1,4 +1,4 @@ -import type { Infer, Schema } from "./utils.types"; +import type { Infer, Schema } from "./validation-adapters"; export const DEFAULT_SERVER_ERROR_MESSAGE = "Something went wrong while executing the operation."; diff --git a/packages/next-safe-action/src/utils.types.ts b/packages/next-safe-action/src/utils.types.ts index 1a95eaf2..3946e98c 100644 --- a/packages/next-safe-action/src/utils.types.ts +++ b/packages/next-safe-action/src/utils.types.ts @@ -1,5 +1,3 @@ -import type { z } from "zod"; - // Takes an object type and makes it more readable. export type Prettify = { [K in keyof T]: T[K]; @@ -7,22 +5,3 @@ export type Prettify = { // Returns type or promise of type. export type MaybePromise = Promise | T; - -// Schema type. -export type Schema = z.ZodType; - -// Infers output schema type. -export type Infer = z.infer; - -// Infers input schema type. -export type InferIn = z.input; - -// Infers output schema type in array of schemas. -export type InferArray = { - [K in keyof BAS]: Infer; -}; - -// Infers input schema type in array of schemas. -export type InferInArray = { - [K in keyof BAS]: InferIn; -}; diff --git a/packages/next-safe-action/src/validation-adapters/index.ts b/packages/next-safe-action/src/validation-adapters/index.ts new file mode 100644 index 00000000..eade091c --- /dev/null +++ b/packages/next-safe-action/src/validation-adapters/index.ts @@ -0,0 +1,3 @@ +export { zodAdapter } from "./libs/zod"; + +export * from "./types"; diff --git a/packages/next-safe-action/src/validation-adapters/libs/zod.ts b/packages/next-safe-action/src/validation-adapters/libs/zod.ts new file mode 100644 index 00000000..df74c8f9 --- /dev/null +++ b/packages/next-safe-action/src/validation-adapters/libs/zod.ts @@ -0,0 +1,24 @@ +import type { z } from "zod"; +import type { Infer, ValidationAdapter } from "../types"; + +class ZodAdapter implements ValidationAdapter { + async validate(schema: S, data: unknown) { + const result = await schema.safeParseAsync(data); + + if (result.success) { + return { + success: true, + data: result.data as Infer, + } as const; + } + + return { + success: false, + issues: result.error.issues.map(({ message, path }) => ({ message, path })), + } as const; + } +} + +export function zodAdapter() { + return new ZodAdapter(); +} diff --git a/packages/next-safe-action/src/validation-adapters/types.ts b/packages/next-safe-action/src/validation-adapters/types.ts new file mode 100644 index 00000000..b4280eb7 --- /dev/null +++ b/packages/next-safe-action/src/validation-adapters/types.ts @@ -0,0 +1,23 @@ +import type { z } from "zod"; + +export type Schema = z.ZodType; +export type Infer = S extends z.ZodType ? z.infer : never; +export type InferIn = S extends z.ZodType ? z.input : never; +export type InferArray = { + [K in keyof BAS]: Infer; +}; +export type InferInArray = { + [K in keyof BAS]: InferIn; +}; + +export type ValidationIssue = { + message: string; + path?: Array; +}; + +export interface ValidationAdapter { + validate( + schema: S, + data: unknown + ): Promise<{ success: true; data: Infer } | { success: false; issues: ValidationIssue[] }>; +} diff --git a/packages/next-safe-action/src/validation-errors.ts b/packages/next-safe-action/src/validation-errors.ts index afdec433..6c134b64 100644 --- a/packages/next-safe-action/src/validation-errors.ts +++ b/packages/next-safe-action/src/validation-errors.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment */ -import type { Schema } from "./utils.types"; +import type { Schema } from "./validation-adapters"; import type { FlattenedBindArgsValidationErrors, FlattenedValidationErrors, diff --git a/packages/next-safe-action/src/validation-errors.types.ts b/packages/next-safe-action/src/validation-errors.types.ts index e84d354a..9c92571d 100644 --- a/packages/next-safe-action/src/validation-errors.types.ts +++ b/packages/next-safe-action/src/validation-errors.types.ts @@ -1,4 +1,5 @@ -import type { Infer, Prettify, Schema } from "./utils.types"; +import type { Prettify } from "./utils.types"; +import type { Infer, Schema } from "./validation-adapters"; export type ValidationIssue = { message: string;