算法艺术创作
algorithmic-art
通过代码和可调参数创作生成艺术与交互式视觉效果。
nodejs-drizzle【设计与创意】Use this skill when working with Drizzle ORM — schema definition, SQL-like queries, migrations, relations, and edge deployment. This skill enforces: schema-first design, explicit SQL patterns, drizzle-kit for migrations, relational query builder, and connection management. Requires drizzle-orm and drizzle-kit. Do NOT use for: Prisma, TypeORM, Mongoos…
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
Design database schemas, write SQL-like queries, manage migrations, define relations, and optimize Drizzle ORM for production and edge deployment.
User request includes: drizzle, drizzle orm, drizzle schema, drizzle migrate, drizzle query, drizzle relation, drizzle edge, drizzle neon, drizzle planetscale.
Schema definition, query examples, migration setup, relation config, connection management.
Produce artifact directly. No preamble, no postamble, no explanations.
4096 tokens
| Criterion | Drizzle ORM | Prisma |
|---|---|---|
| Performance | Fast (thin SQL wrapper) | Moderate (mapped layer) |
| Bundle size | Tiny (tree-shakeable) | Large (generated client) |
| Type safety | Full (inferred from schema) | Full (generated types) |
| SQL control | Direct SQL with types | Prisma Client abstractions |
| Migrations | Drizzle Kit | Prisma Migrate |
| Edge support | First-class (Neon, Turso, PlanetScale) | Via adapter |
| Relations | Relations module (INSERT-friendly) | include/select |
Decision: Performance + SQL control + edge → Drizzle. Rich ORM features + auto-complete → Prisma.
| Database | Driver | Drizzle Package | Best For |
|---|---|---|---|
| PostgreSQL | pg or @neondatabase/serverless | drizzle-orm/pg-core | Full-featured RDBMS |
| MySQL | mysql2 | drizzle-orm/mysql-core | PlanetScale, traditional MySQL |
| SQLite | better-sqlite3 or @libsql/client | drizzle-orm/sqlite-core | Turso, local, edge |
| PostgreSQL (serverless) | @vercel/postgres | drizzle-orm/vercel-postgres | Vercel edge functions |
// src/db/schema/users.ts
import { pgTable, uuid, varchar, boolean, timestamp, uniqueIndex, index } from 'drizzle-orm/pg-core';
import { relations } from 'drizzle-orm';
export const users = pgTable('users', {
id: uuid('id').defaultRandom().primaryKey(),
email: varchar('email', { length: 255 }).notNull(),
name: varchar('name', { length: 100 }).notNull(),
role: varchar('role', { length: 20 }).$type<'admin' | 'user'>().default('user'),
active: boolean('active').default(true),
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at').defaultNow().notNull().$onUpdate(() => new Date()),
}, (table) => ({
emailIdx: uniqueIndex('users_email_idx').on(table.email),
activeIdx: index('users_active_idx').on(table.active),
}));
// src/db/schema/posts.ts
import { pgTable, uuid, varchar, text, boolean, timestamp, index } from 'drizzle-orm/pg-core';
import { users } from './users';
export const posts = pgTable('posts', {
id: uuid('id').defaultRandom().primaryKey(),
title: varchar('title', { length: 255 }).notNull(),
content: text('content'),
published: boolean('published').default(false),
authorId: uuid('author_id').notNull().references(() => users.id, { onDelete: 'cascade' }),
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at').defaultNow().notNull().$onUpdate(() => new Date()),
}, (table) => ({
authorIdx: index('posts_author_idx').on(table.authorId),
publishedIdx: index('posts_published_idx').on(table.authorId, table.published),
}));
// src/db/schema/relations.ts
import { relations } from 'drizzle-orm';
import { users } from './users';
import { posts } from './posts';
export const usersRelations = relations(users, ({ one, many }) => ({
posts: many(posts),
}));
export const postsRelations = relations(posts, ({ one }) => ({
author: one(users, {
fields: [posts.authorId],
references: [users.id],
}),
}));
// src/db/index.ts
import { drizzle } from 'drizzle-orm/node-postgres';
import { Pool } from 'pg';
import * as schema from './schema';
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
export const db = drizzle(pool, { schema });
// src/repositories/user.repository.ts
import { db } from '../db';
import { users } from '../db/schema/users';
import { eq, and, desc, count, ilike } from 'drizzle-orm';
export async function findUserById(id: string) {
const result = await db
.select({
id: users.id,
name: users.name,
email: users.email,
role: users.role,
})
.from(users)
.where(eq(users.id, id))
.limit(1);
return result[0] || null;
}
export async function findUserWithPosts(id: string) {
return db.query.users.findFirst({
where: eq(users.id, id),
with: {
posts: {
where: eq(posts.published, true),
orderBy: [desc(posts.createdAt)],
limit: 10,
},
},
});
}
export async function findUsers(page: number, limit: number) {
const [data, total] = await Promise.all([
db
.select()
.from(users)
.offset((page - 1) * limit)
.limit(limit)
.orderBy(desc(users.createdAt)),
db.select({ count: count() }).from(users),
]);
return { data, total: total[0].count, page };
}
export async function createUser(data: { email: string; name: string }) {
const result = await db.insert(users).values(data).returning();
return result[0];
}
export async function updateUser(id: string, data: Partial<{ name: string; email: string }>) {
const result = await db
.update(users)
.set(data)
.where(eq(users.id, id))
.returning();
return result[0];
}
export async function deleteUser(id: string) {
await db.delete(users).where(eq(users.id, id));
}
export async function searchUsers(query: string) {
return db
.select()
.from(users)
.where(ilike(users.name, `%${query}%`))
.limit(20);
}
// src/db/prepared.ts
import { db } from './index';
import { users } from './schema/users';
import { eq } from 'drizzle-orm';
// Prepared statements for hot paths (cached query plans)
export const getUserById = db.select({
id: users.id,
name: users.name,
email: users.email,
}).from(users).where(eq(users.id, sql.placeholder('id'))).prepare('get_user_by_id');
// Usage
const user = await getUserById.execute({ id: 'some-uuid' });
# Generate migration
npx drizzle-kit generate --name add_user_profile
# Apply migration
npx drizzle-kit migrate
# Push schema (dev only)
npx drizzle-kit push
# Check schema diff
npx drizzle-kit check
# Drizzle Kit config
// drizzle.config.ts
import type { Config } from 'drizzle-kit';
export default {
schema: './src/db/schema/*.ts',
out: './drizzle',
dialect: 'postgresql',
dbCredentials: {
url: process.env.DATABASE_URL!,
},
} satisfies Config;
// src/db/edge.ts — Neon serverless
import { drizzle } from 'drizzle-orm/neon-http';
import { neon } from '@neondatabase/serverless';
import * as schema from './schema';
const sql = neon(process.env.DATABASE_URL!);
export const db = drizzle(sql, { schema });
// src/db/turso.ts — Turso (SQLite edge)
import { drizzle } from 'drizzle-orm/libsql';
import { createClient } from '@libsql/client';
const client = createClient({ url: process.env.TURSO_DB_URL!, authToken: process.env.TURSO_AUTH_TOKEN! });
export const db = drizzle(client, { schema });
export async function createUsers(data: { email: string; name: string }[]) {
return db.insert(users).values(data).returning();
}
import { sql } from 'drizzle-orm';
export async function getActiveUsersCount() {
const result = await db.execute(
sql`SELECT COUNT(*) as count FROM users WHERE active = true`
);
return result.rows[0].count;
}
// Node.js (connection pooling)
const pool = new Pool({ max: 20, idleTimeoutMillis: 30000 });
// Graceful shutdown
process.on('SIGTERM', async () => {
await pool.end();
process.exit(0);
});
db.query.* with with for relation loading (Drizzle batches these)Partial<Model> for updates instead of full objectreturning() for INSERT/UPDATE/DELETE to avoid separate SELECTdrizzle-orm/zod to generate Zod schemas from Drizzle schemas for validation| Anti-Pattern | Why | Fix |
|---|---|---|
Missing .prepare() on hot queries | Full query parse each call | Use .prepare() for frequent queries |
select * in production | Overfetching, type safety loss | Explicit column selection |
| Direct pool in edge runtime | Connection leak, timeout | Use Neon HTTP or Turso HTTP driver |
| N+1 via manual relation queries | Sequential DB calls | Use db.query.*.findFirst({with}) |
No $onUpdate on timestamps | Stale updated_at | Always add $onUpdate(() => new Date()) |
sql template tags are safe (parameterized), sql.raw() is not — avoid itimport { test, expect, beforeAll, afterAll } from 'vitest';
import { Pool } from 'pg';
import { drizzle } from 'drizzle-orm/node-postgres';
import * as schema from '../src/db/schema';
const pool = new Pool({ connectionString: process.env.TEST_DATABASE_URL });
const db = drizzle(pool, { schema });
beforeAll(async () => {
await db.execute(sql`TRUNCATE TABLE users CASCADE`);
});
afterAll(async () => {
await pool.end();
});
test('create and find user', async () => {
const [user] = await db.insert(users).values({
email: 'test@test.com',
name: 'Test',
}).returning();
expect(user.email).toBe('test@test.com');
const found = await db.query.users.findFirst({
where: eq(users.id, user.id),
});
expect(found).toBeDefined();
});
Use TEST_DATABASE_URL for test isolation. Run tests with --pool=forks for parallelism. Use drizzle-kit push to set up test schema.
relations.ts for each domain.db.select({ columns }).from(table).where(condition) over select *.db.query.table.findFirst/findMany({ with, where }) for relation queries..prepare('name') for hot paths.timestamp columns have $onUpdate for mutation tracking.Hand off to backend/nodejs/prisma/SKILL.md for Prisma ORM or backend/nodejs/patterns/SKILL.md for advanced Node patterns.
function createModule<T>(config: ModuleConfig): T { const dependencies = initializeDependencies(config); const module = new Module(dependencies); module.hooks.onInit(); return module as T; }
class ConfigBuilder { private config: AppConfig = new AppConfig(); withDatabase(url: string): ConfigBuilder { ... } withCache(ttl: number): ConfigBuilder { ... } withLogging(level: string): ConfigBuilder { ... } build(): AppConfig { return this.config; } }
| Metric | Threshold | Severity | Action |
|---|---|---|---|
| Error rate | > 1% | Critical | Rollback or fix |
| p95 latency | > 500ms | Warning | Profile and optimize |
| Uptime | < 99.9% | Critical | Investigate infrastructure |
| Memory usage | > 80% | Warning | Check for leaks |
| CPU usage | > 80% | Warning | Scale up or optimize |
import { pgTable, serial, text, timestamp, uuid } from 'drizzle-orm/pg-core';
import { eq } from 'drizzle-orm';
import { db } from './db';
export const users = pgTable('users', {
id: serial('id').primaryKey(),
uuid: uuid('uuid').defaultRandom().notNull(),
email: text('email').notNull().unique(),
name: text('name').notNull(),
createdAt: timestamp('created_at').defaultNow().notNull(),
});
export class UserRepository {
async findById(id: number) {
return db.select().from(users).where(eq(users.id, id)).limit(1);
}
async findByEmail(email: string) {
return db.select().from(users).where(eq(users.email, email)).limit(1);
}
async create(data: { email: string; name: string }) {
return db.insert(users).values(data).returning();
}
async update(id: number, data: Partial<{ email: string; name: string }>) {
return db.update(users).set(data).where(eq(users.id, id)).returning();
}
async delete(id: number) {
return db.delete(users).where(eq(users.id, id)).returning();
}
}
import { drizzle } from 'drizzle-orm/node-postgres';
import { sql } from 'drizzle-orm';
async function createOrderWithItems(orderData: OrderInput, items: ItemInput[]) {
return await db.transaction(async (tx) => {
const [order] = await tx.insert(orders).values(orderData).returning();
const orderItems = items.map(item => ({
...item,
orderId: order.id,
}));
const inserted = await tx.insert(orderItems).values(orderItems).returning();
await tx.update(inventory)
.set({ quantity: sql`quantity - ${items.length}` })
.where(eq(inventory.productId, orderData.productId));
return { order, items: inserted };
});
}
pg pool with Drizzle. Max 20 connections per instance.drizzle-kit push for dev. drizzle-kit migrate for prod. Always version-controlled.| Anti-Pattern | Why It Hurts | Fix |
|---|---|---|
| Raw SQL strings everywhere | No type safety. SQL injection risk. | Use Drizzle query builder. Raw only when absolutely needed. |
| Over-fetching in relations | Select * with joins brings unnecessary columns. | Explicit select with only needed columns. |
| N+1 in relational queries | Loop querying related entities. | Use Drizzle relations with with or batch loading. |
| Missing indexes on foreign keys | Slow JOINs on large tables. | Index all FK columns. Use EXPLAIN ANALYZE. |
| Migrations without review | Schema drift. Production issues. | PR review for all migrations. Test on staging first. |
partial indexes for filtered queries (WHERE status = 'active').EXPLAIN ANALYZE on all slow queries. Look for seq scans on large tables.db.insert(table).values(rows) for bulk operations.limit + offset for pagination. Keyset pagination for large datasets.sql template tag with user input.ssl: true for production. Reject unauthorized certs.sql template with tenant context. Enforce per query.Cache hierarchy: L1 (in-memory local) → L2 (distributed Redis/Memcached) → L3 (CDN/Edge). Cache invalidation: TTL-based (simple, stale), event-based (complex, fresh), write-through (consistent, higher write latency), write-behind (fast writes, eventual consistency).