@kood/claude-code 0.3.8 → 0.3.10

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.
Files changed (37) hide show
  1. package/dist/index.js +1 -1
  2. package/package.json +1 -1
  3. package/templates/.claude/agents/code-reviewer.md +16 -1
  4. package/templates/.claude/agents/dependency-manager.md +16 -1
  5. package/templates/.claude/agents/deployment-validator.md +16 -1
  6. package/templates/.claude/agents/git-operator.md +16 -1
  7. package/templates/.claude/agents/implementation-executor.md +16 -1
  8. package/templates/.claude/agents/lint-fixer.md +16 -1
  9. package/templates/.claude/agents/refactor-advisor.md +16 -1
  10. package/templates/.claude/commands/agent-creator.md +16 -1
  11. package/templates/.claude/commands/bug-fix.md +16 -1
  12. package/templates/.claude/commands/command-creator.md +17 -1
  13. package/templates/.claude/commands/docs-creator.md +17 -1
  14. package/templates/.claude/commands/docs-refactor.md +17 -1
  15. package/templates/.claude/commands/execute.md +17 -1
  16. package/templates/.claude/commands/git-all.md +16 -1
  17. package/templates/.claude/commands/git-session.md +17 -1
  18. package/templates/.claude/commands/git.md +17 -1
  19. package/templates/.claude/commands/lint-fix.md +17 -1
  20. package/templates/.claude/commands/lint-init.md +17 -1
  21. package/templates/.claude/commands/plan.md +17 -1
  22. package/templates/.claude/commands/prd.md +17 -1
  23. package/templates/.claude/commands/pre-deploy.md +17 -1
  24. package/templates/.claude/commands/refactor.md +17 -1
  25. package/templates/.claude/commands/version-update.md +17 -1
  26. package/templates/hono/CLAUDE.md +1 -0
  27. package/templates/nextjs/CLAUDE.md +12 -9
  28. package/templates/nextjs/docs/architecture.md +812 -0
  29. package/templates/npx/CLAUDE.md +1 -0
  30. package/templates/tanstack-start/CLAUDE.md +1 -0
  31. package/templates/tanstack-start/docs/library/better-auth/index.md +225 -185
  32. package/templates/tanstack-start/docs/library/prisma/index.md +1025 -41
  33. package/templates/tanstack-start/docs/library/t3-env/index.md +207 -40
  34. package/templates/tanstack-start/docs/library/tanstack-query/index.md +878 -42
  35. package/templates/tanstack-start/docs/library/tanstack-router/index.md +602 -54
  36. package/templates/tanstack-start/docs/library/tanstack-start/index.md +1334 -33
  37. package/templates/tanstack-start/docs/library/zod/index.md +674 -31
@@ -1,73 +1,1057 @@
1
1
  # Prisma
2
2
 
3
- > **Version**: 7.x | Node.js/TypeScript ORM
3
+ > **Version 7.x** | Node.js/TypeScript ORM
4
4
 
5
- @setup.md
6
- @config.md
7
- @schema.md
8
- @crud.md
9
- @relations.md
10
- @transactions.md
11
- @cloudflare-d1.md
5
+ ---
6
+
7
+ <context>
8
+
9
+ **Purpose:** Node.js/TypeScript용 타입 안전 ORM
10
+
11
+ **Key Features:**
12
+ - 타입 안전 데이터베이스 클라이언트
13
+ - Multi-File 스키마 지원 (v7)
14
+ - 마이그레이션 관리
15
+ - 관계형/NoSQL 데이터베이스 지원
16
+
17
+ **Breaking Changes (v7):**
18
+ - `generator client { provider = "prisma-client" }` (~~prisma-client-js~~)
19
+ - `output` 필드 필수
20
+ - Multi-File 스키마 구조 (`prisma/schema/` 폴더)
21
+
22
+ </context>
12
23
 
13
24
  ---
14
25
 
