db-seed について
db-seedは、プロジェクトのスキーマから実行可能なデータ投入スクリプトを作るスキルです。開発、デモ、テスト向けに、依存関係を考慮したデータと再実行可能な手順を用意します。
主な機能
- 01スキーマ定義とテーブル関係の読み取り
- 02Drizzle TypeScriptまたはSQLの生成
- 03外部キーに沿った挿入順の設定
- 04用途に合うデータと境界例の生成
- 05繰り返し実行できる投入方式
活用シーン
- 01開発用データベースへのデータ投入
- 02デモ用データの準備
- 03テストで制約や境界条件を確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ jezweb/ claude-skills --skill db-seedインストール後、Claude Code で/db-seedを入力して読み込まれたか確認します。
詳細
データベースシードジェネレーター
データベースに、現実的でドメインに適したサンプルデータを投入するシードスクリプトを生成します。スキーマを読み取り、そのまま実行できるシードファイルを作成します。
ワークフロー
1. スキーマを探す
プロジェクト内でスキーマ定義を検索します。
| ソース | 場所のパターン |
|---|---|
| Drizzle スキーマ | src/db/schema.ts、src/schema/*.ts、db/schema.ts |
| D1 マイグレーション | drizzle/*.sql、migrations/*.sql |
| 生の SQL | schema.sql、db/*.sql |
| Prisma | prisma/schema.prisma |
すべてのスキーマファイルを読み取ります。以下の要素を把握します。
- テーブルとそのカラム
- データ型と制約(NOT NULL、UNIQUE、DEFAULT)
- 外部キーの関係(参照先のテーブル)
- TEXT として保存される JSON フィールド(D1/SQLite で一般的)
2. シードのパラメーターを決める
ユーザーに次の項目を尋ねます。
| パラメーター | 選択肢 | デフォルト |
|---|---|---|
| 目的 | dev、demo、testing | dev |
| データ量 | small(テーブルあたり 5~10 行)、medium(20~50 行)、large(100 行以上) | small |
| ドメインのコンテキスト | 「EC ストア」、「SaaS アプリ」、「ブログ」など | スキーマから推測 |
| 出力形式 | TypeScript(Drizzle)、生の SQL、または両方 | プロジェクトの ORM に合わせる |
目的によってデータの品質が変わります。
- dev:多様なデータと、一部のエッジケース(空のフィールド、長い文字列、Unicode)
- demo:スクリーンショットやプレゼンテーションで見栄えのよい、洗練されたデータ
- testing:境界条件、重複、特殊文字を網羅する体系的なデータ
3. 挿入順を計画する
外部キーから依存関係グラフを作成します。親テーブルを子テーブルより先に挿入します。
ブログのスキーマでの順序の例:
1. users (no dependencies)
2. categories (no dependencies)
3. posts (depends on users, categories)
4. comments (depends on users, posts)
5. tags (no dependencies)
6. post_tags (depends on posts, tags)
循環依存:テーブル A が B を参照し、B が A を参照する場合は、NULL を許容する外部キーを使い、2 回に分けて挿入します(最初は NULL で挿入し、その後 UPDATE します)。
4. 現実的なデータを生成する
「test123」、「[email protected]」、「Lorem ipsum」のような汎用的なプレースホルダーは使用しないでください。ドメインに合ったデータを生成します。
データ生成パターン(外部ライブラリは不要)
名前:一般的な名前のリストをハードコードします。性別や文化的背景をさまざまに組み合わせます。
const firstNames = ['Sarah', 'James', 'Priya', 'Mohammed', 'Emma', 'Wei', 'Carlos', 'Aisha'];
const lastNames = ['Chen', 'Smith', 'Patel', 'Garcia', 'Kim', 'O\'Brien', 'Nguyen', 'Wilson'];
メールアドレス:名前から生成します — [email protected]。ドメインには RFC 2606 で予約された example.com を使います。
日付:現実的な範囲内で生成します。D1/SQLite では ISO 8601 形式を使います。
const randomDate = (daysBack: number) => {
const d = new Date();
d.setDate(d.getDate() - Math.floor(Math.random() * daysBack));
return d.toISOString();
};
ID:UUID には crypto.randomUUID() を使い、スキーマで自動インクリメントを使用している場合は連番の整数を使います。
決定論的なシード:データを再現可能にするには、シード付き PRNG を使います。
function seededRandom(seed: number) {
return () => {
seed = (seed * 16807) % 2147483647;
return (seed - 1) / 2147483646;
};
}
const rand = seededRandom(42); // Same seed = same data every time
価格/金額:現実的な範囲を使います。$1~$10 の範囲には (rand() * 900 + 100).toFixed(2) を使います。
説明/コンテンツ:コンテンツの種類ごとに現実的なバリエーションを 3~5 個作成し、繰り返し使います。AI が書いたような文章は生成せず、実際のユーザーデータのように書きます。
5. 出力形式
TypeScript(Drizzle ORM)
// scripts/seed.ts
import { drizzle } from 'drizzle-orm/d1';
import * as schema from '../src/db/schema';
export async function seed(db: ReturnType<typeof drizzle>) {
console.log('Seeding database...');
// Clear existing data (reverse dependency order)
await db.delete(schema.comments);
await db.delete(schema.posts);
await db.delete(schema.users);
// Insert users
const users = [
{ id: crypto.randomUUID(), name: 'Sarah Chen', email: '[email protected]', ... },
// ...
];
// D1 batch limit: 10 rows per INSERT
for (let i = 0; i < users.length; i += 10) {
await db.insert(schema.users).values(users.slice(i, i + 10));
}
// Insert posts (references users)
const posts = [
{ id: crypto.randomUUID(), userId: users[0].id, title: '...', ... },
// ...
];
for (let i = 0; i < posts.length; i += 10) {
await db.insert(schema.posts).values(posts.slice(i, i + 10));
}
console.log(`Seeded: ${users.length} users, ${posts.length} posts`);
}
次のコマンドで実行します:npx tsx scripts/seed.ts
Cloudflare Workers の場合は、シード用エンドポイントを追加します(本番環境にデプロイする前に削除してください)。
app.post('/api/seed', async (c) => {
const db = drizzle(c.env.DB);
await seed(db);
return c.json({ ok: true });
});
生の SQL(D1)
-- seed.sql
-- Run: npx wrangler d1 execute DB_NAME --local --file=./scripts/seed.sql
-- Clear existing (reverse order)
DELETE FROM comments;
DELETE FROM posts;
DELETE FROM users;
-- Users
INSERT INTO users (id, name, email, created_at) VALUES
('uuid-1', 'Sarah Chen', '[email protected]', '2025-01-15T10:30:00Z'),
('uuid-2', 'James Wilson', '[email protected]', '2025-02-01T14:22:00Z');
-- Posts (max 10 rows per INSERT for D1)
INSERT INTO posts (id, user_id, title, body, created_at) VALUES
('post-1', 'uuid-1', 'Getting Started', 'Welcome to...', '2025-03-01T09:00:00Z');
6. 冪等性
シードスクリプトは安全に再実行できる必要があります。
// Option A: Delete-then-insert (simple, loses data)
await db.delete(schema.users);
await db.insert(schema.users).values(seedUsers);
// Option B: Upsert (preserves non-seed data)
for (const user of seedUsers) {
await db.insert(schema.users)
.values(user)
.onConflictDoUpdate({ target: schema.users.id, set: user });
}
dev/testing ではオプション A、ユーザーが独自のデータを追加する場合がある demo ではオプション B をデフォルトにします。
D1 固有の注意点
| 注意点 | 解決策 |
|---|---|
| INSERT 1 回あたりの上限は約 10 行 | 10 行ずつに分けて一括挿入する |
| ネイティブ BOOLEAN 型がない | INTEGER(0/1)を使う |
| ネイティブ DATETIME 型がない | ISO 8601 形式の文字列を TEXT として使う |
| JSON は TEXT として保存される | 挿入前に JSON.stringify() を使う |
| 外部キーは常に有効 | 親テーブルを先に挿入する |
| バインドパラメーターの上限は 100 | 一括処理の行数 × カラム数を 100 未満にする |
品質ルール
- ドメインに合わせる — EC のシードデータには、「Product 1」ではなく、実在しそうな名前や価格の商品を含めます。
- データに変化をつける — すべてのユーザーを「John Smith」にしたり、すべての価格を「$9.99」にしたりしないでください。
- エッジケースを含める(シードデータのテスト用) — 空文字列、非常に長いテキスト、特殊文字、最大値を含めます。
- 実在する ID を参照する — 外部キーは、実際に挿入された親レコードを参照する必要があります。
- シードしたデータを出力する — 正常に動作したことがユーザーにわかるよう、必ず件数をログに記録します。
- 実行コマンドを記載する — ファイルの先頭にあるコメントに記載します。