{"id":"db-seed","name":"db-seed","summary":"リアルなサンプルデータでデータベースシードスクリプトを生成します。DrizzleスキーマやSQLの移行を読み込み、外部キーの順序を尊重し、冪等のTypeScriptやSQLシードファイルを生成します。","body":"# Database Seed Generator\n\nGenerate seed scripts that populate databases with realistic, domain-appropriate sample data. Reads your schema and produces ready-to-run seed files.\n\n## Workflow\n\n### 1. Find the Schema\n\nScan the project for schema definitions:\n\n| Source | Location pattern |\n|--------|-----------------|\n| Drizzle schema | `src/db/schema.ts`, `src/schema/*.ts`, `db/schema.ts` |\n| D1 migrations | `drizzle/*.sql`, `migrations/*.sql` |\n| Raw SQL | `schema.sql`, `db/*.sql` |\n| Prisma | `prisma/schema.prisma` |\n\nRead all schema files. Build a mental model of:\n- Tables and their columns\n- Data types and constraints (NOT NULL, UNIQUE, DEFAULT)\n- Foreign key relationships (which tables reference which)\n- JSON fields stored as TEXT (common in D1/SQLite)\n\n### 2. Determine Seed Parameters\n\nAsk the user:\n\n| Parameter | Options | Default |\n|-----------|---------|---------|\n| Purpose | dev, demo, testing | dev |\n| Volume | small (5-10 rows/table), medium (20-50), large (100+) | small |\n| Domain context | \"e-commerce store\", \"SaaS app\", \"blog\", etc. | Infer from schema |\n| Output format | TypeScript (Drizzle), raw SQL, or both | Match project's ORM |\n\n**Purpose affects data quality**:\n- **dev**: Varied data, some edge cases (empty fields, long strings, unicode)\n- **demo**: Polished data that looks good in screenshots and presentations\n- **testing**: Systematic data covering boundary conditions, duplicates, special characters\n\n### 3. Plan Insert Order\n\nBuild a dependency graph from foreign keys. Insert parent tables before children.\n\nExample order for a blog schema:\n```\n1. users        (no dependencies)\n2. categories   (no dependencies)\n3. posts        (depends on users, categories)\n4. comments     (depends on users, posts)\n5. tags         (no dependencies)\n6. post_tags    (depends on posts, tags)\n```\n\n**Circular dependencies**: If table A references B and B references A, use nullable foreign keys and insert in two passes (insert with NULL, then UPDATE).\n\n### 4. Generate Realistic Data\n\n**Do NOT use generic placeholders** like \"test123\", \"foo@bar.com\", or \"Lorem ipsum\". Generate data that matches the domain.\n\n#### Data Generation Patterns (no external libraries needed)\n\n**Names**: Use a hardcoded list of common names. Mix genders and cultural backgrounds.\n```typescript\nconst firstNames = ['Sarah', 'James', 'Priya', 'Mohammed', 'Emma', 'Wei', 'Carlos', 'Aisha'];\nconst lastNames = ['Chen', 'Smith', 'Patel', 'Garcia', 'Kim', 'O\\'Brien', 'Nguyen', 'Wilson'];\n```\n\n**Emails**: Derive from names — `sarah.chen@example.com`. Use `example.com` domain (RFC 2606 reserved).\n\n**Dates**: Generate within a realistic range. Use ISO 8601 format for D1/SQLite.\n```typescript\nconst randomDate = (daysBack: number) => {\n  const d = new Date();\n  d.setDate(d.getDate() - Math.floor(Math.random() * daysBack));\n  return d.toISOString();\n};\n```\n\n**IDs**: Use `crypto.randomUUID()` for UUIDs, or sequential integers if the schema uses auto-increment.\n\n**Deterministic seeding**: For reproducible data, use a seeded PRNG:\n```typescript\nfunction seededRandom(seed: number) {\n  return () => {\n    seed = (seed * 16807) % 2147483647;\n    return (seed - 1) / 2147483646;\n  };\n}\nconst rand = seededRandom(42); // Same seed = same data every time\n```\n\n**Prices/amounts**: Use realistic ranges. `(rand() * 900 + 100).toFixed(2)` for $1-$10 range.\n\n**Descriptions/content**: Write 3-5 realistic variations per content type and cycle through them. Don't generate AI-sounding prose — write like real user data.\n\n### 5. Output Format\n\n#### TypeScript (Drizzle ORM)\n\n```typescript\n// scripts/seed.ts\nimport { drizzle } from 'drizzle-orm/d1';\nimport * as schema from '../src/db/schema';\n\nexport async function seed(db: ReturnType<typeof drizzle>) {\n  console.log('Seeding database...');\n\n  // Clear existing data (reverse dependency order)\n  await db.delete(schema.comments);\n  await db.delete(schema.posts);\n  await db.delete(schema.users);\n\n  // Insert users\n  const users = [\n    { id: crypto.randomUUID(), name: 'Sarah Chen', email: 'sarah@example.com', ... },\n    // ...\n  ];\n\n  // D1 batch limit: 10 rows per INSERT\n  for (let i = 0; i < users.length; i += 10) {\n    await db.insert(schema.users).values(users.slice(i, i + 10));\n  }\n\n  // Insert posts (references users)\n  const posts = [\n    { id: crypto.randomUUID(), userId: users[0].id, title: '...', ... },\n    // ...\n  ];\n\n  for (let i = 0; i < posts.length; i += 10) {\n    await db.insert(schema.posts).values(posts.slice(i, i + 10));\n  }\n\n  console.log(`Seeded: ${users.length} users, ${posts.length} posts`);\n}\n```\n\nRun with: `npx tsx scripts/seed.ts`\n\nFor Cloudflare Workers, add a seed endpoint (remove before production):\n```typescript\napp.post('/api/seed', async (c) => {\n  const db = drizzle(c.env.DB);\n  await seed(db);\n  return c.json({ ok: true });\n});\n```\n\n#### Raw SQL (D1)\n\n```sql\n-- seed.sql\n-- Run: npx wrangler d1 execute DB_NAME --local --file=./scripts/seed.sql\n\n-- Clear existing (reverse order)\nDELETE FROM comments;\nDELETE FROM posts;\nDELETE FROM users;\n\n-- Users\nINSERT INTO users (id, name, email, created_at) VALUES\n  ('uuid-1', 'Sarah Chen', 'sarah@example.com', '2025-01-15T10:30:00Z'),\n  ('uuid-2', 'James Wilson', 'james@example.com', '2025-02-01T14:22:00Z');\n\n-- Posts (max 10 rows per INSERT for D1)\nINSERT INTO posts (id, user_id, title, body, created_at) VALUES\n  ('post-1', 'uuid-1', 'Getting Started', 'Welcome to...', '2025-03-01T09:00:00Z');\n```\n\n### 6. Idempotency\n\nSeed scripts must be safe to re-run:\n\n```typescript\n// Option A: Delete-then-insert (simple, loses data)\nawait db.delete(schema.users);\nawait db.insert(schema.users).values(seedUsers);\n\n// Option B: Upsert (preserves non-seed data)\nfor (const user of seedUsers) {\n  await db.insert(schema.users)\n    .values(user)\n    .onConflictDoUpdate({ target: schema.users.id, set: user });\n}\n```\n\nDefault to Option A for dev/testing, Option B for demo (where users may have added their own data).\n\n## D1-Specific Gotchas\n\n| Gotcha | Solution |\n|--------|----------|\n| Max ~10 rows per INSERT | Batch inserts in chunks of 10 |\n| No native BOOLEAN | Use INTEGER (0/1) |\n| No native DATETIME | Use TEXT with ISO 8601 strings |\n| JSON stored as TEXT | `JSON.stringify()` before insert |\n| Foreign keys always enforced | Insert parent tables first |\n| 100 bound parameter limit | Keep batch size × columns < 100 |\n\n## Quality Rules\n\n1. **Match the domain** — an e-commerce seed has products with real-sounding names and prices, not \"Product 1\"\n2. **Vary the data** — don't make every user \"John Smith\" or every price \"$9.99\"\n3. **Include edge cases** (for testing seeds) — empty strings, very long text, special characters, maximum values\n4. **Reference real IDs** — foreign keys must point to actually-inserted parent rows\n5. **Print what was seeded** — always log counts so the user knows it worked\n6. **Document the run command** — put it in a comment at the top of the file","author":"@jezweb","ownerProfile":null,"authorContacts":null,"sourceUrl":"https://github.com/jezweb/claude-skills/tree/main/plugins/cloudflare/skills/db-seed","license":"MIT","category":"writing","lang":"en","tokens":1829,"stars":0,"calls30d":1,"claimed":false,"visibility":"public","origin":"crawler","version":"0.1.0","createdAt":"2026-08-22","updatedAt":"2026-08-22","files":[],"requires":{"mcp":[],"tools":["Read","Write","Edit","Glob","Grep","Bash"]},"safety":{"flags":[],"scannedAt":"2026-08-22","hasScripts":false,"networkEndpoints":[]}}