$npx -y skills add jezweb/claude-skills --skill db-seedGenerate database seed scripts with realistic sample data. Reads Drizzle schemas or SQL migrations, respects foreign key ordering, produces idempotent TypeScript or SQL seed files. Handles D1 batch limits, unique constraints, and domain-appropriate data. Use when populating dev/d
| 1 | # Database Seed Generator |
| 2 | |
| 3 | Generate seed scripts that populate databases with realistic, domain-appropriate sample data. Reads your schema and produces ready-to-run seed files. |
| 4 | |
| 5 | ## Workflow |
| 6 | |
| 7 | ### 1. Find the Schema |
| 8 | |
| 9 | Scan the project for schema definitions: |
| 10 | |
| 11 | | Source | Location pattern | |
| 12 | |--------|-----------------| |
| 13 | | Drizzle schema | `src/db/schema.ts`, `src/schema/*.ts`, `db/schema.ts` | |
| 14 | | D1 migrations | `drizzle/*.sql`, `migrations/*.sql` | |
| 15 | | Raw SQL | `schema.sql`, `db/*.sql` | |
| 16 | | Prisma | `prisma/schema.prisma` | |
| 17 | |
| 18 | Read all schema files. Build a mental model of: |
| 19 | - Tables and their columns |
| 20 | - Data types and constraints (NOT NULL, UNIQUE, DEFAULT) |
| 21 | - Foreign key relationships (which tables reference which) |
| 22 | - JSON fields stored as TEXT (common in D1/SQLite) |
| 23 | |
| 24 | ### 2. Determine Seed Parameters |
| 25 | |
| 26 | Ask the user: |
| 27 | |
| 28 | | Parameter | Options | Default | |
| 29 | |-----------|---------|---------| |
| 30 | | Purpose | dev, demo, testing | dev | |
| 31 | | Volume | small (5-10 rows/table), medium (20-50), large (100+) | small | |
| 32 | | Domain context | "e-commerce store", "SaaS app", "blog", etc. | Infer from schema | |
| 33 | | Output format | TypeScript (Drizzle), raw SQL, or both | Match project's ORM | |
| 34 | |
| 35 | **Purpose affects data quality**: |
| 36 | - **dev**: Varied data, some edge cases (empty fields, long strings, unicode) |
| 37 | - **demo**: Polished data that looks good in screenshots and presentations |
| 38 | - **testing**: Systematic data covering boundary conditions, duplicates, special characters |
| 39 | |
| 40 | ### 3. Plan Insert Order |
| 41 | |
| 42 | Build a dependency graph from foreign keys. Insert parent tables before children. |
| 43 | |
| 44 | Example order for a blog schema: |
| 45 | ``` |
| 46 | 1. users (no dependencies) |
| 47 | 2. categories (no dependencies) |
| 48 | 3. posts (depends on users, categories) |
| 49 | 4. comments (depends on users, posts) |
| 50 | 5. tags (no dependencies) |
| 51 | 6. post_tags (depends on posts, tags) |
| 52 | ``` |
| 53 | |
| 54 | **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). |
| 55 | |
| 56 | ### 4. Generate Realistic Data |
| 57 | |
| 58 | **Do NOT use generic placeholders** like "test123", "foo@bar.com", or "Lorem ipsum". Generate data that matches the domain. |
| 59 | |
| 60 | #### Data Generation Patterns (no external libraries needed) |
| 61 | |
| 62 | **Names**: Use a hardcoded list of common names. Mix genders and cultural backgrounds. |
| 63 | ```typescript |
| 64 | const firstNames = ['Sarah', 'James', 'Priya', 'Mohammed', 'Emma', 'Wei', 'Carlos', 'Aisha']; |
| 65 | const lastNames = ['Chen', 'Smith', 'Patel', 'Garcia', 'Kim', 'O\'Brien', 'Nguyen', 'Wilson']; |
| 66 | ``` |
| 67 | |
| 68 | **Emails**: Derive from names — `sarah.chen@example.com`. Use `example.com` domain (RFC 2606 reserved). |
| 69 | |
| 70 | **Dates**: Generate within a realistic range. Use ISO 8601 format for D1/SQLite. |
| 71 | ```typescript |
| 72 | const randomDate = (daysBack: number) => { |
| 73 | const d = new Date(); |
| 74 | d.setDate(d.getDate() - Math.floor(Math.random() * daysBack)); |
| 75 | return d.toISOString(); |
| 76 | }; |
| 77 | ``` |
| 78 | |
| 79 | **IDs**: Use `crypto.randomUUID()` for UUIDs, or sequential integers if the schema uses auto-increment. |
| 80 | |
| 81 | **Deterministic seeding**: For reproducible data, use a seeded PRNG: |
| 82 | ```typescript |
| 83 | function seededRandom(seed: number) { |
| 84 | return () => { |
| 85 | seed = (seed * 16807) % 2147483647; |
| 86 | return (seed - 1) / 2147483646; |
| 87 | }; |
| 88 | } |
| 89 | const rand = seededRandom(42); // Same seed = same data every time |
| 90 | ``` |
| 91 | |
| 92 | **Prices/amounts**: Use realistic ranges. `(rand() * 900 + 100).toFixed(2)` for $1-$10 range. |
| 93 | |
| 94 | **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. |
| 95 | |
| 96 | ### 5. Output Format |
| 97 | |
| 98 | #### TypeScript (Drizzle ORM) |
| 99 | |
| 100 | ```typescript |
| 101 | // scripts/seed.ts |
| 102 | import { drizzle } from 'drizzle-orm/d1'; |
| 103 | import * as schema from '../src/db/schema'; |
| 104 | |
| 105 | export async function seed(db: ReturnType<typeof drizzle>) { |
| 106 | console.log('Seeding database...'); |
| 107 | |
| 108 | // Clear existing data (reverse dependency order) |
| 109 | await db.delete(schema.comments); |
| 110 | await db.delete(schema.posts); |
| 111 | await db.delete(schema.users); |
| 112 | |
| 113 | // Insert users |
| 114 | const users = [ |
| 115 | { id: crypto.randomUUID(), name: 'Sarah Chen', email: 'sarah@example.com', ... }, |
| 116 | // ... |
| 117 | ]; |
| 118 | |
| 119 | // D1 batch limit: 10 rows per INSERT |
| 120 | for (let i = 0; i < users.length; i += 10) { |
| 121 | await db.insert(schema.users).values(users.slice(i, i + 10)); |
| 122 | } |
| 123 | |
| 124 | // Insert posts (references users) |
| 125 | const posts = [ |
| 126 | { id: crypto.randomUUID(), userId: users[0].id, title: '...', ... }, |
| 127 | // ... |
| 128 | ]; |
| 129 | |
| 130 | for (let i = 0; i < posts.length; i |