data-seeding

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Data Seeding Skill

数据填充技能

Seed scripts live in
packages/database/src/seed/
.
种子脚本存放于
packages/database/src/seed/
目录下。

Running Seeds

运行种子脚本

bash
pnpm -F @sgcarstrends/database db:seed           # Run all seeds
pnpm -F @sgcarstrends/database db:seed:cars      # Seed specific table
bash
pnpm -F @sgcarstrends/database db:seed           # 运行所有种子脚本
pnpm -F @sgcarstrends/database db:seed:cars      # 为指定表填充数据

Basic Seed Pattern

基础种子脚本模式

typescript
// packages/database/src/seed/cars.ts
import { db } from "../index";
import { cars } from "../db/schema";
import { nanoid } from "nanoid";

export async function seedCars() {
  console.log("Seeding cars...");

  const carData = [
    { id: nanoid(), make: "Toyota", model: "Camry", vehicleClass: "Sedan", fuelType: "Petrol", month: "2024-01", number: 150 },
    { id: nanoid(), make: "Honda", model: "Civic", vehicleClass: "Sedan", fuelType: "Petrol", month: "2024-01", number: 120 },
  ];

  await db.insert(cars).values(carData);
  console.log(`Seeded ${carData.length} cars`);
}
typescript
// packages/database/src/seed/cars.ts
import { db } from "../index";
import { cars } from "../db/schema";
import { nanoid } from "nanoid";

export async function seedCars() {
  console.log("Seeding cars...");

  const carData = [
    { id: nanoid(), make: "Toyota", model: "Camry", vehicleClass: "Sedan", fuelType: "Petrol", month: "2024-01", number: 150 },
    { id: nanoid(), make: "Honda", model: "Civic", vehicleClass: "Sedan", fuelType: "Petrol", month: "2024-01", number: 120 },
  ];

  await db.insert(cars).values(carData);
  console.log(`Seeded ${carData.length} cars`);
}

Main Seed Runner

主种子执行器

typescript
// packages/database/src/seed/index.ts
export async function seed() {
  console.log("Starting database seed...");

  await clearDatabase();  // Optional: clear existing data
  await seedCars();
  await seedCOE();
  await seedPosts();

  console.log("Database seeded successfully!");
}

async function clearDatabase() {
  // Delete in reverse order of dependencies
  await db.delete(posts);
  await db.delete(coe);
  await db.delete(cars);
}
typescript
// packages/database/src/seed/index.ts
export async function seed() {
  console.log("Starting database seed...");

  await clearDatabase();  // 可选:清空现有数据
  await seedCars();
  await seedCOE();
  await seedPosts();

  console.log("Database seeded successfully!");
}

async function clearDatabase() {
  // 按依赖逆序删除数据
  await db.delete(posts);
  await db.delete(coe);
  await db.delete(cars);
}

Seed with Faker.js

使用Faker.js生成填充数据

bash
pnpm -F @sgcarstrends/database add -D @faker-js/faker
typescript
import { faker } from "@faker-js/faker";

export async function seedRealisticCars(count = 100) {
  const makes = ["Toyota", "Honda", "BMW", "Mercedes"];
  const carData = Array.from({ length: count }, () => ({
    id: nanoid(),
    make: faker.helpers.arrayElement(makes),
    model: faker.vehicle.model(),
    month: faker.date.between({ from: "2020-01-01", to: "2024-12-31" }).toISOString().slice(0, 7),
    number: faker.number.int({ min: 10, max: 500 }),
  }));

  // Batch insert for performance
  const batchSize = 50;
  for (let i = 0; i < carData.length; i += batchSize) {
    await db.insert(cars).values(carData.slice(i, i + batchSize));
  }
}
bash
pnpm -F @sgcarstrends/database add -D @faker-js/faker
typescript
import { faker } from "@faker-js/faker";

export async function seedRealisticCars(count = 100) {
  const makes = ["Toyota", "Honda", "BMW", "Mercedes"];
  const carData = Array.from({ length: count }, () => ({
    id: nanoid(),
    make: faker.helpers.arrayElement(makes),
    model: faker.vehicle.model(),
    month: faker.date.between({ from: "2020-01-01", to: "2024-12-31" }).toISOString().slice(0, 7),
    number: faker.number.int({ min: 10, max: 500 }),
  }));

  // 批量插入提升性能
  const batchSize = 50;
  for (let i = 0; i < carData.length; i += batchSize) {
    await db.insert(cars).values(carData.slice(i, i + batchSize));
  }
}

