|
| 1 | +# Tips for zod v4 schema |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +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. |
| 6 | + |
| 7 | +## Key Differences from Zod v3 |
| 8 | + |
| 9 | +### Type System Changes |
| 10 | + |
| 11 | +Zod v4 introduces changes to the `ZodType` generic parameters: |
| 12 | + |
| 13 | +```ts |
| 14 | +// Zod v3 |
| 15 | +z.ZodType<Output, Def extends z.ZodTypeDef, Input = Output> |
| 16 | + |
| 17 | +// Zod v4 |
| 18 | +z.ZodType<Output = unknown, Input = unknown> |
| 19 | +``` |
| 20 | + |
| 21 | +The `Properties` type definition has been updated accordingly: |
| 22 | + |
| 23 | +```ts |
| 24 | +// Updated for Zod v4 |
| 25 | +type Properties<T> = Required<{ |
| 26 | + [K in keyof T]: z.ZodType<T[K]>; |
| 27 | +}>; |
| 28 | +``` |
| 29 | + |
| 30 | +### Enum Handling |
| 31 | + |
| 32 | +Zod v4 changes how enums are handled: |
| 33 | + |
| 34 | +```ts |
| 35 | +// Zod v3 |
| 36 | +z.nativeEnum(ButtonComponentType) |
| 37 | + |
| 38 | +// Zod v4 |
| 39 | +z.enum(ButtonComponentType) |
| 40 | +``` |
| 41 | + |
| 42 | +## How to overwrite generated schema? |
| 43 | + |
| 44 | +You can use zod [extend API](https://zod.dev/api#extend), same as with Zod v3: |
| 45 | + |
| 46 | +```ts |
| 47 | +const AttributeInputSchemaWithCUID = AttributeInputSchema().extend({ |
| 48 | + key: z.string().cuid(), |
| 49 | +}); |
| 50 | +``` |
| 51 | + |
| 52 | +## Apply input validator via ts decorator |
| 53 | + |
| 54 | +Validate the input object via typescript decorators when implementing resolvers. The implementation is compatible with Zod v4's type system: |
| 55 | + |
| 56 | +### Usage |
| 57 | + |
| 58 | +```ts |
| 59 | +class Mutation { |
| 60 | + @validateInput(SignupInputSchema) |
| 61 | + async signup( |
| 62 | + _root: Record<string, never>, |
| 63 | + { input: { email, password } }: MutationSignupArgs, |
| 64 | + context: Context |
| 65 | + ): Promise<SignupPayload> { |
| 66 | + // The input here is automatically valid to adhere to SignupInputSchema |
| 67 | + } |
| 68 | +} |
| 69 | +``` |
| 70 | + |
| 71 | +### Implementation: |
| 72 | + |
| 73 | +```ts |
| 74 | +type ZodResolver<T extends ZodType<any, any>> = ResolverFn< |
| 75 | + any, |
| 76 | + any, |
| 77 | + any, |
| 78 | + { input: TypeOf<T> } |
| 79 | +> |
| 80 | + |
| 81 | +/** |
| 82 | + * Method decorator that validates the argument of the target function against the given schema. |
| 83 | + * Updated for Zod v4 type system. |
| 84 | + * |
| 85 | + * @export |
| 86 | + * @template T The type of the zod schema. |
| 87 | + * @param {T} arg The zod schema used for the validation. |
| 88 | + * @return {MethodDecorator} A {@link MethodDecorator}. |
| 89 | + */ |
| 90 | +export function validateInput<T extends AnyZodObject>( |
| 91 | + arg: T | (() => T) |
| 92 | +): MethodDecorator<ZodResolver<T>> { |
| 93 | + return function (_target, _propertyKey, descriptor) { |
| 94 | + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion |
| 95 | + const originalMethod = descriptor.value! |
| 96 | + // @ts-expect-error: should be fine |
| 97 | + descriptor.value = function (root, { input }, context, info) { |
| 98 | + const schema = typeof arg === 'function' ? arg() : arg |
| 99 | + const result = schema.safeParse(input) |
| 100 | + |
| 101 | + if (result.success) { |
| 102 | + // eslint-disable-next-line @typescript-eslint/no-unsafe-return |
| 103 | + return originalMethod.call( |
| 104 | + this, |
| 105 | + root, |
| 106 | + { input: result.data }, |
| 107 | + context, |
| 108 | + info |
| 109 | + ) |
| 110 | + } else { |
| 111 | + return { problems: result.error.issues } |
| 112 | + } |
| 113 | + } |
| 114 | + return descriptor |
| 115 | + } |
| 116 | +} |
| 117 | +``` |
| 118 | + |
| 119 | +## Migration from Zod v3 |
| 120 | + |
| 121 | +If you're migrating from Zod v3 to v4, consider the following: |
| 122 | + |
| 123 | +### 1. Using Zod v4 with v3 compatibility layer |
| 124 | + |
| 125 | +You can use the `zodImportPath` option to import from Zod v4's v3 compatibility layer: |
| 126 | + |
| 127 | +```yml |
| 128 | +generates: |
| 129 | + path/to/schemas.ts: |
| 130 | + plugins: |
| 131 | + - graphql-codegen-validation-schema |
| 132 | + config: |
| 133 | + schema: zod |
| 134 | + zodImportPath: zod/v3 # Use v3 compatibility layer |
| 135 | +``` |
| 136 | +
|
| 137 | +### 2. Using zodv4 schema type |
| 138 | +
|
| 139 | +Alternatively, use the `zodv4` schema type for full Zod v4 compatibility: |
| 140 | + |
| 141 | +```yml |
| 142 | +generates: |
| 143 | + path/to/schemas.ts: |
| 144 | + plugins: |
| 145 | + - graphql-codegen-validation-schema |
| 146 | + config: |
| 147 | + schema: zodv4 # Use zodv4 schema type |
| 148 | +``` |
| 149 | + |
| 150 | +## Performance and Type Safety |
| 151 | + |
| 152 | +Zod v4 provides improvements in: |
| 153 | + |
| 154 | +1. **Stricter Type Checking**: Enhanced type safety with simplified generic parameters |
| 155 | +2. **Better API Design**: More intuitive and consistent API |
| 156 | +3. **Internal Optimizations**: Performance improvements in validation logic |
| 157 | + |
| 158 | +These changes result in more reliable and maintainable validation code. |
0 commit comments