15
- ## Quick Reference
26
+ <forbidden>
27
+
28
+ | 분류 | 금지 |
29
+ |------|------|
30
+ | **자동 실행** | ❌ `prisma db push` 자동 실행 |
31
+ | | ❌ `prisma migrate` 자동 실행 |
32
+ | | ❌ `prisma generate` 자동 실행 |
33
+ | **스키마 변경** | ❌ `schema.prisma` 임의 변경 (사용자 확인 없이) |
34
+ | **구조** | ❌ Single-File 스키마 (`schema.prisma`) |
35
+
36
+ </forbidden>
37
+
38
+ ---
39
+
40
+ <required>
41
+
42
+ | 분류 | 필수 |
43
+ |------|------|
44
+ | **구조** | ✅ Multi-File 스키마 (`prisma/schema/` 폴더) |
45
+ | **주석** | ✅ 모든 모델/필드/enum에 한글 주석 |
46
+ | **v7 설정** | ✅ `generator client { provider = "prisma-client" }` |
47
+ | | ✅ `output` 필드 명시 (`../generated/prisma`) |
48
+ | **Import** | ✅ `import { PrismaClient } from './generated/prisma'` |
49
+
50
+ </required>
51
+
52
+ ---
53
+
54
+ <version_v7>
55
+
56
+ ## v7 Breaking Changes
57
+
58
+ ### 1. Generator Provider
59
+
60
+ ```prisma
61
+ // ❌ v6 (이전)
62
+ generator client {
63
+ provider = "prisma-client-js"
64
+ }
65
+
66
+ // ✅ v7 (필수)
67
+ generator client {
68
+ provider = "prisma-client"
69
+ output = "../generated/prisma" // output 필수!
70
+ }
71
+ ```
72
+
73
+ ### 2. Multi-File Schema
74
+
75
+ ```
76
+ // ❌ v6 구조
77
+ prisma/
78
+ └── schema.prisma
79
+
80
+ // ✅ v7 구조
81
+ prisma/
82
+ ├── schema/
83
+ │ ├── +base.prisma # datasource, generator
84
+ │ ├── +enum.prisma # 모든 enum
85
+ │ ├── user.prisma # User 모델
86
+ │ └── post.prisma # Post 모델
87
+ └── migrations/
88
+ ```
89
+
90
+ ### 3. Import Path
16
91
 
17
92
  ```typescript
18
- import { PrismaClient } from './generated/prisma' // v7 path!
19
- export const prisma = new PrismaClient()
93
+ // v6
94
+ import { PrismaClient } from '@prisma/client'
20
95
 
21
- // CRUD
22
- const users = await prisma.user.findMany()
23
- const user = await prisma.user.create({ data: { email, name } })
24
- const updated = await prisma.user.update({ where: { id }, data: { name } })
25
- const deleted = await prisma.user.delete({ where: { id } })
96
+ // ✅ v7
97
+ import { PrismaClient } from './generated/prisma'
98
+ ```
26
99
 
