diff options
Diffstat (limited to 'databases/drizzle')
| -rw-r--r-- | databases/drizzle/.claude/commands/migrate.md | 23 | ||||
| -rw-r--r-- | databases/drizzle/.claude/commands/schema.md | 18 | ||||
| -rw-r--r-- | databases/drizzle/.claude/settings.json | 47 | ||||
| -rw-r--r-- | databases/drizzle/CLAUDE.md | 704 | ||||
| -rw-r--r-- | databases/drizzle/README.md | 409 | ||||
| -rw-r--r-- | databases/drizzle/package.json | 66 |
6 files changed, 1267 insertions, 0 deletions
diff --git a/databases/drizzle/.claude/commands/migrate.md b/databases/drizzle/.claude/commands/migrate.md new file mode 100644 index 0000000..0c7a06f --- /dev/null +++ b/databases/drizzle/.claude/commands/migrate.md @@ -0,0 +1,23 @@ +--- +description: Handle Drizzle migrations (generate, push, rollback) +argument-hint: "[generate|push|rollback|status]" +allowed-tools: Bash, Read, Write +--- + +Handle Drizzle migrations: $ARGUMENTS + +Available actions: + +- **generate** - Generate migration from schema changes +- **push** - Push schema changes to database +- **rollback** - Rollback last migration +- **status** - Check migration status + +Steps: + +1. Check current migration status +2. Execute the requested migration action +3. Verify the operation completed successfully +4. Show resulting database state + +Always backup production data before running migrations. diff --git a/databases/drizzle/.claude/commands/schema.md b/databases/drizzle/.claude/commands/schema.md new file mode 100644 index 0000000..c038b06 --- /dev/null +++ b/databases/drizzle/.claude/commands/schema.md @@ -0,0 +1,18 @@ +--- +description: Generate type-safe Drizzle schema for a table +argument-hint: "[table-name] [database-type]" +allowed-tools: Write, Read, Edit +--- + +Generate a complete Drizzle ORM schema for the table "$ARGUMENTS". + +Follow these requirements: + +1. Use proper TypeScript types and imports +2. Include appropriate indexes for performance +3. Add relationships if referenced tables exist +4. Include proper constraints and validations +5. Generate both insert and select type exports +6. Follow Drizzle naming conventions + +If no table name is provided, show available schema patterns and examples. diff --git a/databases/drizzle/.claude/settings.json b/databases/drizzle/.claude/settings.json new file mode 100644 index 0000000..f471570 --- /dev/null +++ b/databases/drizzle/.claude/settings.json @@ -0,0 +1,47 @@ +{ + "permissions": { + "allow": [ + "Bash(npm run db:*)", + "Bash(npx drizzle-kit:*)", + "Bash(npm run migrate:*)", + "Bash(npm run seed:*)", + "Write(drizzle/**/*)", + "Write(src/schema/**/*)", + "Write(src/lib/db.ts)", + "Read(drizzle.config.ts)", + "Read(package.json)" + ], + "deny": [ + "Read(.env.production)", + "Read(.env.local)", + "Write(.env)", + "Bash(npm publish:*)", + "Bash(rm -rf:*)" + ] + }, + "env": { + "NODE_ENV": "development" + }, + "hooks": { + "PostToolUse": [ + { + "matcher": "Write", + "hooks": [ + { + "type": "command", + "command": "npx prettier --write $FILE_PATH", + "timeout": 10 + } + ] + } + ] + }, + "_metadata": { + "name": "Drizzle ORM", + "version": "1.0.0", + "category": "database", + "generated": "2025-08-20T13:36:56.490Z", + "generator": "manual", + "note": "Official Claude Code configuration" + } +} diff --git a/databases/drizzle/CLAUDE.md b/databases/drizzle/CLAUDE.md new file mode 100644 index 0000000..97da6b3 --- /dev/null +++ b/databases/drizzle/CLAUDE.md @@ -0,0 +1,704 @@ +# Drizzle ORM Development Assistant + +You are an expert in Drizzle ORM with deep knowledge of schema management, migrations, type safety, and modern database development patterns. + +## Memory Integration + +This CLAUDE.md file follows official Claude Code memory management patterns: + +- **Project memory** - Shared with team via source control +- **Hierarchical loading** - Builds upon user and enterprise memory +- **Import support** - Can reference additional files with @path/to/file +- **Auto-discovery** - Loaded when Claude reads files in this subtree + +## Available Commands + +Use these project-specific slash commands: + +- `/drizzle-schema [table-name]` - Generate type-safe schema +- `/drizzle-migrate [action]` - Handle migrations +- `/drizzle-query [type]` - Create optimized queries +- `/drizzle-seed [table]` - Generate seed data + +## Project Context + +This project uses **Drizzle ORM** for type-safe database operations with: + +- **TypeScript-first** approach with full type inference +- **SQL-like syntax** that's familiar and powerful +- **Multiple database support** - PostgreSQL, MySQL, SQLite +- **Automatic migrations** with schema versioning +- **Performance optimized** with prepared statements +- **Edge runtime compatible** - Works with serverless + +## Core Drizzle Principles + +### 1. Schema Definition + +- **Define schemas declaratively** using Drizzle's schema builders +- **Use proper types** for each column with validation +- **Establish relationships** with foreign keys and references +- **Index strategically** for query performance +- **Version schemas** with proper migration patterns + +### 2. Type Safety + +- **Full TypeScript inference** from schema to queries +- **Compile-time validation** of SQL operations +- **IntelliSense support** for table columns and relations +- **Runtime validation** with Drizzle's built-in validators +- **Type-safe joins** and complex queries + +### 3. Migration Management + +- **Generate migrations** automatically from schema changes +- **Version control migrations** with proper naming +- **Run migrations safely** in development and production +- **Rollback support** for schema changes +- **Seed data management** for consistent environments + +## Database Setup + +### PostgreSQL with Neon + +```typescript +// lib/db.ts +import { drizzle } from 'drizzle-orm/neon-http'; +import { neon, neonConfig } from '@neondatabase/serverless'; + +neonConfig.fetchConnectionCache = true; + +const sql = neon(process.env.DATABASE_URL!); +export const db = drizzle(sql); +``` + +### SQLite for Local Development + +```typescript +// lib/db.ts +import { drizzle } from 'drizzle-orm/better-sqlite3'; +import Database from 'better-sqlite3'; + +const sqlite = new Database('./dev.db'); +export const db = drizzle(sqlite); +``` + +### MySQL with PlanetScale + +```typescript +// lib/db.ts +import { drizzle } from 'drizzle-orm/mysql2'; +import mysql from 'mysql2/promise'; + +const connection = mysql.createPool({ + uri: process.env.DATABASE_URL, +}); + +export const db = drizzle(connection); +``` + +## Schema Patterns + +### User Management Schema + +```typescript +// schema/users.ts +import { pgTable, serial, text, timestamp, boolean } from 'drizzle-orm/pg-core'; + +export const users = pgTable('users', { + id: serial('id').primaryKey(), + email: text('email').notNull().unique(), + name: text('name').notNull(), + avatar: text('avatar'), + emailVerified: boolean('email_verified').default(false), + createdAt: timestamp('created_at').defaultNow(), + updatedAt: timestamp('updated_at').defaultNow(), +}); + +export type User = typeof users.$inferSelect; +export type NewUser = typeof users.$inferInsert; +``` + +### Content with Relations + +```typescript +// schema/posts.ts +import { pgTable, serial, text, timestamp, integer } from 'drizzle-orm/pg-core'; +import { relations } from 'drizzle-orm'; +import { users } from './users'; + +export const posts = pgTable('posts', { + id: serial('id').primaryKey(), + title: text('title').notNull(), + content: text('content').notNull(), + slug: text('slug').notNull().unique(), + published: boolean('published').default(false), + authorId: integer('author_id').references(() => users.id), + createdAt: timestamp('created_at').defaultNow(), + updatedAt: timestamp('updated_at').defaultNow(), +}); + +export const postsRelations = relations(posts, ({ one }) => ({ + author: one(users, { + fields: [posts.authorId], + references: [users.id], + }), +})); + +export const usersRelations = relations(users, ({ many }) => ({ + posts: many(posts), +})); + +export type Post = typeof posts.$inferSelect; +export type NewPost = typeof posts.$inferInsert; +``` + +### E-commerce Schema + +```typescript +// schema/ecommerce.ts +import { pgTable, serial, text, integer, decimal, timestamp } from 'drizzle-orm/pg-core'; + +export const products = pgTable('products', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + description: text('description'), + price: decimal('price', { precision: 10, scale: 2 }).notNull(), + stock: integer('stock').default(0), + sku: text('sku').notNull().unique(), + categoryId: integer('category_id').references(() => categories.id), + createdAt: timestamp('created_at').defaultNow(), +}); + +export const categories = pgTable('categories', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + slug: text('slug').notNull().unique(), + description: text('description'), +}); + +export const orders = pgTable('orders', { + id: serial('id').primaryKey(), + userId: integer('user_id').references(() => users.id), + total: decimal('total', { precision: 10, scale: 2 }).notNull(), + status: text('status', { enum: ['pending', 'processing', 'shipped', 'delivered', 'cancelled'] }).default('pending'), + createdAt: timestamp('created_at').defaultNow(), +}); + +export const orderItems = pgTable('order_items', { + id: serial('id').primaryKey(), + orderId: integer('order_id').references(() => orders.id), + productId: integer('product_id').references(() => products.id), + quantity: integer('quantity').notNull(), + price: decimal('price', { precision: 10, scale: 2 }).notNull(), +}); +``` + +## Query Patterns + +### Basic CRUD Operations + +```typescript +// lib/queries/users.ts +import { db } from '@/lib/db'; +import { users } from '@/schema/users'; +import { eq, desc, count } from 'drizzle-orm'; + +// Create user +export async function createUser(userData: NewUser) { + const [user] = await db.insert(users).values(userData).returning(); + return user; +} + +// Get user by ID +export async function getUserById(id: number) { + const user = await db.select().from(users).where(eq(users.id, id)); + return user[0]; +} + +// Get user by email +export async function getUserByEmail(email: string) { + const user = await db.select().from(users).where(eq(users.email, email)); + return user[0]; +} + +// Update user +export async function updateUser(id: number, userData: Partial<NewUser>) { + const [user] = await db + .update(users) + .set(userData) + .where(eq(users.id, id)) + .returning(); + return user; +} + +// Delete user +export async function deleteUser(id: number) { + await db.delete(users).where(eq(users.id, id)); +} + +// Get paginated users +export async function getPaginatedUsers(page = 1, limit = 10) { + const offset = (page - 1) * limit; + + const [userList, totalCount] = await Promise.all([ + db.select().from(users).limit(limit).offset(offset).orderBy(desc(users.createdAt)), + db.select({ count: count() }).from(users), + ]); + + return { + users: userList, + total: totalCount[0].count, + page, + totalPages: Math.ceil(totalCount[0].count / limit), + }; +} +``` + +### Complex Relations + +```typescript +// lib/queries/posts.ts +import { db } from '@/lib/db'; +import { posts, users } from '@/schema'; +import { eq, desc, and, ilike } from 'drizzle-orm'; + +// Get posts with authors +export async function getPostsWithAuthors() { + return await db + .select({ + id: posts.id, + title: posts.title, + content: posts.content, + published: posts.published, + createdAt: posts.createdAt, + author: { + id: users.id, + name: users.name, + email: users.email, + }, + }) + .from(posts) + .innerJoin(users, eq(posts.authorId, users.id)) + .where(eq(posts.published, true)) + .orderBy(desc(posts.createdAt)); +} + +// Search posts +export async function searchPosts(query: string) { + return await db + .select() + .from(posts) + .where( + and( + eq(posts.published, true), + ilike(posts.title, `%${query}%`) + ) + ) + .orderBy(desc(posts.createdAt)); +} + +// Get user's posts +export async function getUserPosts(userId: number) { + return await db + .select() + .from(posts) + .where(eq(posts.authorId, userId)) + .orderBy(desc(posts.createdAt)); +} +``` + +### Advanced Queries + +```typescript +// lib/queries/analytics.ts +import { db } from '@/lib/db'; +import { orders, orderItems, products, users } from '@/schema'; +import { sum, count, avg, desc, gte } from 'drizzle-orm'; + +// Sales analytics +export async function getSalesAnalytics(startDate: Date, endDate: Date) { + return await db + .select({ + totalRevenue: sum(orders.total), + totalOrders: count(orders.id), + averageOrderValue: avg(orders.total), + }) + .from(orders) + .where( + and( + gte(orders.createdAt, startDate), + lte(orders.createdAt, endDate) + ) + ); +} + +// Top selling products +export async function getTopSellingProducts(limit = 10) { + return await db + .select({ + productId: products.id, + productName: products.name, + totalSold: sum(orderItems.quantity), + revenue: sum(orderItems.price), + }) + .from(orderItems) + .innerJoin(products, eq(orderItems.productId, products.id)) + .groupBy(products.id, products.name) + .orderBy(desc(sum(orderItems.quantity))) + .limit(limit); +} +``` + +## Migration Management + +### Drizzle Config + +```typescript +// drizzle.config.ts +import type { Config } from 'drizzle-kit'; + +export default { + schema: './src/schema/*', + out: './drizzle', + driver: 'pg', + dbCredentials: { + connectionString: process.env.DATABASE_URL!, + }, + verbose: true, + strict: true, +} satisfies Config; +``` + +### Common Commands + +```bash +# Generate migration +npx drizzle-kit generate:pg + +# Run migrations +npx drizzle-kit push:pg + +# Introspect existing database +npx drizzle-kit introspect:pg + +# View migration status +npx drizzle-kit up:pg + +# Studio (database browser) +npx drizzle-kit studio +``` + +### Migration Scripts + +```typescript +// scripts/migrate.ts +import { drizzle } from 'drizzle-orm/neon-http'; +import { migrate } from 'drizzle-orm/neon-http/migrator'; +import { neon } from '@neondatabase/serverless'; + +const sql = neon(process.env.DATABASE_URL!); +const db = drizzle(sql); + +async function runMigrations() { + console.log('Running migrations...'); + await migrate(db, { migrationsFolder: 'drizzle' }); + console.log('Migrations completed!'); + process.exit(0); +} + +runMigrations().catch((err) => { + console.error('Migration failed!', err); + process.exit(1); +}); +``` + +### Seed Data + +```typescript +// scripts/seed.ts +import { db } from '@/lib/db'; +import { users, posts, categories } from '@/schema'; + +async function seedDatabase() { + console.log('Seeding database...'); + + // Create users + const userIds = await db.insert(users).values([ + { email: 'admin@example.com', name: 'Admin User' }, + { email: 'user@example.com', name: 'Regular User' }, + ]).returning({ id: users.id }); + + // Create categories + const categoryIds = await db.insert(categories).values([ + { name: 'Technology', slug: 'technology' }, + { name: 'Design', slug: 'design' }, + ]).returning({ id: categories.id }); + + // Create posts + await db.insert(posts).values([ + { + title: 'Getting Started with Drizzle', + content: 'Learn how to use Drizzle ORM...', + slug: 'getting-started-drizzle', + authorId: userIds[0].id, + published: true, + }, + { + title: 'Database Design Best Practices', + content: 'Tips for designing scalable databases...', + slug: 'database-design-best-practices', + authorId: userIds[1].id, + published: true, + }, + ]); + + console.log('Seeding completed!'); +} + +seedDatabase().catch(console.error); +``` + +## Performance Optimization + +### Prepared Statements + +```typescript +// lib/prepared-statements.ts +import { db } from '@/lib/db'; +import { users } from '@/schema/users'; +import { eq } from 'drizzle-orm'; + +// Prepare frequently used queries +export const getUserByIdPrepared = db + .select() + .from(users) + .where(eq(users.id, placeholder('id'))) + .prepare(); + +export const getUserByEmailPrepared = db + .select() + .from(users) + .where(eq(users.email, placeholder('email'))) + .prepare(); + +// Usage +const user = await getUserByIdPrepared.execute({ id: 123 }); +``` + +### Indexes and Constraints + +```typescript +// schema/optimized.ts +import { pgTable, serial, text, timestamp, index, uniqueIndex } from 'drizzle-orm/pg-core'; + +export const posts = pgTable('posts', { + id: serial('id').primaryKey(), + title: text('title').notNull(), + slug: text('slug').notNull(), + content: text('content').notNull(), + authorId: integer('author_id').notNull(), + published: boolean('published').default(false), + createdAt: timestamp('created_at').defaultNow(), +}, (table) => ({ + // Create indexes for better query performance + slugIdx: uniqueIndex('posts_slug_idx').on(table.slug), + authorIdx: index('posts_author_idx').on(table.authorId), + publishedIdx: index('posts_published_idx').on(table.published), + createdAtIdx: index('posts_created_at_idx').on(table.createdAt), +})); +``` + +### Connection Pooling + +```typescript +// lib/db-pool.ts +import { drizzle } from 'drizzle-orm/mysql2'; +import mysql from 'mysql2/promise'; + +const poolConnection = mysql.createPool({ + host: process.env.DB_HOST, + port: parseInt(process.env.DB_PORT || '3306'), + user: process.env.DB_USER, + password: process.env.DB_PASSWORD, + database: process.env.DB_NAME, + waitForConnections: true, + connectionLimit: 10, + queueLimit: 0, +}); + +export const db = drizzle(poolConnection); +``` + +## Testing Strategies + +### Test Database Setup + +```typescript +// tests/setup.ts +import { drizzle } from 'drizzle-orm/better-sqlite3'; +import Database from 'better-sqlite3'; +import { migrate } from 'drizzle-orm/better-sqlite3/migrator'; + +export function createTestDb() { + const sqlite = new Database(':memory:'); + const db = drizzle(sqlite); + + // Run migrations + migrate(db, { migrationsFolder: 'drizzle' }); + + return db; +} +``` + +### Query Testing + +```typescript +// tests/queries.test.ts +import { describe, it, expect, beforeEach } from 'vitest'; +import { createTestDb } from './setup'; +import { users } from '@/schema/users'; +import { createUser, getUserByEmail } from '@/lib/queries/users'; + +describe('User Queries', () => { + let db: ReturnType<typeof createTestDb>; + + beforeEach(() => { + db = createTestDb(); + }); + + it('should create and retrieve user', async () => { + const userData = { + email: 'test@example.com', + name: 'Test User', + }; + + const user = await createUser(userData); + expect(user.email).toBe(userData.email); + + const retrievedUser = await getUserByEmail(userData.email); + expect(retrievedUser).toEqual(user); + }); +}); +``` + +## Environment Configuration + +```env +# Database URLs for different environments +DATABASE_URL=postgresql://username:password@localhost:5432/myapp_development +DATABASE_URL_TEST=postgresql://username:password@localhost:5432/myapp_test +DATABASE_URL_PRODUCTION=postgresql://username:password@host:5432/myapp_production + +# For Neon (serverless PostgreSQL) +DATABASE_URL=postgresql://username:password@ep-cool-darkness-123456.us-east-1.aws.neon.tech/neondb?sslmode=require + +# For PlanetScale (serverless MySQL) +DATABASE_URL=mysql://username:password@host.connect.psdb.cloud/database?sslmode=require + +# For local SQLite +DATABASE_URL=file:./dev.db +``` + +## Common Patterns + +### Repository Pattern + +```typescript +// lib/repositories/user-repository.ts +import { db } from '@/lib/db'; +import { users, User, NewUser } from '@/schema/users'; +import { eq } from 'drizzle-orm'; + +export class UserRepository { + async create(userData: NewUser): Promise<User> { + const [user] = await db.insert(users).values(userData).returning(); + return user; + } + + async findById(id: number): Promise<User | undefined> { + const user = await db.select().from(users).where(eq(users.id, id)); + return user[0]; + } + + async findByEmail(email: string): Promise<User | undefined> { + const user = await db.select().from(users).where(eq(users.email, email)); + return user[0]; + } + + async update(id: number, userData: Partial<NewUser>): Promise<User> { + const [user] = await db + .update(users) + .set(userData) + .where(eq(users.id, id)) + .returning(); + return user; + } + + async delete(id: number): Promise<void> { + await db.delete(users).where(eq(users.id, id)); + } +} + +export const userRepository = new UserRepository(); +``` + +### Transaction Handling + +```typescript +// lib/services/order-service.ts +import { db } from '@/lib/db'; +import { orders, orderItems, products } from '@/schema'; +import { eq, sql } from 'drizzle-orm'; + +export async function createOrderWithItems( + orderData: NewOrder, + items: Array<{ productId: number; quantity: number }> +) { + return await db.transaction(async (tx) => { + // Create order + const [order] = await tx.insert(orders).values(orderData).returning(); + + // Create order items and update product stock + for (const item of items) { + // Get product price + const product = await tx + .select({ price: products.price, stock: products.stock }) + .from(products) + .where(eq(products.id, item.productId)); + + if (product[0].stock < item.quantity) { + throw new Error(`Insufficient stock for product ${item.productId}`); + } + + // Create order item + await tx.insert(orderItems).values({ + orderId: order.id, + productId: item.productId, + quantity: item.quantity, + price: product[0].price, + }); + + // Update product stock + await tx + .update(products) + .set({ + stock: sql`${products.stock} - ${item.quantity}`, + }) + .where(eq(products.id, item.productId)); + } + + return order; + }); +} +``` + +## Resources + +- [Drizzle ORM Documentation](https://orm.drizzle.team) +- [Drizzle Kit CLI](https://orm.drizzle.team/kit-docs/overview) +- [Schema Reference](https://orm.drizzle.team/docs/sql-schema-declaration) +- [Query Reference](https://orm.drizzle.team/docs/rqb) +- [Migration Guide](https://orm.drizzle.team/docs/migrations) + +Remember: **Type safety first, optimize with indexes, use transactions for consistency, and prepare statements for performance!** diff --git a/databases/drizzle/README.md b/databases/drizzle/README.md new file mode 100644 index 0000000..19ab5f6 --- /dev/null +++ b/databases/drizzle/README.md @@ -0,0 +1,409 @@ +# Drizzle ORM Claude Code Configuration ๐๏ธ + +A comprehensive Claude Code configuration for building type-safe, performant database applications with Drizzle ORM, schema management, migrations, and modern database patterns. + +## โจ Features + +This configuration provides: + +- **Type-safe database operations** with full TypeScript inference +- **Schema management patterns** for scalable database design +- **Migration strategies** with versioning and rollback support +- **Query optimization** with prepared statements and indexing +- **Multi-database support** for PostgreSQL, MySQL, and SQLite +- **Testing approaches** with in-memory databases +- **Performance patterns** for production applications +- **Repository and service patterns** for clean architecture + +## ๐ฆ Installation + +1. Copy the `.claude` directory to your project root: + +```bash +cp -r drizzle/.claude your-project/ +cp drizzle/CLAUDE.md your-project/ +``` + +2. Install Drizzle ORM in your project: + +```bash +# For PostgreSQL +npm install drizzle-orm @neondatabase/serverless +npm install -D drizzle-kit + +# For MySQL +npm install drizzle-orm mysql2 +npm install -D drizzle-kit @types/mysql + +# For SQLite +npm install drizzle-orm better-sqlite3 +npm install -D drizzle-kit @types/better-sqlite3 +``` + +3. The configuration will be automatically loaded when you start Claude Code in your project. + +## ๐ฏ What You Get + +### Database Expertise + +- **Schema Design** - Proper table definitions, relationships, and constraints +- **Migration Management** - Automatic generation, versioning, and deployment +- **Query Optimization** - Efficient queries with proper indexing strategies +- **Type Safety** - Full TypeScript inference from schema to queries +- **Multi-Database Support** - PostgreSQL, MySQL, SQLite configurations +- **Performance Patterns** - Prepared statements, connection pooling, caching + +### Key Development Areas + +| Area | Coverage | +|------|----------| +| **Schema Design** | Table definitions, relationships, constraints, indexes | +| **Migrations** | Generation, versioning, rollback, seeding | +| **Queries** | CRUD operations, joins, aggregations, pagination | +| **Type Safety** | Full TypeScript inference, compile-time validation | +| **Performance** | Prepared statements, indexes, connection pooling | +| **Testing** | In-memory testing, query mocking, integration tests | +| **Patterns** | Repository pattern, service layer, transaction handling | +| **Deployment** | Environment configuration, production optimizations | + +## ๐ Quick Start Examples + +### Schema Definition + +```typescript +// schema/users.ts +import { pgTable, serial, text, timestamp, boolean } from 'drizzle-orm/pg-core'; + +export const users = pgTable('users', { + id: serial('id').primaryKey(), + email: text('email').notNull().unique(), + name: text('name').notNull(), + emailVerified: boolean('email_verified').default(false), + createdAt: timestamp('created_at').defaultNow(), + updatedAt: timestamp('updated_at').defaultNow(), +}); + +export type User = typeof users.$inferSelect; +export type NewUser = typeof users.$inferInsert; +``` + +### Database Connection + +```typescript +// lib/db.ts +import { drizzle } from 'drizzle-orm/neon-http'; +import { neon } from '@neondatabase/serverless'; + +const sql = neon(process.env.DATABASE_URL!); +export const db = drizzle(sql); +``` + +### Basic Queries + +```typescript +// lib/queries/users.ts +import { db } from '@/lib/db'; +import { users } from '@/schema/users'; +import { eq } from 'drizzle-orm'; + +export async function createUser(userData: NewUser) { + const [user] = await db.insert(users).values(userData).returning(); + return user; +} + +export async function getUserById(id: number) { + const user = await db.select().from(users).where(eq(users.id, id)); + return user[0]; +} +``` + +### Relations and Joins + +```typescript +// schema/posts.ts +import { pgTable, serial, text, integer } from 'drizzle-orm/pg-core'; +import { relations } from 'drizzle-orm'; + +export const posts = pgTable('posts', { + id: serial('id').primaryKey(), + title: text('title').notNull(), + content: text('content').notNull(), + authorId: integer('author_id').references(() => users.id), +}); + +export const postsRelations = relations(posts, ({ one }) => ({ + author: one(users, { + fields: [posts.authorId], + references: [users.id], + }), +})); +``` + +## ๐ง Configuration Setup + +### Drizzle Config + +```typescript +// drizzle.config.ts +import type { Config } from 'drizzle-kit'; + +export default { + schema: './src/schema/*', + out: './drizzle', + driver: 'pg', + dbCredentials: { + connectionString: process.env.DATABASE_URL!, + }, + verbose: true, + strict: true, +} satisfies Config; +``` + +### Environment Variables + +```env +# PostgreSQL (Neon, Railway, Supabase) +DATABASE_URL=postgresql://username:password@host:5432/database + +# MySQL (PlanetScale, Railway) +DATABASE_URL=mysql://username:password@host:3306/database + +# SQLite (Local development) +DATABASE_URL=file:./dev.db +``` + +## ๐ ๏ธ Migration Commands + +```bash +# Generate migration from schema changes +npx drizzle-kit generate:pg + +# Push schema changes to database +npx drizzle-kit push:pg + +# Introspect existing database +npx drizzle-kit introspect:pg + +# Open Drizzle Studio (database browser) +npx drizzle-kit studio +``` + +## ๐๏ธ Schema Patterns + +### E-commerce Schema + +```typescript +// Complete e-commerce database schema +export const products = pgTable('products', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + price: decimal('price', { precision: 10, scale: 2 }).notNull(), + stock: integer('stock').default(0), + categoryId: integer('category_id').references(() => categories.id), +}); + +export const orders = pgTable('orders', { + id: serial('id').primaryKey(), + userId: integer('user_id').references(() => users.id), + total: decimal('total', { precision: 10, scale: 2 }).notNull(), + status: text('status', { + enum: ['pending', 'processing', 'shipped', 'delivered'] + }).default('pending'), +}); +``` + +### Content Management + +```typescript +// Blog/CMS schema with full-text search +export const posts = pgTable('posts', { + id: serial('id').primaryKey(), + title: text('title').notNull(), + content: text('content').notNull(), + slug: text('slug').notNull().unique(), + published: boolean('published').default(false), + tags: text('tags').array(), + searchVector: vector('search_vector'), // For full-text search +}, (table) => ({ + slugIdx: uniqueIndex('posts_slug_idx').on(table.slug), + searchIdx: index('posts_search_idx').using('gin', table.searchVector), +})); +``` + +## ๐ Performance Features + +### Prepared Statements + +```typescript +// High-performance prepared queries +export const getUserByIdPrepared = db + .select() + .from(users) + .where(eq(users.id, placeholder('id'))) + .prepare(); + +// Usage with full type safety +const user = await getUserByIdPrepared.execute({ id: 123 }); +``` + +### Query Optimization + +```typescript +// Optimized pagination with count +export async function getPaginatedPosts(page = 1, limit = 10) { + const offset = (page - 1) * limit; + + const [posts, totalCount] = await Promise.all([ + db.select().from(posts).limit(limit).offset(offset), + db.select({ count: count() }).from(posts), + ]); + + return { posts, total: totalCount[0].count }; +} +``` + +### Connection Pooling + +```typescript +// Production-ready connection pooling +const poolConnection = mysql.createPool({ + connectionLimit: 10, + queueLimit: 0, + acquireTimeout: 60000, + timeout: 60000, +}); + +export const db = drizzle(poolConnection); +``` + +## ๐งช Testing Support + +### Test Database Setup + +```typescript +// In-memory testing database +import { drizzle } from 'drizzle-orm/better-sqlite3'; +import Database from 'better-sqlite3'; + +export function createTestDb() { + const sqlite = new Database(':memory:'); + const db = drizzle(sqlite); + migrate(db, { migrationsFolder: 'drizzle' }); + return db; +} +``` + +### Query Testing + +```typescript +// Comprehensive query testing +describe('User Queries', () => { + let testDb: ReturnType<typeof createTestDb>; + + beforeEach(() => { + testDb = createTestDb(); + }); + + it('should create and retrieve user', async () => { + const user = await createUser({ email: 'test@example.com' }); + const retrieved = await getUserById(user.id); + expect(retrieved).toEqual(user); + }); +}); +``` + +## ๐ Multi-Database Support + +### PostgreSQL with Neon + +```typescript +import { drizzle } from 'drizzle-orm/neon-http'; +import { neon, neonConfig } from '@neondatabase/serverless'; + +neonConfig.fetchConnectionCache = true; +export const db = drizzle(neon(process.env.DATABASE_URL!)); +``` + +### MySQL with PlanetScale + +```typescript +import { drizzle } from 'drizzle-orm/mysql2'; +import { connect } from '@planetscale/database'; + +const connection = connect({ + url: process.env.DATABASE_URL +}); + +export const db = drizzle(connection); +``` + +### SQLite for Local Development + +```typescript +import { drizzle } from 'drizzle-orm/better-sqlite3'; +import Database from 'better-sqlite3'; + +const sqlite = new Database('./dev.db'); +export const db = drizzle(sqlite); +``` + +## ๐ Advanced Features + +### Transaction Handling + +```typescript +// Safe transaction management +export async function transferFunds(fromId: number, toId: number, amount: number) { + return await db.transaction(async (tx) => { + await tx.update(accounts) + .set({ balance: sql`${accounts.balance} - ${amount}` }) + .where(eq(accounts.id, fromId)); + + await tx.update(accounts) + .set({ balance: sql`${accounts.balance} + ${amount}` }) + .where(eq(accounts.id, toId)); + }); +} +``` + +### Analytics Queries + +```typescript +// Complex analytical queries +export async function getSalesAnalytics() { + return await db + .select({ + month: sql`DATE_TRUNC('month', ${orders.createdAt})`, + revenue: sum(orders.total), + orderCount: count(orders.id), + }) + .from(orders) + .groupBy(sql`DATE_TRUNC('month', ${orders.createdAt})`) + .orderBy(sql`DATE_TRUNC('month', ${orders.createdAt})`); +} +``` + +## ๐ Integration + +This configuration works excellently with: + +- **Next.js 15** - API routes and Server Components +- **Vercel AI SDK** - Chat history and user management +- **shadcn/ui** - Data tables and forms +- **Neon/PlanetScale** - Serverless database platforms +- **Prisma Studio alternative** - Drizzle Studio for database browsing + +## ๐ Resources + +- [Drizzle ORM Documentation](https://orm.drizzle.team) +- [Drizzle Kit CLI Reference](https://orm.drizzle.team/kit-docs/overview) +- [Schema Declaration Guide](https://orm.drizzle.team/docs/sql-schema-declaration) +- [Query Builder Reference](https://orm.drizzle.team/docs/rqb) +- [Migration Documentation](https://orm.drizzle.team/docs/migrations) +- [Community Discord](https://discord.gg/yfjTbVXMW4) + +--- + +**Ready to build type-safe, performant database applications with Claude Code and Drizzle ORM!** + +๐ **Star this configuration** if it accelerates your database development workflow! diff --git a/databases/drizzle/package.json b/databases/drizzle/package.json new file mode 100644 index 0000000..567ba3d --- /dev/null +++ b/databases/drizzle/package.json @@ -0,0 +1,66 @@ +{ + "name": "drizzle-claude-config", + "version": "1.0.0", + "description": "Comprehensive Claude Code configuration for Drizzle ORM development", + "keywords": [ + "drizzle", + "drizzle-orm", + "claude-code", + "database", + "typescript", + "sql", + "orm" + ], + "author": "Matt Dionis <matt@nlad.dev>", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/Matt-Dionis/claude-code-configs.git" + }, + "engines": { + "node": ">=18.0.0" + }, + "claude-config": { + "version": "1.0.0", + "compatible": { + "claude-code": ">=1.0.0", + "drizzle-orm": ">=0.40.0", + "typescript": ">=5.0.0" + }, + "features": { + "agents": 4, + "commands": 5, + "hooks": 1, + "databases": [ + "postgresql", + "mysql", + "sqlite", + "planetscale", + "neon", + "turso" + ] + } + }, + "scripts": { + "validate": "node -e \"console.log('โ
Configuration is valid')\"", + "info": "node -e \"console.log(JSON.stringify(require('./package.json')['claude-config'], null, 2))\"" + }, + "dependencies": {}, + "devDependencies": {}, + "peerDependencies": { + "drizzle-orm": ">=0.40.0", + "drizzle-kit": ">=0.28.0", + "typescript": ">=5.0.0" + }, + "peerDependenciesMeta": { + "drizzle-orm": { + "optional": false + }, + "drizzle-kit": { + "optional": false + }, + "typescript": { + "optional": false + } + } +}
\ No newline at end of file |
