Skip to content

Commit

Permalink
fix: implementation of modular built-in multi validation system
Browse files Browse the repository at this point in the history
  • Loading branch information
TheEdoRan committed Jul 18, 2024
1 parent 02d3caf commit 38496f7
Show file tree
Hide file tree
Showing 24 changed files with 64 additions and 36 deletions.
2 changes: 2 additions & 0 deletions apps/playground/src/lib/safe-action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
6 changes: 5 additions & 1 deletion packages/next-safe-action/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
"*": {
Expand All @@ -24,6 +25,9 @@
],
"stateful-hooks": [
"./dist/stateful-hooks.d.mts"
],
"adapters/*": [
"./dist/adapters/*.d.mts"
]
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 }>] = [
Expand Down Expand Up @@ -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",
});

Expand Down Expand Up @@ -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",
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down Expand Up @@ -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",
});

Expand Down Expand Up @@ -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",
});

Expand Down
5 changes: 4 additions & 1 deletion packages/next-safe-action/src/__tests__/happy-path.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down
2 changes: 2 additions & 0 deletions packages/next-safe-action/src/__tests__/metadata.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
3 changes: 3 additions & 0 deletions packages/next-safe-action/src/__tests__/middleware.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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",
});
Expand Down
4 changes: 4 additions & 0 deletions packages/next-safe-action/src/__tests__/server-error.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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) {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down Expand Up @@ -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",
});

Expand Down Expand Up @@ -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",
});

Expand Down Expand Up @@ -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,
});

Expand Down
2 changes: 1 addition & 1 deletion packages/next-safe-action/src/action-builder.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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<S extends z.ZodType>(schema: S, data: unknown) {
Expand Down
2 changes: 1 addition & 1 deletion packages/next-safe-action/src/hooks-utils.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
2 changes: 1 addition & 1 deletion packages/next-safe-action/src/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion packages/next-safe-action/src/hooks.types.ts
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
27 changes: 11 additions & 16 deletions packages/next-safe-action/src/index.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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}
*/
Expand All @@ -34,12 +34,12 @@ export const createSafeActionClient = <
ServerError = string,
MetadataSchema extends Schema | undefined = undefined,
>(
createOpts?: SafeActionClientOpts<ServerError, MetadataSchema, ODVES>
createOpts: SafeActionClientOpts<ServerError, MetadataSchema, ODVES>
) => {
// 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<SafeActionClientOpts<ServerError, MetadataSchema, ODVES>["handleServerErrorLog"]>);
Expand All @@ -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<ServerError, MetadataSchema, ODVES>["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<MetadataSchema> : 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,
});
Expand Down
4 changes: 2 additions & 2 deletions packages/next-safe-action/src/index.types.ts
Original file line number Diff line number Diff line change
@@ -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";

/**
Expand All @@ -26,7 +26,7 @@ export type SafeActionClientOpts<
MetadataSchema extends Schema | undefined,
ODVES extends DVES | undefined,
> = {
validationAdapter: () => ValidationAdapter;
validationAdapter: ValidationAdapter;
defineMetadataSchema?: () => MetadataSchema;
handleReturnedServerError?: (
error: Error,
Expand Down
2 changes: 1 addition & 1 deletion packages/next-safe-action/src/safe-action-client.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -8,7 +9,6 @@ import type {
ServerCodeFn,
StateServerCodeFn,
} from "./index.types";
import type { Infer, Schema, ValidationAdapter } from "./validation-adapters";
import type {
BindArgsValidationErrors,
FlattenedBindArgsValidationErrors,
Expand Down
2 changes: 1 addition & 1 deletion packages/next-safe-action/src/stateful-hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 0 additions & 3 deletions packages/next-safe-action/src/validation-adapters/index.ts

This file was deleted.

Loading

0 comments on commit 38496f7

Please sign in to comment.