@qazuor/claude-code-config 0.5.0 → 0.6.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/README.md +106 -41
- package/dist/bin.cjs +963 -84
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +963 -84
- package/dist/bin.js.map +1 -1
- package/dist/index.cjs +73 -56
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +73 -56
- package/dist/index.js.map +1 -1
- package/package.json +23 -24
- package/templates/CLAUDE.md.template +60 -5
- package/templates/agents/README.md +58 -39
- package/templates/agents/_registry.json +43 -202
- package/templates/agents/engineering/{hono-engineer.md → api-engineer.md} +61 -70
- package/templates/agents/engineering/database-engineer.md +253 -0
- package/templates/agents/engineering/frontend-engineer.md +302 -0
- package/templates/hooks/on-notification.sh +0 -0
- package/templates/scripts/add-changelogs.sh +0 -0
- package/templates/scripts/generate-code-registry.ts +0 -0
- package/templates/scripts/health-check.sh +0 -0
- package/templates/scripts/sync-registry.sh +0 -0
- package/templates/scripts/telemetry-report.ts +0 -0
- package/templates/scripts/validate-docs.sh +0 -0
- package/templates/scripts/validate-registry.sh +0 -0
- package/templates/scripts/validate-structure.sh +0 -0
- package/templates/scripts/worktree-cleanup.sh +0 -0
- package/templates/scripts/worktree-create.sh +0 -0
- package/templates/skills/README.md +99 -90
- package/templates/skills/_registry.json +323 -16
- package/templates/skills/api-frameworks/express-patterns.md +411 -0
- package/templates/skills/api-frameworks/fastify-patterns.md +419 -0
- package/templates/skills/api-frameworks/hono-patterns.md +388 -0
- package/templates/skills/api-frameworks/nestjs-patterns.md +497 -0
- package/templates/skills/database/drizzle-patterns.md +449 -0
- package/templates/skills/database/mongoose-patterns.md +503 -0
- package/templates/skills/database/prisma-patterns.md +487 -0
- package/templates/skills/frontend-frameworks/astro-patterns.md +415 -0
- package/templates/skills/frontend-frameworks/nextjs-patterns.md +470 -0
- package/templates/skills/frontend-frameworks/react-patterns.md +516 -0
- package/templates/skills/frontend-frameworks/tanstack-start-patterns.md +469 -0
- package/templates/skills/patterns/atdd-methodology.md +364 -0
- package/templates/skills/patterns/bdd-methodology.md +281 -0
- package/templates/skills/patterns/clean-architecture.md +444 -0
- package/templates/skills/patterns/hexagonal-architecture.md +567 -0
- package/templates/skills/patterns/vertical-slice-architecture.md +502 -0
- package/templates/agents/engineering/astro-engineer.md +0 -293
- package/templates/agents/engineering/db-drizzle-engineer.md +0 -360
- package/templates/agents/engineering/express-engineer.md +0 -316
- package/templates/agents/engineering/fastify-engineer.md +0 -399
- package/templates/agents/engineering/mongoose-engineer.md +0 -473
- package/templates/agents/engineering/nestjs-engineer.md +0 -429
- package/templates/agents/engineering/nextjs-engineer.md +0 -451
- package/templates/agents/engineering/prisma-engineer.md +0 -432
- package/templates/agents/engineering/react-senior-dev.md +0 -394
- package/templates/agents/engineering/tanstack-start-engineer.md +0 -447
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
# Prisma ORM Patterns
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Prisma is a next-generation ORM with auto-generated types and intuitive API. This skill provides patterns for database operations with Prisma.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Schema Definition
|
|
10
|
+
|
|
11
|
+
### Model with Relations
|
|
12
|
+
|
|
13
|
+
```prisma
|
|
14
|
+
// schema.prisma
|
|
15
|
+
generator client {
|
|
16
|
+
provider = "prisma-client-js"
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
datasource db {
|
|
20
|
+
provider = "postgresql"
|
|
21
|
+
url = env("DATABASE_URL")
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
model User {
|
|
25
|
+
id String @id @default(cuid())
|
|
26
|
+
email String @unique
|
|
27
|
+
name String?
|
|
28
|
+
password String
|
|
29
|
+
role Role @default(USER)
|
|
30
|
+
items Item[]
|
|
31
|
+
createdAt DateTime @default(now())
|
|
32
|
+
updatedAt DateTime @updatedAt
|
|
33
|
+
deletedAt DateTime?
|
|
34
|
+
|
|
35
|
+
@@index([email])
|
|
36
|
+
@@map("users")
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
model Item {
|
|
40
|
+
id String @id @default(cuid())
|
|
41
|
+
title String
|
|
42
|
+
description String?
|
|
43
|
+
status Status @default(ACTIVE)
|
|
44
|
+
price Int
|
|
45
|
+
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
|
|
46
|
+
authorId String
|
|
47
|
+
tags ItemTag[]
|
|
48
|
+
createdAt DateTime @default(now())
|
|
49
|
+
updatedAt DateTime @updatedAt
|
|
50
|
+
deletedAt DateTime?
|
|
51
|
+
|
|
52
|
+
@@index([authorId])
|
|
53
|
+
@@index([status])
|
|
54
|
+
@@map("items")
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
model Tag {
|
|
58
|
+
id String @id @default(cuid())
|
|
59
|
+
name String @unique
|
|
60
|
+
items ItemTag[]
|
|
61
|
+
|
|
62
|
+
@@map("tags")
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
model ItemTag {
|
|
66
|
+
item Item @relation(fields: [itemId], references: [id], onDelete: Cascade)
|
|
67
|
+
itemId String
|
|
68
|
+
tag Tag @relation(fields: [tagId], references: [id], onDelete: Cascade)
|
|
69
|
+
tagId String
|
|
70
|
+
|
|
71
|
+
@@id([itemId, tagId])
|
|
72
|
+
@@map("item_tags")
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
enum Role {
|
|
76
|
+
USER
|
|
77
|
+
ADMIN
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
enum Status {
|
|
81
|
+
ACTIVE
|
|
82
|
+
ARCHIVED
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Query Patterns
|
|
89
|
+
|
|
90
|
+
### Basic CRUD
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
import { prisma } from './client';
|
|
94
|
+
|
|
95
|
+
// Create
|
|
96
|
+
const item = await prisma.item.create({
|
|
97
|
+
data: {
|
|
98
|
+
title: 'New Item',
|
|
99
|
+
price: 100,
|
|
100
|
+
authorId: userId,
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// Read one with relations
|
|
105
|
+
const item = await prisma.item.findUnique({
|
|
106
|
+
where: { id: itemId },
|
|
107
|
+
include: {
|
|
108
|
+
author: {
|
|
109
|
+
select: {
|
|
110
|
+
id: true,
|
|
111
|
+
name: true,
|
|
112
|
+
email: true,
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
tags: {
|
|
116
|
+
include: {
|
|
117
|
+
tag: true,
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Read many with filters
|
|
124
|
+
const items = await prisma.item.findMany({
|
|
125
|
+
where: {
|
|
126
|
+
status: 'ACTIVE',
|
|
127
|
+
deletedAt: null,
|
|
128
|
+
},
|
|
129
|
+
include: {
|
|
130
|
+
author: {
|
|
131
|
+
select: { name: true },
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
orderBy: { createdAt: 'desc' },
|
|
135
|
+
take: 10,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Update
|
|
139
|
+
const updated = await prisma.item.update({
|
|
140
|
+
where: { id: itemId },
|
|
141
|
+
data: {
|
|
142
|
+
title: 'Updated Title',
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// Delete
|
|
147
|
+
await prisma.item.delete({
|
|
148
|
+
where: { id: itemId },
|
|
149
|
+
});
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Pagination
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
async function findPaginated(input: {
|
|
156
|
+
page: number;
|
|
157
|
+
pageSize: number;
|
|
158
|
+
status?: Status;
|
|
159
|
+
}) {
|
|
160
|
+
const { page, pageSize, status } = input;
|
|
161
|
+
const skip = (page - 1) * pageSize;
|
|
162
|
+
|
|
163
|
+
const where = {
|
|
164
|
+
deletedAt: null,
|
|
165
|
+
...(status && { status }),
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const [items, total] = await Promise.all([
|
|
169
|
+
prisma.item.findMany({
|
|
170
|
+
where,
|
|
171
|
+
include: {
|
|
172
|
+
author: {
|
|
173
|
+
select: { name: true },
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
orderBy: { createdAt: 'desc' },
|
|
177
|
+
take: pageSize,
|
|
178
|
+
skip,
|
|
179
|
+
}),
|
|
180
|
+
prisma.item.count({ where }),
|
|
181
|
+
]);
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
data: items,
|
|
185
|
+
pagination: {
|
|
186
|
+
total,
|
|
187
|
+
page,
|
|
188
|
+
pageSize,
|
|
189
|
+
totalPages: Math.ceil(total / pageSize),
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Cursor Pagination
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
async function findWithCursor(cursor?: string, take = 10) {
|
|
199
|
+
const items = await prisma.item.findMany({
|
|
200
|
+
take: take + 1,
|
|
201
|
+
...(cursor && {
|
|
202
|
+
skip: 1,
|
|
203
|
+
cursor: { id: cursor },
|
|
204
|
+
}),
|
|
205
|
+
where: { deletedAt: null },
|
|
206
|
+
orderBy: { createdAt: 'desc' },
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
const hasMore = items.length > take;
|
|
210
|
+
const results = hasMore ? items.slice(0, -1) : items;
|
|
211
|
+
const nextCursor = hasMore ? results[results.length - 1].id : null;
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
items: results,
|
|
215
|
+
nextCursor,
|
|
216
|
+
hasMore,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Soft Delete
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
// Soft delete
|
|
225
|
+
await prisma.item.update({
|
|
226
|
+
where: { id: itemId },
|
|
227
|
+
data: { deletedAt: new Date() },
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// Restore
|
|
231
|
+
await prisma.item.update({
|
|
232
|
+
where: { id: itemId },
|
|
233
|
+
data: { deletedAt: null },
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// Middleware for automatic filtering
|
|
237
|
+
prisma.$use(async (params, next) => {
|
|
238
|
+
if (params.model === 'Item') {
|
|
239
|
+
if (params.action === 'findUnique' || params.action === 'findMany') {
|
|
240
|
+
params.args.where = {
|
|
241
|
+
...params.args.where,
|
|
242
|
+
deletedAt: null,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return next(params);
|
|
247
|
+
});
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Transactions
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
// Sequential transaction
|
|
254
|
+
const [item, user] = await prisma.$transaction([
|
|
255
|
+
prisma.item.create({
|
|
256
|
+
data: { title: 'New', price: 100, authorId: userId },
|
|
257
|
+
}),
|
|
258
|
+
prisma.user.update({
|
|
259
|
+
where: { id: userId },
|
|
260
|
+
data: { itemsCount: { increment: 1 } },
|
|
261
|
+
}),
|
|
262
|
+
]);
|
|
263
|
+
|
|
264
|
+
// Interactive transaction
|
|
265
|
+
const result = await prisma.$transaction(async (tx) => {
|
|
266
|
+
const user = await tx.user.findUnique({
|
|
267
|
+
where: { id: userId },
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
if (!user || user.itemsCount >= 100) {
|
|
271
|
+
throw new Error('Item limit reached');
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const item = await tx.item.create({
|
|
275
|
+
data: { title, price, authorId: userId },
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
await tx.user.update({
|
|
279
|
+
where: { id: userId },
|
|
280
|
+
data: { itemsCount: { increment: 1 } },
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
return item;
|
|
284
|
+
});
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
## Service Pattern
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
import { prisma } from './client';
|
|
293
|
+
import type { Prisma } from '@prisma/client';
|
|
294
|
+
|
|
295
|
+
export class ItemService {
|
|
296
|
+
async findById(id: string) {
|
|
297
|
+
return prisma.item.findUnique({
|
|
298
|
+
where: { id, deletedAt: null },
|
|
299
|
+
include: { author: true },
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
async findByAuthor(authorId: string) {
|
|
304
|
+
return prisma.item.findMany({
|
|
305
|
+
where: { authorId, deletedAt: null },
|
|
306
|
+
orderBy: { createdAt: 'desc' },
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
async create(data: Prisma.ItemCreateInput) {
|
|
311
|
+
return prisma.item.create({ data });
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
async update(id: string, data: Prisma.ItemUpdateInput) {
|
|
315
|
+
return prisma.item.update({
|
|
316
|
+
where: { id },
|
|
317
|
+
data,
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
async softDelete(id: string) {
|
|
322
|
+
return prisma.item.update({
|
|
323
|
+
where: { id },
|
|
324
|
+
data: { deletedAt: new Date() },
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
---
|
|
331
|
+
|
|
332
|
+
## Migrations
|
|
333
|
+
|
|
334
|
+
### Commands
|
|
335
|
+
|
|
336
|
+
```bash
|
|
337
|
+
# Create migration
|
|
338
|
+
pnpm prisma migrate dev --name add_items_table
|
|
339
|
+
|
|
340
|
+
# Apply migrations in production
|
|
341
|
+
pnpm prisma migrate deploy
|
|
342
|
+
|
|
343
|
+
# Reset database (dev only)
|
|
344
|
+
pnpm prisma migrate reset
|
|
345
|
+
|
|
346
|
+
# Generate client after schema changes
|
|
347
|
+
pnpm prisma generate
|
|
348
|
+
|
|
349
|
+
# Open Prisma Studio
|
|
350
|
+
pnpm prisma studio
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### Seeding
|
|
354
|
+
|
|
355
|
+
```typescript
|
|
356
|
+
// prisma/seed.ts
|
|
357
|
+
import { prisma } from '../src/lib/prisma';
|
|
358
|
+
|
|
359
|
+
async function main() {
|
|
360
|
+
// Create users
|
|
361
|
+
const user = await prisma.user.upsert({
|
|
362
|
+
where: { email: 'admin@example.com' },
|
|
363
|
+
update: {},
|
|
364
|
+
create: {
|
|
365
|
+
email: 'admin@example.com',
|
|
366
|
+
name: 'Admin',
|
|
367
|
+
password: 'hashed_password',
|
|
368
|
+
role: 'ADMIN',
|
|
369
|
+
},
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
// Create items
|
|
373
|
+
await prisma.item.createMany({
|
|
374
|
+
data: [
|
|
375
|
+
{ title: 'Item 1', price: 100, authorId: user.id },
|
|
376
|
+
{ title: 'Item 2', price: 200, authorId: user.id },
|
|
377
|
+
],
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
console.log('Seed completed');
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
main()
|
|
384
|
+
.catch(console.error)
|
|
385
|
+
.finally(() => prisma.$disconnect());
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
---
|
|
389
|
+
|
|
390
|
+
## Client Setup
|
|
391
|
+
|
|
392
|
+
```typescript
|
|
393
|
+
// lib/prisma.ts
|
|
394
|
+
import { PrismaClient } from '@prisma/client';
|
|
395
|
+
|
|
396
|
+
const globalForPrisma = globalThis as unknown as {
|
|
397
|
+
prisma: PrismaClient | undefined;
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
export const prisma =
|
|
401
|
+
globalForPrisma.prisma ??
|
|
402
|
+
new PrismaClient({
|
|
403
|
+
log: process.env.NODE_ENV === 'development'
|
|
404
|
+
? ['query', 'error', 'warn']
|
|
405
|
+
: ['error'],
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
409
|
+
globalForPrisma.prisma = prisma;
|
|
410
|
+
}
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
---
|
|
414
|
+
|
|
415
|
+
## Testing
|
|
416
|
+
|
|
417
|
+
```typescript
|
|
418
|
+
import { describe, it, expect, beforeEach, afterAll } from 'vitest';
|
|
419
|
+
import { PrismaClient } from '@prisma/client';
|
|
420
|
+
import { ItemService } from '../services/item.service';
|
|
421
|
+
|
|
422
|
+
const prisma = new PrismaClient({
|
|
423
|
+
datasourceUrl: process.env.TEST_DATABASE_URL,
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
describe('ItemService', () => {
|
|
427
|
+
const service = new ItemService();
|
|
428
|
+
|
|
429
|
+
beforeEach(async () => {
|
|
430
|
+
await prisma.item.deleteMany();
|
|
431
|
+
await prisma.user.deleteMany();
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
afterAll(async () => {
|
|
435
|
+
await prisma.$disconnect();
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
describe('create', () => {
|
|
439
|
+
it('should create item', async () => {
|
|
440
|
+
const user = await prisma.user.create({
|
|
441
|
+
data: {
|
|
442
|
+
email: 'test@example.com',
|
|
443
|
+
name: 'Test',
|
|
444
|
+
password: 'hash',
|
|
445
|
+
},
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
const item = await service.create({
|
|
449
|
+
title: 'Test Item',
|
|
450
|
+
price: 100,
|
|
451
|
+
author: { connect: { id: user.id } },
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
expect(item.id).toBeDefined();
|
|
455
|
+
expect(item.title).toBe('Test Item');
|
|
456
|
+
});
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
describe('findById', () => {
|
|
460
|
+
it('should return null for non-existent', async () => {
|
|
461
|
+
const item = await service.findById('non-existent-id');
|
|
462
|
+
expect(item).toBeNull();
|
|
463
|
+
});
|
|
464
|
+
});
|
|
465
|
+
});
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
---
|
|
469
|
+
|
|
470
|
+
## Best Practices
|
|
471
|
+
|
|
472
|
+
### Good
|
|
473
|
+
|
|
474
|
+
- Use `@@map` for custom table names
|
|
475
|
+
- Use `@@index` for frequently queried fields
|
|
476
|
+
- Use `select` over `include` when you don't need all fields
|
|
477
|
+
- Use transactions for multi-step operations
|
|
478
|
+
- Use soft deletes for recoverability
|
|
479
|
+
- Review generated migrations before applying
|
|
480
|
+
|
|
481
|
+
### Bad
|
|
482
|
+
|
|
483
|
+
- No indexes (poor query performance)
|
|
484
|
+
- Include everything (fetches unnecessary data)
|
|
485
|
+
- Ignoring migrations (production deployment issues)
|
|
486
|
+
- Duplicate types (let Prisma generate types)
|
|
487
|
+
- No soft deletes (data loss risk)
|