Initial commit
This commit is contained in:
446
skills/reviewing-prisma-patterns/references/example-reviews.md
Normal file
446
skills/reviewing-prisma-patterns/references/example-reviews.md
Normal file
@@ -0,0 +1,446 @@
|
||||
# Example Reviews
|
||||
|
||||
Complete code review examples showing typical findings and recommendations.
|
||||
|
||||
---
|
||||
|
||||
## Example 1: E-commerce API (Next.js)
|
||||
|
||||
**Context:** Next.js 14 App Router with Prisma, deployed to Vercel
|
||||
|
||||
**Project Structure:**
|
||||
```
|
||||
app/
|
||||
├── api/
|
||||
│ ├── products/route.ts
|
||||
│ ├── users/route.ts
|
||||
│ └── search/route.ts
|
||||
├── lib/
|
||||
│ └── db.ts
|
||||
└── .env
|
||||
```
|
||||
|
||||
**Findings:**
|
||||
|
||||
```
|
||||
Prisma Code Review - E-commerce API
|
||||
Generated: 2025-11-21
|
||||
Files Reviewed: 15
|
||||
|
||||
CRITICAL Issues (P0): 2
|
||||
HIGH Issues (P1): 1
|
||||
MEDIUM Issues (P2): 3
|
||||
LOW Issues (P3): 2
|
||||
|
||||
Overall Assessment: CRITICAL ISSUES - Do not deploy
|
||||
|
||||
---
|
||||
|
||||
[P0] Multiple PrismaClient Instances
|
||||
Files:
|
||||
- app/api/products/route.ts:8
|
||||
- app/api/users/route.ts:12
|
||||
- lib/db.ts:5
|
||||
Count: 3 instances found
|
||||
|
||||
Code (app/api/products/route.ts:8):
|
||||
```typescript
|
||||
import { PrismaClient } from '@prisma/client'
|
||||
const prisma = new PrismaClient()
|
||||
|
||||
export async function GET() {
|
||||
const products = await prisma.product.findMany()
|
||||
return Response.json(products)
|
||||
}
|
||||
```
|
||||
|
||||
Impact: CRITICAL - Connection pool exhaustion under load
|
||||
- Each API route creates separate connection pool
|
||||
- Vercel scales to 100+ concurrent functions
|
||||
- 3 routes × 100 instances × 10 connections = 3000 connections!
|
||||
- Database will reject connections (P1017)
|
||||
|
||||
Fix: Create global singleton in lib/db.ts, import everywhere
|
||||
|
||||
Remediation Steps:
|
||||
1. Create lib/prisma.ts with global singleton pattern
|
||||
2. Replace all `new PrismaClient()` with imports
|
||||
3. Verify with grep (should find only 1 instance)
|
||||
|
||||
Reference: @prisma-6/CLIENT-singleton-pattern
|
||||
|
||||
---
|
||||
|
||||
[P0] SQL Injection Vulnerability
|
||||
File: app/api/search/route.ts:23
|
||||
|
||||
Code:
|
||||
```typescript
|
||||
export async function GET(request: Request) {
|
||||
const { searchParams } = new URL(request.url)
|
||||
const query = searchParams.get('q')
|
||||
|
||||
const products = await prisma.$queryRawUnsafe(
|
||||
`SELECT * FROM products WHERE name LIKE '%${query}%'`
|
||||
)
|
||||
|
||||
return Response.json(products)
|
||||
}
|
||||
```
|
||||
|
||||
Impact: CRITICAL - Enables SQL injection attacks
|
||||
- User controls `query` parameter
|
||||
- Direct string interpolation allows injection
|
||||
- Attacker can execute arbitrary SQL
|
||||
|
||||
Example attack:
|
||||
```
|
||||
/api/search?q=%27;%20DROP%20TABLE%20products;--
|
||||
```
|
||||
|
||||
Fix: Use $queryRaw tagged template with automatic parameterization
|
||||
|
||||
Remediation:
|
||||
```typescript
|
||||
const products = await prisma.$queryRaw`
|
||||
SELECT * FROM products WHERE name LIKE ${'%' + query + '%'}
|
||||
`
|
||||
```
|
||||
|
||||
Reference: @prisma-6/SECURITY-sql-injection
|
||||
|
||||
---
|
||||
|
||||
[P1] Missing Serverless Configuration
|
||||
File: .env
|
||||
|
||||
Current:
|
||||
```
|
||||
DATABASE_URL="postgresql://user:pass@host:5432/db"
|
||||
```
|
||||
|
||||
Impact: HIGH - Connection exhaustion in Vercel deployment
|
||||
- Default pool_size = 10 connections per instance
|
||||
- Vercel can scale to 100+ instances
|
||||
- 100 instances × 10 connections = 1000 connections
|
||||
- Most databases have 100-200 connection limit
|
||||
|
||||
Fix: Add ?connection_limit=1 to DATABASE_URL
|
||||
|
||||
Remediation:
|
||||
```
|
||||
DATABASE_URL="postgresql://user:pass@host:5432/db?connection_limit=1&pool_timeout=10"
|
||||
```
|
||||
|
||||
Why this works:
|
||||
- Each Vercel function instance gets 1 connection
|
||||
- 100 instances × 1 connection = 100 connections (sustainable)
|
||||
- pool_timeout=10 prevents hanging on exhaustion
|
||||
|
||||
Reference: @prisma-6/CLIENT-serverless-config
|
||||
|
||||
---
|
||||
|
||||
[P2] Missing Input Validation
|
||||
Files: app/api/users/route.ts, app/api/products/route.ts
|
||||
|
||||
Code (app/api/users/route.ts):
|
||||
```typescript
|
||||
export async function POST(request: Request) {
|
||||
const data = await request.json()
|
||||
const user = await prisma.user.create({ data })
|
||||
return Response.json(user)
|
||||
}
|
||||
```
|
||||
|
||||
Impact: MEDIUM - Invalid data can reach database
|
||||
- No validation of email format
|
||||
- No validation of required fields
|
||||
- Type mismatches cause runtime errors
|
||||
|
||||
Fix: Add Zod validation schemas before Prisma operations
|
||||
|
||||
Remediation:
|
||||
```typescript
|
||||
import { z } from 'zod'
|
||||
|
||||
const userSchema = z.object({
|
||||
email: z.string().email(),
|
||||
name: z.string().min(1),
|
||||
})
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const data = await request.json()
|
||||
const validated = userSchema.parse(data)
|
||||
const user = await prisma.user.create({ data: validated })
|
||||
return Response.json(user)
|
||||
}
|
||||
```
|
||||
|
||||
Reference: @prisma-6/SECURITY-input-validation
|
||||
|
||||
---
|
||||
|
||||
[P3] Inefficient Pagination
|
||||
File: app/api/products/route.ts:15
|
||||
|
||||
Code:
|
||||
```typescript
|
||||
const page = parseInt(searchParams.get('page') ?? '0')
|
||||
const products = await prisma.product.findMany({
|
||||
skip: page * 100,
|
||||
take: 100
|
||||
})
|
||||
```
|
||||
|
||||
Impact: LOW - Slow queries on large datasets
|
||||
- Product table has 50k+ records
|
||||
- Offset pagination degrades with page number
|
||||
- Page 500 skips 50k records (slow!)
|
||||
|
||||
Fix: Use cursor-based pagination with id cursor
|
||||
|
||||
Remediation:
|
||||
```typescript
|
||||
const cursor = searchParams.get('cursor')
|
||||
const products = await prisma.product.findMany({
|
||||
take: 100,
|
||||
cursor: cursor ? { id: cursor } : undefined,
|
||||
orderBy: { id: 'asc' }
|
||||
})
|
||||
|
||||
const nextCursor = products.length === 100
|
||||
? products[99].id
|
||||
: null
|
||||
```
|
||||
|
||||
Reference: @prisma-6/QUERIES-pagination
|
||||
|
||||
---
|
||||
|
||||
RECOMMENDATION: Fix P0 issues immediately before any deployment. P1 issues will cause production failures under load.
|
||||
|
||||
Priority Actions:
|
||||
1. Implement global singleton pattern (blocking)
|
||||
2. Fix SQL injection in search endpoint (blocking)
|
||||
3. Add connection_limit to DATABASE_URL (high priority)
|
||||
4. Add Zod validation to API routes (recommended)
|
||||
5. Optimize pagination for products (nice to have)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Example 2: Internal Dashboard (Express)
|
||||
|
||||
**Context:** Express API with PostgreSQL, traditional server deployment
|
||||
|
||||
**Project Structure:**
|
||||
```
|
||||
src/
|
||||
├── controllers/
|
||||
│ ├── users.ts
|
||||
│ └── reports.ts
|
||||
├── db.ts
|
||||
└── index.ts
|
||||
```
|
||||
|
||||
**Findings:**
|
||||
|
||||
```
|
||||
Prisma Code Review - Internal Dashboard
|
||||
Generated: 2025-11-21
|
||||
Files Reviewed: 8
|
||||
|
||||
CRITICAL Issues (P0): 0
|
||||
HIGH Issues (P1): 0
|
||||
MEDIUM Issues (P2): 1
|
||||
LOW Issues (P3): 3
|
||||
|
||||
Overall Assessment: GOOD - Minor improvements recommended
|
||||
|
||||
---
|
||||
|
||||
[P2] Generic Error Handling
|
||||
File: src/controllers/users.ts:45-52
|
||||
|
||||
Code:
|
||||
```typescript
|
||||
async function createUser(req: Request, res: Response) {
|
||||
try {
|
||||
const user = await prisma.user.create({
|
||||
data: req.body
|
||||
})
|
||||
res.json(user)
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: 'Database error' })
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Impact: MEDIUM - P2002/P2025 not handled specifically
|
||||
- User gets generic "Database error" for all failures
|
||||
- Duplicate email returns 500 instead of 409
|
||||
- Poor developer experience debugging issues
|
||||
|
||||
Fix: Check error.code for P2002 (unique), P2025 (not found)
|
||||
|
||||
Remediation:
|
||||
```typescript
|
||||
import { PrismaClientKnownRequestError } from '@prisma/client/runtime/library'
|
||||
|
||||
async function createUser(req: Request, res: Response) {
|
||||
try {
|
||||
const user = await prisma.user.create({
|
||||
data: req.body
|
||||
})
|
||||
res.json(user)
|
||||
} catch (error) {
|
||||
if (error instanceof PrismaClientKnownRequestError) {
|
||||
if (error.code === 'P2002') {
|
||||
return res.status(409).json({
|
||||
error: 'User with this email already exists'
|
||||
})
|
||||
}
|
||||
}
|
||||
res.status(500).json({ error: 'Unexpected error' })
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Reference: @prisma-6/TRANSACTIONS-error-handling
|
||||
|
||||
---
|
||||
|
||||
[P3] Inefficient Pagination
|
||||
File: src/controllers/reports.ts:78
|
||||
|
||||
Code:
|
||||
```typescript
|
||||
const reports = await prisma.report.findMany({
|
||||
skip: page * 100,
|
||||
take: 100,
|
||||
orderBy: { createdAt: 'desc' }
|
||||
})
|
||||
```
|
||||
|
||||
Context:
|
||||
- Reports table has 50k+ records
|
||||
- Used in admin dashboard for audit logs
|
||||
- Page 500 requires scanning 50k records
|
||||
|
||||
Impact: LOW - Slow queries on large datasets
|
||||
- Query time increases with page number
|
||||
- Database performs full table scan
|
||||
- Admin dashboard feels sluggish
|
||||
|
||||
Fix: Use cursor-based pagination with id cursor
|
||||
|
||||
Remediation:
|
||||
```typescript
|
||||
const reports = await prisma.report.findMany({
|
||||
take: 100,
|
||||
cursor: lastId ? { id: lastId } : undefined,
|
||||
orderBy: { createdAt: 'desc' }
|
||||
})
|
||||
```
|
||||
|
||||
Reference: @prisma-6/QUERIES-pagination
|
||||
|
||||
---
|
||||
|
||||
[P3] Missing Select Optimization
|
||||
Files: 8 files with findMany() lacking select
|
||||
|
||||
Examples:
|
||||
- src/controllers/users.ts:23
|
||||
- src/controllers/reports.ts:45
|
||||
- src/controllers/analytics.ts:67
|
||||
|
||||
Code pattern:
|
||||
```typescript
|
||||
const users = await prisma.user.findMany()
|
||||
```
|
||||
|
||||
Impact: LOW - Fetching unnecessary fields
|
||||
- Returns all columns including large text fields
|
||||
- Increases response payload size
|
||||
- Wastes database bandwidth
|
||||
|
||||
Fix: Add select: { id, name, email } to queries
|
||||
|
||||
Remediation:
|
||||
```typescript
|
||||
const users = await prisma.user.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
role: true
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
Reference: @prisma-6/QUERIES-select-optimization
|
||||
|
||||
---
|
||||
|
||||
[P3] Missing Select in List Endpoints
|
||||
File: src/controllers/users.ts:88
|
||||
|
||||
Code:
|
||||
```typescript
|
||||
const users = await prisma.user.findMany({
|
||||
include: { posts: true }
|
||||
})
|
||||
```
|
||||
|
||||
Impact: LOW - Over-fetching related data
|
||||
- Returns ALL posts for each user
|
||||
- User with 1000 posts = huge payload
|
||||
- Should paginate posts separately
|
||||
|
||||
Fix: Limit included records or use separate query
|
||||
|
||||
Remediation:
|
||||
```typescript
|
||||
const users = await prisma.user.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
_count: {
|
||||
select: { posts: true }
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
Reference: @prisma-6/QUERIES-select-optimization
|
||||
|
||||
---
|
||||
|
||||
ASSESSMENT: Code quality is good. No critical issues found.
|
||||
|
||||
Recommended Improvements:
|
||||
1. Improve error handling with P-code checks (user experience)
|
||||
2. Optimize pagination for reports table (performance)
|
||||
3. Add select clauses to list endpoints (efficiency)
|
||||
|
||||
These improvements are optional but will enhance code quality and performance.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
**E-commerce API:**
|
||||
- High-risk serverless deployment with critical security/stability issues
|
||||
- Must fix P0 issues before deployment
|
||||
- Typical of AI-generated code without production hardening
|
||||
|
||||
**Internal Dashboard:**
|
||||
- Low-risk traditional server deployment with minor optimizations
|
||||
- No blocking issues
|
||||
- Good baseline quality with room for improvement
|
||||
|
||||
Both examples demonstrate the importance of systematic code review before production deployment.
|
||||
433
skills/reviewing-prisma-patterns/references/validation-checks.md
Normal file
433
skills/reviewing-prisma-patterns/references/validation-checks.md
Normal file
@@ -0,0 +1,433 @@
|
||||
# Validation Checks
|
||||
|
||||
Complete validation patterns for all 7 critical issue categories in Prisma code review.
|
||||
|
||||
---
|
||||
|
||||
## 1. SQL Injection Detection (CRITICAL - P0)
|
||||
|
||||
**Pattern:** Unsafe raw SQL usage
|
||||
|
||||
**Detection Command:**
|
||||
```bash
|
||||
grep -rn "\$queryRawUnsafe\|Prisma\.raw" --include="*.ts" --include="*.js" .
|
||||
```
|
||||
|
||||
**Red flags:**
|
||||
- `$queryRawUnsafe` with string concatenation
|
||||
- `Prisma.raw()` with template literals (non-tagged)
|
||||
- Dynamic table/column names via string interpolation
|
||||
- Filter conditions with user input interpolation
|
||||
|
||||
**Example violations:**
|
||||
|
||||
```typescript
|
||||
const users = await prisma.$queryRawUnsafe(
|
||||
`SELECT * FROM users WHERE email = '${email}'`
|
||||
);
|
||||
|
||||
const posts = await prisma.$queryRaw(
|
||||
Prisma.raw(`SELECT * FROM posts WHERE title LIKE '%${search}%'`)
|
||||
);
|
||||
```
|
||||
|
||||
**Remediation:**
|
||||
|
||||
Use `$queryRaw` tagged template for automatic parameterization:
|
||||
|
||||
```typescript
|
||||
const users = await prisma.$queryRaw`
|
||||
SELECT * FROM users WHERE email = ${email}
|
||||
`;
|
||||
|
||||
const posts = await prisma.$queryRaw`
|
||||
SELECT * FROM posts WHERE title LIKE ${'%' + search + '%'}
|
||||
`;
|
||||
```
|
||||
|
||||
Use Prisma.sql for composition:
|
||||
|
||||
```typescript
|
||||
import { Prisma } from '@prisma/client'
|
||||
|
||||
const emailFilter = Prisma.sql`email = ${email}`
|
||||
const users = await prisma.$queryRaw`
|
||||
SELECT * FROM users WHERE ${emailFilter}
|
||||
`
|
||||
```
|
||||
|
||||
**Impact:** CRITICAL - SQL injection enables arbitrary database access, data exfiltration, deletion
|
||||
|
||||
**Reference:** @prisma-6/SECURITY-sql-injection
|
||||
|
||||
---
|
||||
|
||||
## 2. Multiple PrismaClient Instances (CRITICAL - P0)
|
||||
|
||||
**Pattern:** Multiple client instantiation
|
||||
|
||||
**Detection Command:**
|
||||
```bash
|
||||
grep -rn "new PrismaClient()" --include="*.ts" --include="*.js" . | wc -l
|
||||
```
|
||||
|
||||
**Red flags:**
|
||||
- Count > 1 across codebase
|
||||
- Function-scoped client creation
|
||||
- Missing global singleton pattern
|
||||
- Test files creating separate instances
|
||||
|
||||
**Example violations:**
|
||||
|
||||
```typescript
|
||||
export function getUser(id: string) {
|
||||
const prisma = new PrismaClient();
|
||||
return prisma.user.findUnique({ where: { id } });
|
||||
}
|
||||
|
||||
export function getPost(id: string) {
|
||||
const prisma = new PrismaClient();
|
||||
return prisma.post.findUnique({ where: { id } });
|
||||
}
|
||||
```
|
||||
|
||||
**Remediation:**
|
||||
|
||||
Create global singleton:
|
||||
|
||||
```typescript
|
||||
import { PrismaClient } from '@prisma/client'
|
||||
|
||||
const globalForPrisma = globalThis as unknown as {
|
||||
prisma: PrismaClient | undefined
|
||||
}
|
||||
|
||||
export const prisma = globalForPrisma.prisma ?? new PrismaClient()
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
|
||||
```
|
||||
|
||||
Import singleton everywhere:
|
||||
|
||||
```typescript
|
||||
import { prisma } from '@/lib/prisma'
|
||||
|
||||
export function getUser(id: string) {
|
||||
return prisma.user.findUnique({ where: { id } });
|
||||
}
|
||||
```
|
||||
|
||||
**Impact:** CRITICAL - Connection pool exhaustion, P1017 errors, production outages
|
||||
|
||||
**Reference:** @prisma-6/CLIENT-singleton-pattern
|
||||
|
||||
---
|
||||
|
||||
## 3. Missing Serverless Configuration (HIGH - P1)
|
||||
|
||||
**Pattern:** Serverless deployment without connection limits
|
||||
|
||||
**Detection:**
|
||||
|
||||
1. Check for serverless context:
|
||||
```bash
|
||||
test -f vercel.json || test -d app/ || grep -q "lambda" package.json
|
||||
```
|
||||
|
||||
2. Check for connection_limit:
|
||||
```bash
|
||||
grep -rn "connection_limit=1" --include="*.env*" --include="schema.prisma" .
|
||||
```
|
||||
|
||||
**Red flags:**
|
||||
- Serverless deployment detected (Vercel, Lambda, Cloudflare Workers)
|
||||
- No `connection_limit=1` in DATABASE_URL
|
||||
- No PgBouncer configuration
|
||||
- Default pool_timeout settings
|
||||
|
||||
**Example violation:**
|
||||
|
||||
```
|
||||
DATABASE_URL="postgresql://user:pass@host:5432/db"
|
||||
```
|
||||
|
||||
**Remediation:**
|
||||
|
||||
Add connection_limit to DATABASE_URL:
|
||||
|
||||
```
|
||||
DATABASE_URL="postgresql://user:pass@host:5432/db?connection_limit=1&pool_timeout=10"
|
||||
```
|
||||
|
||||
For Next.js on Vercel:
|
||||
|
||||
```typescript
|
||||
export const prisma = new PrismaClient({
|
||||
datasources: {
|
||||
db: {
|
||||
url: process.env.DATABASE_URL + '?connection_limit=1'
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
**Impact:** HIGH - Production database connection exhaustion under load
|
||||
|
||||
**Reference:** @prisma-6/CLIENT-serverless-config
|
||||
|
||||
---
|
||||
|
||||
## 4. Deprecated Buffer API (HIGH - P1)
|
||||
|
||||
**Pattern:** Prisma 6 breaking change - Buffer on Bytes fields
|
||||
|
||||
**Detection Command:**
|
||||
```bash
|
||||
grep -rn "Buffer\.from\|\.toString()" --include="*.ts" --include="*.js" . | grep -i "bytes\|binary"
|
||||
```
|
||||
|
||||
**Red flags:**
|
||||
- `Buffer.from()` used with Bytes fields
|
||||
- `.toString()` called on Bytes field results
|
||||
- Missing Uint8Array conversion
|
||||
- Missing TextEncoder/TextDecoder
|
||||
|
||||
**Example violations:**
|
||||
|
||||
```typescript
|
||||
const user = await prisma.user.create({
|
||||
data: {
|
||||
avatar: Buffer.from(base64Data, 'base64')
|
||||
}
|
||||
});
|
||||
|
||||
const avatarString = user.avatar.toString('base64');
|
||||
```
|
||||
|
||||
**Remediation:**
|
||||
|
||||
Use Uint8Array instead of Buffer:
|
||||
|
||||
```typescript
|
||||
const base64ToUint8Array = (base64: string) => {
|
||||
const binary = atob(base64);
|
||||
const bytes = new Uint8Array(binary.length);
|
||||
for (let i = 0; i < binary.length; i++) {
|
||||
bytes[i] = binary.charCodeAt(i);
|
||||
}
|
||||
return bytes;
|
||||
};
|
||||
|
||||
const user = await prisma.user.create({
|
||||
data: {
|
||||
avatar: base64ToUint8Array(base64Data)
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Use TextEncoder/TextDecoder:
|
||||
|
||||
```typescript
|
||||
const encoder = new TextEncoder();
|
||||
const decoder = new TextDecoder();
|
||||
|
||||
const user = await prisma.user.create({
|
||||
data: {
|
||||
content: encoder.encode('Hello')
|
||||
}
|
||||
});
|
||||
|
||||
const text = decoder.decode(user.content);
|
||||
```
|
||||
|
||||
**Impact:** HIGH - Type errors, runtime failures after Prisma 6 upgrade
|
||||
|
||||
**Reference:** @prisma-6/MIGRATIONS-v6-upgrade
|
||||
|
||||
---
|
||||
|
||||
## 5. Generic Error Handling (MEDIUM - P2)
|
||||
|
||||
**Pattern:** Missing Prisma error code handling
|
||||
|
||||
**Detection Command:**
|
||||
```bash
|
||||
grep -rn "catch.*error" --include="*.ts" --include="*.js" . | grep -L "P2002\|P2025\|PrismaClientKnownRequestError"
|
||||
```
|
||||
|
||||
**Red flags:**
|
||||
- Generic `catch (error)` without P-code checking
|
||||
- No differentiation between error types
|
||||
- Exposing raw Prisma errors to clients
|
||||
- Missing unique constraint handling (P2002)
|
||||
- Missing not found handling (P2025)
|
||||
|
||||
**Example violation:**
|
||||
|
||||
```typescript
|
||||
try {
|
||||
await prisma.user.create({ data });
|
||||
} catch (error) {
|
||||
throw new Error('Database error');
|
||||
}
|
||||
```
|
||||
|
||||
**Remediation:**
|
||||
|
||||
Check error.code for specific P-codes:
|
||||
|
||||
```typescript
|
||||
import { PrismaClientKnownRequestError } from '@prisma/client/runtime/library'
|
||||
|
||||
try {
|
||||
await prisma.user.create({ data })
|
||||
} catch (error) {
|
||||
if (error instanceof PrismaClientKnownRequestError) {
|
||||
if (error.code === 'P2002') {
|
||||
throw new Error('User with this email already exists')
|
||||
}
|
||||
if (error.code === 'P2025') {
|
||||
throw new Error('Record not found')
|
||||
}
|
||||
}
|
||||
throw new Error('Unexpected error')
|
||||
}
|
||||
```
|
||||
|
||||
**Impact:** MEDIUM - Poor user experience, unclear error messages, potential info leakage
|
||||
|
||||
**Reference:** @prisma-6/TRANSACTIONS-error-handling, @prisma-6/SECURITY-error-exposure
|
||||
|
||||
---
|
||||
|
||||
## 6. Missing Input Validation (MEDIUM - P2)
|
||||
|
||||
**Pattern:** No validation before database operations
|
||||
|
||||
**Detection Command:**
|
||||
```bash
|
||||
grep -rn "prisma\.\w+\.(create\|update\|upsert)" --include="*.ts" --include="*.js" . | grep -L "parse\|validate\|schema"
|
||||
```
|
||||
|
||||
**Red flags:**
|
||||
- Direct database operations with external input
|
||||
- No Zod/Yup/Joi schema validation
|
||||
- Type assertions without runtime checks
|
||||
- Missing email/phone/URL validation
|
||||
|
||||
**Example violation:**
|
||||
|
||||
```typescript
|
||||
export async function createUser(data: any) {
|
||||
return prisma.user.create({ data });
|
||||
}
|
||||
```
|
||||
|
||||
**Remediation:**
|
||||
|
||||
Add Zod schema validation:
|
||||
|
||||
```typescript
|
||||
import { z } from 'zod'
|
||||
|
||||
const userSchema = z.object({
|
||||
email: z.string().email(),
|
||||
name: z.string().min(1),
|
||||
age: z.number().int().positive().optional()
|
||||
})
|
||||
|
||||
export async function createUser(data: unknown) {
|
||||
const validated = userSchema.parse(data)
|
||||
return prisma.user.create({ data: validated })
|
||||
}
|
||||
```
|
||||
|
||||
**Impact:** MEDIUM - Type mismatches, invalid data in database, runtime errors
|
||||
|
||||
**Reference:** @prisma-6/SECURITY-input-validation
|
||||
|
||||
---
|
||||
|
||||
## 7. Inefficient Queries (LOW - P3)
|
||||
|
||||
**Pattern:** Performance anti-patterns
|
||||
|
||||
**Detection Commands:**
|
||||
```bash
|
||||
grep -rn "\.skip\|\.take" --include="*.ts" --include="*.js" .
|
||||
grep -rn "prisma\.\w+\.findMany()" --include="*.ts" --include="*.js" . | grep -v "select\|include"
|
||||
```
|
||||
|
||||
**Red flags:**
|
||||
- Offset pagination (skip/take) on large datasets (> 10k records)
|
||||
- Missing `select` for partial queries
|
||||
- N+1 queries (findMany in loops without include)
|
||||
- Missing indexes for frequent queries
|
||||
|
||||
**Example violations:**
|
||||
|
||||
Offset pagination on large dataset:
|
||||
```typescript
|
||||
const users = await prisma.user.findMany({
|
||||
skip: page * 100,
|
||||
take: 100
|
||||
});
|
||||
```
|
||||
|
||||
Missing select optimization:
|
||||
```typescript
|
||||
const users = await prisma.user.findMany();
|
||||
```
|
||||
|
||||
N+1 query:
|
||||
```typescript
|
||||
const users = await prisma.user.findMany();
|
||||
for (const user of users) {
|
||||
const posts = await prisma.post.findMany({
|
||||
where: { authorId: user.id }
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
**Remediation:**
|
||||
|
||||
Use cursor-based pagination:
|
||||
```typescript
|
||||
const users = await prisma.user.findMany({
|
||||
take: 100,
|
||||
cursor: lastId ? { id: lastId } : undefined,
|
||||
orderBy: { id: 'asc' }
|
||||
});
|
||||
```
|
||||
|
||||
Add select for partial queries:
|
||||
```typescript
|
||||
const users = await prisma.user.findMany({
|
||||
select: { id: true, email: true, name: true }
|
||||
});
|
||||
```
|
||||
|
||||
Fix N+1 with include:
|
||||
```typescript
|
||||
const users = await prisma.user.findMany({
|
||||
include: { posts: true }
|
||||
});
|
||||
```
|
||||
|
||||
**Impact:** LOW - Slow queries, high database load, poor performance at scale
|
||||
|
||||
**Reference:** @prisma-6/QUERIES-pagination, @prisma-6/QUERIES-select-optimization
|
||||
|
||||
---
|
||||
|
||||
## Summary Table
|
||||
|
||||
| Check | Severity | Detection | Common Fix | Skill Reference |
|
||||
|-------|----------|-----------|------------|-----------------|
|
||||
| SQL Injection | P0 | `$queryRawUnsafe` | Use `$queryRaw` tagged template | SECURITY-sql-injection |
|
||||
| Multiple Clients | P0 | Count `new PrismaClient()` | Global singleton pattern | CLIENT-singleton-pattern |
|
||||
| Serverless Config | P1 | Missing `connection_limit` | Add `?connection_limit=1` | CLIENT-serverless-config |
|
||||
| Buffer API | P1 | `Buffer.from` with Bytes | Use Uint8Array | MIGRATIONS-v6-upgrade |
|
||||
| Error Handling | P2 | Generic catch | Check P-codes (P2002, P2025) | TRANSACTIONS-error-handling |
|
||||
| Input Validation | P2 | No validation before DB | Add Zod schema | SECURITY-input-validation |
|
||||
| Query Efficiency | P3 | skip/take, no select | Cursor pagination, select | QUERIES-pagination |
|
||||
Reference in New Issue
Block a user