drizzle-migrations by bobmatnyc/claude-mpm-skills
npx skills add https://github.com/bobmatnyc/claude-mpm-skills --skill drizzle-migrations使用 Drizzle ORM 为 TypeScript/JavaScript 项目实现迁移优先的数据库开发工作流。
在以下情况下使用此技能:
关键规则:模式变更必须始终从迁移开始,绝不能代码优先。
❌ 错误:先编写 TypeScript 模式
// 不要先做这个
export const users = pgTable('users', {
id: uuid('id').primaryKey(),
email: text('email').notNull(),
});
✅ 正确:先编写 SQL 迁移
-- drizzle/0001_add_users_table.sql
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email TEXT NOT NULL UNIQUE,
created_at TIMESTAMP DEFAULT NOW()
);
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
创建描述性的 SQL 迁移文件:
-- drizzle/0001_create_school_calendars.sql
CREATE TABLE school_calendars (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
school_id UUID NOT NULL REFERENCES schools(id) ON DELETE CASCADE,
start_date DATE NOT NULL,
end_date DATE NOT NULL,
academic_year TEXT NOT NULL,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- 为查询性能添加索引
CREATE INDEX idx_school_calendars_school_id ON school_calendars(school_id);
CREATE INDEX idx_school_calendars_academic_year ON school_calendars(academic_year);
-- 添加约束
ALTER TABLE school_calendars
ADD CONSTRAINT check_date_range
CHECK (end_date > start_date);
命名约定:
0001_、0002_ 等create_school_calendars、add_user_rolesXXXX_descriptive_name.sqlDrizzle Kit 从 SQL 生成 TypeScript 类型:
# 生成 TypeScript 模式和快照
pnpm drizzle-kit generate
# 或使用 npm
npm run db:generate
这会创建什么:
drizzle-kit push)drizzle/meta/XXXX_snapshot.json 中的快照文件快照支持模式漂移检测:
// drizzle/meta/0001_snapshot.json (自动生成)
{
"version": "5",
"dialect": "postgresql",
"tables": {
"school_calendars": {
"name": "school_calendars",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"school_id": {
"name": "school_id",
"type": "uuid",
"notNull": true
}
}
}
}
}
版本控制中的快照:
现在编写与 SQL 迁移匹配的 TypeScript 模式:
// src/lib/db/schema/school/calendar.ts
import { pgTable, uuid, date, text, timestamp } from 'drizzle-orm/pg-core';
import { schools } from './school';
export const schoolCalendars = pgTable('school_calendars', {
id: uuid('id').primaryKey().defaultRandom(),
schoolId: uuid('school_id')
.notNull()
.references(() => schools.id, { onDelete: 'cascade' }),
startDate: date('start_date').notNull(),
endDate: date('end_date').notNull(),
academicYear: text('academic_year').notNull(),
createdAt: timestamp('created_at').defaultNow(),
updatedAt: timestamp('updated_at').defaultNow(),
});
// 类型推断
export type SchoolCalendar = typeof schoolCalendars.$inferSelect;
export type NewSchoolCalendar = typeof schoolCalendars.$inferInsert;
关键点:
school_id → 'school_id'schoolId为可维护性构建模式结构:
src/lib/db/schema/
├── index.ts # 导出所有模式
├── school/
│ ├── index.ts
│ ├── district.ts
│ ├── holiday.ts
│ ├── school.ts
│ └── calendar.ts
├── providers.ts
├── cart.ts
└── users.ts
index.ts(导出所有):
// src/lib/db/schema/index.ts
export * from './school';
export * from './providers';
export * from './cart';
export * from './users';
school/index.ts:
// src/lib/db/schema/school/index.ts
export * from './district';
export * from './holiday';
export * from './school';
export * from './calendar';
在 CI/CD 中验证模式一致性:
# .github/workflows/quality.yml
name: Quality Checks
on:
pull_request:
branches: [main, develop]
push:
branches: [main]
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Check database schema drift
run: pnpm drizzle-kit check
- name: Verify migrations (dry-run)
run: pnpm drizzle-kit push --dry-run
env:
DATABASE_URL: ${{ secrets.STAGING_DATABASE_URL }}
- name: Run type checking
run: pnpm tsc --noEmit
- name: Lint code
run: pnpm lint
CI 检查说明:
drizzle-kit check:验证快照是否与模式匹配drizzle-kit push --dry-run:测试迁移而不应用在生产环境之前,在预发布环境测试迁移:
# 1. 在预发布环境运行迁移
STAGING_DATABASE_URL="..." pnpm drizzle-kit push
# 2. 验证模式
pnpm drizzle-kit check
# 3. 测试受影响的 API 路由
curl https://staging.example.com/api/schools/calendars
# 4. 检查数据完整性问题
# 运行查询以验证数据看起来正确
# 5. 监控错误日志
# 检查应用程序日志中是否有迁移相关错误
预发布环境检查清单:
-- drizzle/0005_add_user_phone.sql
ALTER TABLE users
ADD COLUMN phone TEXT;
-- 如果按电话查询,则添加索引
CREATE INDEX idx_users_phone ON users(phone);
TypeScript:
export const users = pgTable('users', {
id: uuid('id').primaryKey(),
email: text('email').notNull(),
phone: text('phone'), // 新列
});
-- drizzle/0006_create_provider_specialties.sql
CREATE TABLE provider_specialties (
provider_id UUID NOT NULL REFERENCES providers(id) ON DELETE CASCADE,
specialty_id UUID NOT NULL REFERENCES specialties(id) ON DELETE CASCADE,
PRIMARY KEY (provider_id, specialty_id)
);
CREATE INDEX idx_provider_specialties_provider ON provider_specialties(provider_id);
CREATE INDEX idx_provider_specialties_specialty ON provider_specialties(specialty_id);
TypeScript:
export const providerSpecialties = pgTable('provider_specialties', {
providerId: uuid('provider_id')
.notNull()
.references(() => providers.id, { onDelete: 'cascade' }),
specialtyId: uuid('specialty_id')
.notNull()
.references(() => specialties.id, { onDelete: 'cascade' }),
}, (table) => ({
pk: primaryKey(table.providerId, table.specialtyId),
}));
-- drizzle/0007_change_price_to_decimal.sql
ALTER TABLE services
ALTER COLUMN price TYPE DECIMAL(10, 2);
TypeScript:
import { decimal } from 'drizzle-orm/pg-core';
export const services = pgTable('services', {
id: uuid('id').primaryKey(),
name: text('name').notNull(),
price: decimal('price', { precision: 10, scale: 2 }).notNull(),
});
-- drizzle/0008_add_email_constraint.sql
ALTER TABLE users
ADD CONSTRAINT users_email_unique UNIQUE (email);
ALTER TABLE users
ADD CONSTRAINT users_email_format CHECK (email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}$');
import type { Config } from 'drizzle-kit';
export default {
schema: './src/lib/db/schema/index.ts',
out: './drizzle',
driver: 'pg',
dbCredentials: {
connectionString: process.env.DATABASE_URL!,
},
} satisfies Config;
{
"scripts": {
"db:generate": "drizzle-kit generate:pg",
"db:push": "drizzle-kit push:pg",
"db:studio": "drizzle-kit studio",
"db:check": "drizzle-kit check:pg",
"db:up": "drizzle-kit up:pg"
}
}
# 1. 创建迁移
echo "CREATE TABLE test (...)" > drizzle/0009_test.sql
# 2. 生成 TypeScript
pnpm db:generate
# 3. 推送到本地数据库
pnpm db:push
# 4. 验证模式
pnpm db:check
# 5. 在应用程序中测试
pnpm dev
# 手动测试受影响的功能
# 6. 运行测试
pnpm test
-- drizzle/0010_add_feature.sql (向上迁移)
CREATE TABLE new_feature (...);
-- drizzle/0010_add_feature_down.sql (向下迁移)
DROP TABLE new_feature;
应用回滚:
# 手动运行向下迁移
psql $DATABASE_URL -f drizzle/0010_add_feature_down.sql
drizzle-kit checkdrizzle-kit push(使用适当的迁移)错误:Schema drift detected
解决方案:
# 检查发生了什么变化
pnpm drizzle-kit check
# 重新生成快照
pnpm drizzle-kit generate
# 审查变更并提交
git add drizzle/meta/
git commit -m "Update schema snapshots"
错误:迁移因数据约束违规而失败
解决方案:
-- 首先:迁移数据
UPDATE users SET status = 'active' WHERE status IS NULL;
-- 然后:添加约束
ALTER TABLE users
ALTER COLUMN status SET NOT NULL;
错误:TypeScript 类型与数据库不匹配
解决方案:
# 重新生成所有内容
pnpm db:generate
pnpm tsc --noEmit
# 如果仍然有问题,检查模式文件
# 确保列名与 SQL 完全匹配
universal-data-database-migration - 通用迁移模式toolchains-typescript-data-drizzle - Drizzle ORM 使用模式toolchains-typescript-core - TypeScript 最佳实践universal-debugging-verification-before-completion - 验证工作流每周安装次数
417
代码仓库
GitHub 星标数
18
首次出现时间
2026年1月23日
安全审计
安装于
opencode387
codex383
gemini-cli378
cursor372
github-copilot368
kimi-cli342
Migration-first database development workflow using Drizzle ORM for TypeScript/JavaScript projects.
Use this skill when:
Critical Rule : Schema changes ALWAYS start with migrations, never code-first.
❌ WRONG : Writing TypeScript schema first
// DON'T DO THIS FIRST
export const users = pgTable('users', {
id: uuid('id').primaryKey(),
email: text('email').notNull(),
});
✅ CORRECT : Write SQL migration first
-- drizzle/0001_add_users_table.sql
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email TEXT NOT NULL UNIQUE,
created_at TIMESTAMP DEFAULT NOW()
);
Create descriptive SQL migration file:
-- drizzle/0001_create_school_calendars.sql
CREATE TABLE school_calendars (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
school_id UUID NOT NULL REFERENCES schools(id) ON DELETE CASCADE,
start_date DATE NOT NULL,
end_date DATE NOT NULL,
academic_year TEXT NOT NULL,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- Add indexes for query performance
CREATE INDEX idx_school_calendars_school_id ON school_calendars(school_id);
CREATE INDEX idx_school_calendars_academic_year ON school_calendars(academic_year);
-- Add constraints
ALTER TABLE school_calendars
ADD CONSTRAINT check_date_range
CHECK (end_date > start_date);
Naming Convention :
0001_, 0002_, etc.create_school_calendars, add_user_rolesXXXX_descriptive_name.sqlDrizzle Kit generates TypeScript types from SQL:
# Generate TypeScript schema and snapshots
pnpm drizzle-kit generate
# Or using npm
npm run db:generate
What This Creates :
drizzle-kit push)drizzle/meta/XXXX_snapshot.jsonSnapshots enable schema drift detection:
// drizzle/meta/0001_snapshot.json (auto-generated)
{
"version": "5",
"dialect": "postgresql",
"tables": {
"school_calendars": {
"name": "school_calendars",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"school_id": {
"name": "school_id",
"type": "uuid",
"notNull": true
}
}
}
}
}
Snapshots in Version Control :
Now write TypeScript schema that mirrors SQL migration:
// src/lib/db/schema/school/calendar.ts
import { pgTable, uuid, date, text, timestamp } from 'drizzle-orm/pg-core';
import { schools } from './school';
export const schoolCalendars = pgTable('school_calendars', {
id: uuid('id').primaryKey().defaultRandom(),
schoolId: uuid('school_id')
.notNull()
.references(() => schools.id, { onDelete: 'cascade' }),
startDate: date('start_date').notNull(),
endDate: date('end_date').notNull(),
academicYear: text('academic_year').notNull(),
createdAt: timestamp('created_at').defaultNow(),
updatedAt: timestamp('updated_at').defaultNow(),
});
// Type inference
export type SchoolCalendar = typeof schoolCalendars.$inferSelect;
export type NewSchoolCalendar = typeof schoolCalendars.$inferInsert;
Key Points :
school_id → 'school_id'schoolIdStructure schemas for maintainability:
src/lib/db/schema/
├── index.ts # Export all schemas
├── school/
│ ├── index.ts
│ ├── district.ts
│ ├── holiday.ts
│ ├── school.ts
│ └── calendar.ts
├── providers.ts
├── cart.ts
└── users.ts
index.ts (export all):
// src/lib/db/schema/index.ts
export * from './school';
export * from './providers';
export * from './cart';
export * from './users';
school/index.ts :
// src/lib/db/schema/school/index.ts
export * from './district';
export * from './holiday';
export * from './school';
export * from './calendar';
Validate schema consistency in CI/CD:
# .github/workflows/quality.yml
name: Quality Checks
on:
pull_request:
branches: [main, develop]
push:
branches: [main]
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Check database schema drift
run: pnpm drizzle-kit check
- name: Verify migrations (dry-run)
run: pnpm drizzle-kit push --dry-run
env:
DATABASE_URL: ${{ secrets.STAGING_DATABASE_URL }}
- name: Run type checking
run: pnpm tsc --noEmit
- name: Lint code
run: pnpm lint
CI Checks Explained :
drizzle-kit check: Validates snapshots match schemadrizzle-kit push --dry-run: Tests migration without applyingBefore production, test migration on staging:
# 1. Run migration on staging
STAGING_DATABASE_URL="..." pnpm drizzle-kit push
# 2. Verify schema
pnpm drizzle-kit check
# 3. Test affected API routes
curl https://staging.example.com/api/schools/calendars
# 4. Check for data integrity issues
# Run queries to verify data looks correct
# 5. Monitor logs for errors
# Check application logs for migration-related errors
Staging Checklist :
-- drizzle/0005_add_user_phone.sql
ALTER TABLE users
ADD COLUMN phone TEXT;
-- Add index if querying by phone
CREATE INDEX idx_users_phone ON users(phone);
TypeScript:
export const users = pgTable('users', {
id: uuid('id').primaryKey(),
email: text('email').notNull(),
phone: text('phone'), // New column
});
-- drizzle/0006_create_provider_specialties.sql
CREATE TABLE provider_specialties (
provider_id UUID NOT NULL REFERENCES providers(id) ON DELETE CASCADE,
specialty_id UUID NOT NULL REFERENCES specialties(id) ON DELETE CASCADE,
PRIMARY KEY (provider_id, specialty_id)
);
CREATE INDEX idx_provider_specialties_provider ON provider_specialties(provider_id);
CREATE INDEX idx_provider_specialties_specialty ON provider_specialties(specialty_id);
TypeScript:
export const providerSpecialties = pgTable('provider_specialties', {
providerId: uuid('provider_id')
.notNull()
.references(() => providers.id, { onDelete: 'cascade' }),
specialtyId: uuid('specialty_id')
.notNull()
.references(() => specialties.id, { onDelete: 'cascade' }),
}, (table) => ({
pk: primaryKey(table.providerId, table.specialtyId),
}));
-- drizzle/0007_change_price_to_decimal.sql
ALTER TABLE services
ALTER COLUMN price TYPE DECIMAL(10, 2);
TypeScript:
import { decimal } from 'drizzle-orm/pg-core';
export const services = pgTable('services', {
id: uuid('id').primaryKey(),
name: text('name').notNull(),
price: decimal('price', { precision: 10, scale: 2 }).notNull(),
});
-- drizzle/0008_add_email_constraint.sql
ALTER TABLE users
ADD CONSTRAINT users_email_unique UNIQUE (email);
ALTER TABLE users
ADD CONSTRAINT users_email_format CHECK (email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}$');
import type { Config } from 'drizzle-kit';
export default {
schema: './src/lib/db/schema/index.ts',
out: './drizzle',
driver: 'pg',
dbCredentials: {
connectionString: process.env.DATABASE_URL!,
},
} satisfies Config;
{
"scripts": {
"db:generate": "drizzle-kit generate:pg",
"db:push": "drizzle-kit push:pg",
"db:studio": "drizzle-kit studio",
"db:check": "drizzle-kit check:pg",
"db:up": "drizzle-kit up:pg"
}
}
# 1. Create migration
echo "CREATE TABLE test (...)" > drizzle/0009_test.sql
# 2. Generate TypeScript
pnpm db:generate
# 3. Push to local database
pnpm db:push
# 4. Verify schema
pnpm db:check
# 5. Test in application
pnpm dev
# Manually test affected features
# 6. Run tests
pnpm test
-- drizzle/0010_add_feature.sql (up migration)
CREATE TABLE new_feature (...);
-- drizzle/0010_add_feature_down.sql (down migration)
DROP TABLE new_feature;
Apply rollback:
# Manually run down migration
psql $DATABASE_URL -f drizzle/0010_add_feature_down.sql
drizzle-kit check in CIdrizzle-kit push in production (use proper migrations)Error : Schema drift detected
Solution :
# Check what changed
pnpm drizzle-kit check
# Regenerate snapshots
pnpm drizzle-kit generate
# Review changes and commit
git add drizzle/meta/
git commit -m "Update schema snapshots"
Error : Migration fails with data constraint violation
Solution :
-- First: Migrate data
UPDATE users SET status = 'active' WHERE status IS NULL;
-- Then: Add constraint
ALTER TABLE users
ALTER COLUMN status SET NOT NULL;
Error : TypeScript types don't match database
Solution :
# Regenerate everything
pnpm db:generate
pnpm tsc --noEmit
# If still broken, check schema files
# Ensure column names match SQL exactly
universal-data-database-migration - Universal migration patternstoolchains-typescript-data-drizzle - Drizzle ORM usage patternstoolchains-typescript-core - TypeScript best practicesuniversal-debugging-verification-before-completion - Verification workflowsWeekly Installs
417
Repository
GitHub Stars
18
First Seen
Jan 23, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode387
codex383
gemini-cli378
cursor372
github-copilot368
kimi-cli342
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
103,800 周安装