Skip to content

Commit 8476997

Browse files
authoredAug 11, 2024··
feat(validation): add support for TypeBox library (#228)
This PR adds support for validation via TypeBox library. re #225
1 parent 1aa8ed7 commit 8476997

File tree

9 files changed

+180
-22
lines changed

9 files changed

+180
-22
lines changed
 

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

+6-1
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
},
6868
"devDependencies": {
6969
"@eslint/js": "^9.2.0",
70+
"@sinclair/typebox": "^0.33.3",
7071
"@types/node": "^20.14.11",
7172
"@types/react": "^18.3.1",
7273
"@types/react-dom": "18.3.0",
@@ -92,8 +93,9 @@
9293
"react": ">= 18.2.0",
9394
"react-dom": ">= 18.2.0",
9495
"valibot": ">= 0.36.0",
96+
"yup": ">= 1.0.0",
9597
"zod": ">= 3.0.0",
96-
"yup": ">= 1.0.0"
98+
"@sinclair/typebox": ">= 0.33.3"
9799
},
98100
"peerDependenciesMeta": {
99101
"zod": {
@@ -104,6 +106,9 @@
104106
},
105107
"yup": {
106108
"optional": true
109+
},
110+
"@sinclair/typebox": {
111+
"optional": true
107112
}
108113
},
109114
"repository": {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Code courtesy of https://github.com/decs/typeschema/blob/main/packages/typebox/src/validation.ts
2+
3+
// MIT License
4+
5+
// Copyright (c) 2023 André Costa
6+
7+
// Permission is hereby granted, free of charge, to any person obtaining a copy
8+
// of this software and associated documentation files (the "Software"), to deal
9+
// in the Software without restriction, including without limitation the rights
10+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
// copies of the Software, and to permit persons to whom the Software is
12+
// furnished to do so, subject to the following conditions:
13+
14+
// The above copyright notice and this permission notice shall be included in all
15+
// copies or substantial portions of the Software.
16+
17+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23+
// SOFTWARE.
24+
25+
import type { TSchema } from "@sinclair/typebox";
26+
import { TypeCompiler } from "@sinclair/typebox/compiler";
27+
import type { IfInstalled, Infer, ValidationAdapter } from "./types";
28+
29+
class TypeboxAdapter implements ValidationAdapter {
30+
async validate<S extends IfInstalled<TSchema>>(schema: S, data: unknown) {
31+
const result = TypeCompiler.Compile(schema);
32+
33+
if (result.Check(data)) {
34+
return {
35+
success: true,
36+
data: data as Infer<S>,
37+
} as const;
38+
}
39+
40+
return {
41+
success: false,
42+
issues: [...result.Errors(data)].map(({ message, path }) => ({
43+
message,
44+
path: path.split("/").slice(1),
45+
})),
46+
} as const;
47+
}
48+
}
49+
50+
export function typeboxAdapter() {
51+
return new TypeboxAdapter();
52+
}

‎packages/next-safe-action/src/adapters/types.ts

+14-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// Code courtesy of/highly inspired by https://github.com/decs/typeschema
22

3+
import type { Static, TSchema } from "@sinclair/typebox";
34
import type { GenericSchema, GenericSchemaAsync, InferInput, InferOutput } from "valibot";
45
import type { InferType, Schema as YupSchema } from "yup";
56
import type { z } from "zod";
@@ -10,7 +11,8 @@ export type Schema =
1011
| IfInstalled<z.ZodType>
1112
| IfInstalled<GenericSchema>
1213
| IfInstalled<GenericSchemaAsync>
13-
| IfInstalled<YupSchema>;
14+
| IfInstalled<YupSchema>
15+
| IfInstalled<TSchema>;
1416

1517
export type Infer<S extends Schema> =
1618
S extends IfInstalled<z.ZodType>
@@ -21,7 +23,9 @@ export type Infer<S extends Schema> =
2123
? InferOutput<S>
2224
: S extends IfInstalled<YupSchema>
2325
? InferType<S>
24-
: never;
26+
: S extends IfInstalled<TSchema>
27+
? Static<S>
28+
: never;
2529

2630
export type InferIn<S extends Schema> =
2731
S extends IfInstalled<z.ZodType>
@@ -32,7 +36,9 @@ export type InferIn<S extends Schema> =
3236
? InferInput<S>
3337
: S extends IfInstalled<YupSchema>
3438
? InferType<S>
35-
: never;
39+
: S extends IfInstalled<TSchema>
40+
? Static<S>
41+
: never;
3642

3743
export type InferArray<BAS extends readonly Schema[]> = {
3844
[K in keyof BAS]: Infer<BAS[K]>;
@@ -71,4 +77,9 @@ export interface ValidationAdapter {
7177
schema: S,
7278
data: unknown
7379
): Promise<{ success: true; data: Infer<S> } | { success: false; issues: ValidationIssue[] }>;
80+
// typebox
81+
validate<S extends IfInstalled<TSchema>>(
82+
schema: S,
83+
data: unknown
84+
): Promise<{ success: true; data: Infer<S> } | { success: false; issues: ValidationIssue[] }>;
7485
}

‎packages/next-safe-action/src/adapters/valibot.ts

+22
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,27 @@
11
// Code courtesy of https://github.com/decs/typeschema/blob/main/packages/valibot/src/validation.ts
22

3+
// MIT License
4+
5+
// Copyright (c) 2023 André Costa
6+
7+
// Permission is hereby granted, free of charge, to any person obtaining a copy
8+
// of this software and associated documentation files (the "Software"), to deal
9+
// in the Software without restriction, including without limitation the rights
10+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
// copies of the Software, and to permit persons to whom the Software is
12+
// furnished to do so, subject to the following conditions:
13+
14+
// The above copyright notice and this permission notice shall be included in all
15+
// copies or substantial portions of the Software.
16+
17+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23+
// SOFTWARE.
24+
325
import { getDotPath, safeParseAsync, type GenericSchema, type GenericSchemaAsync } from "valibot";
426
import type { IfInstalled, Infer, ValidationAdapter } from "./types";
527

‎packages/next-safe-action/src/adapters/yup.ts

+24-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,26 @@
1-
// https://github.com/decs/typeschema/blob/main/packages/yup/src/validation.ts
1+
// Code courtesy of https://github.com/decs/typeschema/blob/main/packages/yup/src/validation.ts
2+
3+
// MIT License
4+
5+
// Copyright (c) 2023 André Costa
6+
7+
// Permission is hereby granted, free of charge, to any person obtaining a copy
8+
// of this software and associated documentation files (the "Software"), to deal
9+
// in the Software without restriction, including without limitation the rights
10+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
// copies of the Software, and to permit persons to whom the Software is
12+
// furnished to do so, subject to the following conditions:
13+
14+
// The above copyright notice and this permission notice shall be included in all
15+
// copies or substantial portions of the Software.
16+
17+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23+
// SOFTWARE.
224

325
import type { Schema as YupSchema } from "yup";
426
import { ValidationError } from "yup";
@@ -22,7 +44,7 @@ class YupAdapter implements ValidationAdapter {
2244
issues: [
2345
{
2446
message,
25-
path: path && path.length > 0 ? [path] : undefined,
47+
path: path && path.length > 0 ? path.split(".") : undefined,
2648
},
2749
] as ValidationIssue[],
2850
} as const;

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

+23-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,30 @@
1-
// https://github.com/decs/typeschema/blob/main/packages/zod/src/validation.ts
1+
// Code courtesy of https://github.com/decs/typeschema/blob/main/packages/zod/src/validation.ts
2+
3+
// MIT License
4+
5+
// Copyright (c) 2023 André Costa
6+
7+
// Permission is hereby granted, free of charge, to any person obtaining a copy
8+
// of this software and associated documentation files (the "Software"), to deal
9+
// in the Software without restriction, including without limitation the rights
10+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
// copies of the Software, and to permit persons to whom the Software is
12+
// furnished to do so, subject to the following conditions:
13+
14+
// The above copyright notice and this permission notice shall be included in all
15+
// copies or substantial portions of the Software.
16+
17+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23+
// SOFTWARE.
224

325
import type { z } from "zod";
426
import type { IfInstalled, Infer, ValidationAdapter } from "./types";
527

6-
export type ZodSchema = z.ZodType;
7-
828
class ZodAdapter implements ValidationAdapter {
929
async validate<S extends IfInstalled<z.ZodType>>(schema: S, data: unknown) {
1030
const result = await schema.safeParseAsync(data);

‎pnpm-lock.yaml

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

‎website/docs/getting-started.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ description: Getting started with next-safe-action version 7.
1010
- Next.js >= 14 (>= 15 for [`useStateAction`](/docs/execution/hooks/usestateaction) hook)
1111
- React >= 18.2.0
1212
- TypeScript >= 5
13-
- Zod or Valibot or Yup
13+
- Zod or Valibot or Yup or TypeBox
1414
:::
1515

1616
**next-safe-action** provides a typesafe Server Actions implementation for Next.js App Router.

‎website/docs/recipes/validation-libraries-support.md

+21-3
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,20 @@ description: Use a validation library of your choice with next-safe-action.
77

88
Starting from version 6.0.0, and up to version 7.1.3, next-safe-action used [TypeSchema](https://typeschema.com/) to enable support for multiple validation libraries. This has worked pretty well, but caused some issues too, such as the [Edge Runtime incompatibility](/docs/troubleshooting#typeschema-issues-with-edge-runtime) or [lack of support for TypeScript >= 5.5](/docs/troubleshooting#schema-and-parsedinput-are-typed-any-broken-types-and-build-issues).
99

10-
To solve these issues, next-safe-action v7.2.0 and later versions ship with a built-in modular support for multiple validation libraries, at this time: Zod, Valibot and Yup.
10+
To solve these issues, next-safe-action v7.2.0 and later versions ship with a built-in modular support for multiple validation libraries, at this time:
11+
- Zod
12+
- Valibot
13+
- Yup
14+
- TypeBox
15+
16+
## Instructions
1117

1218
If you used a TypeSchema adapter before, you should uninstall it, since you just need the validation library of your choice from now on.
1319

1420
The configuration is pretty simple. If you use Zod, you don't have to do anything. If you choose to use Valibot or Yup, other than obviously installing the validation library itself, you need to specify the correct validation adapter when you're initializing the safe action client:
1521

1622

17-
For Valibot:
23+
### Valibot
1824

1925
```typescript title="@/lib/safe-action.ts"
2026
import { createSafeActionClient } from "next-safe-action";
@@ -26,7 +32,7 @@ export const actionClient = createSafeActionClient({
2632
});
2733
```
2834

29-
For Yup:
35+
### Yup
3036

3137
```typescript title="@/lib/safe-action.ts"
3238
import { createSafeActionClient } from "next-safe-action";
@@ -38,6 +44,18 @@ export const actionClient = createSafeActionClient({
3844
});
3945
```
4046

47+
### TypeBox
48+
49+
```typescript title="@/lib/safe-action.ts"
50+
import { createSafeActionClient } from "next-safe-action";
51+
import { typeboxAdapter } from "next-safe-action/adapters/typebox"; // import the adapter
52+
53+
export const actionClient = createSafeActionClient({
54+
validationAdapter: typeboxAdapter(), // <-- and then pass it to the client
55+
// other options here...
56+
});
57+
```
58+
4159
And you're done! You could also do the same thing for Zod, but it's not required right now, as it's the default validation library.
4260

4361
If you want more information about the TypeSchema to built-in system change, there's a dedicated discussion on GitHub for that, [here](https://github.com/TheEdoRan/next-safe-action/discussions/201).

0 commit comments

Comments
 (0)
Please sign in to comment.