zod-prisma-types 中文文档教程

发布于 2年前 浏览 14 项目主页 更新于 2年前

NPM 版本 星星 Contirbutors License Issues

zod-prisma-types

zod-prisma-typesprisma 从 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 验证器,而不是在创建的输入类型架构上使用(UserCreateInputUserUpdateManyInput等)您可以禁用此功能。

generator zod {
  // ...rest of config
  addInputTypeValidation = false
}

validateWhereUniqueInput

默认:

默认情况下,生成器不会在多文件模式下验证 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 IntFloat 字段,您可以使用 @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 键。由于 zodz.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 函数中手动添加该字段,例如createupdate 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-prismazod-prisma-types 之间存在一些差异。 以下部分应帮助您从 zod-prisma 迁移到 zod-prisma-types

生成器选项

zod-prisma-types 不支持或以不同方式实现 zod-prisma 中的以下生成器选项:

relationModel

您可以生成架构通过将以下选项传递给生成器来包含模型的所有关系:

generator zod {
  // ... other options
  createRelationValuesTypes = true
}

有关详细信息,请参阅 createRelationValuesTypes

modelCase

模型的大小写固定为 prisma schema 中使用的大小写,并且无法更改。这样,在生成 inputTypesenumsargTypes 等时,具有混合大小写的模型名称(例如 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
}

导入助手

您可以在生成器中导入自定义助手。请参阅有关自定义导入的部分以了解更多信息。

NPM version Stars Contirbutors License Issues

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!

"Buy Me A Coffee"

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.

createInputTypes

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
}

addInputTypeValidation

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
}

validateWhereUniqueInput

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.

Custom Enums

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);
    }),
});

Adding comments

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.

更多

友情链接

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文