drizzle-valibot 实战指南:从 Drizzle ORM Schema 自动生成 Valibot 校验 Schema

发布时间:2026/9/19 23:12:39
drizzle-valibot 实战指南:从 Drizzle ORM Schema 自动生成 Valibot 校验 Schema drizzle-valibot 实战指南从 Drizzle ORM Schema 自动生成 Valibot 校验 Schema【免费下载链接】drizzle-ormORM项目地址: https://gitcode.com/gh_mirrors/dr/drizzle-orm导读本文围绕 Drizzle ORM 官方插件drizzle-valibot讲解如何从 Drizzle ORM 的表、视图与枚举定义自动生成 valibot 运行时校验 Schema用于 API 请求参数插入/更新与响应数据查询的校验。阅读本文后你将掌握createSelectSchema/createInsertSchema/createUpdateSchema三个核心 API 的用法、字段覆盖与精炼refine技巧并理解底层列类型 → valibot Schema的映射规则、可空/可选/默认值推导逻辑以及各数据库方言的支持范围。drizzle-valibot 是什么drizzle-valibot是 Drizzle ORM 官方维护的插件位于仓库的 drizzle-valibot 目录npm 包版本 0.4.2其职责是从 Drizzle ORM 的 Schema 定义自动生成 valibot 校验 Schema从而消除数据库表定义与运行时数据校验规则之间的手工重复维护。它提供的核心能力见 README为表、视图和枚举生成select查询Schema为表生成insert插入与update更新Schema支持全部方言PostgreSQL、MySQL 与 SQLite。从 package.json 可以确认其安装前提drizzle-orm 0.36.0valibot 1.0.0-beta.7并且通过exports字段同时提供了 ESM.mjs/.d.mts与 CJS.cjs/.d.cjs产物兼容两种模块体系的项目。快速上手从一张表生成三种 Schema在 README 给出的标准用法中先定义一张 PostgreSQL 用户表然后即可一次性得到插入、更新、查询三种 valibot Schemaimport { pgEnum, pgTable, serial, text, timestamp } from drizzle-orm/pg-core; import { createInsertSchema, createSelectSchema } from drizzle-valibot; import { string, parse, number, pipe } from valibot; const users pgTable(users, { id: serial(id).primaryKey(), name: text(name).notNull(), email: text(email).notNull(), role: text(role, { enum: [admin, user] }).notNull(), createdAt: timestamp(created_at).notNull().defaultNow(), }); // Schema for inserting a user - can be used to validate API requests const insertUserSchema createInsertSchema(users); // Schema for updating a user - can be used to validate API requests const updateUserSchema createUpdateSchema(users); // Schema for selecting a user - can be used to validate API responses const selectUserSchema createSelectSchema(users); // Usage const isUserValid parse(insertUserSchema, { name: John Doe, email: johndoetest.com, role: admin, });注意示例中实际还使用了createUpdateSchema在 schema.ts 中有完整实现并在 pg.test.ts 中有对应测试README 的导入语句中未列出它但你只需从drizzle-valibot一并导入即可。生成的三个 Schema 行为不同这正是插件最有价值的地方insertUserSchema用于校验新增一条记录时提交的请求体updateUserSchema用于校验更新记录时的请求体selectUserSchema用于校验从数据库查询返回的结果API 响应。字段的默认推导规则nullable / optional / 剔除生成结果并非简单地把列类型一比一翻译而是根据列修饰符推导出精确的可选性。核心逻辑集中在 schema.ts 的handleColumns与三个工厂函数的conditions配置中export const createSelectSchema (entity, refine?) { // ... return handleColumns(columns, refine ?? {}, { never: () false, optional: () false, nullable: (column) !column.notNull, }); }; export const createInsertSchema (entity, refine?) { return handleColumns(columns, refine ?? {}, { never: (column) column?.generated?.type always || column?.generatedIdentity?.type always, optional: (column) !column.notNull || (column.notNull column.hasDefault), nullable: (column) !column.notNull, }); }; export const createUpdateSchema (entity, refine?) { return handleColumns(columns, refine ?? {}, { never: (column) column?.generated?.type always || column?.generatedIdentity?.type always, optional: () true, nullable: (column) !column.notNull, }); };推导规则可总结如下Schema 类型剔除never可选optional可空nullableselect无永不列非notNull时insertgenerated always/generatedIdentity always的列非notNull的列以及有默认值的notNull列列非notNull时update同上生成列所有列列非notNull时这些行为在 pg.test.ts 中有精确的类型级断言例如test(table - insert, (t) { const table pgTable(test, { id: integer().generatedAlwaysAsIdentity().primaryKey(), name: text().notNull(), age: integer(), }); const result createInsertSchema(table); const expected v.object({ name: textSchema, age: v.optional(v.nullable(integerSchema)) }); expectSchemaShape(t, expected).from(result); ExpectEqualtypeof result, typeof expected(); });可以看到generatedAlwaysAsIdentity的id被直接剔除无需也不应手动传入notNull且无默认值的name成为必填项可空的age变成v.optional(v.nullable(...))。update场景下所有字段都变为可选name: v.optional(textSchema)、age: v.optional(v.nullable(integerSchema))因为部分更新天然允许只传少数字段。覆盖与精炼按需调整字段自动生成的 Schema 未必完全符合业务要求drizzle-valibot为每个工厂函数都提供了第二个参数refine支持两种调整方式README1. 直接覆盖字段以 valibot Schema 替换const insertUserSchema createInsertSchema(users, { role: string(), });此时role列原本由text(role, { enum: [admin, user] })推导出的枚举校验会被替换为宽松的string()。2. 精炼字段对生成的 Schema 再做管道处理const insertUserSchema createInsertSchema(users, { id: (schema) pipe([schema, minValue(0)]), role: string(), });精炼回调接收当前列已生成的 valibot Schema 作为输入返回经过pipe追加约束后的新 Schema——注意 README 注释强调精炼发生在字段被处理为 nullable/optional 之前因此你是在原始列类型的基础上追加规则非常适合补充minValue、maxLength、email、regex等业务约束。从类型层面看schema.types.internal.ts 的NoUnknownKeysrefine对象中不允许出现不存在的列名——如果误写了未知键会触发DrizzleTypeErrorFound unknown key in refinement: xxx从而在编译期就拦截拼写错误。列类型到 valibot Schema 的映射原理自动生成的核心是 column.ts 中的columnToSchema函数它按列的类型元信息column.columnType/column.dataType分派枚举列列带enumValues时生成v.enum(mapEnumValues(column.enumValues))通过mapEnumValues将字符串数组映射为同名键值对象否则退化为v.string()数值列走numberColumnToSchema依据具体列类型套用最小/最大值常量定义在 constants.ts并视类型追加v.integer()MySqlTinyInt/SingleStoreTinyInt→ INT8 范围-128 ~ 127unsigned 0 ~ 255PgSmallInt/MySqlSmallInt/SingleStoreSmallInt/PgSmallSerial→ INT16 范围PgInteger/PgSerial/MySqlInt/SingleStoreInt→ INT32 范围PgDoublePrecision/MySqlDouble/MySqlReal/SQLiteReal等 → INT48 范围PgBigInt53/MySqlBigInt53/SQLiteInteger等 →Number.MIN_SAFE_INTEGER~Number.MAX_SAFE_INTEGERMySqlYear/SingleStoreYear→ 1901 ~ 2155MySQL/SingleStore 的unsigned列会从 0 起算通过column.getSQLType().includes(unsigned)判断MySqlSerial等自增类型也按无符号处理。bigint 列dataType bigint走bigintColumnToSchema生成v.pipe(v.bigint(), v.minValue(INT64_MIN), v.maxValue(INT64_MAX))使用BigInt字面量参与比较布尔列→v.boolean()日期列→v.date()字符串列走stringColumnToSchema包含如下精细化规则PgUUID→v.pipe(v.string(), v.uuid())PgVarchar/SQLiteText→ 按column.length追加v.maxLengthMySqlVarChar/SingleStoreVarChar→ 未指定长度时上限取INT16_UNSIGNED_MAX65535MySqlText/SingleStoreText→ 按textTypetinytext/text/mediumtext/longtext分别映射 255 / 65535 / 16777215 / 4294967295PgChar/MySqlChar/SingleStoreChar定长→ 使用v.length(max)精确匹配PgBinaryVector→ 追加/^[01]$/正则校验并限制长度为维度数。JSON 列→ 导出并复用jsonSchemav.union([literalSchema, v.array(v.any()), v.record(v.string(), v.any())])其中literalSchema v.union([v.string(), v.number(), v.boolean(), v.null()])buffer 列→bufferSchema基于v.custom的instanceof Buffer检查PostgreSQL 空间类型PgGeometry/PgPointTuple→v.tuple([v.number(), v.number()])PgPointObject/PgGeometryObject→v.object({ x: v.number(), y: v.number() })PgLine元组形式→ 三元组 tuplePgLineABC→{ a, b, c }对象PgVector/PgHalfVector→v.array(v.number())有维度时追加v.length(dimensions)。数组列PgArray递归处理baseColumn生成v.array(...)带size时追加v.length(size)其他dataType array退化为v.array(v.any())未知类型兜底v.any()。这种映射同时存在于运行时columnToSchema与类型层column.types.ts 的GetValibotType、HandleSelectColumn/HandleInsertColumn/HandleUpdateColumn等并通过ExpectEqualtypeof result, typeof expected()之类的测试保证运行时结果与静态类型严格一致——这正是类型安全校验体验的来源。视图与枚举Select Schema 的额外来源createSelectSchema的入参类型schema.types.ts不仅支持Table还支持View与PostgreSQL 枚举传入视图时插件通过getViewSelectedFields获取视图的选中字段并同样生成v.object(...)且类型基于TView[$inferSelect]底层对嵌套对象字段子查询选择集也会递归处理见 schema.ts 的handleColumns递归分支。测试覆盖了pgView、pgMaterializedView等场景见 pg.test.ts传入pgEnum枚举时直接生成v.enum(...)const roleEnum pgEnum(role, [admin, user]); const roleSchema createSelectSchema(roleEnum); // v.enum({ admin: admin, user: user })其运行时判定依据是 utils.ts 中的isPgEnum检测enumValues是否为非空字符串数组对应类型为v.EnumSchema...。源码模块速览想要深入阅读实现可以按以下文件脉络展开文件职责drizzle-valibot/src/schema.ts三个工厂函数与handleColumns核心循环drizzle-valibot/src/column.ts列类型 → valibot Schema 的映射数值/字符串/bigint/JSON/bufferdrizzle-valibot/src/constants.ts各整数类型的 min/max 常量INT8 ~ INT64drizzle-valibot/src/schema.types.tsCreateSelectSchema/CreateInsertSchema/CreateUpdateSchema的类型签名drizzle-valibot/src/schema.types.internal.tsBuildSchema/NoUnknownKeys等类型构建工具drizzle-valibot/src/column.types.tsGetValibotType/HandleColumn类型级映射drizzle-valibot/src/utils.tsisColumnType/isWithEnum/Json等辅助工具drizzle-valibot/src/index.ts包对外导出入口drizzle-valibot/tests/pg.test.ts行为与类型双重测试pg 方言drizzle-valibot/tests/mysql.test.ts 等MySQL / SQLite / SingleStore 方言测试drizzle-valibot/package.json版本、peerDependencies、构建产物信息使用建议与注意事项依赖版本drizzle-valibot要求drizzle-orm 0.36.0且valibot 1.0.0-beta.7当前仓库内测试固定使用valibot 1.0.0-beta.7见 package.json安装前请确认项目版本满足要求。方言差异由列类型天然表达PostgreSQL 的pgEnum、PgVector、PgUUIDMySQL 的unsigned与textTypeSQLite 的integer/real等都会被翻译为对应的 valibot 约束无需为方言编写额外适配代码。善用第二个参数业务级校验如minValue(0)、邮箱格式、正则、自定义精炼应放在refine回调中追加到自动生成的 Schema 之上既保留数据库约束又避免手工重复定义整个 Schema。不要在refine中使用未知字段名类型系统会通过NoUnknownKeys报错编译期即可发现问题。select 与 insert/update 用途要区分查询响应校验不应对字段做 optional 化createSelectSchema的 optional 恒为 false而请求体校验则应使用 insert/update 版本以获得正确的默认值推导。综上drizzle-valibot把数据库表结构变成了唯一的校验规则事实来源定义一次 Drizzle Schema即可同时获得类型安全的查询结果与请求体校验 Schema适合任何以 Drizzle ORM 为数据层的 TypeScript 服务端项目。【免费下载链接】drizzle-ormORM项目地址: https://gitcode.com/gh_mirrors/dr/drizzle-orm创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考