Skip to content

Commit

Permalink
feat(action-client): export a copy of safe action client from /zod
Browse files Browse the repository at this point in the history
…path (#115)

Sometimes, TypeSchema causes errors with its dynamic import system, to handle multiple validation libraries.
The code in this PR exports a copy of the safe action client from the `/zod` path, that support just Zod validation library, as the name implies. This should fix problems with the edge runtime and hopefully future bundler issues too.
  • Loading branch information
TheEdoRan committed May 7, 2024
1 parent efb6b35 commit 20a2ef5
Show file tree
Hide file tree
Showing 13 changed files with 338 additions and 271 deletions.
5 changes: 2 additions & 3 deletions apps/playground/src/lib/safe-action.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { randomUUID } from "crypto";
import {
DEFAULT_SERVER_ERROR_MESSAGE,
createSafeActionClient,
Expand Down Expand Up @@ -58,13 +57,13 @@ export const action = createSafeActionClient({
});

async function getSessionId() {
return randomUUID();
return crypto.randomUUID();
}

export const authAction = action
// In this case, context is used for (fake) auth purposes.
.use(async ({ next }) => {
const userId = randomUUID();
const userId = crypto.randomUUID();

console.log("HELLO FROM FIRST AUTH ACTION MIDDLEWARE, USER ID:", userId);

Expand Down
20 changes: 16 additions & 4 deletions packages/next-safe-action/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
],
"exports": {
".": "./dist/index.mjs",
"./zod": "./dist/zod.mjs",
"./hooks": "./dist/hooks.mjs",
"./status": "./dist/status.mjs"
},
Expand All @@ -19,6 +20,9 @@
".": [
"./dist/index.d.mts"
],
"zod": [
"./dist/zod.d.mts"
],
"hooks": [
"./dist/hooks.d.mts"
],
Expand Down Expand Up @@ -65,7 +69,6 @@
"@types/node": "^20.12.10",
"@types/react": "^18.3.1",
"@typeschema/core": "^0.13.2",
"@typeschema/zod": "^0.13.3",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-define-config": "^2.1.0",
Expand All @@ -76,12 +79,21 @@
"semantic-release": "^23.0.8",
"tsup": "^8.0.2",
"typescript": "^5.4.5",
"typescript-eslint": "^7.8.0",
"zod": "^3.23.6"
"typescript-eslint": "^7.8.0"
},
"peerDependencies": {
"next": ">= 14.3.0-canary.42",
"react": ">= 18.3.1"
"react": ">= 18.3.1",
"@typeschema/zod": "^0.13.3",
"zod": "^3.23.6"
},
"peerDependenciesMeta": {
"zod": {
"optional": true
},
"@typeschema/zod": {
"optional": true
}
},
"repository": {
"type": "git",
Expand Down
13 changes: 9 additions & 4 deletions packages/next-safe-action/src/action-builder.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Infer } from "@typeschema/main";
import { validate, type Schema } from "@typeschema/main";
import { validate, type Infer, type Schema } from "@typeschema/main";
import { validate as zodValidate } from "@typeschema/zod";
import { isNotFoundError } from "next/dist/client/components/not-found.js";
import { isRedirectError } from "next/dist/client/components/redirect.js";
import type {} from "zod";
Expand Down Expand Up @@ -41,6 +41,7 @@ export function actionBuilder<
handleReturnedServerError: NonNullable<SafeActionClientOpts<ServerError, any>["handleReturnedServerError"]>;
middlewareFns: MiddlewareFn<ServerError, any, any, MD>[];
ctxType: Ctx;
validationStrategy: "typeschema" | "zod";
}) {
const bindArgsSchemas = (args.bindArgsSchemas ?? []) as BAS;

Expand Down Expand Up @@ -116,11 +117,15 @@ export function actionBuilder<
}

// Otherwise, parse input with the schema.
return validate(args.schema, input);
return args.validationStrategy === "zod"
? zodValidate(args.schema, input)
: validate(args.schema, input);
}

// Otherwise, we're processing bind args client inputs.
return validate(bindArgsSchemas[i]!, input);
return args.validationStrategy === "zod"
? zodValidate(bindArgsSchemas[i]!, input)
: validate(bindArgsSchemas[i]!, input);
})
);

Expand Down
17 changes: 16 additions & 1 deletion packages/next-safe-action/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
export { createSafeActionClient } from "./safe-action-client";
import type { Schema } from "@typeschema/main";
import type { SafeActionClientOpts } from "./index.types";
import { createClientWithStrategy } from "./safe-action-client";

export { DEFAULT_SERVER_ERROR_MESSAGE, EMPTY_HOOK_RESULT as EMPTY_RESULT } from "./utils";
export { flattenBindArgsValidationErrors, flattenValidationErrors, returnValidationErrors } from "./validation-errors";

export type * from "./index.types";
export type * from "./validation-errors.types";

