red64-cli 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/parseArgs.d.ts.map +1 -1
- package/dist/cli/parseArgs.js +5 -0
- package/dist/cli/parseArgs.js.map +1 -1
- package/dist/components/init/CompleteStep.d.ts.map +1 -1
- package/dist/components/init/CompleteStep.js +2 -2
- package/dist/components/init/CompleteStep.js.map +1 -1
- package/dist/components/init/TestCheckStep.d.ts +16 -0
- package/dist/components/init/TestCheckStep.d.ts.map +1 -0
- package/dist/components/init/TestCheckStep.js +120 -0
- package/dist/components/init/TestCheckStep.js.map +1 -0
- package/dist/components/init/index.d.ts +1 -0
- package/dist/components/init/index.d.ts.map +1 -1
- package/dist/components/init/index.js +1 -0
- package/dist/components/init/index.js.map +1 -1
- package/dist/components/init/types.d.ts +9 -0
- package/dist/components/init/types.d.ts.map +1 -1
- package/dist/components/screens/InitScreen.d.ts.map +1 -1
- package/dist/components/screens/InitScreen.js +69 -6
- package/dist/components/screens/InitScreen.js.map +1 -1
- package/dist/components/screens/StartScreen.d.ts.map +1 -1
- package/dist/components/screens/StartScreen.js +89 -3
- package/dist/components/screens/StartScreen.js.map +1 -1
- package/dist/services/ConfigService.d.ts +1 -0
- package/dist/services/ConfigService.d.ts.map +1 -1
- package/dist/services/ConfigService.js.map +1 -1
- package/dist/services/ProjectDetector.d.ts +28 -0
- package/dist/services/ProjectDetector.d.ts.map +1 -0
- package/dist/services/ProjectDetector.js +236 -0
- package/dist/services/ProjectDetector.js.map +1 -0
- package/dist/services/TestRunner.d.ts +46 -0
- package/dist/services/TestRunner.d.ts.map +1 -0
- package/dist/services/TestRunner.js +85 -0
- package/dist/services/TestRunner.js.map +1 -0
- package/dist/services/index.d.ts +2 -0
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +2 -0
- package/dist/services/index.js.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/framework/agents/claude/.claude/agents/red64/spec-impl.md +131 -2
- package/framework/agents/claude/.claude/commands/red64/spec-impl.md +24 -0
- package/framework/agents/codex/.codex/agents/red64/spec-impl.md +131 -2
- package/framework/agents/codex/.codex/commands/red64/spec-impl.md +24 -0
- package/framework/stacks/generic/feedback.md +80 -0
- package/framework/stacks/nextjs/accessibility.md +437 -0
- package/framework/stacks/nextjs/api.md +431 -0
- package/framework/stacks/nextjs/coding-style.md +282 -0
- package/framework/stacks/nextjs/commenting.md +226 -0
- package/framework/stacks/nextjs/components.md +411 -0
- package/framework/stacks/nextjs/conventions.md +333 -0
- package/framework/stacks/nextjs/css.md +310 -0
- package/framework/stacks/nextjs/error-handling.md +442 -0
- package/framework/stacks/nextjs/feedback.md +124 -0
- package/framework/stacks/nextjs/migrations.md +332 -0
- package/framework/stacks/nextjs/models.md +362 -0
- package/framework/stacks/nextjs/queries.md +410 -0
- package/framework/stacks/nextjs/responsive.md +338 -0
- package/framework/stacks/nextjs/tech-stack.md +177 -0
- package/framework/stacks/nextjs/test-writing.md +475 -0
- package/framework/stacks/nextjs/validation.md +467 -0
- package/framework/stacks/python/api.md +468 -0
- package/framework/stacks/python/authentication.md +342 -0
- package/framework/stacks/python/code-quality.md +283 -0
- package/framework/stacks/python/code-refactoring.md +315 -0
- package/framework/stacks/python/coding-style.md +462 -0
- package/framework/stacks/python/conventions.md +399 -0
- package/framework/stacks/python/error-handling.md +512 -0
- package/framework/stacks/python/feedback.md +92 -0
- package/framework/stacks/python/implement-ai-llm.md +468 -0
- package/framework/stacks/python/migrations.md +388 -0
- package/framework/stacks/python/models.md +399 -0
- package/framework/stacks/python/python.md +232 -0
- package/framework/stacks/python/queries.md +451 -0
- package/framework/stacks/python/structure.md +245 -58
- package/framework/stacks/python/tech.md +92 -35
- package/framework/stacks/python/testing.md +380 -0
- package/framework/stacks/python/validation.md +471 -0
- package/framework/stacks/rails/authentication.md +176 -0
- package/framework/stacks/rails/code-quality.md +287 -0
- package/framework/stacks/rails/code-refactoring.md +299 -0
- package/framework/stacks/rails/feedback.md +130 -0
- package/framework/stacks/rails/implement-ai-llm-with-rubyllm.md +342 -0
- package/framework/stacks/rails/rails.md +301 -0
- package/framework/stacks/rails/rails8-best-practices.md +498 -0
- package/framework/stacks/rails/rails8-css.md +573 -0
- package/framework/stacks/rails/structure.md +140 -0
- package/framework/stacks/rails/tech.md +108 -0
- package/framework/stacks/react/code-quality.md +521 -0
- package/framework/stacks/react/components.md +625 -0
- package/framework/stacks/react/data-fetching.md +586 -0
- package/framework/stacks/react/feedback.md +110 -0
- package/framework/stacks/react/forms.md +694 -0
- package/framework/stacks/react/performance.md +640 -0
- package/framework/stacks/react/product.md +22 -9
- package/framework/stacks/react/state-management.md +472 -0
- package/framework/stacks/react/structure.md +351 -44
- package/framework/stacks/react/tech.md +219 -30
- package/framework/stacks/react/testing.md +690 -0
- package/package.json +1 -1
- package/framework/stacks/node/product.md +0 -27
- package/framework/stacks/node/structure.md +0 -82
- package/framework/stacks/node/tech.md +0 -63
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
# Prisma Query Patterns
|
|
2
|
+
|
|
3
|
+
Best practices for Prisma Client queries, N+1 prevention, transactions, pagination, and performance optimization.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Philosophy
|
|
8
|
+
|
|
9
|
+
- **Select what you need**: Never fetch entire models when a subset of fields suffices
|
|
10
|
+
- **Prevent N+1 at write time**: Use `include` and `select` deliberately, not as an afterthought
|
|
11
|
+
- **Type safety end-to-end**: Let Prisma's generated types flow from query to UI component
|
|
12
|
+
- **Connection awareness**: Serverless and edge runtimes need connection pooling
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Prisma Client Singleton
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
// lib/prisma.ts
|
|
20
|
+
import { PrismaClient } from "@prisma/client";
|
|
21
|
+
|
|
22
|
+
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient };
|
|
23
|
+
|
|
24
|
+
export const prisma =
|
|
25
|
+
globalForPrisma.prisma ||
|
|
26
|
+
new PrismaClient({
|
|
27
|
+
log: process.env.NODE_ENV === "development" ? ["query", "warn", "error"] : ["error"],
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
if (process.env.NODE_ENV !== "production") {
|
|
31
|
+
globalForPrisma.prisma = prisma;
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**Why globalThis**: Next.js dev server clears module cache on every request. Without this, each request creates a new `PrismaClient`, exhausting the connection pool.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Basic Queries
|
|
40
|
+
|
|
41
|
+
### Find Unique
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
const user = await prisma.user.findUnique({
|
|
45
|
+
where: { id: userId },
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// With specific fields
|
|
49
|
+
const user = await prisma.user.findUnique({
|
|
50
|
+
where: { id: userId },
|
|
51
|
+
select: { id: true, name: true, email: true },
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Find Many with Filtering
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
const publishedPosts = await prisma.post.findMany({
|
|
59
|
+
where: {
|
|
60
|
+
status: "PUBLISHED",
|
|
61
|
+
deletedAt: null,
|
|
62
|
+
author: { isActive: true },
|
|
63
|
+
},
|
|
64
|
+
orderBy: { createdAt: "desc" },
|
|
65
|
+
take: 20,
|
|
66
|
+
});
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Create
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
const user = await prisma.user.create({
|
|
73
|
+
data: {
|
|
74
|
+
email: "jane@example.com",
|
|
75
|
+
name: "Jane Doe",
|
|
76
|
+
hashedPassword: await hash(password),
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Update
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
const user = await prisma.user.update({
|
|
85
|
+
where: { id: userId },
|
|
86
|
+
data: { name: "Updated Name" },
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Upsert
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
const user = await prisma.user.upsert({
|
|
94
|
+
where: { email: "jane@example.com" },
|
|
95
|
+
create: { email: "jane@example.com", name: "Jane", hashedPassword: hash },
|
|
96
|
+
update: { name: "Jane" },
|
|
97
|
+
});
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Delete
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
// Hard delete
|
|
104
|
+
await prisma.user.delete({ where: { id: userId } });
|
|
105
|
+
|
|
106
|
+
// Soft delete (preferred)
|
|
107
|
+
await prisma.user.update({
|
|
108
|
+
where: { id: userId },
|
|
109
|
+
data: { deletedAt: new Date() },
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## N+1 Prevention
|
|
116
|
+
|
|
117
|
+
### The Problem
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
// BAD: N+1 - one query per post to fetch author
|
|
121
|
+
const posts = await prisma.post.findMany();
|
|
122
|
+
for (const post of posts) {
|
|
123
|
+
const author = await prisma.user.findUnique({ where: { id: post.authorId } });
|
|
124
|
+
// This runs N additional queries
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Solution: include
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
// GOOD: Single query with JOIN
|
|
132
|
+
const posts = await prisma.post.findMany({
|
|
133
|
+
include: {
|
|
134
|
+
author: { select: { id: true, name: true, avatarUrl: true } },
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
// posts[0].author.name is available
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Solution: select (Leaner)
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
// BETTER: Only fetch exactly what you need
|
|
144
|
+
const posts = await prisma.post.findMany({
|
|
145
|
+
select: {
|
|
146
|
+
id: true,
|
|
147
|
+
title: true,
|
|
148
|
+
createdAt: true,
|
|
149
|
+
author: { select: { name: true, avatarUrl: true } },
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Nested Includes
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
// Fetch post with author and comments (including comment authors)
|
|
158
|
+
const post = await prisma.post.findUnique({
|
|
159
|
+
where: { id: postId },
|
|
160
|
+
include: {
|
|
161
|
+
author: { select: { id: true, name: true } },
|
|
162
|
+
comments: {
|
|
163
|
+
where: { deletedAt: null },
|
|
164
|
+
orderBy: { createdAt: "asc" },
|
|
165
|
+
include: {
|
|
166
|
+
author: { select: { id: true, name: true, avatarUrl: true } },
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### When to Use include vs select
|
|
174
|
+
|
|
175
|
+
| Approach | Use When |
|
|
176
|
+
|---|---|
|
|
177
|
+
| `include` | Need all model fields plus relations |
|
|
178
|
+
| `select` | Need specific fields only (API responses, lists) |
|
|
179
|
+
|
|
180
|
+
**Rule**: Default to `select` for list queries. Use `include` for detail views where you need the full model.
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## Pagination
|
|
185
|
+
|
|
186
|
+
### Offset-Based (Simple, for Admin UIs)
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
interface PaginationParams {
|
|
190
|
+
page: number;
|
|
191
|
+
limit: number;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async function getUsers({ page, limit }: PaginationParams) {
|
|
195
|
+
const [users, total] = await Promise.all([
|
|
196
|
+
prisma.user.findMany({
|
|
197
|
+
skip: (page - 1) * limit,
|
|
198
|
+
take: limit,
|
|
199
|
+
orderBy: { createdAt: "desc" },
|
|
200
|
+
select: { id: true, name: true, email: true, createdAt: true },
|
|
201
|
+
}),
|
|
202
|
+
prisma.user.count(),
|
|
203
|
+
]);
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
items: users,
|
|
207
|
+
total,
|
|
208
|
+
page,
|
|
209
|
+
limit,
|
|
210
|
+
totalPages: Math.ceil(total / limit),
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Cursor-Based (Scalable, for Feeds)
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
async function getPosts(cursor?: string, limit = 20) {
|
|
219
|
+
const posts = await prisma.post.findMany({
|
|
220
|
+
take: limit + 1, // Fetch one extra to check if there's a next page
|
|
221
|
+
...(cursor && {
|
|
222
|
+
cursor: { id: cursor },
|
|
223
|
+
skip: 1, // Skip the cursor itself
|
|
224
|
+
}),
|
|
225
|
+
orderBy: { createdAt: "desc" },
|
|
226
|
+
select: {
|
|
227
|
+
id: true,
|
|
228
|
+
title: true,
|
|
229
|
+
createdAt: true,
|
|
230
|
+
author: { select: { name: true } },
|
|
231
|
+
},
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
const hasNext = posts.length > limit;
|
|
235
|
+
const items = hasNext ? posts.slice(0, -1) : posts;
|
|
236
|
+
|
|
237
|
+
return {
|
|
238
|
+
items,
|
|
239
|
+
hasNext,
|
|
240
|
+
nextCursor: hasNext ? items[items.length - 1]?.id : null,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### When to Use Each
|
|
246
|
+
|
|
247
|
+
| Type | Use Case | Tradeoff |
|
|
248
|
+
|---|---|---|
|
|
249
|
+
| Offset | Admin dashboards, small datasets | Slow on large tables (COUNT + OFFSET) |
|
|
250
|
+
| Cursor | Infinite scroll, feeds, public APIs | Cannot jump to arbitrary page |
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## Transactions
|
|
255
|
+
|
|
256
|
+
### Interactive Transactions
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
async function transferCredits(fromId: string, toId: string, amount: number) {
|
|
260
|
+
return prisma.$transaction(async (tx) => {
|
|
261
|
+
const sender = await tx.user.findUnique({ where: { id: fromId } });
|
|
262
|
+
if (!sender || sender.credits < amount) {
|
|
263
|
+
throw new Error("Insufficient credits");
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
await tx.user.update({
|
|
267
|
+
where: { id: fromId },
|
|
268
|
+
data: { credits: { decrement: amount } },
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
await tx.user.update({
|
|
272
|
+
where: { id: toId },
|
|
273
|
+
data: { credits: { increment: amount } },
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
return { fromId, toId, amount };
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### Batch Transactions
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
// Multiple operations in a single transaction
|
|
285
|
+
const [user, post] = await prisma.$transaction([
|
|
286
|
+
prisma.user.create({ data: userData }),
|
|
287
|
+
prisma.post.create({ data: postData }),
|
|
288
|
+
]);
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Transaction Options
|
|
292
|
+
|
|
293
|
+
```typescript
|
|
294
|
+
await prisma.$transaction(
|
|
295
|
+
async (tx) => { /* ... */ },
|
|
296
|
+
{
|
|
297
|
+
maxWait: 5000, // Max time to wait for a connection from the pool
|
|
298
|
+
timeout: 10000, // Max time for the transaction to complete
|
|
299
|
+
isolationLevel: "Serializable", // Strictest isolation
|
|
300
|
+
}
|
|
301
|
+
);
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
## Filtering Patterns
|
|
307
|
+
|
|
308
|
+
### Dynamic Filters
|
|
309
|
+
|
|
310
|
+
```typescript
|
|
311
|
+
interface PostFilters {
|
|
312
|
+
status?: string;
|
|
313
|
+
authorId?: string;
|
|
314
|
+
search?: string;
|
|
315
|
+
from?: Date;
|
|
316
|
+
to?: Date;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
async function getPosts(filters: PostFilters) {
|
|
320
|
+
const where: Prisma.PostWhereInput = {
|
|
321
|
+
deletedAt: null,
|
|
322
|
+
...(filters.status && { status: filters.status as PostStatus }),
|
|
323
|
+
...(filters.authorId && { authorId: filters.authorId }),
|
|
324
|
+
...(filters.search && {
|
|
325
|
+
OR: [
|
|
326
|
+
{ title: { contains: filters.search, mode: "insensitive" } },
|
|
327
|
+
{ body: { contains: filters.search, mode: "insensitive" } },
|
|
328
|
+
],
|
|
329
|
+
}),
|
|
330
|
+
...(filters.from && { createdAt: { gte: filters.from } }),
|
|
331
|
+
...(filters.to && { createdAt: { ...( filters.from ? { gte: filters.from } : {}), lte: filters.to } }),
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
return prisma.post.findMany({ where, orderBy: { createdAt: "desc" } });
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### Sorting
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
type SortField = "createdAt" | "title" | "updatedAt";
|
|
342
|
+
type SortDirection = "asc" | "desc";
|
|
343
|
+
|
|
344
|
+
function buildOrderBy(sort: string): Prisma.PostOrderByWithRelationInput {
|
|
345
|
+
const desc = sort.startsWith("-");
|
|
346
|
+
const field = (desc ? sort.slice(1) : sort) as SortField;
|
|
347
|
+
return { [field]: desc ? "desc" : "asc" };
|
|
348
|
+
}
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
---
|
|
352
|
+
|
|
353
|
+
## Raw Queries
|
|
354
|
+
|
|
355
|
+
### When Prisma Client Is Not Enough
|
|
356
|
+
|
|
357
|
+
```typescript
|
|
358
|
+
// Type-safe raw query with tagged template
|
|
359
|
+
const users = await prisma.$queryRaw<{ id: string; postCount: number }[]>`
|
|
360
|
+
SELECT u.id, COUNT(p.id)::int AS "postCount"
|
|
361
|
+
FROM users u
|
|
362
|
+
LEFT JOIN posts p ON p.author_id = u.id AND p.deleted_at IS NULL
|
|
363
|
+
GROUP BY u.id
|
|
364
|
+
HAVING COUNT(p.id) > ${minPosts}
|
|
365
|
+
ORDER BY "postCount" DESC
|
|
366
|
+
LIMIT ${limit}
|
|
367
|
+
`;
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
**Rule**: Use raw queries only for complex aggregations, window functions, or CTEs that Prisma Client cannot express. Always use tagged templates (never string interpolation) to prevent SQL injection.
|
|
371
|
+
|
|
372
|
+
---
|
|
373
|
+
|
|
374
|
+
## Connection Pooling
|
|
375
|
+
|
|
376
|
+
### Serverless (Vercel / Neon)
|
|
377
|
+
|
|
378
|
+
```
|
|
379
|
+
# .env
|
|
380
|
+
DATABASE_URL="postgresql://user:pass@host/db?pgbouncer=true&connection_limit=1"
|
|
381
|
+
DIRECT_URL="postgresql://user:pass@direct-host/db"
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
```prisma
|
|
385
|
+
datasource db {
|
|
386
|
+
provider = "postgresql"
|
|
387
|
+
url = env("DATABASE_URL")
|
|
388
|
+
directUrl = env("DIRECT_URL")
|
|
389
|
+
}
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
**Why**: `DATABASE_URL` goes through a connection pooler (PgBouncer/Neon) for query traffic. `directUrl` connects directly for migrations.
|
|
393
|
+
|
|
394
|
+
---
|
|
395
|
+
|
|
396
|
+
## Anti-Patterns
|
|
397
|
+
|
|
398
|
+
| Anti-Pattern | Problem | Correct Approach |
|
|
399
|
+
|---|---|---|
|
|
400
|
+
| Fetching all fields for list views | Over-fetching, slower queries | Use `select` for lists |
|
|
401
|
+
| No `include`/`select` on relations | N+1 queries at runtime | Include relations at query time |
|
|
402
|
+
| String interpolation in raw queries | SQL injection vulnerability | Use tagged template literals |
|
|
403
|
+
| No connection pooling in serverless | Connection exhaustion | Use PgBouncer or Neon pooler |
|
|
404
|
+
| Offset pagination on large tables | Slow due to OFFSET scan | Switch to cursor-based pagination |
|
|
405
|
+
| Transactions for single operations | Unnecessary overhead | Transactions only for multi-step operations |
|
|
406
|
+
| Count queries without filters | Full table scan | Always apply the same `where` clause to count |
|
|
407
|
+
|
|
408
|
+
---
|
|
409
|
+
|
|
410
|
+
_Queries define performance. Select only what you need, prevent N+1 at write time, and let Prisma's types guarantee correctness._
|