AgentSkillsCN

drizzle-migrations

创建并修改 Drizzle ORM 的模式与迁移。当您需要添加字段、创建表,或修改数据库模式时使用。

SKILL.md
--- frontmatter
name: drizzle-migrations
description: Create and modify Drizzle ORM schemas and migrations. Use when adding fields, creating tables, or modifying database schemas.

Drizzle Migrations Skill

You are an expert at creating and modifying database schemas with Drizzle ORM for PostgreSQL.

Critical Rules

1. Schema File Organization

Schemas live in packages/db/src/schema/ with one file per domain:

  • auth.ts - Users, organizations, members
  • contacts.ts - Contacts, topics, subscriptions
  • workflows.ts - Workflows, executions, steps
  • templates.ts - Email templates, versions
  • batch.ts - Batch sends, messages

2. Table Definition Pattern

typescript
import { pgTable, text, timestamp, jsonb, index, uniqueIndex } from "drizzle-orm/pg-core";
import { sql } from "drizzle-orm";

export const myTable = pgTable(
  "my_table",
  {
    // UUID primary key
    id: text("id")
      .primaryKey()
      .$defaultFn(() => crypto.randomUUID()),

    // ALWAYS include organizationId for multi-tenancy
    organizationId: text("organization_id")
      .references(() => organization.id, { onDelete: "cascade" })
      .notNull(),

    // Typed enums
    status: text("status").$type<"active" | "inactive">().default("active"),

    // JSONB for flexible data
    config: jsonb("config").$type<MyConfigType>().default({}),

    // Audit fields (include on ALL mutable tables)
    createdBy: text("created_by").references(() => user.id),
    createdAt: timestamp("created_at").defaultNow().notNull(),
    updatedAt: timestamp("updated_at").defaultNow().notNull(),
  },
  (table) => ({
    // Organization index (REQUIRED for multi-tenancy)
    orgIdx: index("my_table_org_idx").on(table.organizationId),

    // Composite indexes for common queries
    statusIdx: index("my_table_status_idx").on(table.organizationId, table.status),

    // Unique constraint with partial index
    uniqueSlug: uniqueIndex("my_table_unique_slug_idx")
      .on(table.organizationId, table.slug)
      .where(sql`slug IS NOT NULL`),
  })
);

3. Enum Pattern

typescript
import { pgEnum } from "drizzle-orm/pg-core";

// Define enum BEFORE table that uses it
export const workflowStatusEnum = pgEnum("workflow_status", [
  "draft",
  "enabled",
  "paused",
  "archived",
]);

// Use in table
status: workflowStatusEnum("status").default("draft").notNull(),

4. Relations

typescript
import { relations } from "drizzle-orm";

export const workflowRelations = relations(workflow, ({ one, many }) => ({
  organization: one(organization, {
    fields: [workflow.organizationId],
    references: [organization.id],
  }),
  executions: many(workflowExecution),
  createdByUser: one(user, {
    fields: [workflow.createdBy],
    references: [user.id],
    relationName: "createdWorkflows", // Required when multiple relations to same table
  }),
}));

5. Type Inference

typescript
// Export inferred types
export type Workflow = typeof workflow.$inferSelect;
export type NewWorkflow = typeof workflow.$inferInsert;

Migration Commands

bash
# Generate migration from schema changes
pnpm --filter @wraps/db db:generate

# Push changes directly (dev only, no migration file)
pnpm --filter @wraps/db db:push

# Run migrations
pnpm --filter @wraps/db db:migrate

# Open Drizzle Studio
pnpm --filter @wraps/db db:studio

Common Mistakes to Avoid

1. Missing Organization Scoping

typescript
// BAD - No org reference
organizationId: text("organization_id"),

// GOOD - With cascade delete
organizationId: text("organization_id")
  .references(() => organization.id, { onDelete: "cascade" })
  .notNull(),

2. Missing Indexes

typescript
// BAD - No index on organizationId
(table) => ({})

// GOOD - Always index organizationId
(table) => ({
  orgIdx: index("table_org_idx").on(table.organizationId),
})

3. Breaking Changes

typescript
// BAD - Removing column or renaming (breaks existing data)
// name: text("name"),  // Removed!

// GOOD - Add new column with default, deprecate old one
newName: text("new_name").default(""),
// Keep old column for migration period

Key Files

  • Config: packages/db/drizzle.config.ts
  • Schemas: packages/db/src/schema/*.ts
  • Migrations: packages/db/src/migrations/
  • Entry: packages/db/src/index.ts