@kood/claude-code 0.5.3 → 0.5.5
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/index.js +552 -340
- package/package.json +1 -1
- package/templates/.claude/agents/document-writer.md +73 -306
- package/templates/.claude/instructions/agent-patterns/index.md +7 -7
- package/templates/.claude/instructions/document-templates/ralph-templates.md +71 -0
- package/templates/.claude/instructions/index.md +14 -14
- package/templates/.claude/instructions/multi-agent/agent-roster.md +14 -14
- package/templates/.claude/instructions/multi-agent/index.md +4 -4
- package/templates/.claude/skills/docs-creator/AGENTS.md +54 -176
- package/templates/.claude/skills/docs-creator/SKILL.md +98 -464
- package/templates/.claude/skills/docs-refactor/AGENTS.md +61 -190
- package/templates/.claude/skills/docs-refactor/SKILL.md +67 -443
- package/templates/.claude/skills/execute/SKILL.md +540 -13
- package/templates/.claude/skills/plan/SKILL.md +84 -18
- package/templates/.claude/skills/ralph/SKILL.md +17 -14
- package/templates/.claude/skills/refactor/AGENTS.md +269 -0
- package/templates/.claude/skills/refactor/SKILL.md +424 -66
- package/templates/.claude/skills/stitch-design/README.md +34 -0
- package/templates/.claude/skills/stitch-design/SKILL.md +213 -0
- package/templates/.claude/skills/stitch-design/examples/DESIGN.md +154 -0
- package/templates/.claude/skills/stitch-loop/README.md +54 -0
- package/templates/.claude/skills/stitch-loop/SKILL.md +316 -0
- package/templates/.claude/skills/stitch-loop/examples/SITE.md +73 -0
- package/templates/.claude/skills/stitch-loop/examples/next-prompt.md +25 -0
- package/templates/.claude/skills/stitch-loop/resources/baton-schema.md +61 -0
- package/templates/.claude/skills/stitch-loop/resources/site-template.md +104 -0
- package/templates/.claude/skills/stitch-react/README.md +36 -0
- package/templates/.claude/skills/stitch-react/SKILL.md +323 -0
- package/templates/.claude/skills/stitch-react/examples/gold-standard-card.tsx +88 -0
- package/templates/.claude/skills/stitch-react/package-lock.json +231 -0
- package/templates/.claude/skills/stitch-react/package.json +16 -0
- package/templates/.claude/skills/stitch-react/resources/architecture-checklist.md +15 -0
- package/templates/.claude/skills/stitch-react/resources/component-template.tsx +37 -0
- package/templates/.claude/skills/stitch-react/resources/stitch-api-reference.md +14 -0
- package/templates/.claude/skills/stitch-react/resources/style-guide.json +24 -0
- package/templates/.claude/skills/stitch-react/scripts/fetch-stitch.sh +30 -0
- package/templates/.claude/skills/stitch-react/scripts/validate.js +77 -0
- package/templates/hono/CLAUDE.md +28 -28
- package/templates/hono/docs/architecture.md +24 -24
- package/templates/hono/docs/deployment/cloudflare.md +18 -18
- package/templates/hono/docs/deployment/docker.md +13 -13
- package/templates/hono/docs/deployment/index.md +19 -19
- package/templates/hono/docs/deployment/railway.md +32 -32
- package/templates/hono/docs/deployment/vercel.md +29 -29
- package/templates/hono/docs/guides/conventions.md +57 -57
- package/templates/hono/docs/guides/env-setup.md +47 -47
- package/templates/hono/docs/guides/getting-started.md +27 -27
- package/templates/hono/docs/library/hono/error-handling.md +11 -11
- package/templates/hono/docs/library/hono/index.md +4 -4
- package/templates/hono/docs/library/hono/middleware.md +18 -18
- package/templates/hono/docs/library/hono/rpc.md +7 -7
- package/templates/hono/docs/library/hono/validation.md +6 -6
- package/templates/hono/docs/library/prisma/cloudflare-d1.md +29 -29
- package/templates/hono/docs/library/prisma/config.md +16 -16
- package/templates/hono/docs/library/prisma/index.md +32 -32
- package/templates/hono/docs/library/t3-env/index.md +22 -22
- package/templates/hono/docs/library/zod/index.md +31 -31
- package/templates/nextjs/CLAUDE.md +54 -54
- package/templates/nextjs/docs/architecture.md +146 -146
- package/templates/nextjs/docs/design.md +183 -183
- package/templates/nextjs/docs/guides/conventions.md +86 -86
- package/templates/nextjs/docs/guides/getting-started.md +28 -28
- package/templates/nextjs/docs/guides/routes.md +32 -32
- package/templates/nextjs/docs/library/better-auth/index.md +70 -70
- package/templates/nextjs/docs/library/nextjs/app-router.md +43 -43
- package/templates/nextjs/docs/library/nextjs/caching.md +73 -73
- package/templates/nextjs/docs/library/nextjs/index.md +51 -51
- package/templates/nextjs/docs/library/nextjs/middleware.md +41 -41
- package/templates/nextjs/docs/library/nextjs/route-handlers.md +31 -31
- package/templates/nextjs/docs/library/nextjs/server-actions.md +34 -34
- package/templates/nextjs/docs/library/prisma/cloudflare-d1.md +20 -20
- package/templates/nextjs/docs/library/prisma/config.md +18 -18
- package/templates/nextjs/docs/library/prisma/crud.md +17 -17
- package/templates/nextjs/docs/library/prisma/index.md +18 -18
- package/templates/nextjs/docs/library/prisma/relations.md +16 -16
- package/templates/nextjs/docs/library/prisma/schema.md +23 -23
- package/templates/nextjs/docs/library/prisma/setup.md +6 -6
- package/templates/nextjs/docs/library/prisma/transactions.md +10 -10
- package/templates/nextjs/docs/library/tanstack-query/index.md +6 -6
- package/templates/nextjs/docs/library/tanstack-query/invalidation.md +20 -20
- package/templates/nextjs/docs/library/tanstack-query/optimistic-updates.md +4 -4
- package/templates/nextjs/docs/library/tanstack-query/use-mutation.md +15 -15
- package/templates/nextjs/docs/library/tanstack-query/use-query.md +22 -22
- package/templates/nextjs/docs/library/zod/complex-types.md +11 -11
- package/templates/nextjs/docs/library/zod/index.md +8 -8
- package/templates/nextjs/docs/library/zod/transforms.md +11 -11
- package/templates/nextjs/docs/library/zod/validation.md +9 -9
- package/templates/npx/CLAUDE.md +38 -38
- package/templates/npx/docs/library/commander/index.md +12 -12
- package/templates/npx/docs/library/fs-extra/index.md +9 -9
- package/templates/npx/docs/library/prompts/index.md +3 -3
- package/templates/npx/docs/references/patterns.md +12 -12
- package/templates/tanstack-start/CLAUDE.md +54 -54
- package/templates/tanstack-start/docs/architecture.md +128 -128
- package/templates/tanstack-start/docs/design.md +169 -169
- package/templates/tanstack-start/docs/guides/conventions.md +43 -43
- package/templates/tanstack-start/docs/guides/env-setup.md +35 -35
- package/templates/tanstack-start/docs/guides/getting-started.md +19 -19
- package/templates/tanstack-start/docs/guides/hooks.md +45 -45
- package/templates/tanstack-start/docs/guides/routes.md +54 -54
- package/templates/tanstack-start/docs/guides/services.md +45 -45
- package/templates/tanstack-start/docs/library/prisma/cloudflare-d1.md +19 -19
- package/templates/tanstack-start/docs/library/prisma/config.md +16 -16
- package/templates/tanstack-start/docs/library/prisma/crud.md +17 -17
- package/templates/tanstack-start/docs/library/prisma/relations.md +16 -16
- package/templates/tanstack-start/docs/library/prisma/schema.md +23 -23
- package/templates/tanstack-start/docs/library/prisma/setup.md +6 -6
- package/templates/tanstack-start/docs/library/prisma/transactions.md +10 -10
- package/templates/tanstack-start/docs/library/tanstack-query/invalidation.md +19 -19
- package/templates/tanstack-start/docs/library/tanstack-query/optimistic-updates.md +4 -4
- package/templates/tanstack-start/docs/library/tanstack-query/use-mutation.md +14 -14
- package/templates/tanstack-start/docs/library/tanstack-query/use-query.md +21 -21
- package/templates/tanstack-start/docs/library/tanstack-router/error-handling.md +9 -9
- package/templates/tanstack-start/docs/library/tanstack-router/hooks.md +11 -11
- package/templates/tanstack-start/docs/library/tanstack-router/navigation.md +17 -17
- package/templates/tanstack-start/docs/library/tanstack-router/route-context.md +5 -5
- package/templates/tanstack-start/docs/library/tanstack-router/search-params.md +10 -10
- package/templates/tanstack-start/docs/library/tanstack-start/auth-patterns.md +8 -8
- package/templates/tanstack-start/docs/library/tanstack-start/middleware.md +9 -9
- package/templates/tanstack-start/docs/library/tanstack-start/routing.md +6 -6
- package/templates/tanstack-start/docs/library/tanstack-start/server-functions.md +18 -18
- package/templates/tanstack-start/docs/library/tanstack-start/setup.md +4 -4
- package/templates/tanstack-start/docs/library/zod/complex-types.md +11 -11
- package/templates/tanstack-start/docs/library/zod/transforms.md +11 -11
- package/templates/tanstack-start/docs/library/zod/validation.md +9 -9
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# Server Actions
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> 타입 안전한 서버 함수 (React 19 Server Actions)
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## 기본 사용법
|
|
8
8
|
|
|
9
|
-
###
|
|
9
|
+
### 파일 상단 선언
|
|
10
10
|
|
|
11
11
|
```typescript
|
|
12
12
|
// app/actions.ts
|
|
@@ -28,7 +28,7 @@ export async function createPost(formData: FormData) {
|
|
|
28
28
|
}
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
###
|
|
31
|
+
### 인라인 선언
|
|
32
32
|
|
|
33
33
|
```typescript
|
|
34
34
|
// app/posts/page.tsx
|
|
@@ -46,7 +46,7 @@ export default function PostsPage() {
|
|
|
46
46
|
|
|
47
47
|
---
|
|
48
48
|
|
|
49
|
-
## Zod
|
|
49
|
+
## Zod 검증
|
|
50
50
|
|
|
51
51
|
```typescript
|
|
52
52
|
"use server"
|
|
@@ -74,7 +74,7 @@ export async function createPost(formData: FormData) {
|
|
|
74
74
|
|
|
75
75
|
---
|
|
76
76
|
|
|
77
|
-
##
|
|
77
|
+
## 인증
|
|
78
78
|
|
|
79
79
|
```typescript
|
|
80
80
|
"use server"
|
|
@@ -92,7 +92,7 @@ export async function deletePost(id: string) {
|
|
|
92
92
|
await prisma.post.delete({
|
|
93
93
|
where: {
|
|
94
94
|
id,
|
|
95
|
-
userId: session.user.id, //
|
|
95
|
+
userId: session.user.id, // 본인 게시물만 삭제
|
|
96
96
|
},
|
|
97
97
|
})
|
|
98
98
|
|
|
@@ -102,7 +102,7 @@ export async function deletePost(id: string) {
|
|
|
102
102
|
|
|
103
103
|
---
|
|
104
104
|
|
|
105
|
-
##
|
|
105
|
+
## 에러 처리
|
|
106
106
|
|
|
107
107
|
```typescript
|
|
108
108
|
"use server"
|
|
@@ -116,14 +116,14 @@ export async function updatePost(id: string, data: PostInput) {
|
|
|
116
116
|
if (error instanceof z.ZodError) {
|
|
117
117
|
return { success: false, errors: error.errors }
|
|
118
118
|
}
|
|
119
|
-
return { success: false, message: "
|
|
119
|
+
return { success: false, message: "업데이트 실패" }
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
122
|
```
|
|
123
123
|
|
|
124
124
|
---
|
|
125
125
|
|
|
126
|
-
##
|
|
126
|
+
## 클라이언트에서 사용
|
|
127
127
|
|
|
128
128
|
### Form Action
|
|
129
129
|
|
|
@@ -137,7 +137,7 @@ export function CreatePostForm() {
|
|
|
137
137
|
<form action={createPost}>
|
|
138
138
|
<input name="title" required />
|
|
139
139
|
<textarea name="content" required />
|
|
140
|
-
<button type="submit"
|
|
140
|
+
<button type="submit">등록</button>
|
|
141
141
|
</form>
|
|
142
142
|
)
|
|
143
143
|
}
|
|
@@ -173,7 +173,7 @@ export function CreatePostForm() {
|
|
|
173
173
|
>
|
|
174
174
|
<input name="title" required />
|
|
175
175
|
<button type="submit" disabled={mutation.isPending}>
|
|
176
|
-
{mutation.isPending ? "
|
|
176
|
+
{mutation.isPending ? "등록 중..." : "등록"}
|
|
177
177
|
</button>
|
|
178
178
|
</form>
|
|
179
179
|
)
|
|
@@ -191,7 +191,7 @@ export async function createPost(prevState: any, formData: FormData) {
|
|
|
191
191
|
const title = formData.get("title") as string
|
|
192
192
|
|
|
193
193
|
if (!title) {
|
|
194
|
-
return { error: "
|
|
194
|
+
return { error: "제목을 입력하세요" }
|
|
195
195
|
}
|
|
196
196
|
|
|
197
197
|
const post = await prisma.post.create({ data: { title } })
|
|
@@ -212,7 +212,7 @@ export function CreatePostForm() {
|
|
|
212
212
|
<form action={formAction}>
|
|
213
213
|
<input name="title" />
|
|
214
214
|
{state?.error && <p>{state.error}</p>}
|
|
215
|
-
<button type="submit"
|
|
215
|
+
<button type="submit">등록</button>
|
|
216
216
|
</form>
|
|
217
217
|
)
|
|
218
218
|
}
|
|
@@ -220,7 +220,7 @@ export function CreatePostForm() {
|
|
|
220
220
|
|
|
221
221
|
---
|
|
222
222
|
|
|
223
|
-
##
|
|
223
|
+
## 캐시 무효화
|
|
224
224
|
|
|
225
225
|
### revalidatePath
|
|
226
226
|
|
|
@@ -232,11 +232,11 @@ import { revalidatePath } from "next/cache"
|
|
|
232
232
|
export async function createPost(data: PostInput) {
|
|
233
233
|
const post = await prisma.post.create({ data })
|
|
234
234
|
|
|
235
|
-
//
|
|
235
|
+
// 특정 경로 캐시 무효화
|
|
236
236
|
revalidatePath("/posts")
|
|
237
237
|
revalidatePath(`/posts/${post.id}`)
|
|
238
238
|
|
|
239
|
-
//
|
|
239
|
+
// 레이아웃 포함 모든 캐시 무효화
|
|
240
240
|
revalidatePath("/posts", "layout")
|
|
241
241
|
|
|
242
242
|
return post
|
|
@@ -246,19 +246,19 @@ export async function createPost(data: PostInput) {
|
|
|
246
246
|
### revalidateTag
|
|
247
247
|
|
|
248
248
|
```typescript
|
|
249
|
-
//
|
|
249
|
+
// 데이터 페칭 시 태그 설정
|
|
250
250
|
const posts = await fetch("https://api.example.com/posts", {
|
|
251
251
|
next: { tags: ["posts"] },
|
|
252
252
|
})
|
|
253
253
|
|
|
254
|
-
//
|
|
254
|
+
// Server Action에서 태그 무효화
|
|
255
255
|
"use server"
|
|
256
256
|
|
|
257
257
|
import { revalidateTag } from "next/cache"
|
|
258
258
|
|
|
259
259
|
export async function createPost(data: PostInput) {
|
|
260
260
|
const post = await prisma.post.create({ data })
|
|
261
|
-
revalidateTag("posts") //
|
|
261
|
+
revalidateTag("posts") // "posts" 태그 캐시 무효화
|
|
262
262
|
return post
|
|
263
263
|
}
|
|
264
264
|
```
|
|
@@ -279,13 +279,13 @@ export async function createPost(formData: FormData) {
|
|
|
279
279
|
},
|
|
280
280
|
})
|
|
281
281
|
|
|
282
|
-
redirect(`/posts/${post.id}`) //
|
|
282
|
+
redirect(`/posts/${post.id}`) // 페이지 이동
|
|
283
283
|
}
|
|
284
284
|
```
|
|
285
285
|
|
|
286
286
|
---
|
|
287
287
|
|
|
288
|
-
##
|
|
288
|
+
## 파일 업로드
|
|
289
289
|
|
|
290
290
|
```typescript
|
|
291
291
|
"use server"
|
|
@@ -297,7 +297,7 @@ export async function uploadFile(formData: FormData) {
|
|
|
297
297
|
const file = formData.get("file") as File
|
|
298
298
|
|
|
299
299
|
if (!file) {
|
|
300
|
-
throw new Error("
|
|
300
|
+
throw new Error("파일이 없습니다")
|
|
301
301
|
}
|
|
302
302
|
|
|
303
303
|
const bytes = await file.arrayBuffer()
|
|
@@ -312,17 +312,17 @@ export async function uploadFile(formData: FormData) {
|
|
|
312
312
|
|
|
313
313
|
---
|
|
314
314
|
|
|
315
|
-
##
|
|
315
|
+
## 베스트 프랙티스
|
|
316
316
|
|
|
317
317
|
### ✅ DO
|
|
318
318
|
|
|
319
319
|
```typescript
|
|
320
320
|
"use server"
|
|
321
321
|
|
|
322
|
-
// 1. Zod
|
|
322
|
+
// 1. Zod 검증
|
|
323
323
|
const schema = z.object({ title: z.string().min(1) })
|
|
324
324
|
|
|
325
|
-
// 2.
|
|
325
|
+
// 2. 인증 체크
|
|
326
326
|
const session = await auth()
|
|
327
327
|
if (!session) throw new Error("Unauthorized")
|
|
328
328
|
|
|
@@ -332,35 +332,35 @@ try {
|
|
|
332
332
|
revalidatePath("/posts")
|
|
333
333
|
return { success: true, post }
|
|
334
334
|
} catch (error) {
|
|
335
|
-
return { success: false, message: "
|
|
335
|
+
return { success: false, message: "생성 실패" }
|
|
336
336
|
}
|
|
337
337
|
```
|
|
338
338
|
|
|
339
339
|
### ❌ DON'T
|
|
340
340
|
|
|
341
341
|
```typescript
|
|
342
|
-
// 1.
|
|
342
|
+
// 1. 클라이언트 컴포넌트에서 Server Action 정의
|
|
343
343
|
"use client"
|
|
344
344
|
|
|
345
345
|
async function createPost() {
|
|
346
|
-
"use server" // ❌
|
|
346
|
+
"use server" // ❌ 에러
|
|
347
347
|
}
|
|
348
348
|
|
|
349
|
-
// 2.
|
|
349
|
+
// 2. 검증 없이 사용
|
|
350
350
|
export async function createPost(formData: FormData) {
|
|
351
|
-
const title = formData.get("title") // ❌
|
|
351
|
+
const title = formData.get("title") // ❌ 검증 누락
|
|
352
352
|
await prisma.post.create({ data: { title } })
|
|
353
353
|
}
|
|
354
354
|
|
|
355
|
-
// 3.
|
|
355
|
+
// 3. try-catch 없이 사용
|
|
356
356
|
export async function createPost(data: PostInput) {
|
|
357
|
-
const post = await prisma.post.create({ data }) // ❌
|
|
357
|
+
const post = await prisma.post.create({ data }) // ❌ 에러 처리 누락
|
|
358
358
|
return post
|
|
359
359
|
}
|
|
360
360
|
```
|
|
361
361
|
|
|
362
362
|
---
|
|
363
363
|
|
|
364
|
-
##
|
|
364
|
+
## 참조
|
|
365
365
|
|
|
366
366
|
- [Next.js Server Actions](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations)
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
# Prisma - Cloudflare D1
|
|
2
2
|
|
|
3
|
-
SQLite
|
|
3
|
+
SQLite 기반 서버리스 DB. 일반 Prisma 마이그레이션과 다른 워크플로우.
|
|
4
4
|
|
|
5
|
-
⚠️
|
|
5
|
+
⚠️ 트랜잭션 미지원 | prisma migrate 불가 - wrangler 사용 | Preview 상태
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## 설정
|
|
8
8
|
|
|
9
9
|
```prisma
|
|
10
10
|
// schema.prisma
|
|
11
11
|
generator client {
|
|
12
12
|
provider = "prisma-client"
|
|
13
13
|
output = "../src/generated/prisma"
|
|
14
|
-
runtime = "cloudflare" //
|
|
14
|
+
runtime = "cloudflare" // 필수
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
datasource db {
|
|
@@ -26,7 +26,7 @@ datasource db {
|
|
|
26
26
|
}
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
##
|
|
29
|
+
## 사용법
|
|
30
30
|
|
|
31
31
|
```typescript
|
|
32
32
|
import { PrismaClient } from './generated/prisma'
|
|
@@ -44,33 +44,33 @@ export default {
|
|
|
44
44
|
}
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
-
##
|
|
47
|
+
## 마이그레이션 워크플로우
|
|
48
48
|
|
|
49
49
|
```bash
|
|
50
|
-
# 1.
|
|
50
|
+
# 1. D1 생성
|
|
51
51
|
npx wrangler d1 create my-database
|
|
52
52
|
|
|
53
|
-
# 2.
|
|
53
|
+
# 2. 마이그레이션 생성
|
|
54
54
|
npx wrangler d1 migrations create my-database init
|
|
55
55
|
|
|
56
|
-
# 3.
|
|
56
|
+
# 3. SQL 생성 (초기)
|
|
57
57
|
npx prisma migrate diff --from-empty --to-schema-datamodel prisma/schema.prisma --script --output prisma/migrations/0001.sql
|
|
58
58
|
|
|
59
|
-
# 4.
|
|
59
|
+
# 4. SQL 생성 (후속)
|
|
60
60
|
npx prisma migrate diff --from-local-d1 --to-schema-datamodel prisma/schema.prisma --script
|
|
61
61
|
|
|
62
|
-
# 5.
|
|
63
|
-
npx wrangler d1 migrations apply my-database --local #
|
|
64
|
-
npx wrangler d1 migrations apply my-database --remote #
|
|
62
|
+
# 5. 적용
|
|
63
|
+
npx wrangler d1 migrations apply my-database --local # 로컬
|
|
64
|
+
npx wrangler d1 migrations apply my-database --remote # 프로덕션
|
|
65
65
|
|
|
66
|
-
# 6.
|
|
66
|
+
# 6. Client 생성
|
|
67
67
|
npx prisma generate
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
-
##
|
|
70
|
+
## 제한사항
|
|
71
71
|
|
|
72
|
-
|
|
|
73
|
-
|
|
74
|
-
|
|
|
75
|
-
|
|
|
76
|
-
|
|
|
72
|
+
| 항목 | 일반 SQLite | D1 |
|
|
73
|
+
|------|-------------|-----|
|
|
74
|
+
| 마이그레이션 | prisma migrate | wrangler d1 |
|
|
75
|
+
| 트랜잭션 | ✅ | ❌ |
|
|
76
|
+
| 접속 | 직접 | HTTP 어댑터 |
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
# Prisma - Config
|
|
1
|
+
# Prisma - Config 파일
|
|
2
2
|
|
|
3
|
-
Prisma v7 `prisma.config.ts`
|
|
3
|
+
Prisma v7 `prisma.config.ts` 설정.
|
|
4
4
|
|
|
5
|
-
## Multi-File
|
|
5
|
+
## Multi-File 스키마 (필수)
|
|
6
6
|
|
|
7
7
|
```typescript
|
|
8
8
|
// prisma.config.ts
|
|
@@ -11,7 +11,7 @@ import path from 'node:path'
|
|
|
11
11
|
import { defineConfig, env } from 'prisma/config'
|
|
12
12
|
|
|
13
13
|
export default defineConfig({
|
|
14
|
-
schema: path.join('prisma', 'schema'), //
|
|
14
|
+
schema: path.join('prisma', 'schema'), // 폴더 경로!
|
|
15
15
|
migrations: {
|
|
16
16
|
path: 'prisma/migrations',
|
|
17
17
|
seed: 'tsx prisma/seed.ts',
|
|
@@ -22,16 +22,16 @@ export default defineConfig({
|
|
|
22
22
|
})
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
##
|
|
25
|
+
## 폴더 구조
|
|
26
26
|
|
|
27
27
|
```
|
|
28
|
-
|
|
28
|
+
프로젝트/
|
|
29
29
|
├── prisma.config.ts
|
|
30
30
|
├── prisma/
|
|
31
31
|
│ ├── schema/
|
|
32
32
|
│ │ ├── +base.prisma # datasource, generator
|
|
33
|
-
│ │ ├── +enum.prisma # enum
|
|
34
|
-
│ │ └── user.prisma #
|
|
33
|
+
│ │ ├── +enum.prisma # enum 정의
|
|
34
|
+
│ │ └── user.prisma # 모델
|
|
35
35
|
│ └── migrations/
|
|
36
36
|
```
|
|
37
37
|
|
|
@@ -49,17 +49,17 @@ generator client {
|
|
|
49
49
|
}
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
-
##
|
|
52
|
+
## 설정 옵션
|
|
53
53
|
|
|
54
|
-
|
|
|
55
|
-
|
|
56
|
-
| `schema` |
|
|
57
|
-
| `datasource.url` |
|
|
58
|
-
| `datasource.shadowDatabaseUrl` | Shadow
|
|
59
|
-
| `migrations.path` |
|
|
60
|
-
| `migrations.seed` |
|
|
54
|
+
| 옵션 | 설명 |
|
|
55
|
+
|------|------|
|
|
56
|
+
| `schema` | 스키마 폴더 경로 |
|
|
57
|
+
| `datasource.url` | DB URL (필수) |
|
|
58
|
+
| `datasource.shadowDatabaseUrl` | Shadow DB URL |
|
|
59
|
+
| `migrations.path` | 마이그레이션 폴더 |
|
|
60
|
+
| `migrations.seed` | 시드 명령어 |
|
|
61
61
|
|
|
62
|
-
##
|
|
62
|
+
## 시드 파일
|
|
63
63
|
|
|
64
64
|
```typescript
|
|
65
65
|
// prisma/seed.ts
|
|
@@ -69,7 +69,7 @@ const prisma = new PrismaClient()
|
|
|
69
69
|
|
|
70
70
|
async function main() {
|
|
71
71
|
await prisma.user.create({
|
|
72
|
-
data: { email: 'admin@example.com', name: '
|
|
72
|
+
data: { email: 'admin@example.com', name: '관리자', role: 'ADMIN' },
|
|
73
73
|
})
|
|
74
74
|
}
|
|
75
75
|
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
# Prisma - CRUD
|
|
1
|
+
# Prisma - CRUD 작업
|
|
2
2
|
|
|
3
3
|
## Create
|
|
4
4
|
|
|
5
5
|
```typescript
|
|
6
|
-
//
|
|
6
|
+
// 단일
|
|
7
7
|
const user = await prisma.user.create({
|
|
8
8
|
data: { email: 'alice@prisma.io', name: 'Alice' },
|
|
9
9
|
})
|
|
10
10
|
|
|
11
|
-
//
|
|
11
|
+
// 관계 포함
|
|
12
12
|
const user = await prisma.user.create({
|
|
13
13
|
data: {
|
|
14
14
|
email: 'bob@prisma.io',
|
|
@@ -27,22 +27,22 @@ posts: { create: [{
|
|
|
27
27
|
## Read
|
|
28
28
|
|
|
29
29
|
```typescript
|
|
30
|
-
//
|
|
30
|
+
// 단일
|
|
31
31
|
const user = await prisma.user.findUnique({ where: { email } })
|
|
32
32
|
|
|
33
|
-
//
|
|
33
|
+
// 다중
|
|
34
34
|
const users = await prisma.user.findMany({ where: { name: 'Alice' } })
|
|
35
35
|
|
|
36
|
-
//
|
|
36
|
+
// 관계 포함
|
|
37
37
|
const users = await prisma.user.findMany({ where: { role: 'ADMIN' }, include: { posts: true } })
|
|
38
38
|
|
|
39
|
-
//
|
|
39
|
+
// 필드 선택
|
|
40
40
|
const user = await prisma.user.findUnique({
|
|
41
41
|
where: { email },
|
|
42
42
|
select: { email: true, posts: { select: { title: true } } },
|
|
43
43
|
})
|
|
44
44
|
|
|
45
|
-
//
|
|
45
|
+
// 관계로 필터
|
|
46
46
|
const users = await prisma.user.findMany({
|
|
47
47
|
where: { posts: { some: { published: false } } },
|
|
48
48
|
})
|
|
@@ -51,10 +51,10 @@ const users = await prisma.user.findMany({
|
|
|
51
51
|
## Update
|
|
52
52
|
|
|
53
53
|
```typescript
|
|
54
|
-
//
|
|
54
|
+
// 단일
|
|
55
55
|
const user = await prisma.user.update({ where: { id }, data: { name: 'Updated' } })
|
|
56
56
|
|
|
57
|
-
//
|
|
57
|
+
// 다중
|
|
58
58
|
await prisma.user.updateMany({ where: { role: 'USER' }, data: { role: 'ADMIN' } })
|
|
59
59
|
|
|
60
60
|
// Upsert
|
|
@@ -69,22 +69,22 @@ const user = await prisma.user.upsert({
|
|
|
69
69
|
|
|
70
70
|
```typescript
|
|
71
71
|
await prisma.user.delete({ where: { id } })
|
|
72
|
-
await prisma.user.deleteMany({}) //
|
|
73
|
-
await prisma.post.deleteMany({ where: { published: false } }) //
|
|
72
|
+
await prisma.user.deleteMany({}) // 전체
|
|
73
|
+
await prisma.post.deleteMany({ where: { published: false } }) // 조건부
|
|
74
74
|
```
|
|
75
75
|
|
|
76
|
-
##
|
|
76
|
+
## 필터 연산자
|
|
77
77
|
|
|
78
78
|
```typescript
|
|
79
|
-
//
|
|
79
|
+
// 문자열
|
|
80
80
|
{ contains: 'prisma', startsWith: 'A', endsWith: 'io' }
|
|
81
81
|
|
|
82
|
-
//
|
|
82
|
+
// 숫자
|
|
83
83
|
{ gt: 18, gte: 18, lt: 65, lte: 65 }
|
|
84
84
|
|
|
85
|
-
//
|
|
85
|
+
// 배열
|
|
86
86
|
{ in: [1, 2, 3], notIn: [4, 5] }
|
|
87
87
|
|
|
88
|
-
//
|
|
88
|
+
// 논리
|
|
89
89
|
{ OR: [...], AND: [...], NOT: {...} }
|
|
90
90
|
```
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
## Quick Reference
|
|
16
16
|
|
|
17
17
|
```typescript
|
|
18
|
-
import { PrismaClient } from './generated/prisma' // v7
|
|
18
|
+
import { PrismaClient } from './generated/prisma' // v7 경로!
|
|
19
19
|
export const prisma = new PrismaClient()
|
|
20
20
|
|
|
21
21
|
// CRUD
|
|
@@ -24,34 +24,34 @@ const user = await prisma.user.create({ data: { email, name } })
|
|
|
24
24
|
const updated = await prisma.user.update({ where: { id }, data: { name } })
|
|
25
25
|
const deleted = await prisma.user.delete({ where: { id } })
|
|
26
26
|
|
|
27
|
-
//
|
|
27
|
+
// 관계 포함
|
|
28
28
|
const userWithPosts = await prisma.user.findUnique({
|
|
29
29
|
where: { id },
|
|
30
30
|
include: { posts: true },
|
|
31
31
|
})
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
### v7 schema.prisma (⚠️
|
|
34
|
+
### v7 schema.prisma (⚠️ 중요)
|
|
35
35
|
|
|
36
36
|
```prisma
|
|
37
37
|
generator client {
|
|
38
|
-
provider = "prisma-client" // v7! (
|
|
39
|
-
output = "../generated/prisma" // output
|
|
38
|
+
provider = "prisma-client" // v7! (prisma-client-js 아님)
|
|
39
|
+
output = "../generated/prisma" // output 필수!
|
|
40
40
|
}
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
### ⛔ Claude Code
|
|
43
|
+
### ⛔ Claude Code 금지
|
|
44
44
|
|
|
45
|
-
|
|
|
46
|
-
|
|
47
|
-
|
|
|
48
|
-
|
|
|
49
|
-
|
|
|
50
|
-
|
|
|
45
|
+
| 금지 사항 |
|
|
46
|
+
|----------|
|
|
47
|
+
| prisma db push 자동 실행 |
|
|
48
|
+
| prisma migrate 자동 실행 |
|
|
49
|
+
| prisma generate 자동 실행 |
|
|
50
|
+
| schema.prisma 임의 변경 |
|
|
51
51
|
|
|
52
52
|
---
|
|
53
53
|
|
|
54
|
-
## Prisma Client
|
|
54
|
+
## Prisma Client 설정
|
|
55
55
|
|
|
56
56
|
```typescript
|
|
57
57
|
// lib/prisma.ts
|
|
@@ -62,12 +62,12 @@ export const prisma = globalForPrisma.prisma ?? new PrismaClient({ log: ['query'
|
|
|
62
62
|
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
-
##
|
|
65
|
+
## 마이그레이션 명령어
|
|
66
66
|
|
|
67
67
|
```bash
|
|
68
|
-
npx prisma migrate dev --name init #
|
|
69
|
-
npx prisma migrate deploy #
|
|
70
|
-
npx prisma db push #
|
|
71
|
-
npx prisma generate #
|
|
68
|
+
npx prisma migrate dev --name init # 개발 마이그레이션
|
|
69
|
+
npx prisma migrate deploy # 프로덕션 마이그레이션
|
|
70
|
+
npx prisma db push # 스키마 동기화 (개발용)
|
|
71
|
+
npx prisma generate # Client 생성
|
|
72
72
|
npx prisma studio # GUI
|
|
73
73
|
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# Prisma -
|
|
1
|
+
# Prisma - 관계 쿼리
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 중첩 생성
|
|
4
4
|
|
|
5
5
|
```typescript
|
|
6
6
|
const user = await prisma.user.create({
|
|
@@ -12,53 +12,53 @@ const user = await prisma.user.create({
|
|
|
12
12
|
})
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
##
|
|
15
|
+
## 관계 연결
|
|
16
16
|
|
|
17
17
|
```typescript
|
|
18
|
-
// connect -
|
|
18
|
+
// connect - 기존 연결
|
|
19
19
|
author: { connect: { id: 1 } }
|
|
20
20
|
|
|
21
|
-
// connectOrCreate -
|
|
21
|
+
// connectOrCreate - 있으면 연결, 없으면 생성
|
|
22
22
|
categories: { connectOrCreate: { where: { name: 'Tech' }, create: { name: 'Tech' } } }
|
|
23
23
|
|
|
24
|
-
// disconnect -
|
|
24
|
+
// disconnect - 관계 해제
|
|
25
25
|
author: { disconnect: true }
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
##
|
|
28
|
+
## 관계 포함 조회
|
|
29
29
|
|
|
30
30
|
```typescript
|
|
31
31
|
// include
|
|
32
32
|
const users = await prisma.user.findMany({ include: { posts: true, profile: true } })
|
|
33
33
|
|
|
34
|
-
//
|
|
34
|
+
// 중첩
|
|
35
35
|
include: { posts: { include: { categories: true } } }
|
|
36
36
|
|
|
37
|
-
//
|
|
37
|
+
// 필터 + 정렬
|
|
38
38
|
include: { posts: { where: { published: true }, orderBy: { createdAt: 'desc' }, take: 5 } }
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
-
##
|
|
41
|
+
## 관계로 필터링
|
|
42
42
|
|
|
43
43
|
```typescript
|
|
44
|
-
// some -
|
|
44
|
+
// some - 하나라도 만족
|
|
45
45
|
where: { posts: { some: { published: true } } }
|
|
46
46
|
|
|
47
|
-
// every -
|
|
47
|
+
// every - 모두 만족
|
|
48
48
|
where: { posts: { every: { published: true } } }
|
|
49
49
|
|
|
50
|
-
// none -
|
|
50
|
+
// none - 만족 없음
|
|
51
51
|
where: { posts: { none: { published: false } } }
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
-
##
|
|
54
|
+
## 카운트
|
|
55
55
|
|
|
56
56
|
```typescript
|
|
57
57
|
include: { _count: { select: { posts: true } } }
|
|
58
|
-
//
|
|
58
|
+
// 결과: { _count: { posts: 5 } }
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
-
##
|
|
61
|
+
## 중첩 수정/삭제
|
|
62
62
|
|
|
63
63
|
```typescript
|
|
64
64
|
// updateMany
|