@girardmedia/bootspring 1.1.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/LICENSE +21 -0
- package/README.md +255 -0
- package/agents/README.md +93 -0
- package/agents/api-expert/context.md +416 -0
- package/agents/architecture-expert/context.md +454 -0
- package/agents/backend-expert/context.md +483 -0
- package/agents/code-review-expert/context.md +365 -0
- package/agents/database-expert/context.md +250 -0
- package/agents/devops-expert/context.md +446 -0
- package/agents/frontend-expert/context.md +364 -0
- package/agents/index.js +140 -0
- package/agents/performance-expert/context.md +377 -0
- package/agents/security-expert/context.md +343 -0
- package/agents/testing-expert/context.md +414 -0
- package/agents/ui-ux-expert/context.md +448 -0
- package/agents/vercel-expert/context.md +426 -0
- package/bin/bootspring.js +310 -0
- package/cli/agent.js +337 -0
- package/cli/context.js +194 -0
- package/cli/dashboard.js +150 -0
- package/cli/generate.js +294 -0
- package/cli/init.js +410 -0
- package/cli/loop.js +421 -0
- package/cli/mcp.js +241 -0
- package/cli/memory.js +303 -0
- package/cli/orchestrator.js +400 -0
- package/cli/plugin.js +451 -0
- package/cli/quality.js +332 -0
- package/cli/skill.js +369 -0
- package/cli/task.js +628 -0
- package/cli/telemetry.js +114 -0
- package/cli/todo.js +614 -0
- package/cli/update.js +312 -0
- package/core/config.js +245 -0
- package/core/context.js +329 -0
- package/core/entitlements.js +209 -0
- package/core/index.js +43 -0
- package/core/policies.js +68 -0
- package/core/telemetry.js +247 -0
- package/core/utils.js +380 -0
- package/dashboard/server.js +818 -0
- package/docs/integrations/claude-code.md +42 -0
- package/docs/integrations/codex.md +42 -0
- package/docs/mcp-api-platform.md +102 -0
- package/generators/generate.js +598 -0
- package/generators/index.js +18 -0
- package/hooks/context-detector.js +177 -0
- package/hooks/index.js +35 -0
- package/hooks/prompt-enhancer.js +289 -0
- package/intelligence/git-memory.js +551 -0
- package/intelligence/index.js +59 -0
- package/intelligence/orchestrator.js +964 -0
- package/intelligence/prd.js +447 -0
- package/intelligence/recommendation-weights.json +18 -0
- package/intelligence/recommendations.js +234 -0
- package/mcp/capabilities.js +71 -0
- package/mcp/contracts/mcp-contract.v1.json +497 -0
- package/mcp/registry.js +213 -0
- package/mcp/response-formatter.js +462 -0
- package/mcp/server.js +99 -0
- package/mcp/tools/agent-tool.js +137 -0
- package/mcp/tools/capabilities-tool.js +54 -0
- package/mcp/tools/context-tool.js +49 -0
- package/mcp/tools/dashboard-tool.js +58 -0
- package/mcp/tools/generate-tool.js +46 -0
- package/mcp/tools/loop-tool.js +134 -0
- package/mcp/tools/memory-tool.js +180 -0
- package/mcp/tools/orchestrator-tool.js +232 -0
- package/mcp/tools/plugin-tool.js +76 -0
- package/mcp/tools/quality-tool.js +47 -0
- package/mcp/tools/skill-tool.js +233 -0
- package/mcp/tools/telemetry-tool.js +95 -0
- package/mcp/tools/todo-tool.js +133 -0
- package/package.json +98 -0
- package/plugins/index.js +141 -0
- package/quality/index.js +380 -0
- package/quality/lint-budgets.json +19 -0
- package/skills/index.js +787 -0
- package/skills/patterns/README.md +163 -0
- package/skills/patterns/api/route-handler.md +217 -0
- package/skills/patterns/api/server-action.md +249 -0
- package/skills/patterns/auth/clerk.md +132 -0
- package/skills/patterns/database/prisma.md +180 -0
- package/skills/patterns/payments/stripe.md +272 -0
- package/skills/patterns/security/validation.md +268 -0
- package/skills/patterns/testing/vitest.md +307 -0
- package/templates/bootspring.config.js +83 -0
- package/templates/mcp.json +9 -0
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
# Code Review Expert Agent
|
|
2
|
+
|
|
3
|
+
## Role
|
|
4
|
+
Specialized in code review, best practices, code quality, identifying improvements, and ensuring maintainable, readable code.
|
|
5
|
+
|
|
6
|
+
## Core Expertise
|
|
7
|
+
|
|
8
|
+
### Code Review Checklist
|
|
9
|
+
|
|
10
|
+
#### Functionality
|
|
11
|
+
- [ ] Code does what it's supposed to do
|
|
12
|
+
- [ ] Edge cases handled
|
|
13
|
+
- [ ] Error handling is comprehensive
|
|
14
|
+
- [ ] No regression in existing functionality
|
|
15
|
+
|
|
16
|
+
#### Security
|
|
17
|
+
- [ ] Input validation present
|
|
18
|
+
- [ ] No SQL injection vulnerabilities
|
|
19
|
+
- [ ] No XSS vulnerabilities
|
|
20
|
+
- [ ] Authentication/authorization checked
|
|
21
|
+
- [ ] Sensitive data not exposed
|
|
22
|
+
|
|
23
|
+
#### Performance
|
|
24
|
+
- [ ] No N+1 queries
|
|
25
|
+
- [ ] Appropriate indexing considered
|
|
26
|
+
- [ ] No memory leaks
|
|
27
|
+
- [ ] Efficient algorithms used
|
|
28
|
+
|
|
29
|
+
#### Maintainability
|
|
30
|
+
- [ ] Code is readable
|
|
31
|
+
- [ ] Functions are focused (single responsibility)
|
|
32
|
+
- [ ] No magic numbers/strings
|
|
33
|
+
- [ ] Appropriate comments for complex logic
|
|
34
|
+
|
|
35
|
+
#### Testing
|
|
36
|
+
- [ ] Unit tests added for new code
|
|
37
|
+
- [ ] Edge cases tested
|
|
38
|
+
- [ ] Tests are meaningful, not just coverage
|
|
39
|
+
|
|
40
|
+
### Common Issues & Fixes
|
|
41
|
+
|
|
42
|
+
#### 1. Async/Await Mistakes
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
// Bad: Sequential when could be parallel
|
|
46
|
+
async function getBothUsers(id1: string, id2: string) {
|
|
47
|
+
const user1 = await getUser(id1);
|
|
48
|
+
const user2 = await getUser(id2);
|
|
49
|
+
return [user1, user2];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Good: Parallel execution
|
|
53
|
+
async function getBothUsers(id1: string, id2: string) {
|
|
54
|
+
const [user1, user2] = await Promise.all([
|
|
55
|
+
getUser(id1),
|
|
56
|
+
getUser(id2),
|
|
57
|
+
]);
|
|
58
|
+
return [user1, user2];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Bad: Missing error handling
|
|
62
|
+
async function fetchData() {
|
|
63
|
+
const data = await fetch('/api/data').then(r => r.json());
|
|
64
|
+
return data;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Good: Proper error handling
|
|
68
|
+
async function fetchData() {
|
|
69
|
+
const response = await fetch('/api/data');
|
|
70
|
+
if (!response.ok) {
|
|
71
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
72
|
+
}
|
|
73
|
+
return response.json();
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
#### 2. React Anti-patterns
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
// Bad: useEffect for derived state
|
|
81
|
+
function BadComponent({ items }) {
|
|
82
|
+
const [count, setCount] = useState(0);
|
|
83
|
+
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
setCount(items.length);
|
|
86
|
+
}, [items]);
|
|
87
|
+
|
|
88
|
+
return <div>Count: {count}</div>;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Good: Derive directly
|
|
92
|
+
function GoodComponent({ items }) {
|
|
93
|
+
const count = items.length;
|
|
94
|
+
return <div>Count: {count}</div>;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Bad: Object/array in dependency array
|
|
98
|
+
function BadComponent() {
|
|
99
|
+
const options = { enabled: true };
|
|
100
|
+
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
doSomething(options);
|
|
103
|
+
}, [options]); // New object every render!
|
|
104
|
+
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Good: useMemo or extract
|
|
109
|
+
function GoodComponent() {
|
|
110
|
+
const options = useMemo(() => ({ enabled: true }), []);
|
|
111
|
+
|
|
112
|
+
useEffect(() => {
|
|
113
|
+
doSomething(options);
|
|
114
|
+
}, [options]);
|
|
115
|
+
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Bad: Inline function causing re-renders
|
|
120
|
+
function ParentComponent() {
|
|
121
|
+
return (
|
|
122
|
+
<ChildComponent onClick={() => console.log('clicked')} />
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Good: useCallback for stable reference
|
|
127
|
+
function ParentComponent() {
|
|
128
|
+
const handleClick = useCallback(() => {
|
|
129
|
+
console.log('clicked');
|
|
130
|
+
}, []);
|
|
131
|
+
|
|
132
|
+
return <ChildComponent onClick={handleClick} />;
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
#### 3. TypeScript Issues
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
// Bad: Using 'any'
|
|
140
|
+
function processData(data: any) {
|
|
141
|
+
return data.value;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Good: Proper typing
|
|
145
|
+
interface DataItem {
|
|
146
|
+
value: string;
|
|
147
|
+
count: number;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function processData(data: DataItem) {
|
|
151
|
+
return data.value;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Bad: Non-null assertion abuse
|
|
155
|
+
function getUser(users: User[]) {
|
|
156
|
+
return users.find(u => u.active)!.name; // Might be undefined!
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Good: Handle the undefined case
|
|
160
|
+
function getUser(users: User[]) {
|
|
161
|
+
const activeUser = users.find(u => u.active);
|
|
162
|
+
if (!activeUser) {
|
|
163
|
+
throw new Error('No active user found');
|
|
164
|
+
}
|
|
165
|
+
return activeUser.name;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Good: Optional chaining with fallback
|
|
169
|
+
function getUserName(users: User[]) {
|
|
170
|
+
return users.find(u => u.active)?.name ?? 'Unknown';
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
#### 4. Database Query Issues
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
// Bad: N+1 query problem
|
|
178
|
+
const posts = await prisma.post.findMany();
|
|
179
|
+
for (const post of posts) {
|
|
180
|
+
post.author = await prisma.user.findUnique({
|
|
181
|
+
where: { id: post.authorId }
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Good: Include related data
|
|
186
|
+
const posts = await prisma.post.findMany({
|
|
187
|
+
include: { author: true }
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// Bad: Selecting all fields
|
|
191
|
+
const users = await prisma.user.findMany();
|
|
192
|
+
|
|
193
|
+
// Good: Select only needed fields
|
|
194
|
+
const users = await prisma.user.findMany({
|
|
195
|
+
select: {
|
|
196
|
+
id: true,
|
|
197
|
+
name: true,
|
|
198
|
+
email: true,
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// Bad: Missing transaction for related operations
|
|
203
|
+
await prisma.order.create({ data: orderData });
|
|
204
|
+
await prisma.inventory.update({ data: inventoryData }); // What if this fails?
|
|
205
|
+
|
|
206
|
+
// Good: Use transaction
|
|
207
|
+
await prisma.$transaction([
|
|
208
|
+
prisma.order.create({ data: orderData }),
|
|
209
|
+
prisma.inventory.update({ data: inventoryData }),
|
|
210
|
+
]);
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
#### 5. Security Issues
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
// Bad: SQL injection
|
|
217
|
+
const user = await prisma.$queryRaw`
|
|
218
|
+
SELECT * FROM users WHERE email = '${email}'
|
|
219
|
+
`;
|
|
220
|
+
|
|
221
|
+
// Good: Parameterized query
|
|
222
|
+
const user = await prisma.$queryRaw`
|
|
223
|
+
SELECT * FROM users WHERE email = ${email}
|
|
224
|
+
`;
|
|
225
|
+
|
|
226
|
+
// Bad: Exposing sensitive data
|
|
227
|
+
return NextResponse.json(user); // Includes password hash!
|
|
228
|
+
|
|
229
|
+
// Good: Select safe fields
|
|
230
|
+
return NextResponse.json({
|
|
231
|
+
id: user.id,
|
|
232
|
+
name: user.name,
|
|
233
|
+
email: user.email,
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// Bad: Trusting user input
|
|
237
|
+
const { role } = await request.json();
|
|
238
|
+
await prisma.user.update({
|
|
239
|
+
where: { id },
|
|
240
|
+
data: { role } // User can set themselves as admin!
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// Good: Validate and restrict
|
|
244
|
+
const { role } = await request.json();
|
|
245
|
+
const allowedRoles = ['user', 'editor'];
|
|
246
|
+
if (!allowedRoles.includes(role)) {
|
|
247
|
+
return NextResponse.json({ error: 'Invalid role' }, { status: 400 });
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Code Smells to Watch For
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
// 1. Long functions (> 30 lines)
|
|
255
|
+
// Split into smaller, focused functions
|
|
256
|
+
|
|
257
|
+
// 2. Deep nesting
|
|
258
|
+
// Bad
|
|
259
|
+
if (user) {
|
|
260
|
+
if (user.isActive) {
|
|
261
|
+
if (user.hasPermission) {
|
|
262
|
+
// do something
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Good: Early returns
|
|
268
|
+
if (!user) return;
|
|
269
|
+
if (!user.isActive) return;
|
|
270
|
+
if (!user.hasPermission) return;
|
|
271
|
+
// do something
|
|
272
|
+
|
|
273
|
+
// 3. Magic numbers
|
|
274
|
+
// Bad
|
|
275
|
+
if (items.length > 50) { /* ... */ }
|
|
276
|
+
|
|
277
|
+
// Good
|
|
278
|
+
const MAX_ITEMS = 50;
|
|
279
|
+
if (items.length > MAX_ITEMS) { /* ... */ }
|
|
280
|
+
|
|
281
|
+
// 4. Duplicated code
|
|
282
|
+
// Extract into reusable function or component
|
|
283
|
+
|
|
284
|
+
// 5. Dead code
|
|
285
|
+
// Remove unused imports, variables, functions
|
|
286
|
+
|
|
287
|
+
// 6. Inconsistent naming
|
|
288
|
+
// Follow conventions: camelCase for variables, PascalCase for components
|
|
289
|
+
|
|
290
|
+
// 7. Missing error boundaries
|
|
291
|
+
// Add error handling for async operations
|
|
292
|
+
|
|
293
|
+
// 8. Hardcoded values
|
|
294
|
+
// Use environment variables or configuration
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Review Feedback Templates
|
|
298
|
+
|
|
299
|
+
```markdown
|
|
300
|
+
## Approved ✅
|
|
301
|
+
LGTM! Code is clean and well-structured.
|
|
302
|
+
|
|
303
|
+
## Request Changes 🔄
|
|
304
|
+
### Issue 1: [Brief description]
|
|
305
|
+
**Location:** `file.ts:42`
|
|
306
|
+
**Problem:** [What's wrong]
|
|
307
|
+
**Suggestion:** [How to fix]
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
// Instead of this:
|
|
311
|
+
// current code
|
|
312
|
+
|
|
313
|
+
// Consider this:
|
|
314
|
+
// suggested code
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### Issue 2: ...
|
|
318
|
+
|
|
319
|
+
## Questions ❓
|
|
320
|
+
- Could you explain the reasoning behind X?
|
|
321
|
+
- Have you considered Y approach?
|
|
322
|
+
|
|
323
|
+
## Suggestions 💡
|
|
324
|
+
Not blocking, but consider:
|
|
325
|
+
- [ ] Optional improvement 1
|
|
326
|
+
- [ ] Optional improvement 2
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### Performance Review Checklist
|
|
330
|
+
|
|
331
|
+
```typescript
|
|
332
|
+
// Check for:
|
|
333
|
+
|
|
334
|
+
// 1. Bundle size impact
|
|
335
|
+
// Did new dependencies increase bundle significantly?
|
|
336
|
+
// Can they be dynamically imported?
|
|
337
|
+
|
|
338
|
+
// 2. Render performance
|
|
339
|
+
// Any unnecessary re-renders?
|
|
340
|
+
// Are expensive computations memoized?
|
|
341
|
+
|
|
342
|
+
// 3. Data fetching
|
|
343
|
+
// Are queries optimized?
|
|
344
|
+
// Is caching used appropriately?
|
|
345
|
+
|
|
346
|
+
// 4. Memory usage
|
|
347
|
+
// Are event listeners cleaned up?
|
|
348
|
+
// Are subscriptions unsubscribed?
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
## Review Checklist
|
|
352
|
+
|
|
353
|
+
- [ ] Code compiles without warnings
|
|
354
|
+
- [ ] Tests pass
|
|
355
|
+
- [ ] No console.logs left
|
|
356
|
+
- [ ] Error handling present
|
|
357
|
+
- [ ] Types are correct
|
|
358
|
+
- [ ] No hardcoded secrets
|
|
359
|
+
- [ ] Follows project conventions
|
|
360
|
+
- [ ] Documentation updated if needed
|
|
361
|
+
- [ ] No breaking changes without migration
|
|
362
|
+
- [ ] Accessibility considered
|
|
363
|
+
|
|
364
|
+
## Trigger Keywords
|
|
365
|
+
review, code review, best practice, improve, refactor, clean code, lint, smell, anti-pattern, quality, convention, standard, tech debt
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
# Database Expert Agent
|
|
2
|
+
|
|
3
|
+
## Role
|
|
4
|
+
Specialized in database design, ORM configuration, query optimization, and data modeling for modern web applications.
|
|
5
|
+
|
|
6
|
+
## Core Expertise
|
|
7
|
+
|
|
8
|
+
### Schema Design
|
|
9
|
+
- Relational database normalization (1NF through BCNF)
|
|
10
|
+
- Denormalization strategies for read-heavy workloads
|
|
11
|
+
- Index design and optimization
|
|
12
|
+
- Constraint enforcement (foreign keys, unique, check)
|
|
13
|
+
- Soft delete patterns vs hard delete
|
|
14
|
+
- Audit trail implementation
|
|
15
|
+
|
|
16
|
+
### ORM Mastery
|
|
17
|
+
|
|
18
|
+
#### Prisma
|
|
19
|
+
```prisma
|
|
20
|
+
// Example: User with relations
|
|
21
|
+
model User {
|
|
22
|
+
id String @id @default(cuid())
|
|
23
|
+
email String @unique
|
|
24
|
+
name String?
|
|
25
|
+
posts Post[]
|
|
26
|
+
profile Profile?
|
|
27
|
+
createdAt DateTime @default(now())
|
|
28
|
+
updatedAt DateTime @updatedAt
|
|
29
|
+
deletedAt DateTime? // Soft delete
|
|
30
|
+
|
|
31
|
+
@@index([email])
|
|
32
|
+
@@map("users")
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
model Post {
|
|
36
|
+
id String @id @default(cuid())
|
|
37
|
+
title String
|
|
38
|
+
content String?
|
|
39
|
+
published Boolean @default(false)
|
|
40
|
+
author User @relation(fields: [authorId], references: [id])
|
|
41
|
+
authorId String
|
|
42
|
+
|
|
43
|
+
@@index([authorId, published])
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
#### Drizzle
|
|
48
|
+
```typescript
|
|
49
|
+
// Example: Type-safe schema
|
|
50
|
+
import { pgTable, text, timestamp, boolean, index } from 'drizzle-orm/pg-core';
|
|
51
|
+
|
|
52
|
+
export const users = pgTable('users', {
|
|
53
|
+
id: text('id').primaryKey().$defaultFn(() => createId()),
|
|
54
|
+
email: text('email').notNull().unique(),
|
|
55
|
+
name: text('name'),
|
|
56
|
+
createdAt: timestamp('created_at').defaultNow().notNull(),
|
|
57
|
+
updatedAt: timestamp('updated_at').defaultNow().notNull(),
|
|
58
|
+
deletedAt: timestamp('deleted_at'),
|
|
59
|
+
}, (table) => ({
|
|
60
|
+
emailIdx: index('email_idx').on(table.email),
|
|
61
|
+
}));
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Query Patterns
|
|
65
|
+
|
|
66
|
+
#### Efficient Queries
|
|
67
|
+
```typescript
|
|
68
|
+
// Prisma: Selective loading
|
|
69
|
+
const user = await prisma.user.findUnique({
|
|
70
|
+
where: { id },
|
|
71
|
+
select: {
|
|
72
|
+
id: true,
|
|
73
|
+
email: true,
|
|
74
|
+
posts: {
|
|
75
|
+
where: { published: true },
|
|
76
|
+
take: 10,
|
|
77
|
+
orderBy: { createdAt: 'desc' }
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Drizzle: Join with conditions
|
|
83
|
+
const result = await db
|
|
84
|
+
.select({
|
|
85
|
+
user: users,
|
|
86
|
+
postCount: sql<number>`count(${posts.id})`
|
|
87
|
+
})
|
|
88
|
+
.from(users)
|
|
89
|
+
.leftJoin(posts, eq(posts.authorId, users.id))
|
|
90
|
+
.where(isNull(users.deletedAt))
|
|
91
|
+
.groupBy(users.id);
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
#### Transaction Patterns
|
|
95
|
+
```typescript
|
|
96
|
+
// Prisma transaction
|
|
97
|
+
const result = await prisma.$transaction(async (tx) => {
|
|
98
|
+
const user = await tx.user.create({ data: userData });
|
|
99
|
+
await tx.profile.create({
|
|
100
|
+
data: { ...profileData, userId: user.id }
|
|
101
|
+
});
|
|
102
|
+
return user;
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Drizzle transaction
|
|
106
|
+
const result = await db.transaction(async (tx) => {
|
|
107
|
+
const [user] = await tx.insert(users).values(userData).returning();
|
|
108
|
+
await tx.insert(profiles).values({ ...profileData, userId: user.id });
|
|
109
|
+
return user;
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Migration Strategies
|
|
114
|
+
|
|
115
|
+
#### Safe Migration Practices
|
|
116
|
+
1. **Always backup before migrations**
|
|
117
|
+
2. **Test migrations on staging first**
|
|
118
|
+
3. **Use transactions when possible**
|
|
119
|
+
4. **Plan for rollback scenarios**
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
# Prisma
|
|
123
|
+
npx prisma migrate dev --name add_user_status
|
|
124
|
+
npx prisma migrate deploy # Production
|
|
125
|
+
|
|
126
|
+
# Drizzle
|
|
127
|
+
npx drizzle-kit generate:pg
|
|
128
|
+
npx drizzle-kit push:pg
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
#### Data Migration Pattern
|
|
132
|
+
```typescript
|
|
133
|
+
// Safe data migration with batching
|
|
134
|
+
async function migrateUserStatuses(batchSize = 1000) {
|
|
135
|
+
let processed = 0;
|
|
136
|
+
|
|
137
|
+
while (true) {
|
|
138
|
+
const users = await prisma.user.findMany({
|
|
139
|
+
where: { status: null },
|
|
140
|
+
take: batchSize,
|
|
141
|
+
select: { id: true }
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
if (users.length === 0) break;
|
|
145
|
+
|
|
146
|
+
await prisma.user.updateMany({
|
|
147
|
+
where: { id: { in: users.map(u => u.id) } },
|
|
148
|
+
data: { status: 'active' }
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
processed += users.length;
|
|
152
|
+
console.log(`Migrated ${processed} users`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Performance Optimization
|
|
158
|
+
|
|
159
|
+
### Index Strategy
|
|
160
|
+
```sql
|
|
161
|
+
-- Composite index for common queries
|
|
162
|
+
CREATE INDEX idx_posts_author_published
|
|
163
|
+
ON posts(author_id, published)
|
|
164
|
+
WHERE deleted_at IS NULL;
|
|
165
|
+
|
|
166
|
+
-- Partial index for specific conditions
|
|
167
|
+
CREATE INDEX idx_users_active
|
|
168
|
+
ON users(email)
|
|
169
|
+
WHERE status = 'active';
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Query Analysis
|
|
173
|
+
```sql
|
|
174
|
+
-- Check query plan
|
|
175
|
+
EXPLAIN ANALYZE
|
|
176
|
+
SELECT * FROM posts
|
|
177
|
+
WHERE author_id = 'xxx' AND published = true;
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Connection Pooling
|
|
181
|
+
```typescript
|
|
182
|
+
// Prisma connection config
|
|
183
|
+
const prisma = new PrismaClient({
|
|
184
|
+
datasources: {
|
|
185
|
+
db: {
|
|
186
|
+
url: process.env.DATABASE_URL,
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
// Connection pool settings via URL params
|
|
190
|
+
// ?connection_limit=10&pool_timeout=20
|
|
191
|
+
});
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Common Patterns
|
|
195
|
+
|
|
196
|
+
### Repository Pattern
|
|
197
|
+
```typescript
|
|
198
|
+
// Type-safe repository
|
|
199
|
+
class UserRepository {
|
|
200
|
+
constructor(private prisma: PrismaClient) {}
|
|
201
|
+
|
|
202
|
+
async findById(id: string) {
|
|
203
|
+
return this.prisma.user.findUnique({ where: { id } });
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async findByEmail(email: string) {
|
|
207
|
+
return this.prisma.user.findUnique({ where: { email } });
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async create(data: Prisma.UserCreateInput) {
|
|
211
|
+
return this.prisma.user.create({ data });
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async softDelete(id: string) {
|
|
215
|
+
return this.prisma.user.update({
|
|
216
|
+
where: { id },
|
|
217
|
+
data: { deletedAt: new Date() }
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Pagination
|
|
224
|
+
```typescript
|
|
225
|
+
// Cursor-based pagination (more efficient)
|
|
226
|
+
async function getPaginatedPosts(cursor?: string, limit = 20) {
|
|
227
|
+
return prisma.post.findMany({
|
|
228
|
+
take: limit + 1, // Fetch one extra to check hasMore
|
|
229
|
+
cursor: cursor ? { id: cursor } : undefined,
|
|
230
|
+
skip: cursor ? 1 : 0,
|
|
231
|
+
orderBy: { createdAt: 'desc' }
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## Checklist for Database Work
|
|
237
|
+
|
|
238
|
+
- [ ] Schema follows naming conventions (snake_case for columns)
|
|
239
|
+
- [ ] Appropriate indexes for query patterns
|
|
240
|
+
- [ ] Foreign key constraints defined
|
|
241
|
+
- [ ] Soft delete considered for important data
|
|
242
|
+
- [ ] Timestamps (created_at, updated_at) included
|
|
243
|
+
- [ ] Migration is reversible
|
|
244
|
+
- [ ] N+1 queries avoided
|
|
245
|
+
- [ ] Connection pooling configured
|
|
246
|
+
- [ ] Sensitive data encrypted at rest
|
|
247
|
+
- [ ] Backup strategy in place
|
|
248
|
+
|
|
249
|
+
## Trigger Keywords
|
|
250
|
+
database, schema, prisma, drizzle, migration, query, model, relation, index, orm, sql, postgres, mysql, mongodb, transaction, seed, foreign key, constraint
|