drizzle-orm について
Drizzle ORM を使うアプリケーションリポジトリ向けのスキルです。対象データベースを PostgreSQL とし、スキーマ、マイグレーション、型付きクエリ、ドライバー境界、型の扱いを定めます。
主な機能
- 01静的解析可能なスキーマ定義
- 02スナップショットと移行 SQL の確認
- 03型付きクエリビルダーを優先
- 04境界で外部入力を検証
- 05実行時変換なしでブランド型を扱う
活用シーン
- 01PostgreSQL のスキーマや移行を追加・変更
- 02型付きアプリケーションクエリを作成
- 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 エイリアスを混在させないでください。
- 外部入力が行になる境界では、
drizzle-zod、drizzle-valibot、またはローカルのスキーマパーサーを使用してください。推論された挿入型をランタイム検証として扱わないでください。
クエリビルダーのルール
- アプリケーションのクエリには、型付きクエリビルダーを優先して使用してください。クエリビルダーで適切に表現できない式に限り、生 SQL を使用してください。
- JOIN と選択するデータの形状は、レスポンスの契約を担う呼び出し元の近くに配置してください。
- 必要とするクエリパターンのスキーマ内にインデックスを追加してください。
ドライバーの境界
bun:sqliteとbetter-sqlite3はローカルで同期的に動作する SQLite ドライバーです。Cloudflare Workers では使用しないでください。- D1 は Worker 固有の動作を持つ Cloudflare バインディングです。Worker のコードと生成されたバインディングの内側に配置してください。
ブランド付き文字列には customType ではなく $type() を使用する
実際のデータ変換を行わずに、ブランド付き TypeScript 型のカラムが必要な場合は、customType ではなく $type<T>() を使用してください。
ルール
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);
日付カラムが 3 つある行を 1000 件クエリすると、何も行わない関数呼び出しが 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);