@kood/claude-code 0.3.5 → 0.3.7
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 +1 -1
- package/package.json +1 -1
- package/templates/nextjs/CLAUDE.md +228 -0
- package/templates/nextjs/docs/design.md +558 -0
- package/templates/nextjs/docs/guides/conventions.md +343 -0
- package/templates/nextjs/docs/guides/getting-started.md +367 -0
- package/templates/nextjs/docs/guides/routes.md +342 -0
- package/templates/nextjs/docs/library/better-auth/index.md +541 -0
- package/templates/nextjs/docs/library/nextjs/app-router.md +269 -0
- package/templates/nextjs/docs/library/nextjs/caching.md +351 -0
- package/templates/nextjs/docs/library/nextjs/index.md +291 -0
- package/templates/nextjs/docs/library/nextjs/middleware.md +391 -0
- package/templates/nextjs/docs/library/nextjs/route-handlers.md +382 -0
- package/templates/nextjs/docs/library/nextjs/server-actions.md +366 -0
- package/templates/nextjs/docs/library/prisma/cloudflare-d1.md +76 -0
- package/templates/nextjs/docs/library/prisma/config.md +77 -0
- package/templates/nextjs/docs/library/prisma/crud.md +90 -0
- package/templates/nextjs/docs/library/prisma/index.md +73 -0
- package/templates/nextjs/docs/library/prisma/relations.md +69 -0
- package/templates/nextjs/docs/library/prisma/schema.md +98 -0
- package/templates/nextjs/docs/library/prisma/setup.md +49 -0
- package/templates/nextjs/docs/library/prisma/transactions.md +50 -0
- package/templates/nextjs/docs/library/tanstack-query/index.md +66 -0
- package/templates/nextjs/docs/library/tanstack-query/invalidation.md +54 -0
- package/templates/nextjs/docs/library/tanstack-query/optimistic-updates.md +77 -0
- package/templates/nextjs/docs/library/tanstack-query/use-mutation.md +63 -0
- package/templates/nextjs/docs/library/tanstack-query/use-query.md +70 -0
- package/templates/nextjs/docs/library/zod/complex-types.md +61 -0
- package/templates/nextjs/docs/library/zod/index.md +56 -0
- package/templates/nextjs/docs/library/zod/transforms.md +51 -0
- package/templates/nextjs/docs/library/zod/validation.md +70 -0
- package/templates/tanstack-start/CLAUDE.md +7 -3
- package/templates/tanstack-start/docs/architecture.md +38 -7
- package/templates/tanstack-start/docs/guides/hooks.md +28 -0
- package/templates/tanstack-start/docs/guides/routes.md +29 -10
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
# Server Actions
|
|
2
|
+
|
|
3
|
+
> 타입 안전한 서버 함수 (React 19 Server Actions)
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 기본 사용법
|
|
8
|
+
|
|
9
|
+
### 파일 상단 선언
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
// app/actions.ts
|
|
13
|
+
"use server"
|
|
14
|
+
|
|
15
|
+
import { z } from "zod"
|
|
16
|
+
import { revalidatePath } from "next/cache"
|
|
17
|
+
|
|
18
|
+
export async function createPost(formData: FormData) {
|
|
19
|
+
const title = formData.get("title") as string
|
|
20
|
+
const content = formData.get("content") as string
|
|
21
|
+
|
|
22
|
+
const post = await prisma.post.create({
|
|
23
|
+
data: { title, content },
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
revalidatePath("/posts")
|
|
27
|
+
return post
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### 인라인 선언
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
// app/posts/page.tsx
|
|
35
|
+
export default function PostsPage() {
|
|
36
|
+
async function createPost(formData: FormData) {
|
|
37
|
+
"use server"
|
|
38
|
+
|
|
39
|
+
const title = formData.get("title") as string
|
|
40
|
+
await prisma.post.create({ data: { title } })
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return <form action={createPost}>...</form>
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Zod 검증
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
"use server"
|
|
53
|
+
|
|
54
|
+
import { z } from "zod"
|
|
55
|
+
|
|
56
|
+
const createPostSchema = z.object({
|
|
57
|
+
title: z.string().min(1).max(100),
|
|
58
|
+
content: z.string().min(1),
|
|
59
|
+
published: z.boolean().default(false),
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
export async function createPost(formData: FormData) {
|
|
63
|
+
const parsed = createPostSchema.parse({
|
|
64
|
+
title: formData.get("title"),
|
|
65
|
+
content: formData.get("content"),
|
|
66
|
+
published: formData.get("published") === "on",
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
const post = await prisma.post.create({ data: parsed })
|
|
70
|
+
revalidatePath("/posts")
|
|
71
|
+
return post
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## 인증
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
"use server"
|
|
81
|
+
|
|
82
|
+
import { auth } from "@/lib/auth"
|
|
83
|
+
import { redirect } from "next/navigation"
|
|
84
|
+
|
|
85
|
+
export async function deletePost(id: string) {
|
|
86
|
+
const session = await auth()
|
|
87
|
+
|
|
88
|
+
if (!session?.user) {
|
|
89
|
+
redirect("/login")
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
await prisma.post.delete({
|
|
93
|
+
where: {
|
|
94
|
+
id,
|
|
95
|
+
userId: session.user.id, // 본인 게시물만 삭제
|
|
96
|
+
},
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
revalidatePath("/posts")
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## 에러 처리
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
"use server"
|
|
109
|
+
|
|
110
|
+
export async function updatePost(id: string, data: PostInput) {
|
|
111
|
+
try {
|
|
112
|
+
const post = await prisma.post.update({ where: { id }, data })
|
|
113
|
+
revalidatePath(`/posts/${id}`)
|
|
114
|
+
return { success: true, post }
|
|
115
|
+
} catch (error) {
|
|
116
|
+
if (error instanceof z.ZodError) {
|
|
117
|
+
return { success: false, errors: error.errors }
|
|
118
|
+
}
|
|
119
|
+
return { success: false, message: "업데이트 실패" }
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## 클라이언트에서 사용
|
|
127
|
+
|
|
128
|
+
### Form Action
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
"use client"
|
|
132
|
+
|
|
133
|
+
import { createPost } from "@/actions/posts"
|
|
134
|
+
|
|
135
|
+
export function CreatePostForm() {
|
|
136
|
+
return (
|
|
137
|
+
<form action={createPost}>
|
|
138
|
+
<input name="title" required />
|
|
139
|
+
<textarea name="content" required />
|
|
140
|
+
<button type="submit">등록</button>
|
|
141
|
+
</form>
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### TanStack Query
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
"use client"
|
|
150
|
+
|
|
151
|
+
import { useMutation, useQueryClient } from "@tanstack/react-query"
|
|
152
|
+
import { createPost } from "@/actions/posts"
|
|
153
|
+
|
|
154
|
+
export function CreatePostForm() {
|
|
155
|
+
const queryClient = useQueryClient()
|
|
156
|
+
|
|
157
|
+
const mutation = useMutation({
|
|
158
|
+
mutationFn: async (formData: FormData) => {
|
|
159
|
+
return createPost(formData)
|
|
160
|
+
},
|
|
161
|
+
onSuccess: () => {
|
|
162
|
+
queryClient.invalidateQueries({ queryKey: ["posts"] })
|
|
163
|
+
},
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
return (
|
|
167
|
+
<form
|
|
168
|
+
onSubmit={(e) => {
|
|
169
|
+
e.preventDefault()
|
|
170
|
+
const formData = new FormData(e.currentTarget)
|
|
171
|
+
mutation.mutate(formData)
|
|
172
|
+
}}
|
|
173
|
+
>
|
|
174
|
+
<input name="title" required />
|
|
175
|
+
<button type="submit" disabled={mutation.isPending}>
|
|
176
|
+
{mutation.isPending ? "등록 중..." : "등록"}
|
|
177
|
+
</button>
|
|
178
|
+
</form>
|
|
179
|
+
)
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## useFormState (React 19)
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
"use server"
|
|
189
|
+
|
|
190
|
+
export async function createPost(prevState: any, formData: FormData) {
|
|
191
|
+
const title = formData.get("title") as string
|
|
192
|
+
|
|
193
|
+
if (!title) {
|
|
194
|
+
return { error: "제목을 입력하세요" }
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const post = await prisma.post.create({ data: { title } })
|
|
198
|
+
return { success: true, post }
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
"use client"
|
|
204
|
+
|
|
205
|
+
import { useFormState } from "react-dom"
|
|
206
|
+
import { createPost } from "@/actions/posts"
|
|
207
|
+
|
|
208
|
+
export function CreatePostForm() {
|
|
209
|
+
const [state, formAction] = useFormState(createPost, null)
|
|
210
|
+
|
|
211
|
+
return (
|
|
212
|
+
<form action={formAction}>
|
|
213
|
+
<input name="title" />
|
|
214
|
+
{state?.error && <p>{state.error}</p>}
|
|
215
|
+
<button type="submit">등록</button>
|
|
216
|
+
</form>
|
|
217
|
+
)
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## 캐시 무효화
|
|
224
|
+
|
|
225
|
+
### revalidatePath
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
"use server"
|
|
229
|
+
|
|
230
|
+
import { revalidatePath } from "next/cache"
|
|
231
|
+
|
|
232
|
+
export async function createPost(data: PostInput) {
|
|
233
|
+
const post = await prisma.post.create({ data })
|
|
234
|
+
|
|
235
|
+
// 특정 경로 캐시 무효화
|
|
236
|
+
revalidatePath("/posts")
|
|
237
|
+
revalidatePath(`/posts/${post.id}`)
|
|
238
|
+
|
|
239
|
+
// 레이아웃 포함 모든 캐시 무효화
|
|
240
|
+
revalidatePath("/posts", "layout")
|
|
241
|
+
|
|
242
|
+
return post
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### revalidateTag
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
// 데이터 페칭 시 태그 설정
|
|
250
|
+
const posts = await fetch("https://api.example.com/posts", {
|
|
251
|
+
next: { tags: ["posts"] },
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
// Server Action에서 태그 무효화
|
|
255
|
+
"use server"
|
|
256
|
+
|
|
257
|
+
import { revalidateTag } from "next/cache"
|
|
258
|
+
|
|
259
|
+
export async function createPost(data: PostInput) {
|
|
260
|
+
const post = await prisma.post.create({ data })
|
|
261
|
+
revalidateTag("posts") // "posts" 태그 캐시 무효화
|
|
262
|
+
return post
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## redirect
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
"use server"
|
|
272
|
+
|
|
273
|
+
import { redirect } from "next/navigation"
|
|
274
|
+
|
|
275
|
+
export async function createPost(formData: FormData) {
|
|
276
|
+
const post = await prisma.post.create({
|
|
277
|
+
data: {
|
|
278
|
+
title: formData.get("title") as string,
|
|
279
|
+
},
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
redirect(`/posts/${post.id}`) // 페이지 이동
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
## 파일 업로드
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
"use server"
|
|
292
|
+
|
|
293
|
+
import { writeFile } from "fs/promises"
|
|
294
|
+
import { join } from "path"
|
|
295
|
+
|
|
296
|
+
export async function uploadFile(formData: FormData) {
|
|
297
|
+
const file = formData.get("file") as File
|
|
298
|
+
|
|
299
|
+
if (!file) {
|
|
300
|
+
throw new Error("파일이 없습니다")
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const bytes = await file.arrayBuffer()
|
|
304
|
+
const buffer = Buffer.from(bytes)
|
|
305
|
+
|
|
306
|
+
const path = join(process.cwd(), "public", "uploads", file.name)
|
|
307
|
+
await writeFile(path, buffer)
|
|
308
|
+
|
|
309
|
+
return { url: `/uploads/${file.name}` }
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## 베스트 프랙티스
|
|
316
|
+
|
|
317
|
+
### ✅ DO
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
"use server"
|
|
321
|
+
|
|
322
|
+
// 1. Zod 검증
|
|
323
|
+
const schema = z.object({ title: z.string().min(1) })
|
|
324
|
+
|
|
325
|
+
// 2. 인증 체크
|
|
326
|
+
const session = await auth()
|
|
327
|
+
if (!session) throw new Error("Unauthorized")
|
|
328
|
+
|
|
329
|
+
// 3. try-catch
|
|
330
|
+
try {
|
|
331
|
+
const post = await prisma.post.create({ data })
|
|
332
|
+
revalidatePath("/posts")
|
|
333
|
+
return { success: true, post }
|
|
334
|
+
} catch (error) {
|
|
335
|
+
return { success: false, message: "생성 실패" }
|
|
336
|
+
}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
### ❌ DON'T
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
// 1. 클라이언트 컴포넌트에서 Server Action 정의
|
|
343
|
+
"use client"
|
|
344
|
+
|
|
345
|
+
async function createPost() {
|
|
346
|
+
"use server" // ❌ 에러
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// 2. 검증 없이 사용
|
|
350
|
+
export async function createPost(formData: FormData) {
|
|
351
|
+
const title = formData.get("title") // ❌ 검증 누락
|
|
352
|
+
await prisma.post.create({ data: { title } })
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// 3. try-catch 없이 사용
|
|
356
|
+
export async function createPost(data: PostInput) {
|
|
357
|
+
const post = await prisma.post.create({ data }) // ❌ 에러 처리 누락
|
|
358
|
+
return post
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
---
|
|
363
|
+
|
|
364
|
+
## 참조
|
|
365
|
+
|
|
366
|
+
- [Next.js Server Actions](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Prisma - Cloudflare D1
|
|
2
|
+
|
|
3
|
+
SQLite 기반 서버리스 DB. 일반 Prisma 마이그레이션과 다른 워크플로우.
|
|
4
|
+
|
|
5
|
+
⚠️ 트랜잭션 미지원 | prisma migrate 불가 - wrangler 사용 | Preview 상태
|
|
6
|
+
|
|
7
|
+
## 설정
|
|
8
|
+
|
|
9
|
+
```prisma
|
|
10
|
+
// schema.prisma
|
|
11
|
+
generator client {
|
|
12
|
+
provider = "prisma-client"
|
|
13
|
+
output = "../src/generated/prisma"
|
|
14
|
+
runtime = "cloudflare" // 필수
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
datasource db {
|
|
18
|
+
provider = "sqlite"
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
```jsonc
|
|
23
|
+
// wrangler.jsonc
|
|
24
|
+
{
|
|
25
|
+
"d1_databases": [{ "binding": "DB", "database_name": "my-db", "database_id": "ID" }]
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## 사용법
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { PrismaClient } from './generated/prisma'
|
|
33
|
+
import { PrismaD1 } from '@prisma/adapter-d1'
|
|
34
|
+
|
|
35
|
+
export interface Env { DB: D1Database }
|
|
36
|
+
|
|
37
|
+
export default {
|
|
38
|
+
async fetch(request: Request, env: Env): Promise<Response> {
|
|
39
|
+
const adapter = new PrismaD1(env.DB)
|
|
40
|
+
const prisma = new PrismaClient({ adapter })
|
|
41
|
+
const users = await prisma.user.findMany()
|
|
42
|
+
return new Response(JSON.stringify(users))
|
|
43
|
+
},
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## 마이그레이션 워크플로우
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
# 1. D1 생성
|
|
51
|
+
npx wrangler d1 create my-database
|
|
52
|
+
|
|
53
|
+
# 2. 마이그레이션 생성
|
|
54
|
+
npx wrangler d1 migrations create my-database init
|
|
55
|
+
|
|
56
|
+
# 3. SQL 생성 (초기)
|
|
57
|
+
npx prisma migrate diff --from-empty --to-schema-datamodel prisma/schema.prisma --script --output prisma/migrations/0001.sql
|
|
58
|
+
|
|
59
|
+
# 4. SQL 생성 (후속)
|
|
60
|
+
npx prisma migrate diff --from-local-d1 --to-schema-datamodel prisma/schema.prisma --script
|
|
61
|
+
|
|
62
|
+
# 5. 적용
|
|
63
|
+
npx wrangler d1 migrations apply my-database --local # 로컬
|
|
64
|
+
npx wrangler d1 migrations apply my-database --remote # 프로덕션
|
|
65
|
+
|
|
66
|
+
# 6. Client 생성
|
|
67
|
+
npx prisma generate
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## 제한사항
|
|
71
|
+
|
|
72
|
+
| 항목 | 일반 SQLite | D1 |
|
|
73
|
+
|------|-------------|-----|
|
|
74
|
+
| 마이그레이션 | prisma migrate | wrangler d1 |
|
|
75
|
+
| 트랜잭션 | ✅ | ❌ |
|
|
76
|
+
| 접속 | 직접 | HTTP 어댑터 |
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Prisma - Config 파일
|
|
2
|
+
|
|
3
|
+
Prisma v7 `prisma.config.ts` 설정.
|
|
4
|
+
|
|
5
|
+
## Multi-File 스키마 (필수)
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
// prisma.config.ts
|
|
9
|
+
import 'dotenv/config'
|
|
10
|
+
import path from 'node:path'
|
|
11
|
+
import { defineConfig, env } from 'prisma/config'
|
|
12
|
+
|
|
13
|
+
export default defineConfig({
|
|
14
|
+
schema: path.join('prisma', 'schema'), // 폴더 경로!
|
|
15
|
+
migrations: {
|
|
16
|
+
path: 'prisma/migrations',
|
|
17
|
+
seed: 'tsx prisma/seed.ts',
|
|
18
|
+
},
|
|
19
|
+
datasource: {
|
|
20
|
+
url: env('DATABASE_URL'),
|
|
21
|
+
},
|
|
22
|
+
})
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## 폴더 구조
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
프로젝트/
|
|
29
|
+
├── prisma.config.ts
|
|
30
|
+
├── prisma/
|
|
31
|
+
│ ├── schema/
|
|
32
|
+
│ │ ├── +base.prisma # datasource, generator
|
|
33
|
+
│ │ ├── +enum.prisma # enum 정의
|
|
34
|
+
│ │ └── user.prisma # 모델
|
|
35
|
+
│ └── migrations/
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## +base.prisma
|
|
39
|
+
|
|
40
|
+
```prisma
|
|
41
|
+
datasource db {
|
|
42
|
+
provider = "postgresql"
|
|
43
|
+
url = env("DATABASE_URL")
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
generator client {
|
|
47
|
+
provider = "prisma-client"
|
|
48
|
+
output = "../../generated/prisma"
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## 설정 옵션
|
|
53
|
+
|
|
54
|
+
| 옵션 | 설명 |
|
|
55
|
+
|------|------|
|
|
56
|
+
| `schema` | 스키마 폴더 경로 |
|
|
57
|
+
| `datasource.url` | DB URL (필수) |
|
|
58
|
+
| `datasource.shadowDatabaseUrl` | Shadow DB URL |
|
|
59
|
+
| `migrations.path` | 마이그레이션 폴더 |
|
|
60
|
+
| `migrations.seed` | 시드 명령어 |
|
|
61
|
+
|
|
62
|
+
## 시드 파일
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
// prisma/seed.ts
|
|
66
|
+
import { PrismaClient } from '../generated/prisma'
|
|
67
|
+
|
|
68
|
+
const prisma = new PrismaClient()
|
|
69
|
+
|
|
70
|
+
async function main() {
|
|
71
|
+
await prisma.user.create({
|
|
72
|
+
data: { email: 'admin@example.com', name: '관리자', role: 'ADMIN' },
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
main().catch(console.error).finally(() => prisma.$disconnect())
|
|
77
|
+
```
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Prisma - CRUD 작업
|
|
2
|
+
|
|
3
|
+
## Create
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
// 단일
|
|
7
|
+
const user = await prisma.user.create({
|
|
8
|
+
data: { email: 'alice@prisma.io', name: 'Alice' },
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
// 관계 포함
|
|
12
|
+
const user = await prisma.user.create({
|
|
13
|
+
data: {
|
|
14
|
+
email: 'bob@prisma.io',
|
|
15
|
+
posts: { create: [{ title: 'Hello' }, { title: 'World' }] },
|
|
16
|
+
},
|
|
17
|
+
include: { posts: true },
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
// connectOrCreate
|
|
21
|
+
posts: { create: [{
|
|
22
|
+
title: 'Post',
|
|
23
|
+
categories: { connectOrCreate: [{ create: { name: 'Tech' }, where: { name: 'Tech' } }] },
|
|
24
|
+
}]}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Read
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
// 단일
|
|
31
|
+
const user = await prisma.user.findUnique({ where: { email } })
|
|
32
|
+
|
|
33
|
+
// 다중
|
|
34
|
+
const users = await prisma.user.findMany({ where: { name: 'Alice' } })
|
|
35
|
+
|
|
36
|
+
// 관계 포함
|
|
37
|
+
const users = await prisma.user.findMany({ where: { role: 'ADMIN' }, include: { posts: true } })
|
|
38
|
+
|
|
39
|
+
// 필드 선택
|
|
40
|
+
const user = await prisma.user.findUnique({
|
|
41
|
+
where: { email },
|
|
42
|
+
select: { email: true, posts: { select: { title: true } } },
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
// 관계로 필터
|
|
46
|
+
const users = await prisma.user.findMany({
|
|
47
|
+
where: { posts: { some: { published: false } } },
|
|
48
|
+
})
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Update
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
// 단일
|
|
55
|
+
const user = await prisma.user.update({ where: { id }, data: { name: 'Updated' } })
|
|
56
|
+
|
|
57
|
+
// 다중
|
|
58
|
+
await prisma.user.updateMany({ where: { role: 'USER' }, data: { role: 'ADMIN' } })
|
|
59
|
+
|
|
60
|
+
// Upsert
|
|
61
|
+
const user = await prisma.user.upsert({
|
|
62
|
+
where: { email },
|
|
63
|
+
update: { name: 'Updated' },
|
|
64
|
+
create: { email, name: 'New' },
|
|
65
|
+
})
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Delete
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
await prisma.user.delete({ where: { id } })
|
|
72
|
+
await prisma.user.deleteMany({}) // 전체
|
|
73
|
+
await prisma.post.deleteMany({ where: { published: false } }) // 조건부
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## 필터 연산자
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
// 문자열
|
|
80
|
+
{ contains: 'prisma', startsWith: 'A', endsWith: 'io' }
|
|
81
|
+
|
|
82
|
+
// 숫자
|
|
83
|
+
{ gt: 18, gte: 18, lt: 65, lte: 65 }
|
|
84
|
+
|
|
85
|
+
// 배열
|
|
86
|
+
{ in: [1, 2, 3], notIn: [4, 5] }
|
|
87
|
+
|
|
88
|
+
// 논리
|
|
89
|
+
{ OR: [...], AND: [...], NOT: {...} }
|
|
90
|
+
```
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Prisma
|
|
2
|
+
|
|
3
|
+
> **Version**: 7.x | Node.js/TypeScript ORM
|
|
4
|
+
|
|
5
|
+
@setup.md
|
|
6
|
+
@config.md
|
|
7
|
+
@schema.md
|
|
8
|
+
@crud.md
|
|
9
|
+
@relations.md
|
|
10
|
+
@transactions.md
|
|
11
|
+
@cloudflare-d1.md
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Quick Reference
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { PrismaClient } from './generated/prisma' // v7 경로!
|
|
19
|
+
export const prisma = new PrismaClient()
|
|
20
|
+
|
|
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 } })
|
|
26
|
+
|
|
27
|
+
// 관계 포함
|
|
28
|
+
const userWithPosts = await prisma.user.findUnique({
|
|
29
|
+
where: { id },
|
|
30
|
+
include: { posts: true },
|
|
31
|
+
})
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### v7 schema.prisma (⚠️ 중요)
|
|
35
|
+
|
|
36
|
+
```prisma
|
|
37
|
+
generator client {
|
|
38
|
+
provider = "prisma-client" // v7! (prisma-client-js 아님)
|
|
39
|
+
output = "../generated/prisma" // output 필수!
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### ⛔ Claude Code 금지
|
|
44
|
+
|
|
45
|
+
| 금지 사항 |
|
|
46
|
+
|----------|
|
|
47
|
+
| prisma db push 자동 실행 |
|
|
48
|
+
| prisma migrate 자동 실행 |
|
|
49
|
+
| prisma generate 자동 실행 |
|
|
50
|
+
| schema.prisma 임의 변경 |
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Prisma Client 설정
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
// lib/prisma.ts
|
|
58
|
+
import { PrismaClient } from './generated/prisma'
|
|
59
|
+
|
|
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
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## 마이그레이션 명령어
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
npx prisma migrate dev --name init # 개발 마이그레이션
|
|
69
|
+
npx prisma migrate deploy # 프로덕션 마이그레이션
|
|
70
|
+
npx prisma db push # 스키마 동기화 (개발용)
|
|
71
|
+
npx prisma generate # Client 생성
|
|
72
|
+
npx prisma studio # GUI
|
|
73
|
+
```
|