Environment-Specific Seeds

环境专属填充脚本

typescript
export async function seed() {
  const env = process.env.NODE_ENV || "development";

  switch (env) {
    case "development":
      await seedDevelopment();  // Small, predictable dataset
      break;
    case "test":
      await seedTesting();  // Minimal, deterministic data
      break;
    case "staging":
      await seedStaging();  // Larger, production-like dataset
      break;
  }
}
typescript
export async function seed() {
  const env = process.env.NODE_ENV || "development";

  switch (env) {
    case "development":
      await seedDevelopment();  // 小型可预测数据集
      break;
    case "test":
      await seedTesting();  // 最小化确定性数据
      break;
    case "staging":
      await seedStaging();  // 接近生产环境的大型数据集
      break;
  }
}

Idempotent Seeds (Upsert)

幂等填充(Upsert操作)

typescript
await db.insert(cars).values(carData).onConflictDoUpdate({
  target: cars.id,
  set: { make: carData[0].make, number: carData[0].number, updatedAt: new Date() },
});
typescript
await db.insert(cars).values(carData).onConflictDoUpdate({
  target: cars.id,
  set: { make: carData[0].make, number: carData[0].number, updatedAt: new Date() },
});

Check Before Insert

插入前检查

typescript
export async function seedIfEmpty() {
  const existing = await db.select().from(cars).limit(1);
  if (existing.length > 0) {
    console.log("Database has data, skipping seed");
    return;
  }
  await seedCars();
}
typescript
export async function seedIfEmpty() {
  const existing = await db.select().from(cars).limit(1);
  if (existing.length > 0) {
    console.log("数据库已有数据,跳过填充");
    return;
  }
  await seedCars();
}

Transactions

事务处理

typescript
await db.transaction(async (tx) => {
  await tx.insert(cars).values([...carData]);
  await tx.insert(coe).values([...coeData]);
});
typescript
await db.transaction(async (tx) => {
  await tx.insert(cars).values([...carData]);
  await tx.insert(coe).values([...coeData]);
});

CLI for Selective Seeding

选择性填充的CLI工具

typescript
// packages/database/scripts/seed-cli.ts
const seeders = { cars: seedCars, coe: seedCOE, posts: seedPosts, all: seedAll };
const target = process.argv[2] as keyof typeof seeders;

if (!target || !seeders[target]) {
  console.error("Usage: pnpm db:seed [cars|coe|posts|all]");
  process.exit(1);
}

seeders[target]().then(() => process.exit(0)).catch(() => process.exit(1));
typescript
// packages/database/scripts/seed-cli.ts
const seeders = { cars: seedCars, coe: seedCOE, posts: seedPosts, all: seedAll };
const target = process.argv[2] as keyof typeof seeders;

if (!target || !seeders[target]) {
  console.error("Usage: pnpm db:seed [cars|coe|posts|all]");
  process.exit(1);
}

seeders[target]().then(() => process.exit(0)).catch(() => process.exit(1));

Best Practices

最佳实践

  1. Idempotent: Safe to run multiple times (use upserts or check-before-insert)
  2. Environment-Specific: Different data for dev/test/staging
  3. Batch Inserts: Use batching for large datasets
  4. Relationships: Seed parent tables first
  5. Transactions: Use for atomic seeding
  6. Logging: Provide clear progress feedback
  1. 幂等性:可安全多次运行(使用Upsert或插入前检查)
  2. 环境专属:为开发/测试/预发布环境提供不同数据
  3. 批量插入:处理大型数据集时使用批量操作
  4. 关联表顺序:先填充父表数据
  5. 事务处理:使用事务保证填充操作的原子性
  6. 日志反馈:提供清晰的进度提示

References

参考资料

  • packages/database/CLAUDE.md
    for schema details
  • See
    schema-design
    skill for migrations
  • packages/database/CLAUDE.md
    包含数据库架构细节
  • 查看
    schema-design
    技能了解迁移相关内容