install
source · Clone the upstream repo
git clone https://github.com/Intense-Visions/harness-engineering
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/claude-code/drizzle-schema-definition" ~/.claude/skills/intense-visions-harness-engineering-drizzle-schema-definition && rm -rf "$T"
manifest:
agents/skills/claude-code/drizzle-schema-definition/SKILL.mdsource content
Drizzle Schema Definition
Define Drizzle ORM schemas with pgTable/mysqlTable/sqliteTable, column types, indexes, and constraints
When to Use
- Starting a new project with Drizzle ORM
- Defining database tables, columns, and constraints in TypeScript
- Adding indexes, unique constraints, or composite primary keys
- Choosing between PostgreSQL, MySQL, and SQLite column types
Instructions
- Define a table using the provider-specific function. Export it as a const:
import { pgTable, text, timestamp, boolean, uuid } from 'drizzle-orm/pg-core'; export const users = pgTable('users', { id: uuid('id').primaryKey().defaultRandom(), email: text('email').notNull().unique(), name: text('name'), isActive: boolean('is_active').notNull().default(true), createdAt: timestamp('created_at').notNull().defaultNow(), updatedAt: timestamp('updated_at').notNull().defaultNow(), });
-
Choose the correct column types:
- Text:
,text()
,varchar('col', { length: 255 })char() - Numeric:
,integer()
,bigint()
,real()
,doublePrecision()numeric('col', { precision: 10, scale: 2 }) - Boolean:
boolean() - Dates:
,timestamp()
,date()time() - JSON:
,json()
(PostgreSQL)jsonb() - Binary:
(PostgreSQL)bytea() - Enum:
pgEnum('status', ['active', 'inactive'])
- Text:
-
Add constraints with chained methods:
export const posts = pgTable('posts', { id: uuid('id').primaryKey().defaultRandom(), slug: text('slug').notNull().unique(), title: text('title').notNull(), authorId: uuid('author_id') .notNull() .references(() => users.id), viewCount: integer('view_count').notNull().default(0), });
- Add indexes as a third argument to the table function:
export const posts = pgTable( 'posts', { // ... columns }, (table) => [ index('posts_author_idx').on(table.authorId), uniqueIndex('posts_slug_idx').on(table.slug), index('posts_author_created_idx').on(table.authorId, table.createdAt), ] );
- Define enums (PostgreSQL only) separately and reference them:
import { pgEnum } from 'drizzle-orm/pg-core'; export const roleEnum = pgEnum('role', ['user', 'admin', 'moderator']); export const users = pgTable('users', { role: roleEnum('role').notNull().default('user'), });
- Composite primary keys:
import { primaryKey } from 'drizzle-orm/pg-core'; export const postTags = pgTable( 'post_tags', { postId: uuid('post_id') .notNull() .references(() => posts.id), tagId: uuid('tag_id') .notNull() .references(() => tags.id), }, (table) => [primaryKey({ columns: [table.postId, table.tagId] })] );
- Infer TypeScript types from the schema:
import { InferSelectModel, InferInsertModel } from 'drizzle-orm'; export type User = InferSelectModel<typeof users>; export type NewUser = InferInsertModel<typeof users>;
- Organize schemas by exporting all tables from a central
file that the Drizzle config references.db/schema.ts
Details
Drizzle schemas are pure TypeScript — no code generation step, no DSL. The schema IS the TypeScript code, which means you get autocompletion, refactoring, and type inference for free.
Provider differences:
pgTable (PostgreSQL), mysqlTable (MySQL), sqliteTable (SQLite) each support their native column types. You cannot mix providers in one schema. SQLite has the fewest column types; PostgreSQL has the most (arrays, jsonb, custom types).
Column naming convention: The first argument to each column function is the database column name. Use
snake_case for database names while keeping TypeScript property names in camelCase:
// TypeScript: user.createdAt → Database: created_at createdAt: timestamp('created_at').notNull().defaultNow(),
Default values:
.default(value) sets a JavaScript default. .defaultNow() and .defaultRandom() generate SQL defaults (DEFAULT NOW(), DEFAULT gen_random_uuid()). Use SQL defaults when records may be inserted outside Drizzle.
References and foreign keys:
.references(() => otherTable.column) creates a foreign key constraint. The arrow function is required to handle circular references between tables. Add { onDelete: 'cascade' } for cascade behavior.
Trade-offs:
- No separate schema file or code generation — but schema changes require manual migration creation
- Full TypeScript inference — but complex schemas can slow down IDE type-checking
- Direct SQL control — but you must know your database's column types rather than abstracting them
Source
https://orm.drizzle.team/docs/column-types/pg
Process
- Read the instructions and examples in this document.
- Apply the patterns to your implementation, adapting to your specific context.
- Verify your implementation against the details and edge cases listed above.
Harness Integration
- Type: knowledge — this skill is a reference document, not a procedural workflow.
- No tools or state — consumed as context by other skills and agents.
Success Criteria
- The patterns described in this document are applied correctly in the implementation.
- Edge cases and anti-patterns listed in this document are avoided.