diff --git a/apps/playground/src/lib/safe-action.ts b/apps/playground/src/lib/safe-action.ts index 83723938..f53569d5 100644 --- a/apps/playground/src/lib/safe-action.ts +++ b/apps/playground/src/lib/safe-action.ts @@ -2,11 +2,13 @@ import { DEFAULT_SERVER_ERROR_MESSAGE, createSafeActionClient, } from "next-safe-action"; +import { zodAdapter } from "next-safe-action/adapters/zod"; import { z } from "zod"; export class ActionError extends Error {} export const action = createSafeActionClient({ + validationAdapter: zodAdapter(), // You can provide a custom logging function, otherwise the lib will use `console.error` // as the default logging system. If you want to disable server errors logging, // just pass an empty Promise. diff --git a/packages/next-safe-action/package.json b/packages/next-safe-action/package.json index 335af90b..83b298d4 100644 --- a/packages/next-safe-action/package.json +++ b/packages/next-safe-action/package.json @@ -12,7 +12,8 @@ "exports": { ".": "./dist/index.mjs", "./hooks": "./dist/hooks.mjs", - "./stateful-hooks": "./dist/stateful-hooks.mjs" + "./stateful-hooks": "./dist/stateful-hooks.mjs", + "./adapters/*": "./dist/adapters/*.mjs" }, "typesVersions": { "*": { @@ -24,6 +25,9 @@ ], "stateful-hooks": [ "./dist/stateful-hooks.d.mts" + ], + "adapters/*": [ + "./dist/adapters/*.d.mts" ] } }, diff --git a/packages/next-safe-action/src/__tests__/action-callbacks.test.ts b/packages/next-safe-action/src/__tests__/action-callbacks.test.ts index c865af2e..84fb3d63 100644 --- a/packages/next-safe-action/src/__tests__/action-callbacks.test.ts +++ b/packages/next-safe-action/src/__tests__/action-callbacks.test.ts @@ -4,8 +4,10 @@ import assert from "node:assert"; import { test } from "node:test"; import { z } from "zod"; import { DEFAULT_SERVER_ERROR_MESSAGE, createSafeActionClient, returnValidationErrors } from ".."; +import { zodAdapter } from "../adapters/zod"; const ac = createSafeActionClient({ + validationAdapter: zodAdapter(), defineMetadataSchema() { return z.object({ actionName: z.string(), diff --git a/packages/next-safe-action/src/__tests__/bind-args-validation-errors.test.ts b/packages/next-safe-action/src/__tests__/bind-args-validation-errors.test.ts index 9ec1efa9..0fce6388 100644 --- a/packages/next-safe-action/src/__tests__/bind-args-validation-errors.test.ts +++ b/packages/next-safe-action/src/__tests__/bind-args-validation-errors.test.ts @@ -4,10 +4,13 @@ import assert from "node:assert"; import { test } from "node:test"; import { z } from "zod"; import { createSafeActionClient, flattenBindArgsValidationErrors, formatBindArgsValidationErrors } from ".."; +import { zodAdapter } from "../adapters/zod"; // Default client tests. -const dac = createSafeActionClient(); +const dac = createSafeActionClient({ + validationAdapter: zodAdapter(), +}); test("action with invalid bind args input gives back an object with correct `bindArgsValidationErrors` (default formatted shape)", async () => { const bindArgsSchemas: [age: z.ZodNumber, userId: z.ZodString, product: z.ZodObject<{ id: z.ZodString }>] = [ @@ -87,6 +90,7 @@ test("action with invalid bind args input gives back an object with correct `bin // Formatted shape tests (same as default). const foac = createSafeActionClient({ + validationAdapter: zodAdapter(), defaultValidationErrorsShape: "formatted", }); @@ -168,6 +172,7 @@ test("action with invalid bind args input gives back an object with correct `bin // Flattened shape tests. const flac = createSafeActionClient({ + validationAdapter: zodAdapter(), defaultValidationErrorsShape: "flattened", }); diff --git a/packages/next-safe-action/src/__tests__/combined-validation-errors.test.ts b/packages/next-safe-action/src/__tests__/combined-validation-errors.test.ts index d44c944d..1b7f3403 100644 --- a/packages/next-safe-action/src/__tests__/combined-validation-errors.test.ts +++ b/packages/next-safe-action/src/__tests__/combined-validation-errors.test.ts @@ -10,10 +10,13 @@ import { formatBindArgsValidationErrors, formatValidationErrors, } from ".."; +import { zodAdapter } from "../adapters/zod"; // Default client tests. -const dac = createSafeActionClient(); +const dac = createSafeActionClient({ + validationAdapter: zodAdapter(), +}); test("action with invalid bind args input and valid main input gives back an object with correct `bindArgsValidationErrors` (default formatted shape)", async () => { const schema = z.object({ @@ -110,6 +113,7 @@ test("action with invalid bind args input and invalid main input gives back an o // Formatted shape tests (same as default). const foac = createSafeActionClient({ + validationAdapter: zodAdapter(), defaultValidationErrorsShape: "formatted", }); @@ -209,6 +213,7 @@ test("action with invalid bind args input and valid main input gives back an obj // Flattened shape tests. const flac = createSafeActionClient({ + validationAdapter: zodAdapter(), defaultValidationErrorsShape: "flattened", }); diff --git a/packages/next-safe-action/src/__tests__/happy-path.test.ts b/packages/next-safe-action/src/__tests__/happy-path.test.ts index 32f207b6..c3f368bd 100644 --- a/packages/next-safe-action/src/__tests__/happy-path.test.ts +++ b/packages/next-safe-action/src/__tests__/happy-path.test.ts @@ -4,8 +4,11 @@ import assert from "node:assert"; import { test } from "node:test"; import { z } from "zod"; import { createSafeActionClient } from ".."; +import { zodAdapter } from "../adapters/zod"; -const ac = createSafeActionClient(); +const ac = createSafeActionClient({ + validationAdapter: zodAdapter(), +}); test("action with no input schema returns empty object", async () => { const action = ac.action(async () => { diff --git a/packages/next-safe-action/src/__tests__/metadata.test.ts b/packages/next-safe-action/src/__tests__/metadata.test.ts index 554f86e4..a9f6fbea 100644 --- a/packages/next-safe-action/src/__tests__/metadata.test.ts +++ b/packages/next-safe-action/src/__tests__/metadata.test.ts @@ -4,8 +4,10 @@ import assert from "node:assert"; import { test } from "node:test"; import { z } from "zod"; import { DEFAULT_SERVER_ERROR_MESSAGE, createSafeActionClient } from ".."; +import { zodAdapter } from "../adapters/zod"; const ac = createSafeActionClient({ + validationAdapter: zodAdapter(), handleServerErrorLog() {}, // disable server errors logging for these tests defineMetadataSchema() { return z.object({ diff --git a/packages/next-safe-action/src/__tests__/middleware.test.ts b/packages/next-safe-action/src/__tests__/middleware.test.ts index c7cac88e..60ba0ae5 100644 --- a/packages/next-safe-action/src/__tests__/middleware.test.ts +++ b/packages/next-safe-action/src/__tests__/middleware.test.ts @@ -9,8 +9,10 @@ import { formatValidationErrors, returnValidationErrors, } from ".."; +import { zodAdapter } from "../adapters/zod"; const ac = createSafeActionClient({ + validationAdapter: zodAdapter(), handleServerErrorLog() {}, // disable server errors logging for these tests handleReturnedServerError(e) { return { @@ -292,6 +294,7 @@ test("server validation errors in execution result from middleware are correct", // Flattened validation errors shape. const flac = createSafeActionClient({ + validationAdapter: zodAdapter(), handleServerErrorLog() {}, // disable server errors logging for these tests defaultValidationErrorsShape: "flattened", }); diff --git a/packages/next-safe-action/src/__tests__/server-error.test.ts b/packages/next-safe-action/src/__tests__/server-error.test.ts index 25e0306d..bdc79f8c 100644 --- a/packages/next-safe-action/src/__tests__/server-error.test.ts +++ b/packages/next-safe-action/src/__tests__/server-error.test.ts @@ -3,6 +3,7 @@ import assert from "node:assert"; import { test } from "node:test"; import { DEFAULT_SERVER_ERROR_MESSAGE, createSafeActionClient } from ".."; +import { zodAdapter } from "../adapters/zod"; class ActionError extends Error { constructor(message: string) { @@ -11,6 +12,7 @@ class ActionError extends Error { } const ac1 = createSafeActionClient({ + validationAdapter: zodAdapter(), handleServerErrorLog: () => {}, // disable server errors logging for these tests handleReturnedServerError(e) { if (e instanceof ActionError) { @@ -93,6 +95,7 @@ test("known error occurred in middleware function is unmasked", async () => { // Server error is an object with a 'message' property. const ac2 = createSafeActionClient({ + validationAdapter: zodAdapter(), handleServerErrorLog: () => {}, // disable server errors logging for these tests handleReturnedServerError(e) { return { @@ -138,6 +141,7 @@ test("error occurred in middleware function has the correct shape defined by `ha // Rethrow all server errors. const ac3 = createSafeActionClient({ + validationAdapter: zodAdapter(), handleServerErrorLog: () => {}, // disable server errors logging for these tests handleReturnedServerError(e) { throw e; diff --git a/packages/next-safe-action/src/__tests__/validation-errors.test.ts b/packages/next-safe-action/src/__tests__/validation-errors.test.ts index 2910fd86..25e83adf 100644 --- a/packages/next-safe-action/src/__tests__/validation-errors.test.ts +++ b/packages/next-safe-action/src/__tests__/validation-errors.test.ts @@ -4,10 +4,13 @@ import assert from "node:assert"; import { test } from "node:test"; import { z } from "zod"; import { createSafeActionClient, flattenValidationErrors, formatValidationErrors, returnValidationErrors } from ".."; +import { zodAdapter } from "../adapters/zod"; // Default client tests. -const dac = createSafeActionClient(); +const dac = createSafeActionClient({ + validationAdapter: zodAdapter(), +}); test("action with invalid input gives back an object with correct `validationErrors` (default formatted shape)", async () => { const schema = z.object({ @@ -144,6 +147,7 @@ test("action with invalid input gives back an object with correct `validationErr // Formatted shape tests (same as default). const foac = createSafeActionClient({ + validationAdapter: zodAdapter(), defaultValidationErrorsShape: "formatted", }); @@ -282,6 +286,7 @@ test("action with invalid input gives back an object with correct `validationErr // Flattened shape tests. const flac = createSafeActionClient({ + validationAdapter: zodAdapter(), defaultValidationErrorsShape: "flattened", }); @@ -537,6 +542,7 @@ test("action with errors set via `returnValidationErrors` gives back an object w // `throwValidationErrors` tests. const tveac = createSafeActionClient({ + validationAdapter: zodAdapter(), throwValidationErrors: true, }); diff --git a/packages/next-safe-action/src/action-builder.ts b/packages/next-safe-action/src/action-builder.ts index 407e1bbb..113c76d0 100644 --- a/packages/next-safe-action/src/action-builder.ts +++ b/packages/next-safe-action/src/action-builder.ts @@ -1,6 +1,7 @@ import { isNotFoundError } from "next/dist/client/components/not-found.js"; import { isRedirectError } from "next/dist/client/components/redirect.js"; import type {} from "zod"; +import type { Infer, InferArray, InferIn, InferInArray, Schema, ValidationAdapter } from "./adapters/types"; import type { MiddlewareFn, MiddlewareResult, @@ -13,7 +14,6 @@ import type { StateServerCodeFn, } from "./index.types"; import { ActionMetadataError, DEFAULT_SERVER_ERROR_MESSAGE, isError } from "./utils"; -import type { Infer, InferArray, InferIn, InferInArray, Schema, ValidationAdapter } from "./validation-adapters"; import { ActionValidationError, buildValidationErrors } from "./validation-errors"; import type { BindArgsValidationErrors, diff --git a/packages/next-safe-action/src/validation-adapters/types.ts b/packages/next-safe-action/src/adapters/types.ts similarity index 100% rename from packages/next-safe-action/src/validation-adapters/types.ts rename to packages/next-safe-action/src/adapters/types.ts diff --git a/packages/next-safe-action/src/validation-adapters/libs/zod.ts b/packages/next-safe-action/src/adapters/zod.ts similarity index 89% rename from packages/next-safe-action/src/validation-adapters/libs/zod.ts rename to packages/next-safe-action/src/adapters/zod.ts index e0103ba1..64197708 100644 --- a/packages/next-safe-action/src/validation-adapters/libs/zod.ts +++ b/packages/next-safe-action/src/adapters/zod.ts @@ -1,5 +1,5 @@ import type { z } from "zod"; -import type { Infer, ValidationAdapter } from "../types"; +import type { Infer, ValidationAdapter } from "./types"; class ZodAdapter implements ValidationAdapter { async validate(schema: S, data: unknown) { diff --git a/packages/next-safe-action/src/hooks-utils.ts b/packages/next-safe-action/src/hooks-utils.ts index 25c83460..c1190ab0 100644 --- a/packages/next-safe-action/src/hooks-utils.ts +++ b/packages/next-safe-action/src/hooks-utils.ts @@ -1,8 +1,8 @@ import * as React from "react"; import {} from "react/experimental"; import type {} from "zod"; +import type { InferIn, Schema } from "./adapters/types"; import type { HookActionStatus, HookCallbacks, HookResult } from "./hooks.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 60f42743..bfef56e6 100644 --- a/packages/next-safe-action/src/hooks.ts +++ b/packages/next-safe-action/src/hooks.ts @@ -6,10 +6,10 @@ import * as React from "react"; import * as ReactDOM from "react-dom"; import {} from "react/experimental"; import type {} from "zod"; +import type { InferIn, Schema } from "./adapters/types"; import { getActionShorthandStatusObject, getActionStatus, useActionCallbacks } from "./hooks-utils"; import type { HookCallbacks, HookResult, HookSafeActionFn } from "./hooks.types"; import { isError } from "./utils"; -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 549c3a36..f285d5d5 100644 --- a/packages/next-safe-action/src/hooks.types.ts +++ b/packages/next-safe-action/src/hooks.types.ts @@ -1,6 +1,6 @@ +import type { InferIn, Schema } from "./adapters/types"; import type { SafeActionResult } from "./index.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 ea431d5b..1b777c18 100644 --- a/packages/next-safe-action/src/index.ts +++ b/packages/next-safe-action/src/index.ts @@ -1,7 +1,7 @@ +import type { Infer, Schema } from "./adapters/types"; 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 "./validation-adapters"; import { flattenBindArgsValidationErrors, flattenValidationErrors, @@ -25,7 +25,7 @@ export type * from "./validation-errors.types"; /** * Create a new safe action client. * Note: this client only works with Zod as the validation library. - * @param createOpts Optional initialization options + * @param createOpts Initialization options * * {@link https://next-safe-action.dev/docs/safe-action-client/initialization-options See docs for more information} */ @@ -34,12 +34,12 @@ export const createSafeActionClient = < ServerError = string, MetadataSchema extends Schema | undefined = undefined, >( - createOpts?: SafeActionClientOpts + createOpts: SafeActionClientOpts ) => { // If server log function is not provided, default to `console.error` for logging // server error messages. const handleServerErrorLog = - createOpts?.handleServerErrorLog || + createOpts.handleServerErrorLog || (((originalError: Error) => { console.error("Action error:", originalError.message); }) as unknown as NonNullable["handleServerErrorLog"]>); @@ -48,32 +48,27 @@ export const createSafeActionClient = < // messages returned on the client. // Otherwise mask the error and use a generic message. const handleReturnedServerError = - createOpts?.handleReturnedServerError || + createOpts.handleReturnedServerError || ((() => DEFAULT_SERVER_ERROR_MESSAGE) as unknown as NonNullable< SafeActionClientOpts["handleReturnedServerError"] >); - // FIXME: require validation adapter - if (!createOpts?.validationAdapter) { - throw new Error("Validation adapter is required"); - } - return new SafeActionClient({ middlewareFns: [async ({ next }) => next({ ctx: undefined })], handleServerErrorLog, handleReturnedServerError, schemaFn: undefined, bindArgsSchemas: [], - validationAdapter: createOpts.validationAdapter(), + validationAdapter: createOpts.validationAdapter, ctxType: undefined, - metadataSchema: (createOpts?.defineMetadataSchema?.() ?? undefined) as MetadataSchema, + metadataSchema: (createOpts.defineMetadataSchema?.() ?? undefined) as MetadataSchema, metadata: undefined as MetadataSchema extends Schema ? Infer : undefined, - defaultValidationErrorsShape: (createOpts?.defaultValidationErrorsShape ?? "formatted") as ODVES, - throwValidationErrors: Boolean(createOpts?.throwValidationErrors), + defaultValidationErrorsShape: (createOpts.defaultValidationErrorsShape ?? "formatted") as ODVES, + throwValidationErrors: Boolean(createOpts.throwValidationErrors), handleValidationErrorsShape: - createOpts?.defaultValidationErrorsShape === "flattened" ? flattenValidationErrors : formatValidationErrors, + createOpts.defaultValidationErrorsShape === "flattened" ? flattenValidationErrors : formatValidationErrors, handleBindArgsValidationErrorsShape: - createOpts?.defaultValidationErrorsShape === "flattened" + createOpts.defaultValidationErrorsShape === "flattened" ? flattenBindArgsValidationErrors : formatBindArgsValidationErrors, }); diff --git a/packages/next-safe-action/src/index.types.ts b/packages/next-safe-action/src/index.types.ts index 5286d2ac..ce0fb6b0 100644 --- a/packages/next-safe-action/src/index.types.ts +++ b/packages/next-safe-action/src/index.types.ts @@ -1,5 +1,5 @@ +import type { Infer, InferArray, InferIn, InferInArray, Schema, ValidationAdapter } from "./adapters/types"; import type { MaybePromise, Prettify } from "./utils.types"; -import type { Infer, InferArray, InferIn, InferInArray, Schema, ValidationAdapter } from "./validation-adapters"; import type { BindArgsValidationErrors, ValidationErrors } from "./validation-errors.types"; /** @@ -26,7 +26,7 @@ export type SafeActionClientOpts< MetadataSchema extends Schema | undefined, ODVES extends DVES | undefined, > = { - validationAdapter: () => ValidationAdapter; + validationAdapter: ValidationAdapter; defineMetadataSchema?: () => MetadataSchema; handleReturnedServerError?: ( error: Error, diff --git a/packages/next-safe-action/src/safe-action-client.ts b/packages/next-safe-action/src/safe-action-client.ts index b1fb82af..f0938437 100644 --- a/packages/next-safe-action/src/safe-action-client.ts +++ b/packages/next-safe-action/src/safe-action-client.ts @@ -1,5 +1,6 @@ import type {} from "zod"; import { actionBuilder } from "./action-builder"; +import type { Infer, Schema, ValidationAdapter } from "./adapters/types"; import type { DVES, MiddlewareFn, @@ -8,7 +9,6 @@ import type { ServerCodeFn, StateServerCodeFn, } from "./index.types"; -import type { Infer, Schema, ValidationAdapter } 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 b020b9d5..7cc27398 100644 --- a/packages/next-safe-action/src/stateful-hooks.ts +++ b/packages/next-safe-action/src/stateful-hooks.ts @@ -4,9 +4,9 @@ import * as React from "react"; import * as ReactDOM from "react-dom"; import {} from "react/experimental"; import type {} from "zod"; +import type { InferIn, Schema } from "./adapters/types"; import { getActionShorthandStatusObject, getActionStatus, useActionCallbacks } from "./hooks-utils"; import type { HookCallbacks, HookSafeStateActionFn } from "./hooks.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/validation-adapters/index.ts b/packages/next-safe-action/src/validation-adapters/index.ts deleted file mode 100644 index eade091c..00000000 --- a/packages/next-safe-action/src/validation-adapters/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { zodAdapter } from "./libs/zod"; - -export * from "./types"; diff --git a/packages/next-safe-action/src/validation-errors.ts b/packages/next-safe-action/src/validation-errors.ts index 6c134b64..2252cc18 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 "./validation-adapters"; +import type { Schema } from "./adapters/types"; 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 9c92571d..9288800f 100644 --- a/packages/next-safe-action/src/validation-errors.types.ts +++ b/packages/next-safe-action/src/validation-errors.types.ts @@ -1,5 +1,5 @@ +import type { Infer, Schema } from "./adapters/types"; import type { Prettify } from "./utils.types"; -import type { Infer, Schema } from "./validation-adapters"; export type ValidationIssue = { message: string; diff --git a/packages/next-safe-action/tsup.config.ts b/packages/next-safe-action/tsup.config.ts index 006872e9..cf5c4d13 100644 --- a/packages/next-safe-action/tsup.config.ts +++ b/packages/next-safe-action/tsup.config.ts @@ -1,7 +1,7 @@ import { defineConfig } from "tsup"; export default defineConfig({ - entry: ["src/*.ts", "src/validation-adapters/**/*.ts"], + entry: ["src/*.ts", "src/adapters/*.ts"], bundle: false, format: ["esm"], clean: true,