Skip to content

Commit 20a2ef5

Browse files
authoredMay 7, 2024··
feat(action-client): export a copy of safe action client from /zod 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.
1 parent efb6b35 commit 20a2ef5

File tree

13 files changed

+338
-271
lines changed

13 files changed

+338
-271
lines changed
 

‎apps/playground/src/lib/safe-action.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { randomUUID } from "crypto";
21
import {
32
DEFAULT_SERVER_ERROR_MESSAGE,
43
createSafeActionClient,
@@ -58,13 +57,13 @@ export const action = createSafeActionClient({
5857
});
5958

6059
async function getSessionId() {
61-
return randomUUID();
60+
return crypto.randomUUID();
6261
}
6362

6463
export const authAction = action
6564
// In this case, context is used for (fake) auth purposes.
6665
.use(async ({ next }) => {
67-
const userId = randomUUID();
66+
const userId = crypto.randomUUID();
6867

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

‎packages/next-safe-action/package.json

+16-4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
],
1212
"exports": {
1313
".": "./dist/index.mjs",
14+
"./zod": "./dist/zod.mjs",
1415
"./hooks": "./dist/hooks.mjs",
1516
"./status": "./dist/status.mjs"
1617
},
@@ -19,6 +20,9 @@
1920
".": [
2021
"./dist/index.d.mts"
2122
],
23+
"zod": [
24+
"./dist/zod.d.mts"
25+
],
2226
"hooks": [
2327
"./dist/hooks.d.mts"
2428
],
@@ -65,7 +69,6 @@
6569
"@types/node": "^20.12.10",
6670
"@types/react": "^18.3.1",
6771
"@typeschema/core": "^0.13.2",
68-
"@typeschema/zod": "^0.13.3",
6972
"eslint": "^8.57.0",
7073
"eslint-config-prettier": "^9.1.0",
7174
"eslint-define-config": "^2.1.0",
@@ -76,12 +79,21 @@
7679
"semantic-release": "^23.0.8",
7780
"tsup": "^8.0.2",
7881
"typescript": "^5.4.5",
79-
"typescript-eslint": "^7.8.0",
80-
"zod": "^3.23.6"
82+
"typescript-eslint": "^7.8.0"
8183
},
8284
"peerDependencies": {
8385
"next": ">= 14.3.0-canary.42",
84-
"react": ">= 18.3.1"
86+
"react": ">= 18.3.1",
87+
"@typeschema/zod": "^0.13.3",
88+
"zod": "^3.23.6"
89+
},
90+
"peerDependenciesMeta": {
91+
"zod": {
92+
"optional": true
93+
},
94+
"@typeschema/zod": {
95+
"optional": true
96+
}
8597
},
8698
"repository": {
8799
"type": "git",

‎packages/next-safe-action/src/action-builder.ts

+9-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import type { Infer } from "@typeschema/main";
2-
import { validate, type Schema } from "@typeschema/main";
1+
import { validate, type Infer, type Schema } from "@typeschema/main";
2+
import { validate as zodValidate } from "@typeschema/zod";
33
import { isNotFoundError } from "next/dist/client/components/not-found.js";
44
import { isRedirectError } from "next/dist/client/components/redirect.js";
55
import type {} from "zod";
@@ -41,6 +41,7 @@ export function actionBuilder<
4141
handleReturnedServerError: NonNullable<SafeActionClientOpts<ServerError, any>["handleReturnedServerError"]>;
4242
middlewareFns: MiddlewareFn<ServerError, any, any, MD>[];
4343
ctxType: Ctx;
44+
validationStrategy: "typeschema" | "zod";
4445
}) {
4546
const bindArgsSchemas = (args.bindArgsSchemas ?? []) as BAS;
4647

@@ -116,11 +117,15 @@ export function actionBuilder<
116117
}
117118

118119
// Otherwise, parse input with the schema.
119-
return validate(args.schema, input);
120+
return args.validationStrategy === "zod"
121+
? zodValidate(args.schema, input)
122+
: validate(args.schema, input);
120123
}
121124

122125
// Otherwise, we're processing bind args client inputs.
123-
return validate(bindArgsSchemas[i]!, input);
126+
return args.validationStrategy === "zod"
127+
? zodValidate(bindArgsSchemas[i]!, input)
128+
: validate(bindArgsSchemas[i]!, input);
124129
})
125130
);
126131

+16-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,21 @@
1-
export { createSafeActionClient } from "./safe-action-client";
1+
import type { Schema } from "@typeschema/main";
2+
import type { SafeActionClientOpts } from "./index.types";
3+
import { createClientWithStrategy } from "./safe-action-client";
4+
25
export { DEFAULT_SERVER_ERROR_MESSAGE, EMPTY_HOOK_RESULT as EMPTY_RESULT } from "./utils";
36
export { flattenBindArgsValidationErrors, flattenValidationErrors, returnValidationErrors } from "./validation-errors";
47