/**
* Create a new safe action client.
* @param createOpts Optional initialization options
*
* {@link https://next-safe-action.dev/docs/safe-action-client/initialization-options See docs for more information}
*/
export const createSafeActionClient = <ServerError = string, MetadataSchema extends Schema | undefined = undefined>(
createOpts?: SafeActionClientOpts<ServerError, MetadataSchema>
) => {
return createClientWithStrategy("typeschema", createOpts);
};
19 changes: 12 additions & 7 deletions packages/next-safe-action/src/safe-action-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,21 @@ import type {
class SafeActionClient<ServerError, Ctx = undefined, Metadata = undefined> {
readonly #handleServerErrorLog: NonNullable<SafeActionClientOpts<ServerError, any>["handleServerErrorLog"]>;
readonly #handleReturnedServerError: NonNullable<SafeActionClientOpts<ServerError, any>["handleReturnedServerError"]>;
readonly #validationStrategy: "typeschema" | "zod";

#middlewareFns: MiddlewareFn<ServerError, any, any, any>[];
#ctxType = undefined as Ctx;

constructor(
opts: {
middlewareFns: MiddlewareFn<ServerError, any, any, any>[];
validationStrategy: "typeschema" | "zod";
} & Required<Pick<SafeActionClientOpts<ServerError, any>, "handleReturnedServerError" | "handleServerErrorLog">>
) {
this.#middlewareFns = opts.middlewareFns;
this.#handleServerErrorLog = opts.handleServerErrorLog;
this.#handleReturnedServerError = opts.handleReturnedServerError;
this.#validationStrategy = opts.validationStrategy;
}

/**
Expand All @@ -38,6 +41,7 @@ class SafeActionClient<ServerError, Ctx = undefined, Metadata = undefined> {
middlewareFns: [...this.#middlewareFns, middlewareFn],
handleReturnedServerError: this.#handleReturnedServerError,
handleServerErrorLog: this.#handleServerErrorLog,
validationStrategy: this.#validationStrategy,
});
}

Expand Down Expand Up @@ -75,6 +79,7 @@ class SafeActionClient<ServerError, Ctx = undefined, Metadata = undefined> {
middlewareFns: this.#middlewareFns,
metadata: data,
ctxType: this.#ctxType,
validationStrategy: this.#validationStrategy,
}),
};
}
Expand Down Expand Up @@ -134,6 +139,7 @@ class SafeActionClient<ServerError, Ctx = undefined, Metadata = undefined> {
formatValidationErrors: args.formatValidationErrors,
metadata: args.metadata,
ctxType: this.#ctxType,
validationStrategy: this.#validationStrategy,
}),
};
}
Expand All @@ -151,6 +157,7 @@ class SafeActionClient<ServerError, Ctx = undefined, Metadata = undefined> {
middlewareFns: this.#middlewareFns,
metadata: undefined,
ctxType: this.#ctxType,
validationStrategy: this.#validationStrategy,
}).action(serverCodeFn);
}

Expand All @@ -170,6 +177,7 @@ class SafeActionClient<ServerError, Ctx = undefined, Metadata = undefined> {
middlewareFns: this.#middlewareFns,
metadata: undefined,
ctxType: this.#ctxType,
validationStrategy: this.#validationStrategy,
}).stateAction(serverCodeFn);
}

Expand All @@ -196,17 +204,13 @@ class SafeActionClient<ServerError, Ctx = undefined, Metadata = undefined> {
formatBindArgsValidationErrors: args.formatBindArgsValidationErrors,
metadata: args.metadata,
ctxType: this.#ctxType,
validationStrategy: this.#validationStrategy,
});
}
}

/**
* Create a new safe action client.
* @param createOpts Optional initialization options
*
* {@link https://next-safe-action.dev/docs/safe-action-client/initialization-options See docs for more information}
*/
export const createSafeActionClient = <ServerError = string, MetadataSchema extends Schema | undefined = undefined>(
export const createClientWithStrategy = <ServerError = string, MetadataSchema extends Schema | undefined = undefined>(
validationStrategy: "typeschema" | "zod",
createOpts?: SafeActionClientOpts<ServerError, MetadataSchema>
) => {
// If server log function is not provided, default to `console.error` for logging
Expand All @@ -233,5 +237,6 @@ export const createSafeActionClient = <ServerError = string, MetadataSchema exte
middlewareFns: [async ({ next }) => next({ ctx: undefined })],
handleServerErrorLog,
handleReturnedServerError,
validationStrategy,
});
};
24 changes: 24 additions & 0 deletions packages/next-safe-action/src/zod.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { Schema } from "@typeschema/main";
import type { SafeActionClientOpts } from "./index.types";
import { createClientWithStrategy } from "./safe-action-client";

export { DEFAULT_SERVER_ERROR_MESSAGE, EMPTY_HOOK_RESULT as EMPTY_RESULT } from "./utils";
export { flattenBindArgsValidationErrors, flattenValidationErrors, returnValidationErrors } from "./validation-errors";

export type * from "./index.types";
export type * from "./validation-errors.types";

/**
* Create a new safe action client.
* Note: this client only works with Zod as the validation library.
* This is needed when TypeSchema causes problems in your application.
* Check out the [troubleshooting](https://next-safe-action.dev/docs/troubleshooting/errors-with-typeschema) page of the docs for more information.
* @param createOpts Optional initialization options
*
* {@link https://next-safe-action.dev/docs/safe-action-client/initialization-options See docs for more information}
*/
export const createSafeActionClient = <ServerError = string, MetadataSchema extends Schema | undefined = undefined>(
createOpts?: SafeActionClientOpts<ServerError, MetadataSchema>
) => {
return createClientWithStrategy("zod", createOpts);
};
2 changes: 1 addition & 1 deletion packages/next-safe-action/tsup.config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { defineConfig } from "tsup";

export default defineConfig({
entry: ["src/index.ts", "src/hooks.ts", "src/status.ts"],
entry: ["src/index.ts", "src/zod.ts", "src/hooks.ts", "src/status.ts"],
format: ["esm"],
clean: true,
splitting: false,
Expand Down
Loading

0 comments on commit 20a2ef5

Please sign in to comment.