mcpField
D
db-seed

db-seed

github.com/jezweb数据与数据库1.1k981 次安装3 周前更新
代码仓库

关于 db-seed

db-seed 是一项根据项目数据库结构生成可运行填充脚本的技能,适用于开发、演示和测试,并处理数据依赖与重复运行。

主要功能

  1. 01读取结构定义和表关系
  2. 02生成 Drizzle TypeScript 或原始 SQL
  3. 03按外键依赖安排插入顺序
  4. 04生成领域匹配的数据及边界样例
  5. 05支持可重复执行的填充策略

适用场景

  1. 01填充新的开发数据库
  2. 02为演示准备样例数据
  3. 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
原始 SQLschema.sql、db/*.sql
Prismaprisma/schema.prisma

读取所有架构文件。梳理以下内容:

  • 表及其列
  • 数据类型和约束(NOT NULL、UNIQUE、DEFAULT)
  • 外键关系(哪些表引用了哪些表)
  • 以 TEXT 存储的 JSON 字段(D1/SQLite 中很常见)
2. 确定种子数据参数

询问用户:

参数选项默认值
用途dev、demo、testingdev
数量small(每张表 5-10 行)、medium(20-50 行)、large(100+ 行)small
领域背景“电商商店”、“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,再执行 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]。使用 example.com 域名(由 RFC 2606 保留)。

日期:在合理的范围内生成。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

价格/金额:使用合理的范围。(rand() * 900 + 100).toFixed(2) 可生成 $1-$10 范围内的值。

描述/内容:为每种内容类型撰写 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 最多约 10 行分批插入,每批 10 行
不支持原生 BOOLEAN使用 INTEGER(0/1)
不支持原生 DATETIME使用 TEXT 和 ISO 8601 字符串
JSON 以 TEXT 存储插入前使用 JSON.stringify()
始终启用外键约束先插入父表
绑定参数上限为 100 个保持批次大小 × 列数小于 100

质量规则

  1. 符合领域特征 — 电商种子数据应包含名称听起来真实的商品和价格,而不是“商品 1”
  2. 让数据多样化 — 不要让每个用户都叫“John Smith”,也不要让每个价格都是“$9.99”
  3. 包含边界情况(用于测试种子数据)— 空字符串、超长文本、特殊字符、最大值
  4. 引用真实 ID — 外键必须指向实际插入的父行
  5. 打印已生成的种子数据 — 始终记录数量,让用户知道操作成功
  6. 记录运行命令 — 将其放在文件顶部的注释中

评论

支持 Markdown