Skip to content

Support for Zod 4 #1184

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 71 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

- [x] support [yup](https://github.com/jquense/yup)
- [x] support [zod](https://github.com/colinhacks/zod)
- [x] support [zod v4](https://github.com/colinhacks/zod) (zodv4)
- [x] support [myzod](https://github.com/davidmdm/myzod)
- [x] support [valibot](https://valibot.dev/)

Expand Down Expand Up @@ -49,7 +50,7 @@ type: `ValidationSchema` default: `'yup'`

Specify generete validation schema you want.

You can specify `yup` or `zod` or `myzod`.
You can specify `yup` or `zod` or `zodv4` or `myzod` or `valibot`.

```yml
generates:
Expand All @@ -61,6 +62,26 @@ generates:
schema: yup
```

### `zodImportPath`

type: `string` default: `'zod'`

Specifies a custom import path for the zod package. This is useful when you want to use a specific
version or subpath of zod, such as the v3 compatibility layer in zod v4 ('zod/v3').
Only applies when schema is set to 'zod'.

```yml
generates:
path/to/schemas.ts:
plugins:
- typescript
- typescript-validation-schema
config:
schema: zod
# Use zod v3 compatibility layer when using zod v4
zodImportPath: zod/v3
```

### `importFrom`

type: `string`
Expand Down Expand Up @@ -215,6 +236,16 @@ config:
Email: z.string().email()
```

#### zodv4 schema

```yml
config:
schema: zodv4
scalarSchemas:
Date: z.date()
Email: z.string().email()
```

### `defaultScalarTypeSchema`

type: `string`
Expand All @@ -235,6 +266,13 @@ config:
defaultScalarSchema: z.unknown()
```

#### zodv4 schema
```yml
config:
schema: zodv4
defaultScalarSchema: z.unknown()
```

### `withObjectType`

type: `boolean` default: `false`
Expand Down Expand Up @@ -360,6 +398,38 @@ export function ExampleInputSchema(): z.ZodSchema<ExampleInput> {
}
```

#### zodv4 schema

```yml
generates:
path/to/graphql.ts:
plugins:
- typescript
- typescript-validation-schema
config:
schema: zodv4
directives:
constraint:
minLength: min
# Replace $1 with specified `startsWith` argument value of the constraint directive
startsWith: [regex, /^$1/, message]
format:
# This example means `validation-schema: directive-arg`
# directive-arg is supported String and Enum.
email: email
```

Then generates zodv4 validation schema like below.

```ts
export function ExampleInputSchema(): z.ZodObject<Properties<ExampleInput>> {
return z.object({
email: z.string().min(50).email(),
message: z.string().regex(/^Hello/, 'message')
})
}
```

#### other schema

Please see [example](https://github.com/Code-Hex/graphql-codegen-typescript-validation-schema/tree/main/example) directory.
Expand Down
24 changes: 24 additions & 0 deletions codegen.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,30 @@ generates:
plugins:
- ./dist/cjs/index.js:
schema: zod
zodImportPath: zod/v3
importFrom: ../types
withObjectType: true
directives:
# Write directives like
#
# directive:
# arg1: schemaApi
# arg2: ["schemaApi2", "Hello $1"]
#
# See more examples in `./tests/directive.spec.ts`
# https://github.com/Code-Hex/graphql-codegen-typescript-validation-schema/blob/main/tests/directive.spec.ts
constraint:
minLength: min
# Replace $1 with specified `startsWith` argument value of the constraint directive
startsWith: [regex, /^$1/, message]
format:
email: email
scalars:
ID: string
example/zodv4/schemas.ts:
plugins:
- ./dist/cjs/index.js:
schema: zodv4
importFrom: ../types
withObjectType: true
directives:
Expand Down
2 changes: 1 addition & 1 deletion example/zod/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## How to overwrite generated schema?

You can use zod [extend API](https://github.com/colinhacks/zod#extend).
You can use zod [extend API](https://v3.zod.dev/?id=extend).

```ts
const AttributeInputSchemaWithCUID = AttributeInputSchema().extend({
Expand Down
2 changes: 1 addition & 1 deletion example/zod/schemas.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { z } from 'zod'
import { z } from 'zod/v3'
import { Admin, AttributeInput, ButtonComponentType, ComponentInput, DropDownComponentInput, EventArgumentInput, EventInput, EventOptionType, Guest, HttpInput, HttpMethod, LayoutInput, MyType, MyTypeFooArgs, Namer, PageInput, PageType, User } from '../types'

type Properties<T> = Required<{
Expand Down
158 changes: 158 additions & 0 deletions example/zodv4/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
# Tips for zod v4 schema

## Overview

The `zodv4` schema type is designed to work with Zod v4, which introduces significant changes to the type system and API. This implementation uses the updated type definitions and APIs that are compatible with Zod v4.

## Key Differences from Zod v3

### Type System Changes

Zod v4 introduces changes to the `ZodType` generic parameters:

```ts
// Zod v3
z.ZodType<Output, Def extends z.ZodTypeDef, Input = Output>

// Zod v4
z.ZodType<Output = unknown, Input = unknown>
```

The `Properties` type definition has been updated accordingly:

```ts
// Updated for Zod v4
type Properties<T> = Required<{
[K in keyof T]: z.ZodType<T[K]>;
}>;
```

### Enum Handling

Zod v4 changes how enums are handled:

```ts
// Zod v3
z.nativeEnum(ButtonComponentType)

// Zod v4
z.enum(ButtonComponentType)
```

## How to overwrite generated schema?

You can use zod [extend API](https://zod.dev/api#extend), same as with Zod v3:

```ts
const AttributeInputSchemaWithCUID = AttributeInputSchema().extend({
key: z.string().cuid(),
});
```

## Apply input validator via ts decorator

Validate the input object via typescript decorators when implementing resolvers. The implementation is compatible with Zod v4's type system:

### Usage

```ts
class Mutation {
@validateInput(SignupInputSchema)
async signup(
_root: Record<string, never>,
{ input: { email, password } }: MutationSignupArgs,
context: Context
): Promise<SignupPayload> {
// The input here is automatically valid to adhere to SignupInputSchema
}
}
```

### Implementation:

```ts
type ZodResolver<T extends ZodType<any, any>> = ResolverFn<
any,
any,
any,
{ input: TypeOf<T> }
>

/**
* Method decorator that validates the argument of the target function against the given schema.
* Updated for Zod v4 type system.
*
* @export
* @template T The type of the zod schema.
* @param {T} arg The zod schema used for the validation.
* @return {MethodDecorator} A {@link MethodDecorator}.
*/
export function validateInput<T extends AnyZodObject>(
arg: T | (() => T)
): MethodDecorator<ZodResolver<T>> {
return function (_target, _propertyKey, descriptor) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const originalMethod = descriptor.value!
// @ts-expect-error: should be fine
descriptor.value = function (root, { input }, context, info) {
const schema = typeof arg === 'function' ? arg() : arg
const result = schema.safeParse(input)

if (result.success) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return originalMethod.call(
this,
root,
{ input: result.data },
context,
info
)
} else {
return { problems: result.error.issues }
}
}
return descriptor
}
}
```

## Migration from Zod v3

If you're migrating from Zod v3 to v4, consider the following:

### 1. Using Zod v4 with v3 compatibility layer

You can use the `zodImportPath` option to import from Zod v4's v3 compatibility layer:

```yml
generates:
path/to/schemas.ts:
plugins:
- graphql-codegen-validation-schema
config:
schema: zod
zodImportPath: zod/v3 # Use v3 compatibility layer
```

### 2. Using zodv4 schema type

Alternatively, use the `zodv4` schema type for full Zod v4 compatibility:

```yml
generates:
path/to/schemas.ts:
plugins:
- graphql-codegen-validation-schema
config:
schema: zodv4 # Use zodv4 schema type
```

## Performance and Type Safety

Zod v4 provides improvements in:

1. **Stricter Type Checking**: Enhanced type safety with simplified generic parameters
2. **Better API Design**: More intuitive and consistent API
3. **Internal Optimizations**: Performance improvements in validation logic

These changes result in more reliable and maintainable validation code.
Loading