Skip to content

Commit

Permalink
feat(validation): add support for TypeBox library (#228)
Browse files Browse the repository at this point in the history
This PR adds support for validation via TypeBox library.

re #225
  • Loading branch information
TheEdoRan authored Aug 11, 2024
1 parent 1aa8ed7 commit 8476997
Show file tree
Hide file tree
Showing 9 changed files with 180 additions and 22 deletions.
7 changes: 6 additions & 1 deletion packages/next-safe-action/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
},
"devDependencies": {
"@eslint/js": "^9.2.0",
"@sinclair/typebox": "^0.33.3",
"@types/node": "^20.14.11",
"@types/react": "^18.3.1",
"@types/react-dom": "18.3.0",
Expand All @@ -92,8 +93,9 @@
"react": ">= 18.2.0",
"react-dom": ">= 18.2.0",
"valibot": ">= 0.36.0",
"yup": ">= 1.0.0",
"zod": ">= 3.0.0",
"yup": ">= 1.0.0"
"@sinclair/typebox": ">= 0.33.3"
},
"peerDependenciesMeta": {
"zod": {
Expand All @@ -104,6 +106,9 @@
},
"yup": {
"optional": true
},
"@sinclair/typebox": {
"optional": true
}
},
"repository": {
Expand Down
52 changes: 52 additions & 0 deletions packages/next-safe-action/src/adapters/typebox.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Code courtesy of https://github.com/decs/typeschema/blob/main/packages/typebox/src/validation.ts

// MIT License

// Copyright (c) 2023 André Costa

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

import type { TSchema } from "@sinclair/typebox";
import { TypeCompiler } from "@sinclair/typebox/compiler";
import type { IfInstalled, Infer, ValidationAdapter } from "./types";

class TypeboxAdapter implements ValidationAdapter {
async validate<S extends IfInstalled<TSchema>>(schema: S, data: unknown) {
const result = TypeCompiler.Compile(schema);

if (result.Check(data)) {
return {
success: true,
data: data as Infer<S>,
} as const;
}

return {
success: false,
issues: [...result.Errors(data)].map(({ message, path }) => ({
message,
path: path.split("/").slice(1),
})),
} as const;
}
}

export function typeboxAdapter() {
return new TypeboxAdapter();
}
17 changes: 14 additions & 3 deletions packages/next-safe-action/src/adapters/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Code courtesy of/highly inspired by https://github.com/decs/typeschema

import type { Static, TSchema } from "@sinclair/typebox";
import type { GenericSchema, GenericSchemaAsync, InferInput, InferOutput } from "valibot";
import type { InferType, Schema as YupSchema } from "yup";
import type { z } from "zod";
Expand All @@ -10,7 +11,8 @@ export type Schema =
| IfInstalled<z.ZodType>
| IfInstalled<GenericSchema>
| IfInstalled<GenericSchemaAsync>
| IfInstalled<YupSchema>;
| IfInstalled<YupSchema>
| IfInstalled<TSchema>;

export type Infer<S extends Schema> =
S extends IfInstalled<z.ZodType>
Expand All @@ -21,7 +23,9 @@ export type Infer<S extends Schema> =
? InferOutput<S>
: S extends IfInstalled<YupSchema>
? InferType<S>
: never;
: S extends IfInstalled<TSchema>
? Static<S>
: never;

export type InferIn<S extends Schema> =
S extends IfInstalled<z.ZodType>
Expand All @@ -32,7 +36,9 @@ export type InferIn<S extends Schema> =
? InferInput<S>
: S extends IfInstalled<YupSchema>
? InferType<S>
: never;
: S extends IfInstalled<TSchema>
? Static<S>
: never;

export type InferArray<BAS extends readonly Schema[]> = {
[K in keyof BAS]: Infer<BAS[K]>;
Expand Down Expand Up @@ -71,4 +77,9 @@ export interface ValidationAdapter {
schema: S,
data: unknown
): Promise<{ success: true; data: Infer<S> } | { success: false; issues: ValidationIssue[] }>;
// typebox
validate<S extends IfInstalled<TSchema>>(
schema: S,
data: unknown
): Promise<{ success: true; data: Infer<S> } | { success: false; issues: ValidationIssue[] }>;
}
22 changes: 22 additions & 0 deletions packages/next-safe-action/src/adapters/valibot.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
// Code courtesy of https://github.com/decs/typeschema/blob/main/packages/valibot/src/validation.ts

// MIT License

// Copyright (c) 2023 André Costa

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

import { getDotPath, safeParseAsync, type GenericSchema, type GenericSchemaAsync } from "valibot";
import type { IfInstalled, Infer, ValidationAdapter } from "./types";

Expand Down
26 changes: 24 additions & 2 deletions packages/next-safe-action/src/adapters/yup.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,26 @@
// https://github.com/decs/typeschema/blob/main/packages/yup/src/validation.ts
// Code courtesy of https://github.com/decs/typeschema/blob/main/packages/yup/src/validation.ts

// MIT License

// Copyright (c) 2023 André Costa

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

import type { Schema as YupSchema } from "yup";
import { ValidationError } from "yup";
Expand All @@ -22,7 +44,7 @@ class YupAdapter implements ValidationAdapter {
issues: [
{
message,
path: path && path.length > 0 ? [path] : undefined,
path: path && path.length > 0 ? path.split(".") : undefined,
},
] as ValidationIssue[],
} as const;
Expand Down
26 changes: 23 additions & 3 deletions packages/next-safe-action/src/adapters/zod.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,30 @@
// https://github.com/decs/typeschema/blob/main/packages/zod/src/validation.ts
// Code courtesy of https://github.com/decs/typeschema/blob/main/packages/zod/src/validation.ts

// MIT License

// Copyright (c) 2023 André Costa

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

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

export type ZodSchema = z.ZodType;

class ZodAdapter implements ValidationAdapter {
async validate<S extends IfInstalled<z.ZodType>>(schema: S, data: unknown) {
const result = await schema.safeParseAsync(data);
Expand Down
26 changes: 17 additions & 9 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion website/docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ description: Getting started with next-safe-action version 7.
- Next.js >= 14 (>= 15 for [`useStateAction`](/docs/execution/hooks/usestateaction) hook)
- React >= 18.2.0
- TypeScript >= 5
- Zod or Valibot or Yup
- Zod or Valibot or Yup or TypeBox
:::

**next-safe-action** provides a typesafe Server Actions implementation for Next.js App Router.
Expand Down
24 changes: 21 additions & 3 deletions website/docs/recipes/validation-libraries-support.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,20 @@ description: Use a validation library of your choice with next-safe-action.

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).

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.
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
- Yup
- TypeBox

## Instructions

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

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:


For Valibot:
### Valibot

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

For Yup:
### Yup

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

### TypeBox

```typescript title="@/lib/safe-action.ts"
import { createSafeActionClient } from "next-safe-action";
import { typeboxAdapter } from "next-safe-action/adapters/typebox"; // import the adapter

export const actionClient = createSafeActionClient({
validationAdapter: typeboxAdapter(), // <-- and then pass it to the client
// other options here...
});
```

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.

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 comments on commit 8476997

Please sign in to comment.