27
- // Include relations
28
- const userWithPosts = await prisma.user.findUnique({
29
- where: { id },
30
- include: { posts: true },
100
+ ### 4. Config File
101
+
102
+ ```typescript
103
+ // prisma.config.ts (v7 필수)
104
+ import 'dotenv/config'
105
+ import path from 'node:path'
106
+ import { defineConfig, env } from 'prisma/config'
107
+
108
+ export default defineConfig({
109
+ schema: path.join('prisma', 'schema'), // 폴더!
110
+ migrations: {
111
+ path: 'prisma/migrations',
112
+ seed: 'tsx prisma/seed.ts',
113
+ },
114
+ datasource: {
115
+ url: env('DATABASE_URL'),
116
+ },
31
117
  })
32
118
  ```
33
119
 
34
- ### v7 schema.prisma (⚠️ Important)
120
+ </version_v7>
121
+
122
+ ---
123
+
124
+ <setup>
125
+
126
+ ## 설치 및 설정
127
+
128
+ ### 설치
129
+
130
+ ```bash
131
+ yarn add @prisma/client@7
132
+ yarn add -D prisma@7
133
+ npx prisma init
134
+ ```
135
+
136
+ ### Multi-File Schema 구조 생성
137
+
138
+ ```
139
+ 프로젝트/
140
+ ├── prisma.config.ts
141
+ ├── prisma/
142
+ │ ├── schema/
143
+ │ │ ├── +base.prisma
144
+ │ │ ├── +enum.prisma
145
+ │ │ └── user.prisma
146
+ │ ├── migrations/
147
+ │ └── seed.ts
148
+ └── generated/
149
+ └── prisma/
150
+ ```
151
+
152
+ ### Prisma Client 초기화
153
+
154
+ ```typescript
155
+ // lib/prisma.ts
156
+ import { PrismaClient } from './generated/prisma'
157
+
158
+ const globalForPrisma = globalThis as unknown as {
159
+ prisma: PrismaClient | undefined
160
+ }
161
+
162
+ export const prisma = globalForPrisma.prisma ?? new PrismaClient({
163
+ log: ['query', 'error', 'warn']
164
+ })
165
+
166
+ if (process.env.NODE_ENV !== 'production') {
167
+ globalForPrisma.prisma = prisma
168
+ }
169
+ ```
170
+
171
+ ### TanStack Start 연동
172
+
173
+ ```typescript
174
+ import { createServerFn } from '@tanstack/react-start'
175
+ import { prisma } from '@/lib/prisma'
176
+
177
+ // GET
178
+ export const getUsers = createServerFn({ method: 'GET' })
179
+ .handler(async () => prisma.user.findMany({ include: { posts: true } }))
180
+
181
+ // POST with validation
182
+ export const createUser = createServerFn({ method: 'POST' })
183
+ .inputValidator(createUserSchema)
184
+ .handler(async ({ data }) => prisma.user.create({ data }))
185
+ ```
186
+
187
+ </setup>
188
+
189
+ ---
190
+
191
+ <configuration>
192
+
193
+ ## Configuration (prisma.config.ts)
194
+
195
+ ### 기본 설정
196
+
197
+ ```typescript
198
+ // prisma.config.ts
199
+ import 'dotenv/config'
200
+ import path from 'node:path'
201
+ import { defineConfig, env } from 'prisma/config'
202
+
203
+ export default defineConfig({
204
+ schema: path.join('prisma', 'schema'),
205
+ migrations: {
206
+ path: 'prisma/migrations',
207
+ seed: 'tsx prisma/seed.ts',
208
+ },
209
+ datasource: {
210
+ url: env('DATABASE_URL'),
211
+ },
212
+ })
213
+ ```
214
+
215
+ ### 옵션
216
+
217
+ | 옵션 | 설명 | 필수 |
218
+ |------|------|------|
219
+ | `schema` | 스키마 폴더 경로 | ✅ |
220
+ | `datasource.url` | 데이터베이스 URL | ✅ |
221
+ | `datasource.shadowDatabaseUrl` | Shadow DB URL (개발용) | |
222
+ | `migrations.path` | 마이그레이션 폴더 | |
223
+ | `migrations.seed` | 시드 명령어 | |
224
+
225
+ ### +base.prisma
35
226
 
36
227
  ```prisma
228
+ datasource db {
229
+ provider = "postgresql" // postgresql | mysql | sqlite | mongodb
230
+ url = env("DATABASE_URL")
231
+ }
232
+
37
233
  generator client {
38
- provider = "prisma-client" // v7! (not prisma-client-js)
39
- output = "../generated/prisma" // output is required!
234
+ provider = "prisma-client"
235
+ output = "../../generated/prisma"
236
+ }
237
+ ```
238
+
239
+ ### Seed 파일
240
+
241
+ ```typescript
242
+ // prisma/seed.ts
243
+ import { PrismaClient } from '../generated/prisma'
244
+
245
+ const prisma = new PrismaClient()
246
+
247
+ async function main() {
248
+ // 관리자 계정 생성
249
+ await prisma.user.create({
250
+ data: {
251
+ email: 'admin@example.com',
252
+ name: '관리자',
253
+ role: 'ADMIN',
254
+ },
255
+ })
256
+
257
+ console.log('Seed completed')
40
258
  }
259
+
260
+ main()
261
+ .catch(console.error)
262
+ .finally(() => prisma.$disconnect())
41
263
  ```
42
264
 
43
- ### ⛔ Claude Code Forbidden
265
+ </configuration>
266
+
267
+ ---
268
+
269
+ <schema>
270
+
271
+ ## Schema Definition (Multi-File)
272
+
273
+ ### 구조
274
+
275
+ ```
276
+ prisma/schema/
277
+ ├── +base.prisma # datasource, generator
278
+ ├── +enum.prisma # 모든 enum 정의
279
+ ├── user.prisma # User 모델
280
+ ├── post.prisma # Post 모델
281
+ └── category.prisma # Category 모델
282
+ ```
283
+
284
+ ### +enum.prisma
285
+
286
+ ```prisma
287
+ // +enum.prisma
288
+ // 사용자 역할
289
+ enum Role {
290
+ USER // 일반 사용자
291
+ ADMIN // 관리자
292
+ }
293
+
294
+ // 게시글 상태
295
+ enum PostStatus {
296
+ DRAFT // 임시 저장
297
+ PUBLISHED // 발행됨
298
+ ARCHIVED // 보관됨
299
+ }
300
+ ```
301
+
302
+ ### 모델 정의
303
+
304
+ ```prisma
305
+ // user.prisma
306
+ // 사용자 모델
307
+ model User {
308
+ id Int @id @default(autoincrement())
309
+ email String @unique // 로그인 이메일
310
+ name String? // 표시 이름
311
+ role Role @default(USER) // 권한
312
+ posts Post[] // 작성 게시글 (1:N)
313
+ profile Profile? // 프로필 (1:1)
314
+ createdAt DateTime @default(now())
315
+ updatedAt DateTime @updatedAt
316
+
317
+ @@index([email])
318
+ @@map("users")
319
+ }
44
320
 
45
- | Forbidden Actions |
46
- |----------|
47
- | Auto-run prisma db push |
48
- | Auto-run prisma migrate |
49
- | Auto-run prisma generate |
50
- | Unauthorized schema.prisma modifications |
321
+ // post.prisma
322
+ // 게시글 모델
323
+ model Post {
324
+ id Int @id @default(autoincrement())
325
+ title String // 제목
326
+ content String? // 본문
327
+ status PostStatus @default(DRAFT)
328
+ author User @relation(fields: [authorId], references: [id])
329
+ authorId Int // 작성자 ID
330
+ categories Category[] // 카테고리 (M:N)
331
+ createdAt DateTime @default(now())
332
+ updatedAt DateTime @updatedAt
333
+
334
+ @@index([authorId])
335
+ @@index([status])
336
+ @@map("posts")
337
+ }
338
+ ```
339
+
340
+ ### 관계 패턴
341
+
342
+ | 관계 | 패턴 |
343
+ |------|------|
344
+ | **1:1** | `Profile? @relation(fields: [userId], references: [id])` + `userId Int @unique` |
345
+ | **1:N** | `posts Post[]` (부모) / `author User @relation(...)` (자식) |
346
+ | **M:N (암묵적)** | `categories Category[]` (양쪽) |
347
+ | **M:N (명시적)** | 중간 테이블 + `@@id([id1, id2])` |
348
+
349
+ ### 1:1 관계
350
+
351
+ ```prisma
352
+ // profile.prisma
353
+ model Profile {
354
+ id Int @id @default(autoincrement())
355
+ bio String? // 소개
356
+ user User @relation(fields: [userId], references: [id])
357
+ userId Int @unique // unique 필수!
358
+ }
359
+ ```
360
+
361
+ ### 1:N 관계
362
+
363
+ ```prisma
364
+ // user.prisma
365
+ model User {
366
+ posts Post[] // 관계 배열
367
+ }
368
+
369
+ // post.prisma
370
+ model Post {
371
+ author User @relation(fields: [authorId], references: [id])
372
+ authorId Int // Foreign Key
373
+ }
374
+ ```
375
+
376
+ ### M:N 관계 (암묵적)
377
+
378
+ ```prisma
379
+ // post.prisma
380
+ model Post {
381
+ categories Category[] // 중간 테이블 자동 생성
382
+ }
383
+
384
+ // category.prisma
385
+ model Category {
386
+ posts Post[]
387
+ }
388
+ ```
389
+
390
+ ### M:N 관계 (명시적)
391
+
392
+ ```prisma
393
+ // category-on-post.prisma
394
+ model CategoriesOnPosts {
395
+ post Post @relation(fields: [postId], references: [id])
396
+ postId Int
397
+ category Category @relation(fields: [categoryId], references: [id])
398
+ categoryId Int
399
+ assignedAt DateTime @default(now()) // 추가 필드 가능
400
+
401
+ @@id([postId, categoryId])
402
+ @@index([postId])
403
+ @@index([categoryId])
404
+ }
405
+ ```
406
+
407
+ ### 선택적 관계
408
+
409
+ ```prisma
410
+ author User? @relation(fields: [authorId], references: [id])
411
+ authorId Int? // nullable
412
+ ```
413
+
414
+ ### 인덱스 & 매핑
415
+
416
+ ```prisma
417
+ // 단일 인덱스
418
+ @@index([email])
419
+ @@index([createdAt])
420
+
421
+ // 복합 인덱스
422
+ @@index([authorId, status])
423
+
424
+ // 복합 키
425
+ @@id([postId, categoryId])
426
+
427
+ // 필드 매핑
428
+ userId Int @map("user_id")
429
+
430
+ // 테이블 매핑
431
+ @@map("users")
432
+ ```
433
+
434
+ </schema>
51
435
 
52
436
  ---
53
437
 
54
- ## Prisma Client Setup
438
+ <crud>
439
+
440
+ ## CRUD Operations
441
+
442
+ ### Create
443
+
444
+ ```typescript
445
+ // 단일 생성
446
+ const user = await prisma.user.create({
447
+ data: {
448
+ email: 'alice@prisma.io',
449
+ name: 'Alice',
450
+ role: 'USER',
451
+ },
452
+ })
453
+
454
+ // 관계 포함 생성
455
+ const user = await prisma.user.create({
456
+ data: {
457
+ email: 'bob@prisma.io',
458
+ name: 'Bob',
459
+ posts: {
460
+ create: [
461
+ { title: 'Hello World', status: 'PUBLISHED' },
462
+ { title: 'Second Post', status: 'DRAFT' },
463
+ ],
464
+ },
465
+ },
466
+ include: { posts: true },
467
+ })
468
+
469
+ // connectOrCreate (있으면 연결, 없으면 생성)
470
+ const post = await prisma.post.create({
471
+ data: {
472
+ title: 'Post with Tags',
473
+ categories: {
474
+ connectOrCreate: [
475
+ { where: { name: 'Tech' }, create: { name: 'Tech' } },
476
+ { where: { name: 'Tutorial' }, create: { name: 'Tutorial' } },
477
+ ],
478
+ },
479
+ },
480
+ })
481
+ ```
482
+
483
+ ### Read
484
+
485
+ ```typescript
486
+ // 단일 조회 (findUnique)
487
+ const user = await prisma.user.findUnique({
488
+ where: { email: 'alice@prisma.io' }
489
+ })
490
+
491
+ // 다중 조회 (findMany)
492
+ const users = await prisma.user.findMany({
493
+ where: { role: 'ADMIN' },
494
+ orderBy: { createdAt: 'desc' },
495
+ take: 10,
496
+ })
497
+
498
+ // 관계 포함
499
+ const users = await prisma.user.findMany({
500
+ include: {
501
+ posts: true,
502
+ profile: true,
503
+ },
504
+ })
505
+
506
+ // 필드 선택 (select)
507
+ const user = await prisma.user.findUnique({
508
+ where: { email: 'alice@prisma.io' },
509
+ select: {
510
+ email: true,
511
+ posts: {
512
+ select: { title: true, createdAt: true },
513
+ where: { status: 'PUBLISHED' },
514
+ },
515
+ },
516
+ })
517
+
518
+ // 관계로 필터
519
+ const users = await prisma.user.findMany({
520
+ where: {
521
+ posts: {
522
+ some: { status: 'PUBLISHED' }, // 게시글이 하나라도 있는 사용자
523
+ },
524
+ },
525
+ })
526
+ ```
527
+
528
+ ### Update
529
+
530
+ ```typescript
531
+ // 단일 수정
532
+ const user = await prisma.user.update({
533
+ where: { id: 1 },
534
+ data: { name: 'Updated Name' },
535
+ })
536
+
537
+ // 다중 수정
538
+ await prisma.user.updateMany({
539
+ where: { role: 'USER' },
540
+ data: { role: 'ADMIN' },
541
+ })
542
+
543
+ // Upsert (있으면 수정, 없으면 생성)
544
+ const user = await prisma.user.upsert({
545
+ where: { email: 'alice@prisma.io' },
546
+ update: { name: 'Updated Alice' },
547
+ create: { email: 'alice@prisma.io', name: 'Alice' },
548
+ })
549
+
550
+ // 증감 연산
551
+ await prisma.post.update({
552
+ where: { id: 1 },
553
+ data: {
554
+ views: { increment: 1 },
555
+ likes: { decrement: 1 },
556
+ },
557
+ })
558
+ ```
559
+
560
+ ### Delete
561
+
562
+ ```typescript
563
+ // 단일 삭제
564
+ await prisma.user.delete({ where: { id: 1 } })
565
+
566
+ // 다중 삭제
567
+ await prisma.post.deleteMany({ where: { status: 'DRAFT' } })
568
+
569
+ // 전체 삭제
570
+ await prisma.user.deleteMany({})
571
+ ```
572
+
573
+ ### 필터 연산자
574
+
575
+ | 타입 | 연산자 |
576
+ |------|--------|
577
+ | **문자열** | `contains`, `startsWith`, `endsWith`, `mode: 'insensitive'` |
578
+ | **숫자** | `gt`, `gte`, `lt`, `lte` |
579
+ | **배열** | `in`, `notIn` |
580
+ | **논리** | `OR`, `AND`, `NOT` |
581
+ | **관계** | `some`, `every`, `none` |
582
+
583
+ ```typescript
584
+ // 문자열 검색
585
+ where: { email: { contains: 'prisma', mode: 'insensitive' } }
586
+
587
+ // 숫자 범위
588
+ where: { age: { gte: 18, lte: 65 } }
589
+
590
+ // 배열
591
+ where: { id: { in: [1, 2, 3] } }
592
+
593
+ // 논리 연산
594
+ where: {
595
+ OR: [
596
+ { role: 'ADMIN' },
597
+ { posts: { some: { status: 'PUBLISHED' } } },
598
+ ],
599
+ }
600
+ ```
601
+
602
+ </crud>
603
+
604
+ ---
605
+
606
+ <relations>
607
+
608
+ ## Relation Queries
609
+
610
+ ### 중첩 생성
611
+
612
+ ```typescript
613
+ // 1:N 관계 생성
614
+ const user = await prisma.user.create({
615
+ data: {
616
+ email: 'user@prisma.io',
617
+ posts: {
618
+ create: [
619
+ { title: 'Post 1', status: 'PUBLISHED' },
620
+ { title: 'Post 2', status: 'DRAFT' },
621
+ ],
622
+ },
623
+ },
624
+ include: { posts: true },
625
+ })
626
+ ```
627
+
628
+ ### 관계 연결
629
+
630
+ ```typescript
631
+ // connect - 기존 레코드 연결
632
+ await prisma.post.create({
633
+ data: {
634
+ title: 'New Post',
635
+ author: { connect: { id: 1 } },
636
+ },
637
+ })
638
+
639
+ // connectOrCreate
640
+ await prisma.post.create({
641
+ data: {
642
+ title: 'Post with Category',
643
+ categories: {
644
+ connectOrCreate: {
645
+ where: { name: 'Tech' },
646
+ create: { name: 'Tech' },
647
+ },
648
+ },
649
+ },
650
+ })
651
+
652
+ // disconnect
653
+ await prisma.post.update({
654
+ where: { id: 1 },
655
+ data: {
656
+ author: { disconnect: true },
657
+ },
658
+ })
659
+ ```
660
+
661
+ ### 관계 포함 조회
662
+
663
+ ```typescript
664
+ // include
665
+ const users = await prisma.user.findMany({
666
+ include: {
667
+ posts: true,
668
+ profile: true,
669
+ },
670
+ })
671
+
672
+ // 중첩 include
673
+ const users = await prisma.user.findMany({
674
+ include: {
675
+ posts: {
676
+ include: { categories: true },
677
+ },
678
+ },
679
+ })
680
+
681
+ // 필터 + 정렬
682
+ const users = await prisma.user.findMany({
683
+ include: {
684
+ posts: {
685
+ where: { status: 'PUBLISHED' },
686
+ orderBy: { createdAt: 'desc' },
687
+ take: 5,
688
+ },
689
+ },
690
+ })
691
+ ```
692
+
693
+ ### 관계로 필터링
694
+
695
+ ```typescript
696
+ // some - 하나라도 만족
697
+ where: {
698
+ posts: { some: { status: 'PUBLISHED' } },
699
+ }
700
+
701
+ // every - 모두 만족
702
+ where: {
703
+ posts: { every: { status: 'PUBLISHED' } },
704
+ }
705
+
706
+ // none - 만족하는 것 없음
707
+ where: {
708
+ posts: { none: { status: 'DRAFT' } },
709
+ }
710
+ ```
711
+
712
+ ### 관계 카운트
713
+
714
+ ```typescript
715
+ const users = await prisma.user.findMany({
716
+ include: {
717
+ _count: {
718
+ select: { posts: true },
719
+ },
720
+ },
721
+ })
722
+ // 결과: { ..., _count: { posts: 5 } }
723
+ ```
724
+
725
+ ### 중첩 수정/삭제
726
+
727
+ ```typescript
728
+ // 중첩 수정
729
+ await prisma.user.update({
730
+ where: { id: 1 },
731
+ data: {
732
+ posts: {
733
+ updateMany: {
734
+ where: { status: 'DRAFT' },
735
+ data: { status: 'PUBLISHED' },
736
+ },
737
+ },
738
+ },
739
+ })
740
+
741
+ // 중첩 삭제
742
+ await prisma.user.update({
743
+ where: { id: 1 },
744
+ data: {
745
+ posts: {
746
+ deleteMany: { where: { status: 'DRAFT' } },
747
+ },
748
+ },
749
+ })
750
+ ```
751
+
752
+ </relations>
753
+
754
+ ---
755
+
756
+ <transactions>
757
+
758
+ ## Transactions
759
+
760
+ ### 배열 기반 트랜잭션
761
+
762
+ 하나라도 실패하면 전체 롤백.
763
+
764
+ ```typescript
765
+ const deletePosts = prisma.post.deleteMany({ where: { authorId: 7 } })
766
+ const deleteUser = prisma.user.delete({ where: { id: 7 } })
767
+
768
+ await prisma.$transaction([deletePosts, deleteUser])
769
+ ```
770
+
771
+ ### 인터랙티브 트랜잭션
772
+
773
+ 복잡한 로직, 조건부 처리.
774
+
775
+ ```typescript
776
+ const result = await prisma.$transaction(async (tx) => {
777
+ // 1. 잔액 확인
778
+ const sender = await tx.account.findUnique({ where: { id: senderId } })
779
+ if (!sender || sender.balance < amount) {
780
+ throw new Error('Insufficient balance')
781
+ }
782
+
783
+ // 2. 송금자 차감
784
+ await tx.account.update({
785
+ where: { id: senderId },
786
+ data: { balance: { decrement: amount } },
787
+ })
788
+
789
+ // 3. 수신자 증가
790
+ await tx.account.update({
791
+ where: { id: recipientId },
792
+ data: { balance: { increment: amount } },
793
+ })
794
+
795
+ return { success: true, amount }
796
+ })
797
+ ```
798
+
799
+ ### 트랜잭션 옵션
800
+
801
+ ```typescript
802
+ await prisma.$transaction(async (tx) => {
803
+ // 트랜잭션 로직
804
+ }, {
805
+ maxWait: 5000, // 최대 대기 시간 (ms)
806
+ timeout: 10000, // 타임아웃 (ms)
807
+ isolationLevel: 'Serializable', // 격리 수준
808
+ })
809
+ ```
810
+
811
+ | Isolation Level | 설명 |
812
+ |----------------|------|
813
+ | `ReadUncommitted` | 가장 낮은 수준 (Dirty Read 가능) |
814
+ | `ReadCommitted` | 커밋된 데이터만 읽기 |
815
+ | `RepeatableRead` | 반복 읽기 보장 |
816
+ | `Serializable` | 가장 높은 수준 (직렬화) |
817
+
818
+ ### 에러 처리
819
+
820
+ ```typescript
821
+ try {
822
+ await prisma.$transaction(async (tx) => {
823
+ await tx.user.create({ data: { email: 'test@example.com' } })
824
+
825
+ if (someCondition) {
826
+ throw new Error('Rollback condition')
827
+ }
828
+
829
+ await tx.post.create({ data: { title: 'Post' } })
830
+ })
831
+ } catch (error) {
832
+ console.error('Transaction rolled back:', error)
833
+ // 전체 작업 롤백됨
834
+ }
835
+ ```
836
+
837
+ </transactions>
838
+
839
+ ---
840
+
841
+ <cloudflare_d1>
842
+
843
+ ## Cloudflare D1
844
+
845
+ SQLite 기반 서버리스 데이터베이스.
846
+
847
+ **Limitations:**
848
+ - ❌ 트랜잭션 미지원
849
+ - ❌ `prisma migrate` 불가 (wrangler 사용)
850
+ - ⚠️ Preview 상태
851
+
852
+ ### 설정
853
+
854
+ ```prisma
855
+ // +base.prisma
856
+ generator client {
857
+ provider = "prisma-client"
858
+ output = "../../src/generated/prisma"
859
+ runtime = "cloudflare" // 필수!
860
+ }
861
+
862
+ datasource db {
863
+ provider = "sqlite"
864
+ }
865
+ ```
866
+
867
+ ```jsonc
868
+ // wrangler.jsonc
869
+ {
870
+ "d1_databases": [
871
+ {
872
+ "binding": "DB",
873
+ "database_name": "my-db",
874
+ "database_id": "your-database-id"
875
+ }
876
+ ]
877
+ }
878
+ ```
879
+
880
+ ### 사용법
55
881
 
56
882
  ```typescript
57
- // lib/prisma.ts
58
883
  import { PrismaClient } from './generated/prisma'
884
+ import { PrismaD1 } from '@prisma/adapter-d1'
885
+
886
+ export interface Env {
887
+ DB: D1Database
888
+ }
889
+
890
+ export default {
891
+ async fetch(request: Request, env: Env): Promise<Response> {
892
+ const adapter = new PrismaD1(env.DB)
893
+ const prisma = new PrismaClient({ adapter })
894
+
895
+ const users = await prisma.user.findMany()
896
+
897
+ return new Response(JSON.stringify(users), {
898
+ headers: { 'Content-Type': 'application/json' },
899
+ })
900
+ },
901
+ }
902
+ ```
903
+
904
+ ### 마이그레이션
905
+
906
+ ```bash
907
+ # 1. D1 데이터베이스 생성
908
+ npx wrangler d1 create my-database
909
+
910
+ # 2. 마이그레이션 생성
911
+ npx wrangler d1 migrations create my-database init
912
+
913
+ # 3. SQL 생성 (초기)
914
+ npx prisma migrate diff \
915
+ --from-empty \
916
+ --to-schema-datamodel prisma/schema.prisma \
917
+ --script \
918
+ --output prisma/migrations/0001.sql
59
919
 
60
- const globalForPrisma = globalThis as unknown as { prisma: PrismaClient | undefined }
61
- export const prisma = globalForPrisma.prisma ?? new PrismaClient({ log: ['query'] })
62
- if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
920
+ # 4. SQL 생성 (후속)
921
+ npx prisma migrate diff \
922
+ --from-local-d1 \
923
+ --to-schema-datamodel prisma/schema.prisma \
924
+ --script
925
+
926
+ # 5. 마이그레이션 적용
927
+ npx wrangler d1 migrations apply my-database --local # 로컬
928
+ npx wrangler d1 migrations apply my-database --remote # 프로덕션
929
+
930
+ # 6. Client 생성
931
+ npx prisma generate
63
932
  ```
64
933
 
65
- ## Migration Commands
934
+ ### 비교
935
+
936
+ | 항목 | 일반 SQLite | Cloudflare D1 |
937
+ |------|-------------|---------------|
938
+ | **마이그레이션** | ✅ `prisma migrate` | ❌ `wrangler d1` |
939
+ | **트랜잭션** | ✅ 지원 | ❌ 미지원 |
940
+ | **접속** | ✅ 직접 연결 | HTTP 어댑터 |
941
+ | **환경** | 로컬/서버 | Cloudflare Workers |
942
+
943
+ </cloudflare_d1>
944
+
945
+ ---
946
+
947
+ <commands>
948
+
949
+ ## CLI Commands
66
950
 
67
951
  ```bash
68
- npx prisma migrate dev --name init # Development migration
69
- npx prisma migrate deploy # Production migration
70
- npx prisma db push # Schema sync (dev only)
71
- npx prisma generate # Generate Client
72
- npx prisma studio # GUI
952
+ # 초기화
953
+ npx prisma init
954
+
955
+ # Client 생성
956
+ npx prisma generate
957
+
958
+ # 마이그레이션
959
+ npx prisma migrate dev --name init # 개발 (스키마 변경 + 마이그레이션)
960
+ npx prisma migrate deploy # 프로덕션 (마이그레이션 적용)
961
+ npx prisma migrate reset # DB 초기화
962
+
963
+ # 스키마 동기화 (개발용)
964
+ npx prisma db push
965
+
966
+ # 시드
967
+ npx prisma db seed
968
+
969
+ # GUI
970
+ npx prisma studio
971
+
972
+ # 포맷팅
973
+ npx prisma format
974
+
975
+ # 유효성 검사
976
+ npx prisma validate
977
+ ```
978
+
979
+ </commands>
980
+
981
+ ---
982
+
983
+ <dos_and_donts>
984
+
985
+ ## Do's & Don'ts
986
+
987
+ ### ✅ Do
988
+
989
+ | 상황 | 방법 |
990
+ |------|------|
991
+ | **구조** | Multi-File 스키마 사용 (`prisma/schema/`) |
992
+ | **주석** | 모든 모델/필드/enum에 한글 주석 필수 |
993
+ | **Provider** | v7: `generator client { provider = "prisma-client" }` |
994
+ | **Import** | `import { PrismaClient } from './generated/prisma'` |
995
+ | **트랜잭션** | 복잡한 로직 → 인터랙티브 트랜잭션 사용 |
996
+ | **관계** | `include`로 관계 데이터 로드 |
997
+ | **인덱스** | 자주 조회하는 필드에 `@@index` 추가 |
998
+
999
+ ### ❌ Don't
1000
+
1001
+ | 상황 | 이유 |
1002
+ |------|------|
1003
+ | **Single File** | ❌ `schema.prisma` 단일 파일 사용 금지 |
1004
+ | **v6 Provider** | ❌ `prisma-client-js` 사용 금지 (v7) |
1005
+ | **Import** | ❌ `@prisma/client` import 금지 (v7) |
1006
+ | **자동 실행** | ❌ `prisma migrate/db push` 자동 실행 금지 |
1007
+ | **N+1** | ❌ 루프에서 개별 쿼리 실행 → `include`/`findMany` 사용 |
1008
+ | **Raw Query** | ❌ 타입 안전성 없음 → Prisma Query API 우선 |
1009
+
1010
+ ### N+1 Problem 예방
1011
+
1012
+ ```typescript
1013
+ // ❌ N+1 Problem
1014
+ const users = await prisma.user.findMany()
1015
+ for (const user of users) {
1016
+ const posts = await prisma.post.findMany({ where: { authorId: user.id } })
1017
+ }
1018
+
1019
+ // ✅ include 사용
1020
+ const users = await prisma.user.findMany({
1021
+ include: { posts: true },
1022
+ })
73
1023
  ```
1024
+
1025
+ </dos_and_donts>
1026
+
1027
+ ---
1028
+
1029
+ <quick_reference>
1030
+
1031
+ ## Quick Reference
1032
+
1033
+ ```typescript
1034
+ // Client 초기화
1035
+ import { PrismaClient } from './generated/prisma'
1036
+ export const prisma = new PrismaClient()
1037
+
1038
+ // CRUD
1039
+ const users = await prisma.user.findMany()
1040
+ const user = await prisma.user.create({ data: { email, name } })
1041
+ const updated = await prisma.user.update({ where: { id }, data: { name } })
1042
+ const deleted = await prisma.user.delete({ where: { id } })
1043
+
1044
+ // 관계 포함
1045
+ const userWithPosts = await prisma.user.findUnique({
1046
+ where: { id },
1047
+ include: { posts: true },
1048
+ })
1049
+
1050
+ // 트랜잭션
1051
+ await prisma.$transaction([
1052
+ prisma.user.create({ data: { email: 'a@example.com' } }),
1053
+ prisma.post.create({ data: { title: 'Post' } }),
1054
+ ])
1055
+ ```
1056
+
1057
+ </quick_reference>