58
export type * from "./index.types";
69
export type * from "./validation-errors.types";
10+
11+
/**
12+
* Create a new safe action client.
13+
* @param createOpts Optional initialization options
14+
*
15+
* {@link https://next-safe-action.dev/docs/safe-action-client/initialization-options See docs for more information}
16+
*/
17+
export const createSafeActionClient = <ServerError = string, MetadataSchema extends Schema | undefined = undefined>(
18+
createOpts?: SafeActionClientOpts<ServerError, MetadataSchema>
19+
) => {
20+
return createClientWithStrategy("typeschema", createOpts);
21+
};

‎packages/next-safe-action/src/safe-action-client.ts

+12-7
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,21 @@ import type {
1313
class SafeActionClient<ServerError, Ctx = undefined, Metadata = undefined> {
1414
readonly #handleServerErrorLog: NonNullable<SafeActionClientOpts<ServerError, any>["handleServerErrorLog"]>;
1515
readonly #handleReturnedServerError: NonNullable<SafeActionClientOpts<ServerError, any>["handleReturnedServerError"]>;
16+
readonly #validationStrategy: "typeschema" | "zod";
1617

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

2021
constructor(
2122
opts: {
2223
middlewareFns: MiddlewareFn<ServerError, any, any, any>[];
24+
validationStrategy: "typeschema" | "zod";
2325
} & Required<Pick<SafeActionClientOpts<ServerError, any>, "handleReturnedServerError" | "handleServerErrorLog">>
2426
) {
2527
this.#middlewareFns = opts.middlewareFns;
2628
this.#handleServerErrorLog = opts.handleServerErrorLog;
2729
this.#handleReturnedServerError = opts.handleReturnedServerError;
30+
this.#validationStrategy = opts.validationStrategy;
2831
}
2932

3033
/**
@@ -38,6 +41,7 @@ class SafeActionClient<ServerError, Ctx = undefined, Metadata = undefined> {
3841
middlewareFns: [...this.#middlewareFns, middlewareFn],
3942
handleReturnedServerError: this.#handleReturnedServerError,
4043
handleServerErrorLog: this.#handleServerErrorLog,
44+
validationStrategy: this.#validationStrategy,
4145
});
4246
}
4347

@@ -75,6 +79,7 @@ class SafeActionClient<ServerError, Ctx = undefined, Metadata = undefined> {
7579
middlewareFns: this.#middlewareFns,
7680
metadata: data,
7781
ctxType: this.#ctxType,
82+
validationStrategy: this.#validationStrategy,
7883
}),
7984
};
8085
}
@@ -134,6 +139,7 @@ class SafeActionClient<ServerError, Ctx = undefined, Metadata = undefined> {
134139
formatValidationErrors: args.formatValidationErrors,
135140
metadata: args.metadata,
136141
ctxType: this.#ctxType,
142+
validationStrategy: this.#validationStrategy,
137143
}),
138144
};
139145
}
@@ -151,6 +157,7 @@ class SafeActionClient<ServerError, Ctx = undefined, Metadata = undefined> {
151157
middlewareFns: this.#middlewareFns,
152158
metadata: undefined,
153159
ctxType: this.#ctxType,
160+
validationStrategy: this.#validationStrategy,
154161
}).action(serverCodeFn);
155162
}
156163

@@ -170,6 +177,7 @@ class SafeActionClient<ServerError, Ctx = undefined, Metadata = undefined> {
170177
middlewareFns: this.#middlewareFns,
171178
metadata: undefined,
172179
ctxType: this.#ctxType,
180+
validationStrategy: this.#validationStrategy,
173181
}).stateAction(serverCodeFn);
174182
}
175183

@@ -196,17 +204,13 @@ class SafeActionClient<ServerError, Ctx = undefined, Metadata = undefined> {
196204
formatBindArgsValidationErrors: args.formatBindArgsValidationErrors,
197205
metadata: args.metadata,
198206
ctxType: this.#ctxType,
207+
validationStrategy: this.#validationStrategy,
199208
});
200209
}
201210
}
202211

203-
/**
204-
* Create a new safe action client.
205-
* @param createOpts Optional initialization options
206-
*
207-
* {@link https://next-safe-action.dev/docs/safe-action-client/initialization-options See docs for more information}
208-
*/
209-
export const createSafeActionClient = <ServerError = string, MetadataSchema extends Schema | undefined = undefined>(
212+
export const createClientWithStrategy = <ServerError = string, MetadataSchema extends Schema | undefined = undefined>(
213+
validationStrategy: "typeschema" | "zod",
210214
createOpts?: SafeActionClientOpts<ServerError, MetadataSchema>
211215
) => {
212216
// If server log function is not provided, default to `console.error` for logging
@@ -233,5 +237,6 @@ export const createSafeActionClient = <ServerError = string, MetadataSchema exte
233237
middlewareFns: [async ({ next }) => next({ ctx: undefined })],
234238
handleServerErrorLog,
235239
handleReturnedServerError,
240+
validationStrategy,
236241
});
237242
};

‎packages/next-safe-action/src/zod.ts

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import type { Schema } from "@typeschema/main";
2+
import type { SafeActionClientOpts } from "./index.types";
3+
import { createClientWithStrategy } from "./safe-action-client";
4+
5+
export { DEFAULT_SERVER_ERROR_MESSAGE, EMPTY_HOOK_RESULT as EMPTY_RESULT } from "./utils";
6+
export { flattenBindArgsValidationErrors, flattenValidationErrors, returnValidationErrors } from "./validation-errors";
7+
8+
export type * from "./index.types";
9+
export type * from "./validation-errors.types";
10+
11+
/**
12+
* Create a new safe action client.
13+
* Note: this client only works with Zod as the validation library.
14+
* This is needed when TypeSchema causes problems in your application.
15+
* Check out the [troubleshooting](https://next-safe-action.dev/docs/troubleshooting/errors-with-typeschema) page of the docs for more information.
16+
* @param createOpts Optional initialization options
17+
*
18+
* {@link https://next-safe-action.dev/docs/safe-action-client/initialization-options See docs for more information}
19+
*/
20+
export const createSafeActionClient = <ServerError = string, MetadataSchema extends Schema | undefined = undefined>(
21+
createOpts?: SafeActionClientOpts<ServerError, MetadataSchema>
22+
) => {
23+
return createClientWithStrategy("zod", createOpts);
24+
};

‎packages/next-safe-action/tsup.config.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { defineConfig } from "tsup";
22

33
export default defineConfig({
4-
entry: ["src/index.ts", "src/hooks.ts", "src/status.ts"],
4+
entry: ["src/index.ts", "src/zod.ts", "src/hooks.ts", "src/status.ts"],
55
format: ["esm"],
66
clean: true,
77
splitting: false,

‎pnpm-lock.yaml

+216-249
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎website/docs/contributing.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
sidebar_position: 9
2+
sidebar_position: 10
33
description: Learn how to contribute to next-safe-action via GitHub.
44
---
55

‎website/docs/getting-started.md

+4
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ description: Getting started with next-safe-action version 7.
1818

1919
We will use Zod as our validation library in this documentation, but since version 6 of next-safe-action, you can use your validation library of choice, or even multiple and custom ones at the same time, thanks to the **TypeSchema** library. Note that we also need to install the related TypeSchema adapter for our validation library of choice. You can find supported libraries and related adapters [here](https://typeschema.com/#coverage).
2020

21+
:::info
22+
If you experience an issue related to TypeSchema, check out the ["Errors with TypeSchema"](/docs/troubleshooting/errors-with-typeschema) section of the troubleshooting page to see how to fix it.
23+
:::
24+
2125
## Installation
2226

2327
For Next.js >= 14, assuming you want to use Zod as your validation library, use the following command:

‎website/docs/migrations/index.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
sidebar_position: 8
2+
sidebar_position: 9
33
description: Learn how to migrate from a version to the next one.
44
---
55

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
---
2+
sidebar_position: 1
3+
description: Knwon errors with TypeSchema.
4+
---
5+
6+
# Errors with TypeSchema
7+
8+
[TypeSchema](https://typeschema.com/) library relies on dynamic imports to support a wide range of validation libraries. It works well most of the time, but in some cases it can cause unexpected errors.
9+
10+
For instance, there's a known bug with TypeSchema and Next.js edge runtime. When parsing and validating client input, the server outputs the following error:
11+
12+
```
13+
TypeError [ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING]:
14+
A dynamic import callback was not specified.
15+
```
16+
17+
To solve this, next-safe-action exports a copy of the safe action client from the `/zod` path, with the exact same functionality as the default one. The only difference is that this client **requires** Zod to work, as it supports just that validation library.
18+
19+
So, after you installed `next-safe-action`, `zod` and `@typeschema/zod` packages, as explained in the [installation](/docs/getting-started#installation) section of the getting started page, you just need to update the import path for `createSafeActionClient`, in the `safe-action.ts` file:
20+
21+
```typescript title="src/lib/safe-action.ts"
22+
// Update import path here with /zod
23+
import { createSafeActionClient } from "next-safe-action/zod";
24+
25+
export const actionClient = createSafeActionClient();
26+
```
27+
28+
`next-safe-action/zod` exports the same client, variables, functions and types as the default path.

‎website/docs/troubleshooting/index.md

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
sidebar_position: 8
3+
description: Learn how to fix known issues with next-safe-action.
4+
---
5+
6+
# Troubleshooting
7+
8+
Learn how to fix known issues with next-safe-action.

0 commit comments

Comments
 (0)
Please sign in to comment.