重要前提
安装AI Skills的关键前提是:必须科学上网,且开启TUN模式,这一点至关重要,直接决定安装能否顺利完成,在此郑重提醒三遍:科学上网,科学上网,科学上网。查看完整安装教程 →
npx skills add https://github.com/mgd34msu/goodvibes-plugin --skill database-layerscripts/
database-checklist.sh
references/
orm-comparison.md
本技能指导您在应用程序中实现数据库层,从初始模式设计到查询优化。它利用 GoodVibes 的精确工具和项目分析工具,实现类型安全、可用于生产环境的数据库实现。
在以下情况下使用此技能:
按照以下顺序实现数据库层:
在实现任何数据库更改之前,使用 detect_stack 分析工具了解当前状态:
detect_stack:
project_root: "."
categories: ["database", "orm"]
这将识别:
检查项目记忆中的数据库决策:
precision_read:
files:
- path: ".goodvibes/memory/decisions.json"
- path: ".goodvibes/memory/patterns.json"
verbosity: minimal
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
查找:
如果数据库已存在,映射当前模式:
get_database_schema:
project_root: "."
include_relations: true
include_indexes: true
这将返回:
如果从零开始,请参考 ORM 比较文档以选择合适的技术栈。
请参阅:references/orm-comparison.md 获取决策树。
关键决策因素:
| 因素 | 推荐 |
|---|---|
| 类型安全优先级 | Prisma 或 Drizzle |
| 最大 SQL 控制权 | Kysely 或 Drizzle |
| 文档数据库 | Mongoose (MongoDB) |
| 无服务器/边缘 | Drizzle 配合 libSQL/Turso |
| 现有 PostgreSQL | Prisma 或 Drizzle |
| 学习曲线 | Prisma (最佳开发者体验) |
在记忆中记录您的决策:
选择后,在 .goodvibes/memory/decisions.json 中记录决策,以供将来参考。
首先识别实体和关系:
Entities: User, Post, Comment, Category
Relationships:
- User 1:N Post (author)
- Post N:M Category (through PostCategory)
- Post 1:N Comment
- User 1:N Comment (author)
使用 precision_write 创建模式文件:
对于 Prisma:
precision_write:
files:
- path: "prisma/schema.prisma"
content: |
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(cuid())
email String @unique
name String?
posts Post[]
comments Comment[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Post {
id String @id @default(cuid())
title String
content String
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId String
categories Category[]
comments Comment[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([authorId])
@@index([published, createdAt])
}
model Category {
id String @id @default(cuid())
name String @unique
posts Post[]
}
model Comment {
id String @id @default(cuid())
content String
post Post @relation(fields: [postId], references: [id])
postId String
author User @relation(fields: [authorId], references: [id])
authorId String
createdAt DateTime @default(now())
@@index([postId])
@@index([authorId])
}
verbosity: minimal
对于 Drizzle:
precision_write:
files:
- path: "src/db/schema.ts"
content: |
import { pgTable, text, timestamp, boolean, index } from 'drizzle-orm/pg-core';
import { relations } from 'drizzle-orm';
export const users = pgTable('users', {
id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
email: text('email').notNull().unique(),
name: text('name'),
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at').defaultNow().notNull(),
});
export const posts = pgTable('posts', {
id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
title: text('title').notNull(),
content: text('content').notNull(),
published: boolean('published').default(false).notNull(),
authorId: text('author_id').notNull().references(() => users.id),
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at').defaultNow().notNull(),
}, (table) => ({
authorIdx: index('author_idx').on(table.authorId),
publishedCreatedIdx: index('published_created_idx').on(table.published, table.createdAt),
}));
export const usersRelations = relations(users, ({ many }) => ({
posts: many(posts),
}));
export const postsRelations = relations(posts, ({ one }) => ({
author: one(users, {
fields: [posts.authorId],
references: [users.id],
}),
}));
verbosity: minimal
模式最佳实践:
使用适当的 ID 策略:
添加时间戳:
createdAtupdatedAtdeletedAt 进行软删除策略性地添加索引:
为扩展做规划:
创建环境配置:
precision_write:
files:
- path: ".env.example"
content: |
# Database
DATABASE_URL="postgresql://user:password@localhost:5432/dbname"
# For Prisma with connection pooling
# DATABASE_URL="postgresql://user:password@localhost:5432/dbname?pgbouncer=true"
# DIRECT_URL="postgresql://user:password@localhost:5432/dbname"
mode: overwrite
verbosity: minimal
创建数据库客户端模块:
对于 Prisma:
precision_write:
files:
- path: "src/lib/db.ts"
content: |
import { PrismaClient } from '@prisma/client';
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined;
};
export const db =
globalForPrisma.prisma ??
new PrismaClient({
log:
process.env.NODE_ENV === 'development'
? ['query', 'error', 'warn']
: ['error'],
});
if (process.env.NODE_ENV !== 'production') {
globalForPrisma.prisma = db;
}
verbosity: minimal
对于 Drizzle:
precision_write:
files:
- path: "src/lib/db.ts"
content: |
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';
import * as schema from '@/db/schema';
const connectionString = process.env.DATABASE_URL!;
const client = postgres(connectionString, {
max: process.env.NODE_ENV === 'production' ? 10 : 1,
});
export const db = drizzle(client, { schema });
verbosity: minimal
使用 precision_exec 运行迁移命令并设置期望:
对于 Prisma:
precision_exec:
commands:
- cmd: "npx prisma migrate dev --name init"
timeout_ms: 60000
expect:
exit_code: 0
# 注意:Prisma 将进度输出到 stderr;这是预期行为
- cmd: "npx prisma generate"
expect:
exit_code: 0
verbosity: standard
对于 Drizzle:
precision_exec:
commands:
- cmd: "npx drizzle-kit generate"
expect:
exit_code: 0
- cmd: "npx drizzle-kit push"
timeout_ms: 60000
expect:
exit_code: 0
verbosity: standard
迁移最佳实践:
使用 generate_types 项目工具从您的模式生成 TypeScript 类型:
generate_types:
project_root: "."
source: "database"
output_path: "src/types/db.ts"
这将创建以下类型定义:
验证类型生成:
precision_exec:
commands:
- cmd: "npm run typecheck"
expect:
exit_code: 0
verbosity: minimal
从基本的 CRUD 操作开始:
precision_write:
files:
- path: "src/db/queries/users.ts"
content: |
import { db } from '@/lib/db';
export async function createUser(data: { email: string; name?: string }) {
return db.user.create({
data,
});
}
export async function getUserById(id: string) {
return db.user.findUnique({
where: { id },
include: {
posts: true,
},
});
}
export async function updateUser(
id: string,
data: { email?: string; name?: string }
) {
return db.user.update({
where: { id },
data,
});
}
export async function deleteUser(id: string) {
return db.user.delete({
where: { id },
});
}
verbosity: minimal
使用项目工具检查 N+1 查询模式:
get_prisma_operations:
project_root: "."
analyze_performance: true
这将识别:
include 或 select)优化查询:
使用 select 限制字段:
db.user.findMany({ select: { id: true, email: true }, // 不获取未使用的字段 });
预加载关系:
db.post.findMany({ include: { author: true }, // 防止 N+1 });
使用分页:
db.post.findMany({ take: 20, skip: (page - 1) * 20, });
添加数据库级约束:
@@index([userId, createdAt(sort: Desc)])
对于多步骤操作,使用事务:
Prisma:
export async function createPostWithCategories(
postData: { title: string; content: string; authorId: string },
categoryIds: string[]
) {
return db.$transaction(async (tx) => {
const post = await tx.post.create({
data: {
...postData,
categories: {
connect: categoryIds.map((id) => ({ id })),
},
},
});
await tx.user.update({
where: { id: postData.authorId },
data: { updatedAt: new Date() },
});
return post;
});
}
Drizzle:
export async function createPostWithCategories(
postData: { title: string; content: string; authorId: string },
categoryIds: string[]
) {
return db.transaction(async (tx) => {
const [post] = await tx.insert(posts).values(postData).returning();
await tx.insert(postCategories).values(
categoryIds.map((categoryId) => ({
postId: post.id,
categoryId,
}))
);
return post;
});
}
为本地开发创建填充脚本:
precision_write:
files:
- path: "prisma/seed.ts"
content: |
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function main() {
// Clear existing data
await prisma.comment.deleteMany();
await prisma.post.deleteMany();
await prisma.user.deleteMany();
await prisma.category.deleteMany();
// Create users
const alice = await prisma.user.create({
data: {
email: 'alice@example.com',
name: 'Alice',
},
});
const bob = await prisma.user.create({
data: {
email: 'bob@example.com',
name: 'Bob',
},
});
// Create categories
const tech = await prisma.category.create({
data: { name: 'Technology' },
});
const news = await prisma.category.create({
data: { name: 'News' },
});
// Create posts
await prisma.post.create({
data: {
title: 'First Post',
content: 'This is the first post',
published: true,
authorId: alice.id,
categories: {
connect: [{ id: tech.id }],
},
},
});
console.log('Database seeded successfully');
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});
verbosity: minimal
更新 package.json:
precision_edit:
edits:
- path: "package.json"
find: '"scripts": {'
hints:
near_line: 2
replace: |
"prisma": {
"seed": "tsx prisma/seed.ts"
},
"scripts": {
verbosity: minimal
运行数据库检查清单脚本:
./plugins/goodvibes/skills/outcome/database-layer/scripts/database-checklist.sh .
这将验证:
运行类型检查和测试:
precision_exec:
commands:
- cmd: "npm run typecheck"
expect:
exit_code: 0
- cmd: "npm run test -- db"
expect:
exit_code: 0
verbosity: minimal
使用 query_database 验证数据完整性:
query_database:
project_root: "."
query: "SELECT COUNT(*) FROM users;"
添加 deletedAt 字段并在查询中过滤:
model Post {
id String @id
deletedAt DateTime?
}
// 软删除
await db.post.update({
where: { id },
data: { deletedAt: new Date() },
});
// 仅查询活动记录
await db.post.findMany({
where: { deletedAt: null },
});
使用版本字段防止并发更新:
model Post {
id String @id
version Int @default(0)
}
await db.post.update({
where: {
id: postId,
version: currentVersion,
},
data: {
title: newTitle,
version: { increment: 1 },
},
});
对于无服务器环境,使用连接池:
# PgBouncer
DATABASE_URL="postgresql://user:password@localhost:6543/db?pgbouncer=true"
DIRECT_URL="postgresql://user:password@localhost:5432/db"
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
directUrl = env("DIRECT_URL")
}
PostgreSQL:
@@index([content(ops: raw("gin_trgm_ops"))], type: Gin)
await db.$queryRaw`
SELECT * FROM posts
WHERE to_tsvector('english', content) @@ to_tsquery('search terms')
`;
get_prisma_operations 识别位置include 或 selectdataloader?pool_timeout=10)await (连接未释放)npx prisma validate 验证模式语法实现数据库层后:
有关其他参考资料和决策树,请参阅:
每周安装数
50
代码仓库
GitHub 星标数
6
首次出现
2026年2月17日
安全审计
安装于
amp50
github-copilot50
codex50
kimi-cli50
gemini-cli50
opencode50
scripts/
database-checklist.sh
references/
orm-comparison.md
This skill guides you through implementing database layers in applications, from initial schema design to query optimization. It leverages GoodVibes precision tools and project analysis tools for type-safe, production-ready database implementations.
Use this skill when you need to:
Follow this sequence for database layer implementation:
Before implementing any database changes, understand the current state using the detect_stack analysis tool:
detect_stack:
project_root: "."
categories: ["database", "orm"]
This identifies:
Check project memory for database decisions:
precision_read:
files:
- path: ".goodvibes/memory/decisions.json"
- path: ".goodvibes/memory/patterns.json"
verbosity: minimal
Look for:
If database already exists, map the current schema:
get_database_schema:
project_root: "."
include_relations: true
include_indexes: true
This returns:
If starting fresh, consult the ORM comparison reference to select the appropriate technology stack.
See: references/orm-comparison.md for decision trees.
Key decision factors:
| Factor | Recommendation |
|---|---|
| Type safety priority | Prisma or Drizzle |
| Maximum SQL control | Kysely or Drizzle |
| Document database | Mongoose (MongoDB) |
| Serverless/edge | Drizzle with libSQL/Turso |
| Existing PostgreSQL | Prisma or Drizzle |
| Learning curve | Prisma (best DX) |
Record your decision in memory:
After choosing, document the decision in .goodvibes/memory/decisions.json for future reference.
Identify entities and relationships first:
Entities: User, Post, Comment, Category
Relationships:
- User 1:N Post (author)
- Post N:M Category (through PostCategory)
- Post 1:N Comment
- User 1:N Comment (author)
Create schema files using precision_write:
For Prisma:
precision_write:
files:
- path: "prisma/schema.prisma"
content: |
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(cuid())
email String @unique
name String?
posts Post[]
comments Comment[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Post {
id String @id @default(cuid())
title String
content String
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId String
categories Category[]
comments Comment[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([authorId])
@@index([published, createdAt])
}
model Category {
id String @id @default(cuid())
name String @unique
posts Post[]
}
model Comment {
id String @id @default(cuid())
content String
post Post @relation(fields: [postId], references: [id])
postId String
author User @relation(fields: [authorId], references: [id])
authorId String
createdAt DateTime @default(now())
@@index([postId])
@@index([authorId])
}
verbosity: minimal
For Drizzle:
precision_write:
files:
- path: "src/db/schema.ts"
content: |
import { pgTable, text, timestamp, boolean, index } from 'drizzle-orm/pg-core';
import { relations } from 'drizzle-orm';
export const users = pgTable('users', {
id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
email: text('email').notNull().unique(),
name: text('name'),
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at').defaultNow().notNull(),
});
export const posts = pgTable('posts', {
id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
title: text('title').notNull(),
content: text('content').notNull(),
published: boolean('published').default(false).notNull(),
authorId: text('author_id').notNull().references(() => users.id),
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at').defaultNow().notNull(),
}, (table) => ({
authorIdx: index('author_idx').on(table.authorId),
publishedCreatedIdx: index('published_created_idx').on(table.published, table.createdAt),
}));
export const usersRelations = relations(users, ({ many }) => ({
posts: many(posts),
}));
export const postsRelations = relations(posts, ({ one }) => ({
author: one(users, {
fields: [posts.authorId],
references: [users.id],
}),
}));
verbosity: minimal
Schema best practices:
Use appropriate ID strategy:
Add timestamps:
createdAtupdatedAt for mutable entitiesdeletedAt for soft deletesIndex strategically:
Plan for scale:
Create environment configuration:
precision_write:
files:
- path: ".env.example"
content: |
# Database
DATABASE_URL="postgresql://user:password@localhost:5432/dbname"
# For Prisma with connection pooling
# DATABASE_URL="postgresql://user:password@localhost:5432/dbname?pgbouncer=true"
# DIRECT_URL="postgresql://user:password@localhost:5432/dbname"
mode: overwrite
verbosity: minimal
Create database client module:
For Prisma:
precision_write:
files:
- path: "src/lib/db.ts"
content: |
import { PrismaClient } from '@prisma/client';
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined;
};
export const db =
globalForPrisma.prisma ??
new PrismaClient({
log:
process.env.NODE_ENV === 'development'
? ['query', 'error', 'warn']
: ['error'],
});
if (process.env.NODE_ENV !== 'production') {
globalForPrisma.prisma = db;
}
verbosity: minimal
For Drizzle:
precision_write:
files:
- path: "src/lib/db.ts"
content: |
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';
import * as schema from '@/db/schema';
const connectionString = process.env.DATABASE_URL!;
const client = postgres(connectionString, {
max: process.env.NODE_ENV === 'production' ? 10 : 1,
});
export const db = drizzle(client, { schema });
verbosity: minimal
Use precision_exec to run migration commands with expectations:
For Prisma:
precision_exec:
commands:
- cmd: "npx prisma migrate dev --name init"
timeout_ms: 60000
expect:
exit_code: 0
# Note: Prisma outputs progress to stderr; this is expected behavior
- cmd: "npx prisma generate"
expect:
exit_code: 0
verbosity: standard
For Drizzle:
precision_exec:
commands:
- cmd: "npx drizzle-kit generate"
expect:
exit_code: 0
- cmd: "npx drizzle-kit push"
timeout_ms: 60000
expect:
exit_code: 0
verbosity: standard
Migration best practices:
Use the generate_types project tool to generate TypeScript types from your schema:
generate_types:
project_root: "."
source: "database"
output_path: "src/types/db.ts"
This creates type definitions for:
Verify type generation:
precision_exec:
commands:
- cmd: "npm run typecheck"
expect:
exit_code: 0
verbosity: minimal
Start with basic CRUD operations:
precision_write:
files:
- path: "src/db/queries/users.ts"
content: |
import { db } from '@/lib/db';
export async function createUser(data: { email: string; name?: string }) {
return db.user.create({
data,
});
}
export async function getUserById(id: string) {
return db.user.findUnique({
where: { id },
include: {
posts: true,
},
});
}
export async function updateUser(
id: string,
data: { email?: string; name?: string }
) {
return db.user.update({
where: { id },
data,
});
}
export async function deleteUser(id: string) {
return db.user.delete({
where: { id },
});
}
verbosity: minimal
Check for N+1 query patterns using project tools:
get_prisma_operations:
project_root: "."
analyze_performance: true
This identifies:
include or select)Optimize queries:
Use select to limit fields:
db.user.findMany({ select: { id: true, email: true }, // Don't fetch unused fields });
Eager load relationships:
db.post.findMany({ include: { author: true }, // Prevents N+1 });
Use pagination:
db.post.findMany({ take: 20, skip: (page - 1) * 20, });
Add database-level constraints:
@@index([userId, createdAt(sort: Desc)])
For multi-step operations, use transactions:
Prisma:
export async function createPostWithCategories(
postData: { title: string; content: string; authorId: string },
categoryIds: string[]
) {
return db.$transaction(async (tx) => {
const post = await tx.post.create({
data: {
...postData,
categories: {
connect: categoryIds.map((id) => ({ id })),
},
},
});
await tx.user.update({
where: { id: postData.authorId },
data: { updatedAt: new Date() },
});
return post;
});
}
Drizzle:
export async function createPostWithCategories(
postData: { title: string; content: string; authorId: string },
categoryIds: string[]
) {
return db.transaction(async (tx) => {
const [post] = await tx.insert(posts).values(postData).returning();
await tx.insert(postCategories).values(
categoryIds.map((categoryId) => ({
postId: post.id,
categoryId,
}))
);
return post;
});
}
Create seed script for local development:
precision_write:
files:
- path: "prisma/seed.ts"
content: |
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function main() {
// Clear existing data
await prisma.comment.deleteMany();
await prisma.post.deleteMany();
await prisma.user.deleteMany();
await prisma.category.deleteMany();
// Create users
const alice = await prisma.user.create({
data: {
email: 'alice@example.com',
name: 'Alice',
},
});
const bob = await prisma.user.create({
data: {
email: 'bob@example.com',
name: 'Bob',
},
});
// Create categories
const tech = await prisma.category.create({
data: { name: 'Technology' },
});
const news = await prisma.category.create({
data: { name: 'News' },
});
// Create posts
await prisma.post.create({
data: {
title: 'First Post',
content: 'This is the first post',
published: true,
authorId: alice.id,
categories: {
connect: [{ id: tech.id }],
},
},
});
console.log('Database seeded successfully');
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});
verbosity: minimal
Update package.json:
precision_edit:
edits:
- path: "package.json"
find: '"scripts": {'
hints:
near_line: 2
replace: |
"prisma": {
"seed": "tsx prisma/seed.ts"
},
"scripts": {
verbosity: minimal
Run the database checklist script:
./plugins/goodvibes/skills/outcome/database-layer/scripts/database-checklist.sh .
This validates:
Run type checking and tests:
precision_exec:
commands:
- cmd: "npm run typecheck"
expect:
exit_code: 0
- cmd: "npm run test -- db"
expect:
exit_code: 0
verbosity: minimal
Use query_database to verify data integrity:
query_database:
project_root: "."
query: "SELECT COUNT(*) FROM users;"
Add deletedAt field and filter in queries:
model Post {
id String @id
deletedAt DateTime?
}
// Soft delete
await db.post.update({
where: { id },
data: { deletedAt: new Date() },
});
// Query only active records
await db.post.findMany({
where: { deletedAt: null },
});
Use version field to prevent concurrent updates:
model Post {
id String @id
version Int @default(0)
}
await db.post.update({
where: {
id: postId,
version: currentVersion,
},
data: {
title: newTitle,
version: { increment: 1 },
},
});
For serverless environments, use connection pooling:
# PgBouncer
DATABASE_URL="postgresql://user:password@localhost:6543/db?pgbouncer=true"
DIRECT_URL="postgresql://user:password@localhost:5432/db"
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
directUrl = env("DIRECT_URL")
}
PostgreSQL:
@@index([content(ops: raw("gin_trgm_ops"))], type: Gin)
await db.$queryRaw`
SELECT * FROM posts
WHERE to_tsvector('english', content) @@ to_tsquery('search terms')
`;
get_prisma_operations to identify locationinclude or select with relationsdataloader for complex cases?pool_timeout=10)await (connections not released)npx prisma validateAfter implementing the database layer:
For additional reference material and decision trees, see:
Weekly Installs
50
Repository
GitHub Stars
6
First Seen
Feb 17, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
amp50
github-copilot50
codex50
kimi-cli50
gemini-cli50
opencode50
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
127,000 周安装
VSCode Claude 通知修复技能 - 解决Windows/macOS通知不触发问题 | 原生系统通知
1 周安装
SvelteKit shadcn-svelte 组件库完整指南:安装、配置与使用教程
1 周安装
SvelteKit渐进式架构规范:代码编写、服务器分层与REST API设计指南
1 周安装
SvelteKit媒体存储方案:文件上传、对象存储集成与附件管理最佳实践
1 周安装
站点可靠性工程师工具:Docusaurus MDX验证、预提交检查与自动化健康报告
48 周安装
SvelteKit shadcn-svelte 完整指南:组件安装、使用原则与分类列表
1 周安装