zod-prisma-types
zod-prisma-types
是 prisma 从 prisma 模型生成 zod 架构。这包括模型、枚举、inputTypes、argTypes、过滤器等的模式。它还提供了直接在 prisma 模式注释中编写高级 zod 验证器的选项。
由于我在业余时间维护发电机,如果您喜欢这个项目,请考虑给我买杯咖啡。谢谢!
v2.xx 中的重大更改
请注意,一些生成器选项已被删除,添加了一些新选项,自定义导入的行为已更改,并且不再需要 ts-morph 来生成文件在 v2.0.0 中。
目录
关于此项目
对于我的一个项目,我需要一个能够添加 zod 的生成器valdiators
直接在prisma schema的
rich-comments 并为所有 prisma 模型、枚举、inputTypes、argTypes、过滤器等生成 zod
模式。我还希望能够在前端导入这些模式,例如用于表单验证,并使生成器尽可能灵活,以便它涵盖大量用例。由于没有满足我要求的生成器或者它们不再被积极维护,我决定编写 zod-prisma-type
。
安装
通过npm
npm install zod-prisma-types
:通过yarn:
yarn add zod-prisma-types
通过pnpm:
pnpm add zod-prisma-types
用法
支持 prisma 4.x
只需将以下代码添加到您的 prisma.schema
文件中,即可在 ./ generated/zodindex.ts
文件code> 包含所有 zod prisma 模式的输出文件夹。
generator zod {
provider = "zod-prisma-types"
}
如果您想自定义生成器的行为,可以使用以下选项:
generator zod {
provider = "ts-node-dev ../generator/src/bin.ts"
output = "./generated/zod" // default is ./generated/zod
useMultipleFiles = true // default is false
createInputTypes = false // default is true
createModelTypes = false // default is true
addInputTypeValidation = false // default is true
validateWhereUniqueInput = true // default is false
createOptionalDefaultValuesTypes = true // default is false
createRelationValuesTypes = true // default is false
useDefaultValidators = false // default is true
coerceDate = false // default is true
writeNullishInModelTypes = true // default is false
prismaClientPath = "./path/to/prisma/client" // default is client output path
}
useMultipleFiles
默认:假
如果您想创建多个文件而不是单个 index.ts
文件,您可以将此选项设置为 true
。这将为每个模型、枚举、inputType、argType、过滤器等创建一个文件。这些文件将在指定输出文件夹的子文件夹中创建,并且将在输出文件夹的根目录中添加一个桶文件。
generator zod {
// ...rest of config
useMultipleFiles = false
}
输出
默认:./ generated/zod
提供备用输出路径。
默认值:true
如果您只想为模型和枚举创建 zod 模式,您可以禁用相应输入类型的创建。如果您只想使用模型的 zod 模式来验证 react-hook-form
或一些类似用例中的输入类型,这可能很有用。
generator zod {
// ...rest of config
createInputTypes = false
}
创建模型类型
默认值:true
如果您只想为输入类型创建 zod 模式,您可以禁用相应模型模式的创建。如果您只想在 trpc 查询或类似用例中使用 zod 输入模式进行自动完成,这可能很有用。
generator zod {
// ...rest of config
createModelTypes = false
}
默认值:true
如果您想仅在生成的模型架构上使用通过丰富注释添加的自定义 zod 验证器,而不是在创建的输入类型架构上使用(UserCreateInput
, UserUpdateManyInput
等)您可以禁用此功能。
generator zod {
// ...rest of config
addInputTypeValidation = false
}
默认:假
默认情况下,生成器不会在多文件模式下验证 whereUnique
输入类型,因为通常会生成一堆未使用的导入。如果您想验证 whereUnique
输入类型,您可以将此选项设置为 true
。
请注意,如果您使用需要手动解决的 no-unused-vars
规则,这可能会导致 eslint 错误。
generator zod {
// ...rest of config
validateWhereUniqueInput = true
}
创建OptionalDefaultValuesTypes
默认:假
如果您想要模型的架构,其中具有默认值的字段被标记为 .optional()
您可以传递以下配置选项
generator zod {
// ...rest of config
createOptionalDefaultValuesTypes = true
}
model ModelWithDefaultValues {
id Int @id @default(autoincrement())
string String @default("default")
otherString String
int Int @default(1)
otherInt Int
float Float @default(1.1)
otherFloat Float
boolean Boolean @default(true)
otherBool Boolean
date DateTime @default(now())
otherDate DateTime
}
:然后模型将生成以下模型架构:
export const ModelWithDefaultValuesSchema = z.object({
id: z.number(),
string: z.string(),
otherString: z.string(),
int: z.number(),
otherInt: z.number(),
float: z.number(),
otherFloat: z.number(),
boolean: z.boolean(),
otherBool: z.boolean(),
date: z.date(),
otherDate: z.date(),
});
export const ModelWithDefaultValuesOptionalDefaultsSchema =
ModelWithDefaultValuesSchema.merge(
z.object({
id: z.number().optional(),
string: z.string().optional(),
int: z.number().optional(),
float: z.number().optional(),
boolean: z.boolean().optional(),
date: z.date().optional(),
}),
);
createRelationValuesTypes
默认:假
如果您需要包含所有关系字段的单独模型类型,您可以传递以下选项。由于类型注释需要具有递归类型,因此该模型有一些限制,因为 z.ZodType
不允许某些对象方法,例如 .merge()
、.omit()
等。
generator zod {
// ...rest of config
createRelationValuesTypes = true
}
model User {
id String @id @default(cuid())
email String @unique
name String?
posts Post[]
profile Profile?
role Role[] @default([USER, ADMIN])
enum AnotherEnum @default(ONE)
scalarList String[]
lat Float
lng Float
location Location? @relation(fields: [lat, lng], references: [lat, lng])
}
上述模型将生成以下模型架构:
export const UserSchema = z.object({
role: RoleSchema.array(),
enum: AnotherEnumSchema,
id: z.string().cuid(),
email: z.string(),
name: z.string(),
scalarList: z.string().array(),
lat: z.number(),
lng: z.number(),
});
export type UserRelations = {
posts: PostWithRelations[];
profile?: ProfileWithRelations | null;
location?: LocationWithRelations | null;
};
export type UserWithRelations = z.infer<typeof UserSchema> & UserRelations;
export const UserWithRelationsSchema: z.ZodType<UserWithRelations> =
UserSchema.merge(
z.object({
posts: z.lazy(() => PostWithRelationsSchema).array(),
profile: z.lazy(() => ProfileWithRelationsSchema).nullish(),
location: z.lazy(() => LocationWithRelationsSchema).nullish(),
}),
);
如果该选项与 createOptionalDefaultValuesTypes
结合使用,还会生成以下模型架构:
export type UserOptionalDefaultsWithRelations = z.infer<
typeof UserOptionalDefaultsSchema
> &
UserRelations;
export const UserOptionalDefaultsWithRelationsSchema: z.ZodType<UserOptionalDefaultsWithRelations> =
UserOptionalDefaultsSchema.merge(
z.object({
posts: z.lazy(() => PostWithRelationsSchema).array(),
profile: z.lazy(() => ProfileWithRelationsSchema).nullable(),
location: z.lazy(() => LocationWithRelationsSchema).nullable(),
target: z.lazy(() => LocationWithRelationsSchema).nullable(),
}),
);
使用默认验证器
默认值:true
在某些用例中,生成器会添加默认验证器:
model WithDefaultValidators {
id String @id @default(cuid())
idTwo String @default(uuid())
integer Int
}
export const WithDefaultValidatorsSchema = z.object({
id: z.string().cuid(),
idTwo: z.string().uuid(),
integer: z.number().int(),
});
使用自定义验证器时,这些默认值将被覆盖(请参阅:字段验证器)
或者当您选择不在特定字段上使用默认验证器时:
model WithDefaultValidators {
id String @id @default(cuid()) /// @zod.string.noDefault()
idTwo String @default(uuid()) /// @zod.string.noDefault()
integer Int /// @zod.number.noDefault()
}
export const WithDefaultValidatorsSchema = z.object({
id: z.string(),
idTwo: z.string(),
integer: z.number(),
});
您可以通过将 false 传递给配置选项来完全选择退出此功能。
generator zod {
// ...rest of config
useDefaultValidators = false
}
计划在未来版本中提供更多默认验证器(通过检查架构中的@db.filds)。如果您对默认验证器有一些想法,请随时提出问题。
强制日期
默认值:true
只要您传入有效的 ISO 字符串
或 实例,默认的 DateTime
值就会被强制转换为 Date
对象。日期
。您可以通过将以下选项传递给生成器配置来更改此行为以生成简单的 z.date()
:
generator zod {
// ...rest of config
coerceDate = false
}
writeNullishInModelTypes
默认值:假
默认情况下,当 Prisma 类型中的字段可为空时,生成器仅在 modelTypes 中写入 .nullable()
。如果您希望这些字段接受 null | undefined
,在模式中由 .nullish()
表示,您可以将以下选项传递给生成器配置:
generator zod {
// ...rest of config
writeNullishInModelTypes = true
}
prismaClientPath
默认:从 prisma 模式路径推断
默认情况下,prisma 客户端路径是从 prisma.schema
文件中提供的输出
路径推断的>生成器客户端。如果您仍然需要使用自定义路径,您可以通过此选项将其传递到生成器配置。自定义路径优先于推断的 prisma 客户端输出路径。
generator zod {
// ...rest of config
prismaClientPath = "./path/to/prisma/client"
}
跳过架构生成
您可以根据您当前工作的环境跳过架构生成。例如,您只能在开发
中生成架构,但在 中运行生成时则不能生成架构生产
(因为在生产
中,架构已经创建并通过 git 存储库推送到服务器)。
由于 Prisma 只允许我们在生成器配置中定义字符串,因此我们无法使用 env(MY_ENV_VARIABLE)
方法,该方法在例如 url
下使用。 code>datasource db 已加载:
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
为了仍然能够将环境变量加载到生成器中,只需在根目录中创建一个 zodGenConfig.js
(其中 node_modules
> 文件夹)并添加以下代码:
module.exports = {
skipGenerator: process.env['SKIP_ZOD_PRISMA'],
};
然后将
SKIP_ZOD_PRISMA = 'true';
或
SKIP_ZOD_PRISMA = 'false';
添加到各自的 .env
文件中。这将在 skipGenerator
属性上加载 SKIP_ZOD_PRISMA
环境变量,然后由生成器使用。
您可以选择任意命名环境变量 - 只需让 Shure 在 zodGenConfig.js
中加载正确的变量即可。
对于自定义枚举,会生成一个单独的类型,将枚举值表示为联合。由于在打字稿中联合比枚举更有用,因此这会派上用场。
enum MyEnum {
A
B
C
}
export const MyEnumSchema = z.nativeEnum(PrismaClient.MyEnum);
export type MyEnumType = `${z.infer<typeof MyEnumSchema>}`; // union of "A" | "B" | "C"
Json null 值
当使用 json null 值时,prisma 有一种独特的方式处理数据库 NULL
和 JSON null
,如所述 在文档中。
为了遵循这个概念,您可以将 "DbNull"
或 "JsonNull"
作为字符串传递给可为空的 Json 字段。当架构得到验证时,这些字符串将转换为 Prisma.DbNull 或 Prisma.JsonNull 以满足 prisma.[myModel].create() | 的要求。 .update() | ... 功能。
Decimal
当使用 Decimal 时,使用 refine
方法来验证输入是否符合 prisma 输入联合 string |数量 |十进制| DecimalJsLike
。
model MyModel {
id Int @id @default(autoincrement())
decimal Decimal
}
上述模型将生成以下模式:
// DECIMAL HELPERS
//------------------------------------------------------
export const DecimalJSLikeSchema = z.object({
d: z.array(z.number()),
e: z.number(),
s: z.number(),
});
export type DecimalJSLike = z.infer<typeof DecimalJSLikeSchema>;
export const DECIMAL_STRING_REGEX = /^[0-9.,e+-bxffo_cp]+$|Infinity|NaN/;
export const isValidDecimalInput = (
v?: null | string | number | DecimalJsLike,
) => {
if (!v) return false;
return (
(typeof v === 'object' && 'd' in v && 'e' in v && 's' in v) ||
(typeof v === 'string' && DECIMAL_STRING_REGEX.test(v)) ||
typeof v === 'number'
);
};
// SCHEMA
//------------------------------------------------------
export const MyModelSchema = z.object({
id: z.number(),
decimal: z
.union([z.number(), z.string(), DecimalJSLikeSchema])
.refine((v) => isValidDecimalInput(v), {
message: 'Field "decimal" must be a Decimal',
path: ['Models', 'DecimalModel'],
}),
});
字段验证器
可以使用以下语法在 prisma.schema
文件的注释中添加 zod 验证器(使用 丰富评论 ///
而不是 //
)。
myField [prisma-scalar-type] /// @zod.[zod-type + optional[(zod-error-messages)]].[zod validators for scalar-type]
这可能看起来有点神秘,所以这里是一个示例:
generator zod {
provider = "zod-prisma-types"
output = "./zod"
}
/// @zod.import(["import { myFunction } from 'mypackage';"])
model MyPrismaScalarsType {
/// @zod.string({ invalid_type_error: "some error with special chars: some + -*#'substring[]*#!§$%&/{}[]", required_error: "some other", description: "some description" }).cuid()
id String @id @default(cuid())
/// Some comment about string @zod.string.min(3, { message: "min error" }).max(10, { message: "max error" })
string String?
/// @zod.custom.use(z.string().refine((val) => validator.isBIC(val), { message: 'BIC is not valid' }))
bic String?
/// @zod.number.lt(10, { message: "lt error" }).gt(5, { message: "gt error" })
float Float
floatOpt Float?
/// @zod.number.int({ message: "error" }).gt(5, { message: "gt error" })
int Int
intOpt Int?
decimal Decimal
decimalOpt Decimal?
date DateTime @default(now())
dateOpt DateTime? /// @zod.date({ invalid_type_error: "wrong date type" }) bigInt BigInt /// @zod.bigint({ invalid_type_error: "error" })
bigIntOpt BigInt?
/// @zod.custom.use(z.lazy(() => InputJsonValue).refine((val) => myFunction(val), { message: 'Is not valid' }))
json Json
jsonOpt Json?
bytes Bytes /// @zod.custom.use(z.instanceof(Buffer).refine((val) => val ? true : false, { message: 'Value is not valid' }))
bytesOpt Bytes?
/// @zod.custom.use(z.string().refine((val) => myFunction(val), { message: 'Is not valid' }))
custom String?
exclude String? /// @zod.custom.omit(["model", "input"])
updatedAt DateTime @updatedAt
}
此示例为 prisma/zod/index.ts
中的模型生成以下 zod 模式:
import { z } from 'zod';
import * as PrismaClient from '@prisma/client';
import validator from 'validator';
import { myFunction } from 'mypackage';
export const MyPrismaScalarsTypeSchema = z.object({
id: z
.string({
invalid_type_error:
"some error with special chars: some + -*#'substring[]*#!§$%&/{}[]",
required_error: 'some other',
description: 'some description',
})
.cuid(),
/**
* Some comment about string
*/
string: z
.string()
.min(3, { message: 'min error' })
.max(10, { message: 'max error' })
.nullish(),
bic: z
.string()
.refine((val) => validator.isBIC(val), { message: 'BIC is not valid' })
.nullish(),
float: z
.number()
.lt(10, { message: 'lt error' })
.gt(5, { message: 'gt error' }),
floatOpt: z.number().nullish(),
int: z.number().int({ message: 'error' }).gt(5, { message: 'gt error' }),
intOpt: z.number().int().nullish(),
decimal: z
.union([
z.number(),
z.string(),
z.instanceof(PrismaClient.Prisma.Decimal),
DecimalJSLikeSchema,
])
.refine((v) => isValidDecimalInput(v), {
message: 'Field "decimal" must be a Decimal',
path: ['Models', 'MyPrismaScalarsType'],
}),
decimalOpt: z
.union([
z.number(),
z.string(),
z.instanceof(PrismaClient.Prisma.Decimal),
DecimalJSLikeSchema,
])
.refine((v) => isValidDecimalInput(v), {
message: 'Field "decimalOpt" must be a Decimal',
path: ['Models', 'MyPrismaScalarsType'],
})
.nullish(),
date: z.coerce.date(),
dateOpt: z.coerce.date({ invalid_type_error: 'wrong date type' }).nullish(),
bigIntOpt: z.bigint().nullish(),
json: z
.lazy(() => InputJsonValue)
.refine((val) => myFunction(val), { message: 'Is not valid' }),
jsonOpt: NullableJsonValue.optional(),
bytes: z
.instanceof(Buffer)
.refine((val) => (val ? true : false), { message: 'Value is not valid' }),
bytesOpt: z.instanceof(Buffer).nullish(),
custom: z
.string()
.refine((val) => myFunction(val), { message: 'Is not valid' })
.nullish(),
// omitted: exclude: z.string().nullish(),
updatedAt: z.date(),
});
export type MyPrismaScalarsType = z.infer<typeof MyPrismaScalarsTypeSchema>;
export const MyPrismaScalarsTypeOptionalDefaultsSchema =
MyPrismaScalarsTypeSchema.merge(
z.object({
id: z
.string({
invalid_type_error:
"some error with special chars: some + -*#'substring[]*#!§$%&/{}[]",
required_error: 'some other',
description: 'some description',
})
.cuid()
.optional(),
date: z.date().optional(),
updatedAt: z.date().optional(),
}),
);
此外,prisma input-、enum-、filter-、orderBy-、select-、include 和其他必要类型的所有 zod 模式均已生成,可在 trpc
输入中使用。< /p>
自定义导入
要将自定义导入添加到验证器中,您可以通过 @zod.import([...myCustomimports as strings])
在模型定义的 prismas 丰富注释中添加它们。
例如:
/// @zod.import(["import { myFunction } from 'mypackage'"])
model MyModel {
myField String /// @zod.string().refine((val) => myFunction(val), { message: 'Is not valid' })
}
这将导致如下输出:
import { myFunction } from 'mypackage';
export const MyModelSchema = z.object({
myField: z
.string()
.refine((val) => myFunction(val), { message: 'Is not valid' }),
});
请注意,如果您使用 useMultipleFiles
选项,则必须为相对导入添加额外的级别。
自定义类型错误消息
要将自定义 zod 类型错误消息添加到验证器,您可以通过 @zod.[key]({ ...customTypeErrorMessages }).[validator key]
添加它们。自定义错误消息必须遵循以下类型:
type RawCreateParams =
| {
invalid_type_error?: string;
required_error?: string;
description?: string;
}
| undefined;
例如:
model MyModel {
myField String /// @zod.string({ invalid_type_error: "invalid type error", required_error: "is required", description: "describe the error" })
}
这将导致如下输出:
string: z.string({
invalid_type_error: 'invalid type error',
required_error: 'is required',
description: 'describe the error',
}),
如果您使用错误的密钥或有拼写错误,生成器将抛出错误:
model MyModel {
myField String /// @zod.string({ required_error: "error", invalid_type_errrrrror: "error"})
}
[@zod generator error]: Custom error key 'invalid_type_errrrrror' is not valid. Please check for typos! [Error Location]: Model: 'Test', Field: 'myField'.
字符串验证器
将自定义验证器添加到 prisma String
字段您可以使用 @zod.string
键。在此键上,您可以使用 zod-docs
中提到的所有特定于字符串的验证器。您还可以按照文档中的说明向每个验证器添加自定义错误消息。
model MyModel {
myField String /// @zod.string.min(3, { message: "min error" }).max(10, { message: "max error" }).[...chain more validators]
}
数字验证器
要将自定义验证器添加到 prisma Int
或 Float
字段,您可以使用 @zod.number
键。在此键上,您可以使用 zod-docs
中提到的所有特定于数字的验证器。您还可以按照文档中的说明向每个验证器添加自定义错误消息。
model MyModel {
myField Int
/// @zod.number.lt(10, { message: "lt error" }).gt(5, { message: "gt error" }).[...chain more validators]
}
BigInt 验证器
要将自定义验证器添加到 prisma BigInt
字段,您可以使用 @zod.bigint
键。由于 zod
在 z.bigint()
上没有提供自定义验证器,因此您只能向该字段添加自定义类型错误。
model MyModel {
myField BigInt /// @zod.bigint({ invalid_type_error: "error", ... })
}
日期验证器
要向 prisma DateTime
字段添加自定义验证器,您可以使用 @zod.date
键。在此键上,您可以使用 zod-docs
中提到的所有特定于日期的验证器。您还可以按照文档中的说明向每个验证器添加自定义错误消息。
model MyModel {
myField DateTime /// @zod.date.min(new Date('2020-01-01')).max(new Date('2020-12-31'))
}
自定义验证器
将自定义验证器添加到任何 Prisma标量
字段您可以使用@zod.custom.use()
键。该密钥只有 .use(...your custom code here)
验证器。此代码会覆盖所有其他标准实现,因此您必须准确指定生成器应如何编写 zod 类型。根据您的 prisma 架构类型定义,仅自动添加 .optical()
和 .nullable()
。该字段旨在为您的字段提供 zod .refine
或 .transform
等验证器。
model MyModel {
id Int @id @default(autoincrement())
custom String? /// @zod.custom.use(z.string().refine(val => validator.isBIC(val)).transform(val => val.toUpperCase()))
}
上述模型模式将生成以下 zod 模式:
export const MyModel = z.object({
id: z.number(),
custom: z
.string()
.refine((val) => validator.isBIC(val))
.transform((val) => val.toUpperCase())
.nullable(),
});
省略字段
可以使用 @zod.custom.omit(["model", "input"]) 省略生成的 zod 模式中的字段
。当传递键 "model"
和 "input"
时,生成的模型架构和生成的输入类型都将省略该字段(请参见下面的示例)。如果您只想省略其中一个模式中的字段,只需提供匹配的键即可。您还可以编写不带 "
或 '
的键。
model MyModel {
id Int @id @default(autoincrement())
string String? /// @zod.string.min(4).max(10)
omitField String? /// @zod.custom.omit([model, input])
omitRequired String /// @zod.custom.omit([model, input])
}
上述模型将生成以下 zod 模式(省略的键保留在模型中,但已被注释掉,因此您可以看到查看 zod 模式时忽略了哪些字段):
// MODEL TYPES
// ---------------------------------------
export const MyModelSchema = z.object({
id: z.number(),
string: z.string().min(4).max(10).nullish(),
// omitted: omitField: z.string().nullish(),
// omitted: omitRequired: z.string(),
});
// INPUT TYPES
// ---------------------------------------
export const MyModelCreateInputSchema: z.ZodType<
Omit<PrismaClient.Prisma.MyModelCreateInput, 'omitField' | 'omitRequired'>
> = z
.object({
string: z.string().min(4).max(10).optional().nullable(),
// omitted: omitField: z.string().optional().nullable(),
// omitted: omitRequired: z.string(),
})
.strict();
export const MyModelUncheckedCreateInputSchema: z.ZodType<
Omit<
PrismaClient.Prisma.MyModelUncheckedCreateInput,
'omitField' | 'omitRequired'
>
> = z
.object({
id: z.number().optional(),
string: z.string().min(4).max(10).optional().nullable(),
// omitted: omitField: z.string().optional().nullable(),
// omitted: omitRequired: z.string(),
})
.strict();
export const MyModelUpdateInputSchema: z.ZodType<
Omit<PrismaClient.Prisma.MyModelUpdateInput, 'omitField' | 'omitRequired'>
> = z
.object({
string: z
.union([
z.string().min(4).max(10),
z.lazy(() => NullableStringFieldUpdateOperationsInputSchema),
])
.optional()
.nullable(),
// omitted: omitField: z.union([ z.string(),z.lazy(() => NullableStringFieldUpdateOperationsInputSchema) ]).optional().nullable(),
// omitted: omitRequired: z.union([ z.string(),z.lazy(() => StringFieldUpdateOperationsInputSchema) ]).optional(),
})
.strict();
// AND SO ON...
// ARG TYPES
// ---------------------------------------
// To be compatible with the inputTypes the type of the `ArgSchema` is updated accordingly
export const MyModelCreateArgsSchema: z.ZodType<
Omit<PrismaClient.Prisma.MyModelCreateArgs, 'data'> & {
data:
| z.infer<typeof MyModelCreateInputSchema>
| z.infer<typeof MyModelUncheckedCreateInputSchema>;
}
> = z
.object({
select: MyModelSelectSchema.optional(),
data: z.union([
MyModelCreateInputSchema,
MyModelUncheckedCreateInputSchema,
]),
})
.strict();
当省略必填
字段时,需要在相应的 prisma 函数中手动添加该字段,例如create
、update
、 createMany 等等。否则 Typescript 会抱怨。
const appRouter = t.router({
createMyModel: t.procedure
.input(MyModelCreateArgsSchema) // field `omitRequired` is not included in `data`
.query(({ input }) => {
return prisma.myModel.create({
...input,
data: {
...input.data,
omitRequired: 'foo', // field needs to be added manually
},
});
}),
});
验证错误
为了简化开发人员的体验,生成器会检查提供的 @zod.[key]
是否可用于模型字段的相应类型。它还检查@zod.[key].[validator]
是否可以在指定的@zod.[key]
上使用
错误的zod类型
如果您在错误的 prisma 类型上使用像 @zod.string
这样的验证器密钥,生成器会抛出错误。
model MyModel {
string String /// @zod.string.min(3) -> valid - `string` can be used on `String`
number Number /// @zod.string.min(3) -> invalid - `string` can not be used on `Number`
}
对于上面的示例,错误消息将如下所示:
[@zod generator error]: Validator 'string' is not valid for type 'Int'. [Error Location]: Model: 'MyModel', Field: 'number'
生成器提供确切的位置、出了什么问题以及错误发生的位置。在具有数百个模型和数百个自定义验证字符串的大型棱镜模式中,这可以派上用场。
错误的验证器
如果您在错误的验证器密钥上使用验证器.min
,则生成器会抛出错误。
model MyModel {
number Int /// @zod.number.min(3) -> invalid - `min` can not be used on `number`
}
上面的示例将抛出以下错误:
[@zod generator error]: Validator 'min' is not valid for type 'Int'. [Error Location]: Model: 'MyModel', Field: 'number'.
Typo Errors
如果验证器字符串中有拼写错误,
model MyModel {
string String /// @zod.string.min(3, { mussage: 'Must be at least 3 characters' })
}
生成器将抛出以下错误:
[@zod generator error]: Could not match validator 'min' with validatorPattern
'.min(3, { mussage: 'Must be at least 3 characters' })'. Please check for typos! [Error Location]: Model: 'MyModel', Field: 'string'.
zod 模式命名
zod 类型以生成的 prisma 类型命名带有附加的“Schema”字符串。您只需将鼠标悬停在 prisma 函数上,您就知道要导入哪种类型。对于 trpc v.10,这看起来像这样:
import {
UserFindFirstArgsSchema,
UserFindManyArgsSchema,
UserFindUniqueArgsSchema,
} from './prisma/zod';
const appRouter = t.router({
findManyUser: t.procedure.input(UserFindManyArgsSchema).query(({ input }) => {
return prisma.user.findMany(input);
}),
findUniqueUser: t.procedure
.input(UserFindUniqueArgsSchema)
.query(({ input }) => {
return prisma.user.findUnique(input);
}),
findFirstUser: t.procedure
.input(UserFindFirstArgsSchema)
.query(({ input }) => {
return prisma.user.findFirst(input);
}),
});
您可以添加 rich-comments 到您的模型和字段,然后在生成的 zod 架构中将其打印为 jsDoc。
/// comment line one
/// comment line two
model MyModel {
id Int @id @default(autoincrement())
/// comment before validator @zod.string.min(4).max(10)
/// comment after validator
string String?
}
上述模型将生成以下输出,其中验证器从丰富的评论中提取并添加到字符串字段:
/**
* comment line one
* comment line two
*/
export const MyModelSchema = z.object({
id: z.number(),
/**
* comment before validator
* comment after validator
*/
string: z.string().min(4).max(10).nullish(),
});
The validator is extract from the comments and add to the string
Migration from zod-prisma
There zod-prisma
和 zod-prisma-types
之间存在一些差异。
以下部分应帮助您从 zod-prisma
迁移到 zod-prisma-types
。
生成器选项
zod-prisma-types
不支持或以不同方式实现 zod-prisma
中的以下生成器选项:
relationModel
您可以生成架构通过将以下选项传递给生成器来包含模型的所有关系:
generator zod {
// ... other options
createRelationValuesTypes = true
}
有关详细信息,请参阅 createRelationValuesTypes
。
modelCase
模型的大小写固定为 prisma schema
中使用的大小写,并且无法更改。这样,在生成 inputTypes
、enums
、argTypes
等时,具有混合大小写的模型名称(例如 MYModel
)将按预期工作.
modelSuffix
zod-prisma-types 中的模型后缀固定为 Schema
并且无法更改。
useDecimalJs
zod-prisma-types
不支持 decimal.js
,但使用 prisma 提供的十进制实现来验证 Decimal 类型。有关详细信息,请参阅十进制。
导入
从版本 2.0.0
开始,zod-prisma-types
中的导入通过模型定义上的丰富注释进行处理。有关详细信息,请参阅自定义导入。
prismaJsonNullability
zod-prisma-types 中的 nullablility 的处理方式不同。有关详细信息,请参阅 Json null 值。
扩展 zod 字段
zod-prisma 允许您使用自定义验证器扩展 zod 字段。这也可以通过 zod-prisma-types
和 @zod.[key].[validator]
语法实现。不同的语法用于检查验证器是否可以用于特定的 prisma 类型。有关详细信息,请参阅字段验证器。
// zod-prisma
model MyModel {
string String /// @zod.min(3) -> valid - `string` can be used on `String`
number Number /// @zod.min(3) -> valid - throws error only at runtime
}
//zod-prisma-types
model MyModel {
string String /// @zod.string.min(3) -> valid - `string` can be used on `String`
number Number /// @zod.string.min(3) -> invalid - throws error during generation
}
导入助手
您可以在生成器中导入自定义助手。请参阅有关自定义导入的部分以了解更多信息。
zod-prisma-types
zod-prisma-types
is a generator for prisma that generates zod schemas from your prisma models. This includes schemas of models, enums, inputTypes, argTypes, filters and so on. It also provides options to write advanced zod validators directly in the prisma schema comments.
Since I'm maintaining the generator in my spare time consider buying me a coffee if you like the project. Thanks!
Breaking changes in v2.x.x
Be aware that some generator options have been removed, a few new have been added, the behaviour of custom imports has changed and ts-morph is no longer needed to generate files in v2.0.0.
Table of contents
About this project
For one of my projects I was in need of a generator that offers the possibility of adding zod valdiators
directly in prisma schema's
rich-comments and generates zod
schemas for all prisma models, enums, inputTypes, argTypes, filters and so on. I also wanted to be able to import these schemas in the frontend e.g. for form validation and make the generator as flexible as possbile so it covers a large range of use cases. Since there where no generators out there that met my requirements or they weren't activly maintained anymore I decided to write zod-prisma-type
.
Installation
via npm:
npm install zod-prisma-types
via yarn:
yarn add zod-prisma-types
via pnpm:
pnpm add zod-prisma-types
Usage
Supports prisma 4.x
Just add the following code to your prisma.schema
file to create a single index.ts
file in the ./generated/zod
output folder containing all the zod prisma schemas.
generator zod {
provider = "zod-prisma-types"
}
If you want to customize the behaviour of the generator you can use the following options:
generator zod {
provider = "ts-node-dev ../generator/src/bin.ts"
output = "./generated/zod" // default is ./generated/zod
useMultipleFiles = true // default is false
createInputTypes = false // default is true
createModelTypes = false // default is true
addInputTypeValidation = false // default is true
validateWhereUniqueInput = true // default is false
createOptionalDefaultValuesTypes = true // default is false
createRelationValuesTypes = true // default is false
useDefaultValidators = false // default is true
coerceDate = false // default is true
writeNullishInModelTypes = true // default is false
prismaClientPath = "./path/to/prisma/client" // default is client output path
}
useMultipleFiles
default: false
If you want to create multiple files instead of a single index.ts
file you can set this option to true
. This will create a file for each model, enum, inputType, argType, filter, etc. The files will be created in sub folders in the specified output folder and a barrel file will be added at the root of the output folder.
generator zod {
// ...rest of config
useMultipleFiles = false
}
output
default: ./generated/zod
Provide an alternative output path.
default: true
If you just want to create zod schemas for your models and enums you can disable the creation of the corresponding input types. This may be useful if you just want to use zod schemas of your models for validating input types in react-hook-form
or some similar use cases.
generator zod {
// ...rest of config
createInputTypes = false
}
createModelTypes
default: true
If you just want to create zod schemas for your input types you can disable the creation of the corresponding model schemas. This may be useful if you just want to use the zod input schemas for autocompletion in your trpc queries or similar use cases.
generator zod {
// ...rest of config
createModelTypes = false
}
default: true
If you want to use your custom zod validatiors that you added via rich-comments only on your generated model schemas but not on your created input type schemas (UserCreateInput
, UserUpdateManyInput
, etc.) you can disable this feature.
generator zod {
// ...rest of config
addInputTypeValidation = false
}
default: false
By default the generator will not validate the whereUnique
input types in multifile mode since a bunch of unused imports will often be generated. If you want to validate the whereUnique
input types you can set this option to true
.
Be aware that this can lead to eslint errors if you use the no-unused-vars
rule which you need to resolve manually.
generator zod {
// ...rest of config
validateWhereUniqueInput = true
}
createOptionalDefaultValuesTypes
default: false
If you want to have a schema of your model where where fields with default values are marked as .optional()
you can pass the following config option:
generator zod {
// ...rest of config
createOptionalDefaultValuesTypes = true
}
model ModelWithDefaultValues {
id Int @id @default(autoincrement())
string String @default("default")
otherString String
int Int @default(1)
otherInt Int
float Float @default(1.1)
otherFloat Float
boolean Boolean @default(true)
otherBool Boolean
date DateTime @default(now())
otherDate DateTime
}
The above model would then generate the following model schemas:
export const ModelWithDefaultValuesSchema = z.object({
id: z.number(),
string: z.string(),
otherString: z.string(),
int: z.number(),
otherInt: z.number(),
float: z.number(),
otherFloat: z.number(),
boolean: z.boolean(),
otherBool: z.boolean(),
date: z.date(),
otherDate: z.date(),
});
export const ModelWithDefaultValuesOptionalDefaultsSchema =
ModelWithDefaultValuesSchema.merge(
z.object({
id: z.number().optional(),
string: z.string().optional(),
int: z.number().optional(),
float: z.number().optional(),
boolean: z.boolean().optional(),
date: z.date().optional(),
}),
);
createRelationValuesTypes
default: false
If you need a separate model type that includes all the relation fields you can pass the following option. Due do the type annotation, that is needed to have recursive types, this model has some limitations since z.ZodType<myType>
does not allow some object methods like .merge()
, .omit()
, etc.
generator zod {
// ...rest of config
createRelationValuesTypes = true
}
model User {
id String @id @default(cuid())
email String @unique
name String?
posts Post[]
profile Profile?
role Role[] @default([USER, ADMIN])
enum AnotherEnum @default(ONE)
scalarList String[]
lat Float
lng Float
location Location? @relation(fields: [lat, lng], references: [lat, lng])
}
The above model would generate the following model schemas:
export const UserSchema = z.object({
role: RoleSchema.array(),
enum: AnotherEnumSchema,
id: z.string().cuid(),
email: z.string(),
name: z.string(),
scalarList: z.string().array(),
lat: z.number(),
lng: z.number(),
});
export type UserRelations = {
posts: PostWithRelations[];
profile?: ProfileWithRelations | null;
location?: LocationWithRelations | null;
};
export type UserWithRelations = z.infer<typeof UserSchema> & UserRelations;
export const UserWithRelationsSchema: z.ZodType<UserWithRelations> =
UserSchema.merge(
z.object({
posts: z.lazy(() => PostWithRelationsSchema).array(),
profile: z.lazy(() => ProfileWithRelationsSchema).nullish(),
location: z.lazy(() => LocationWithRelationsSchema).nullish(),
}),
);
If the option is combined with createOptionalDefaultValuesTypes
additionally the following model schemas are generated:
export type UserOptionalDefaultsWithRelations = z.infer<
typeof UserOptionalDefaultsSchema
> &
UserRelations;
export const UserOptionalDefaultsWithRelationsSchema: z.ZodType<UserOptionalDefaultsWithRelations> =
UserOptionalDefaultsSchema.merge(
z.object({
posts: z.lazy(() => PostWithRelationsSchema).array(),
profile: z.lazy(() => ProfileWithRelationsSchema).nullable(),
location: z.lazy(() => LocationWithRelationsSchema).nullable(),
target: z.lazy(() => LocationWithRelationsSchema).nullable(),
}),
);
useDefaultValidators
default: true
In certain use cases the generator adds default validators:
model WithDefaultValidators {
id String @id @default(cuid())
idTwo String @default(uuid())
integer Int
}
export const WithDefaultValidatorsSchema = z.object({
id: z.string().cuid(),
idTwo: z.string().uuid(),
integer: z.number().int(),
});
These defaults are overwritten when using a custom validator (see: Field Validators)
or when you opt out of using a default validator on a specific field:
model WithDefaultValidators {
id String @id @default(cuid()) /// @zod.string.noDefault()
idTwo String @default(uuid()) /// @zod.string.noDefault()
integer Int /// @zod.number.noDefault()
}
export const WithDefaultValidatorsSchema = z.object({
id: z.string(),
idTwo: z.string(),
integer: z.number(),
});
You can opt out of this feature completly by passing false to the config option.
generator zod {
// ...rest of config
useDefaultValidators = false
}
More default validators are planned in future releases (by checking the @db. filds in the schema). If you have some ideas for default validators feel free to open an issue.
coerceDate
default: true
Per default DateTime
values are coerced to Date
objects as long as you pass in a valid ISO string
or an instance of Date
. You can change this behavior to generate a simple z.date()
by passing the following option to the generator config:
generator zod {
// ...rest of config
coerceDate = false
}
writeNullishInModelTypes
default: false
By default the generator just writes .nullable()
in the modelTypes when a field in the Prisma type is nullable. If you want these fields to accept null | undefined
, which would be represented by .nullish()
in the schema, you can pass the following option to the generator config:
generator zod {
// ...rest of config
writeNullishInModelTypes = true
}
prismaClientPath
default: infereed from prisma schema path
By default the prisma client path is infereed from the output
path provided in the prisma.schema
file under generator client
. If you still need to use a custom path you can pass it to the generator config via this option. A custom path takes precedence over the infereed prisma client output path.
generator zod {
// ...rest of config
prismaClientPath = "./path/to/prisma/client"
}
Skip schema generation
You can skip schema generation based on e.g. the environment you are currently working in. For example you can only generate the schemas when you're in development
but not when you run generation in production
(because in production
the schemas would already hav been created and pushed to the server via your git repo).
Since Prisma only lets us define strings
in the generator config we cannot use the env(MY_ENV_VARIABLE)
method that is used when e.g. the url
under datasource db
is loaded:
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
To still be able to load environment variables into the generator, just create a zodGenConfig.js
in your root directory (where the node_modules
folder is located) and add the following code:
module.exports = {
skipGenerator: process.env['SKIP_ZOD_PRISMA'],
};
Then add
SKIP_ZOD_PRISMA = 'true';
or
SKIP_ZOD_PRISMA = 'false';
to your respective .env
file. This will load the SKIP_ZOD_PRISMA
environment variable on the skipGenerator
prop that will then be consumed by the generator.
You can choose to name your environment variable whatever you want - just make shure to load the right variable in zodGenConfig.js
.
For custom enums a separate type is generated that represents the enum values as a union. Since in typescript unions are more useful than enums this can come in handy.
enum MyEnum {
A
B
C
}
export const MyEnumSchema = z.nativeEnum(PrismaClient.MyEnum);
export type MyEnumType = `${z.infer<typeof MyEnumSchema>}`; // union of "A" | "B" | "C"
Json null values
When using json null values prisma has a unique way of handling Database NULL
and JSON null
as stated in the Docs.
To adhere to this concept you can pass "DbNull"
or "JsonNull"
as string to a nullable Json field. When the schema gets validated these strings are transformed to Prisma.DbNull
or Prisma.JsonNull
to satisfy the prisma.[myModel].create() | .update() | ...
functions.
Decimal
When using Decimal a refine
method is used to validate if the input adheres to the prisma input union string | number | Decimal | DecimalJsLike
.
model MyModel {
id Int @id @default(autoincrement())
decimal Decimal
}
The above model would generate the following schema:
// DECIMAL HELPERS
//------------------------------------------------------
export const DecimalJSLikeSchema = z.object({
d: z.array(z.number()),
e: z.number(),
s: z.number(),
});
export type DecimalJSLike = z.infer<typeof DecimalJSLikeSchema>;
export const DECIMAL_STRING_REGEX = /^[0-9.,e+-bxffo_cp]+$|Infinity|NaN/;
export const isValidDecimalInput = (
v?: null | string | number | DecimalJsLike,
) => {
if (!v) return false;
return (
(typeof v === 'object' && 'd' in v && 'e' in v && 's' in v) ||
(typeof v === 'string' && DECIMAL_STRING_REGEX.test(v)) ||
typeof v === 'number'
);
};
// SCHEMA
//------------------------------------------------------
export const MyModelSchema = z.object({
id: z.number(),
decimal: z
.union([z.number(), z.string(), DecimalJSLikeSchema])
.refine((v) => isValidDecimalInput(v), {
message: 'Field "decimal" must be a Decimal',
path: ['Models', 'DecimalModel'],
}),
});
Field validators
It is possible to add zod validators in the comments of the prisma.schema
file with the following syntax (use rich-comments ///
instead of //
).
myField [prisma-scalar-type] /// @zod.[zod-type + optional[(zod-error-messages)]].[zod validators for scalar-type]
This may look a bit cryptc so here is an example:
generator zod {
provider = "zod-prisma-types"
output = "./zod"
}
/// @zod.import(["import { myFunction } from 'mypackage';"])
model MyPrismaScalarsType {
/// @zod.string({ invalid_type_error: "some error with special chars: some + -*#'substring[]*#!§$%&/{}[]", required_error: "some other", description: "some description" }).cuid()
id String @id @default(cuid())
/// Some comment about string @zod.string.min(3, { message: "min error" }).max(10, { message: "max error" })
string String?
/// @zod.custom.use(z.string().refine((val) => validator.isBIC(val), { message: 'BIC is not valid' }))
bic String?
/// @zod.number.lt(10, { message: "lt error" }).gt(5, { message: "gt error" })
float Float
floatOpt Float?
/// @zod.number.int({ message: "error" }).gt(5, { message: "gt error" })
int Int
intOpt Int?
decimal Decimal
decimalOpt Decimal?
date DateTime @default(now())
dateOpt DateTime? /// @zod.date({ invalid_type_error: "wrong date type" }) bigInt BigInt /// @zod.bigint({ invalid_type_error: "error" })
bigIntOpt BigInt?
/// @zod.custom.use(z.lazy(() => InputJsonValue).refine((val) => myFunction(val), { message: 'Is not valid' }))
json Json
jsonOpt Json?
bytes Bytes /// @zod.custom.use(z.instanceof(Buffer).refine((val) => val ? true : false, { message: 'Value is not valid' }))
bytesOpt Bytes?
/// @zod.custom.use(z.string().refine((val) => myFunction(val), { message: 'Is not valid' }))
custom String?
exclude String? /// @zod.custom.omit(["model", "input"])
updatedAt DateTime @updatedAt
}
This example generates the following zod schema for the model in prisma/zod/index.ts
:
import { z } from 'zod';
import * as PrismaClient from '@prisma/client';
import validator from 'validator';
import { myFunction } from 'mypackage';
export const MyPrismaScalarsTypeSchema = z.object({
id: z
.string({
invalid_type_error:
"some error with special chars: some + -*#'substring[]*#!§$%&/{}[]",
required_error: 'some other',
description: 'some description',
})
.cuid(),
/**
* Some comment about string
*/
string: z
.string()
.min(3, { message: 'min error' })
.max(10, { message: 'max error' })
.nullish(),
bic: z
.string()
.refine((val) => validator.isBIC(val), { message: 'BIC is not valid' })
.nullish(),
float: z
.number()
.lt(10, { message: 'lt error' })
.gt(5, { message: 'gt error' }),
floatOpt: z.number().nullish(),
int: z.number().int({ message: 'error' }).gt(5, { message: 'gt error' }),
intOpt: z.number().int().nullish(),
decimal: z
.union([
z.number(),
z.string(),
z.instanceof(PrismaClient.Prisma.Decimal),
DecimalJSLikeSchema,
])
.refine((v) => isValidDecimalInput(v), {
message: 'Field "decimal" must be a Decimal',
path: ['Models', 'MyPrismaScalarsType'],
}),
decimalOpt: z
.union([
z.number(),
z.string(),
z.instanceof(PrismaClient.Prisma.Decimal),
DecimalJSLikeSchema,
])
.refine((v) => isValidDecimalInput(v), {
message: 'Field "decimalOpt" must be a Decimal',
path: ['Models', 'MyPrismaScalarsType'],
})
.nullish(),
date: z.coerce.date(),
dateOpt: z.coerce.date({ invalid_type_error: 'wrong date type' }).nullish(),
bigIntOpt: z.bigint().nullish(),
json: z
.lazy(() => InputJsonValue)
.refine((val) => myFunction(val), { message: 'Is not valid' }),
jsonOpt: NullableJsonValue.optional(),
bytes: z
.instanceof(Buffer)
.refine((val) => (val ? true : false), { message: 'Value is not valid' }),
bytesOpt: z.instanceof(Buffer).nullish(),
custom: z
.string()
.refine((val) => myFunction(val), { message: 'Is not valid' })
.nullish(),
// omitted: exclude: z.string().nullish(),
updatedAt: z.date(),
});
export type MyPrismaScalarsType = z.infer<typeof MyPrismaScalarsTypeSchema>;
export const MyPrismaScalarsTypeOptionalDefaultsSchema =
MyPrismaScalarsTypeSchema.merge(
z.object({
id: z
.string({
invalid_type_error:
"some error with special chars: some + -*#'substring[]*#!§$%&/{}[]",
required_error: 'some other',
description: 'some description',
})
.cuid()
.optional(),
date: z.date().optional(),
updatedAt: z.date().optional(),
}),
);
Additionally all the zod schemas for the prisma input-, enum-, filter-, orderBy-, select-, include and other necessary types are generated ready to be used in e.g. trpc
inputs.
Custom imports
To add custom imports to your validator you can add them via @zod.import([...myCustom imports as strings])
in prismas rich comments on the model definition.
For example:
/// @zod.import(["import { myFunction } from 'mypackage'"])
model MyModel {
myField String /// @zod.string().refine((val) => myFunction(val), { message: 'Is not valid' })
}
This would result in an output like:
import { myFunction } from 'mypackage';
export const MyModelSchema = z.object({
myField: z
.string()
.refine((val) => myFunction(val), { message: 'Is not valid' }),
});
Please be aware that you have to add an additional level to relative imports if you use the useMultipleFiles
option.
Custom type error messages
To add custom zod-type error messages to your validator you can add them via @zod.[key]({ ...customTypeErrorMessages }).[validator key]
. The custom error messages must adhere to the following type:
type RawCreateParams =
| {
invalid_type_error?: string;
required_error?: string;
description?: string;
}
| undefined;
For example:
model MyModel {
myField String /// @zod.string({ invalid_type_error: "invalid type error", required_error: "is required", description: "describe the error" })
}
This would result in an output like:
string: z.string({
invalid_type_error: 'invalid type error',
required_error: 'is required',
description: 'describe the error',
}),
If you use a wrong key or have a typo the generator would throw an error:
model MyModel {
myField String /// @zod.string({ required_error: "error", invalid_type_errrrrror: "error"})
}
[@zod generator error]: Custom error key 'invalid_type_errrrrror' is not valid. Please check for typos! [Error Location]: Model: 'Test', Field: 'myField'.
String validators
To add custom validators to the prisma String
field you can use the @zod.string
key. On this key you can use all string-specific validators that are mentioned in the zod-docs
. You can also add a custom error message to each validator as stated in the docs.
model MyModel {
myField String /// @zod.string.min(3, { message: "min error" }).max(10, { message: "max error" }).[...chain more validators]
}
Number validators
To add custom validators to the prisma Int
or Float
field you can use the @zod.number
key. On this key you can use all number-specific validators that are mentioned in the zod-docs
. You can also add a custom error message to each validator as stated in the docs.
model MyModel {
myField Int
/// @zod.number.lt(10, { message: "lt error" }).gt(5, { message: "gt error" }).[...chain more validators]
}
BigInt validators
To add custom validators to the prisma BigInt
field you can use the @zod.bigint
key. Due to the fact that there are no custom validators provided by zod
on z.bigint()
you can only add customized type errors to the field.
model MyModel {
myField BigInt /// @zod.bigint({ invalid_type_error: "error", ... })
}
Date validators
To add custom validators to the prisma DateTime
field you can use the @zod.date
key. On this key you can use all date-specific validators that are mentioned in the zod-docs
. You can also add a custom error message to each validator as stated in the docs.
model MyModel {
myField DateTime /// @zod.date.min(new Date('2020-01-01')).max(new Date('2020-12-31'))
}
Custom validators
To add custom validators to any Prisma Scalar
field you can use the @zod.custom.use()
key. This key has only the .use(...your custom code here)
validator. This code overwrites all other standard implementations so you have to exactly specify the zod type
how it should be written by the generator. Only .optional()
and .nullable()
are added automatically based on your prisma schema type definition. This field is inteded to provide validators like zod .refine
or .transform
on your fields.
model MyModel {
id Int @id @default(autoincrement())
custom String? /// @zod.custom.use(z.string().refine(val => validator.isBIC(val)).transform(val => val.toUpperCase()))
}
The above model schema would generate the following zod schema:
export const MyModel = z.object({
id: z.number(),
custom: z
.string()
.refine((val) => validator.isBIC(val))
.transform((val) => val.toUpperCase())
.nullable(),
});
Omit Fields
It is possible to omit fields in the generated zod schemas by using @zod.custom.omit(["model", "input"])
. When passing both keys "model"
and "input"
the field is omitted in both, the generated model schema and the generated input types (see example below). If you just want to omit the field in one of the schemas just provide the matching key. You can also write the keys without "
or '
.
model MyModel {
id Int @id @default(autoincrement())
string String? /// @zod.string.min(4).max(10)
omitField String? /// @zod.custom.omit([model, input])
omitRequired String /// @zod.custom.omit([model, input])
}
The above model would generate the following zod schemas (the omitted keys are left in the model but are commented out so you see at a glance which fields are omitted when looking on the zod schema):
// MODEL TYPES
// ---------------------------------------
export const MyModelSchema = z.object({
id: z.number(),
string: z.string().min(4).max(10).nullish(),
// omitted: omitField: z.string().nullish(),
// omitted: omitRequired: z.string(),
});
// INPUT TYPES
// ---------------------------------------
export const MyModelCreateInputSchema: z.ZodType<
Omit<PrismaClient.Prisma.MyModelCreateInput, 'omitField' | 'omitRequired'>
> = z
.object({
string: z.string().min(4).max(10).optional().nullable(),
// omitted: omitField: z.string().optional().nullable(),
// omitted: omitRequired: z.string(),
})
.strict();
export const MyModelUncheckedCreateInputSchema: z.ZodType<
Omit<
PrismaClient.Prisma.MyModelUncheckedCreateInput,
'omitField' | 'omitRequired'
>
> = z
.object({
id: z.number().optional(),
string: z.string().min(4).max(10).optional().nullable(),
// omitted: omitField: z.string().optional().nullable(),
// omitted: omitRequired: z.string(),
})
.strict();
export const MyModelUpdateInputSchema: z.ZodType<
Omit<PrismaClient.Prisma.MyModelUpdateInput, 'omitField' | 'omitRequired'>
> = z
.object({
string: z
.union([
z.string().min(4).max(10),
z.lazy(() => NullableStringFieldUpdateOperationsInputSchema),
])
.optional()
.nullable(),
// omitted: omitField: z.union([ z.string(),z.lazy(() => NullableStringFieldUpdateOperationsInputSchema) ]).optional().nullable(),
// omitted: omitRequired: z.union([ z.string(),z.lazy(() => StringFieldUpdateOperationsInputSchema) ]).optional(),
})
.strict();
// AND SO ON...
// ARG TYPES
// ---------------------------------------
// To be compatible with the inputTypes the type of the `ArgSchema` is updated accordingly
export const MyModelCreateArgsSchema: z.ZodType<
Omit<PrismaClient.Prisma.MyModelCreateArgs, 'data'> & {
data:
| z.infer<typeof MyModelCreateInputSchema>
| z.infer<typeof MyModelUncheckedCreateInputSchema>;
}
> = z
.object({
select: MyModelSelectSchema.optional(),
data: z.union([
MyModelCreateInputSchema,
MyModelUncheckedCreateInputSchema,
]),
})
.strict();
When a required
field is omitted the field needs to be added manually in the respective prisma function like create
, update
, createMany
and so on. Otherwise Typescript would complain.
const appRouter = t.router({
createMyModel: t.procedure
.input(MyModelCreateArgsSchema) // field `omitRequired` is not included in `data`
.query(({ input }) => {
return prisma.myModel.create({
...input,
data: {
...input.data,
omitRequired: 'foo', // field needs to be added manually
},
});
}),
});
Validation errors
To ease the developer experience the generator checks if the provided @zod.[key]
can be used on the respective type of the model field. It also checks if the @zod.[key].[validator]
can be used on the specified @zod.[key]
Wrong zod type
The generator throws an error if you use a validator key like @zod.string
on the wrong prisma type.
model MyModel {
string String /// @zod.string.min(3) -> valid - `string` can be used on `String`
number Number /// @zod.string.min(3) -> invalid - `string` can not be used on `Number`
}
For the above example the Error message would look like this:
[@zod generator error]: Validator 'string' is not valid for type 'Int'. [Error Location]: Model: 'MyModel', Field: 'number'
The generator provides the exact location, what went wrong and where the error happend. In big prisma schemas with hundreds of models and hundreds of custom validation strings this can come in handy.
Wrong validator
The generator throws an error if you use a validator .min
on the wrong validator key.
model MyModel {
number Int /// @zod.number.min(3) -> invalid - `min` can not be used on `number`
}
The above example would throw the following error:
[@zod generator error]: Validator 'min' is not valid for type 'Int'. [Error Location]: Model: 'MyModel', Field: 'number'.
Typo Errors
If you have typos in your validator strings like
model MyModel {
string String /// @zod.string.min(3, { mussage: 'Must be at least 3 characters' })
}
that the generator would throw the following error:
[@zod generator error]: Could not match validator 'min' with validatorPattern
'.min(3, { mussage: 'Must be at least 3 characters' })'. Please check for typos! [Error Location]: Model: 'MyModel', Field: 'string'.
Naming of zod schemas
The zod types are named after the generated prisma types with an appended "Schema"
string. You just need to hover over a prisma function and you know which type to import. This would look something like this for trpc v.10:
import {
UserFindFirstArgsSchema,
UserFindManyArgsSchema,
UserFindUniqueArgsSchema,
} from './prisma/zod';
const appRouter = t.router({
findManyUser: t.procedure.input(UserFindManyArgsSchema).query(({ input }) => {
return prisma.user.findMany(input);
}),
findUniqueUser: t.procedure
.input(UserFindUniqueArgsSchema)
.query(({ input }) => {
return prisma.user.findUnique(input);
}),
findFirstUser: t.procedure
.input(UserFindFirstArgsSchema)
.query(({ input }) => {
return prisma.user.findFirst(input);
}),
});
You can add rich-comments to your models and fields that are then printed as jsDoc in your generated zod schema.
/// comment line one
/// comment line two
model MyModel {
id Int @id @default(autoincrement())
/// comment before validator @zod.string.min(4).max(10)
/// comment after validator
string String?
}
The above model would generate the following output where the validator is extracted from the rich-comments and added to the string field:
/**
* comment line one
* comment line two
*/
export const MyModelSchema = z.object({
id: z.number(),
/**
* comment before validator
* comment after validator
*/
string: z.string().min(4).max(10).nullish(),
});
The validator is extracted from the comments and added to the string
Migration from zod-prisma
There are a few differences between zod-prisma
and zod-prisma-types
.
The following sections should help you migrate from zod-prisma
to zod-prisma-types
.
Generator options
The following generator options from zod-prisma
are not supported or implemented differently by zod-prisma-types
:
relationModel
You can generate a schema that contains all relations of a model by passing the following option to the generator:
generator zod {
// ... other options
createRelationValuesTypes = true
}
See createRelationValuesTypes
for more information.
modelCase
The casing of the model is fixed to the casing used in the prisma schema
and can not be changed. This way model names with mixed casing like MYModel
will work as expected when generating inputTypes
, enums
, argTypes
, etc.
modelSuffix
The model suffix in zod-prisma-types
is fixed to Schema
and can not be changed.
useDecimalJs
zod-prisma-types
does not support decimal.js
but uses the decimal implementation provided by prisma to validate Decimal types. See Decimal for more information.
imports
As of version 2.0.0
imports in zod-prisma-types
are handled with rich-comments on the model definition. See Custom imports for more information.
prismaJsonNullability
The nullablility in zod-prisma-types
is handled differently. See Json null values for more information.
Extending zod fields
zod-prisma
allows you to extend the zod fields with custom validators. This is also possible with zod-prisma-types
and the @zod.[key].[validator]
syntax. The different syntax is used to check if a validator can be used on a specific prisma type. See Field validators for more information.
// zod-prisma
model MyModel {
string String /// @zod.min(3) -> valid - `string` can be used on `String`
number Number /// @zod.min(3) -> valid - throws error only at runtime
}
//zod-prisma-types
model MyModel {
string String /// @zod.string.min(3) -> valid - `string` can be used on `String`
number Number /// @zod.string.min(3) -> invalid - throws error during generation
}
Importing helpers
You can import custom helpers in the generator. Please refer to the section about custom imports for more information.