mcpField
D
drizzle-orm

drizzle-orm

github.com/epicenterhq数据与数据库4.8k84 次安装2 周前更新
代码仓库

关于 drizzle-orm

面向使用 Drizzle ORM 的应用仓库技能,限定其数据库为 PostgreSQL,并规范 Schema、迁移、类型化查询、驱动边界和类型处理。

主要功能

  1. 01保持 Schema 定义可静态检查
  2. 02结合快照审查迁移 SQL
  3. 03优先使用类型化查询构建器
  4. 04在边界校验外部输入
  5. 05为品牌类型选择无运行时转换的写法

适用场景

  1. 01新增或修改 PostgreSQL Schema 与迁移
  2. 02构建类型化应用查询
  3. 03选择 $type () 或 customType

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/epicenterhq/epicenter --skill drizzle-orm

安装后在 Claude Code 中输入/drizzle-orm确认已加载。

详细介绍

Drizzle ORM 指南

参考仓库

  • Drizzle ORM:类似 SQL 的查询构建器 TypeScript ORM

Drizzle 在这里使用的数据库只有 Postgres:apps/api/drizzle.config.ts 声明了 dialect: 'postgresql',且唯一导入的方言是 pg-core。仓库中的其他 SQLite 引擎都不是 Drizzle,因此关于 SQLite 的问题通常与它们有关。

基于上游资料

当 Drizzle 模式定义、迁移快照、查询构建器 API、列类型、自定义类型或驱动集成会影响正确性时,在依赖记忆前,先基于源代码或文档进行核实。如果有 DeepWiki MCP 可用,针对 drizzle-team/drizzle-orm 提出一个范围明确的问题。如果 DeepWiki 不可用或未收录该仓库,请直接查阅上游源代码或官方文档。将 DeepWiki 作为了解方向的参考,然后在修改代码前,对关键细节通过本地已安装的类型、生成的迁移、源代码、驱动版本或官方文档进行核实。

对于下文已记录的仓库本地模式命名和存储边界约定,无需查询 DeepWiki。

模式与迁移规则

  • 从应用的数据库模块导出一个模式对象,并将同一个对象传递给 drizzle(...) 和 Drizzle Kit 配置。
  • 明确声明表定义、关系和模式导出。避免使用 Drizzle Kit 无法静态检查的动态模式构造。
  • 将 Drizzle Kit 快照视为差异来源的事实依据。一起审查生成的 SQL 和快照变更。
  • 每个数据库只选择一种命名大小写策略。除非由边界负责映射,否则不要混用应用层 camelCase 和临时 SQL 别名。
  • 当外部输入在 IO 边界转换为行时,使用 drizzle-zod、drizzle-valibot 或本地模式解析器。不要将推断出的插入类型视为运行时验证。

查询构建器规则

  • 应用查询优先使用类型化查询构建器。仅在查询构建器无法简洁表达某个表达式时才使用原始 SQL。
  • 将联接和选取的数据形状放在负责响应契约的调用方附近。
  • 在模式定义中,为需要相应查询模式的表添加索引。

驱动边界

  • bun:sqlite 和 better-sqlite3 是本地同步 SQLite 驱动。不要在 Cloudflare Workers 中使用它们。
  • D1 是具有 Worker 特定行为的 Cloudflare 绑定。将其限制在 Worker 代码和生成的绑定中。

对品牌字符串使用 $type(),而非 customType

需要使用品牌化 TypeScript 类型的列,但不需要实际转换数据时,请使用 $type<T>(),而不是 customType。

规则

如果 toDriver 和 fromDriver 都会是恒等函数 (x) => x,请改用 $type<T>()。

原因

即使函数是恒等函数,customType 仍会对每一行调用 mapFromDriverValue:

// drizzle-orm/src/utils.ts - runs for EVERY column of EVERY row
const rawValue = row[columnIndex]!;
const value = rawValue === null ? null : decoder.mapFromDriverValue(rawValue);

查询 1000 行、包含 3 个日期列,就会产生 3000 次无用的函数调用。

不好的模式
// Runtime overhead for identity functions
customType<{ data: DateTimeString; driverParam: DateTimeString }>({
	dataType: () => 'text',
	toDriver: (value) => value, // called on every write
	fromDriver: (value) => value, // called on every read
});
好的模式
// Zero runtime overhead - pure type assertion
text().$type<DateTimeString>();

$type<T>() 只在编译时覆盖类型:

// drizzle-orm/src/column-builder.ts
$type<TType>(): $Type<this, TType> {
  return this as $Type<this, TType>;
}
何时使用 customType

仅当数据在应用与数据库之间确实会发生转换时使用:

// JSON: object ↔ string - actual transformation
customType<{ data: UserPrefs; driverParam: string }>({
	toDriver: (value) => JSON.stringify(value),
	fromDriver: (value) => JSON.parse(value),
});

在中间表示中保留数据

优先让数据在系统中以序列化形式(字符串)流转,只在边缘(UI 组件)进行解析。

原则:如果数据以序列化形式进入并以序列化形式离开,就让它在中间过程保持序列化。在确实需要丰富表示的边缘进行解析。

示例:DateTimeString

不要在数据库层将 DateTimeString 解析为 Temporal.ZonedDateTime:

// Bad: parse on every read, re-serialize at API boundaries
customType<{ data: Temporal.ZonedDateTime; driverParam: string }>({
	fromDriver: (value) => fromDateTimeString(value),
});

保留字符串格式,直到 UI 确实需要它:

// Good: string stays string, parse only in date-picker component
text().$type<DateTimeString>();

// In UI component:
const temporal = fromDateTimeString(row.createdAt);
// After edit:
const updated = toDateTimeString(temporal);

评论

支持 Markdown