@kood/claude-code 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -0
- package/dist/index.js +297 -0
- package/package.json +47 -0
- package/templates/hono/CLAUDE.md +376 -0
- package/templates/hono/docs/deployment/cloudflare.md +328 -0
- package/templates/hono/docs/deployment/index.md +291 -0
- package/templates/hono/docs/git/index.md +180 -0
- package/templates/hono/docs/library/hono/error-handling.md +400 -0
- package/templates/hono/docs/library/hono/index.md +241 -0
- package/templates/hono/docs/library/hono/middleware.md +334 -0
- package/templates/hono/docs/library/hono/rpc.md +454 -0
- package/templates/hono/docs/library/hono/validation.md +328 -0
- package/templates/hono/docs/library/prisma/index.md +427 -0
- package/templates/hono/docs/library/zod/index.md +413 -0
- package/templates/hono/docs/mcp/context7.md +106 -0
- package/templates/hono/docs/mcp/index.md +94 -0
- package/templates/hono/docs/mcp/sequential-thinking.md +101 -0
- package/templates/hono/docs/mcp/sgrep.md +105 -0
- package/templates/hono/docs/skills/gemini-review/SKILL.md +220 -0
- package/templates/hono/docs/skills/gemini-review/references/checklists.md +136 -0
- package/templates/hono/docs/skills/gemini-review/references/prompt-templates.md +303 -0
- package/templates/tanstack-start/CLAUDE.md +279 -0
- package/templates/tanstack-start/docs/architecture/architecture.md +547 -0
- package/templates/tanstack-start/docs/deployment/cloudflare.md +346 -0
- package/templates/tanstack-start/docs/deployment/index.md +102 -0
- package/templates/tanstack-start/docs/deployment/nitro.md +211 -0
- package/templates/tanstack-start/docs/deployment/railway.md +364 -0
- package/templates/tanstack-start/docs/deployment/vercel.md +287 -0
- package/templates/tanstack-start/docs/design/accessibility.md +433 -0
- package/templates/tanstack-start/docs/design/color.md +235 -0
- package/templates/tanstack-start/docs/design/components.md +409 -0
- package/templates/tanstack-start/docs/design/index.md +107 -0
- package/templates/tanstack-start/docs/design/safe-area.md +317 -0
- package/templates/tanstack-start/docs/design/spacing.md +341 -0
- package/templates/tanstack-start/docs/design/tailwind-setup.md +470 -0
- package/templates/tanstack-start/docs/design/typography.md +324 -0
- package/templates/tanstack-start/docs/git/index.md +203 -0
- package/templates/tanstack-start/docs/guides/best-practices.md +753 -0
- package/templates/tanstack-start/docs/guides/getting-started.md +304 -0
- package/templates/tanstack-start/docs/guides/husky-lint-staged.md +303 -0
- package/templates/tanstack-start/docs/guides/prettier.md +189 -0
- package/templates/tanstack-start/docs/guides/project-templates.md +710 -0
- package/templates/tanstack-start/docs/library/better-auth/2fa.md +136 -0
- package/templates/tanstack-start/docs/library/better-auth/advanced.md +138 -0
- package/templates/tanstack-start/docs/library/better-auth/index.md +83 -0
- package/templates/tanstack-start/docs/library/better-auth/plugins.md +111 -0
- package/templates/tanstack-start/docs/library/better-auth/session.md +127 -0
- package/templates/tanstack-start/docs/library/better-auth/setup.md +123 -0
- package/templates/tanstack-start/docs/library/prisma/crud.md +218 -0
- package/templates/tanstack-start/docs/library/prisma/index.md +165 -0
- package/templates/tanstack-start/docs/library/prisma/relations.md +191 -0
- package/templates/tanstack-start/docs/library/prisma/schema.md +177 -0
- package/templates/tanstack-start/docs/library/prisma/setup.md +156 -0
- package/templates/tanstack-start/docs/library/prisma/transactions.md +140 -0
- package/templates/tanstack-start/docs/library/tanstack-query/index.md +146 -0
- package/templates/tanstack-start/docs/library/tanstack-query/invalidation.md +146 -0
- package/templates/tanstack-start/docs/library/tanstack-query/optimistic-updates.md +196 -0
- package/templates/tanstack-start/docs/library/tanstack-query/setup.md +110 -0
- package/templates/tanstack-start/docs/library/tanstack-query/use-mutation.md +170 -0
- package/templates/tanstack-start/docs/library/tanstack-query/use-query.md +173 -0
- package/templates/tanstack-start/docs/library/tanstack-start/auth-patterns.md +171 -0
- package/templates/tanstack-start/docs/library/tanstack-start/index.md +114 -0
- package/templates/tanstack-start/docs/library/tanstack-start/middleware.md +142 -0
- package/templates/tanstack-start/docs/library/tanstack-start/routing.md +163 -0
- package/templates/tanstack-start/docs/library/tanstack-start/server-functions.md +128 -0
- package/templates/tanstack-start/docs/library/tanstack-start/setup.md +85 -0
- package/templates/tanstack-start/docs/library/zod/basic-types.md +186 -0
- package/templates/tanstack-start/docs/library/zod/complex-types.md +204 -0
- package/templates/tanstack-start/docs/library/zod/index.md +186 -0
- package/templates/tanstack-start/docs/library/zod/transforms.md +174 -0
- package/templates/tanstack-start/docs/library/zod/validation.md +208 -0
- package/templates/tanstack-start/docs/mcp/context7.md +204 -0
- package/templates/tanstack-start/docs/mcp/index.md +116 -0
- package/templates/tanstack-start/docs/mcp/sequential-thinking.md +180 -0
- package/templates/tanstack-start/docs/mcp/sgrep.md +174 -0
- package/templates/tanstack-start/docs/skills/gemini-review/SKILL.md +220 -0
- package/templates/tanstack-start/docs/skills/gemini-review/references/checklists.md +150 -0
- package/templates/tanstack-start/docs/skills/gemini-review/references/prompt-templates.md +293 -0
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
# Prisma v7 - Database ORM
|
|
2
|
+
|
|
3
|
+
> Type-safe 데이터베이스 ORM
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## ⚠️ 버전 주의
|
|
8
|
+
|
|
9
|
+
이 문서는 **Prisma v7** 기준입니다. v6 이하와 설정이 다릅니다.
|
|
10
|
+
|
|
11
|
+
```prisma
|
|
12
|
+
generator client {
|
|
13
|
+
provider = "prisma-client" // ✅ v7 (prisma-client-js 아님!)
|
|
14
|
+
output = "./generated/client" // ✅ output 필수
|
|
15
|
+
}
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## ⛔ 금지 사항
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
❌ prisma db push 자동 실행 금지
|
|
24
|
+
❌ prisma migrate 자동 실행 금지
|
|
25
|
+
❌ prisma generate 자동 실행 금지
|
|
26
|
+
❌ schema.prisma 임의 변경 금지 (요청된 것만)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## 설치
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install prisma-client
|
|
35
|
+
npm install -D prisma
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Schema 설정
|
|
41
|
+
|
|
42
|
+
### schema.prisma (v7)
|
|
43
|
+
|
|
44
|
+
```prisma
|
|
45
|
+
generator client {
|
|
46
|
+
provider = "prisma-client" // ✅ v7
|
|
47
|
+
output = "./generated/client" // ✅ output 필수
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
datasource db {
|
|
51
|
+
provider = "postgresql"
|
|
52
|
+
url = env("DATABASE_URL")
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
model User {
|
|
56
|
+
id String @id @default(cuid())
|
|
57
|
+
email String @unique
|
|
58
|
+
name String?
|
|
59
|
+
posts Post[]
|
|
60
|
+
createdAt DateTime @default(now())
|
|
61
|
+
updatedAt DateTime @updatedAt
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
model Post {
|
|
65
|
+
id String @id @default(cuid())
|
|
66
|
+
title String
|
|
67
|
+
content String?
|
|
68
|
+
published Boolean @default(false)
|
|
69
|
+
author User @relation(fields: [authorId], references: [id])
|
|
70
|
+
authorId String
|
|
71
|
+
createdAt DateTime @default(now())
|
|
72
|
+
updatedAt DateTime @updatedAt
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Prisma Client 설정
|
|
79
|
+
|
|
80
|
+
### database/prisma.ts
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
import { PrismaClient } from '../prisma/generated/client'
|
|
84
|
+
|
|
85
|
+
// 싱글톤 패턴
|
|
86
|
+
const globalForPrisma = globalThis as unknown as {
|
|
87
|
+
prisma: PrismaClient | undefined
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export const prisma =
|
|
91
|
+
globalForPrisma.prisma ??
|
|
92
|
+
new PrismaClient({
|
|
93
|
+
log: process.env.NODE_ENV === 'development' ? ['query'] : [],
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
97
|
+
globalForPrisma.prisma = prisma
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## CRUD 작업
|
|
104
|
+
|
|
105
|
+
### Create
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
// 단일 생성
|
|
109
|
+
const user = await prisma.user.create({
|
|
110
|
+
data: {
|
|
111
|
+
email: 'user@example.com',
|
|
112
|
+
name: 'John',
|
|
113
|
+
},
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
// 관계와 함께 생성
|
|
117
|
+
const userWithPosts = await prisma.user.create({
|
|
118
|
+
data: {
|
|
119
|
+
email: 'user@example.com',
|
|
120
|
+
name: 'John',
|
|
121
|
+
posts: {
|
|
122
|
+
create: [
|
|
123
|
+
{ title: 'Post 1', content: 'Content 1' },
|
|
124
|
+
{ title: 'Post 2', content: 'Content 2' },
|
|
125
|
+
],
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
include: {
|
|
129
|
+
posts: true,
|
|
130
|
+
},
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
// 다수 생성
|
|
134
|
+
const users = await prisma.user.createMany({
|
|
135
|
+
data: [
|
|
136
|
+
{ email: 'user1@example.com', name: 'User 1' },
|
|
137
|
+
{ email: 'user2@example.com', name: 'User 2' },
|
|
138
|
+
],
|
|
139
|
+
skipDuplicates: true,
|
|
140
|
+
})
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Read
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
// 단일 조회
|
|
147
|
+
const user = await prisma.user.findUnique({
|
|
148
|
+
where: { id: 'user-id' },
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
// 필수 조회 (없으면 에러)
|
|
152
|
+
const user = await prisma.user.findUniqueOrThrow({
|
|
153
|
+
where: { id: 'user-id' },
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
// 조건 조회
|
|
157
|
+
const user = await prisma.user.findFirst({
|
|
158
|
+
where: { email: { contains: '@example.com' } },
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
// 다수 조회
|
|
162
|
+
const users = await prisma.user.findMany({
|
|
163
|
+
where: { name: { not: null } },
|
|
164
|
+
orderBy: { createdAt: 'desc' },
|
|
165
|
+
take: 10,
|
|
166
|
+
skip: 0,
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
// 관계 포함
|
|
170
|
+
const userWithPosts = await prisma.user.findUnique({
|
|
171
|
+
where: { id: 'user-id' },
|
|
172
|
+
include: {
|
|
173
|
+
posts: {
|
|
174
|
+
where: { published: true },
|
|
175
|
+
orderBy: { createdAt: 'desc' },
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
// 특정 필드만 선택
|
|
181
|
+
const userEmail = await prisma.user.findUnique({
|
|
182
|
+
where: { id: 'user-id' },
|
|
183
|
+
select: {
|
|
184
|
+
id: true,
|
|
185
|
+
email: true,
|
|
186
|
+
},
|
|
187
|
+
})
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Update
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
// 단일 수정
|
|
194
|
+
const user = await prisma.user.update({
|
|
195
|
+
where: { id: 'user-id' },
|
|
196
|
+
data: { name: 'New Name' },
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
// 다수 수정
|
|
200
|
+
const users = await prisma.user.updateMany({
|
|
201
|
+
where: { name: null },
|
|
202
|
+
data: { name: 'Anonymous' },
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
// 없으면 생성 (Upsert)
|
|
206
|
+
const user = await prisma.user.upsert({
|
|
207
|
+
where: { email: 'user@example.com' },
|
|
208
|
+
update: { name: 'Updated Name' },
|
|
209
|
+
create: { email: 'user@example.com', name: 'New User' },
|
|
210
|
+
})
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Delete
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
// 단일 삭제
|
|
217
|
+
const user = await prisma.user.delete({
|
|
218
|
+
where: { id: 'user-id' },
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
// 다수 삭제
|
|
222
|
+
const users = await prisma.user.deleteMany({
|
|
223
|
+
where: { email: { contains: '@test.com' } },
|
|
224
|
+
})
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## 필터링
|
|
230
|
+
|
|
231
|
+
### 비교 연산자
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
await prisma.user.findMany({
|
|
235
|
+
where: {
|
|
236
|
+
age: { gt: 18 }, // >
|
|
237
|
+
age: { gte: 18 }, // >=
|
|
238
|
+
age: { lt: 65 }, // <
|
|
239
|
+
age: { lte: 65 }, // <=
|
|
240
|
+
age: { not: 30 }, // !=
|
|
241
|
+
name: { equals: 'John' },
|
|
242
|
+
},
|
|
243
|
+
})
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### 문자열 필터
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
await prisma.user.findMany({
|
|
250
|
+
where: {
|
|
251
|
+
email: { contains: '@gmail.com' },
|
|
252
|
+
email: { startsWith: 'admin' },
|
|
253
|
+
email: { endsWith: '.com' },
|
|
254
|
+
name: { mode: 'insensitive' }, // 대소문자 무시
|
|
255
|
+
},
|
|
256
|
+
})
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### 논리 연산자
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
await prisma.user.findMany({
|
|
263
|
+
where: {
|
|
264
|
+
AND: [
|
|
265
|
+
{ email: { contains: '@' } },
|
|
266
|
+
{ name: { not: null } },
|
|
267
|
+
],
|
|
268
|
+
OR: [
|
|
269
|
+
{ role: 'admin' },
|
|
270
|
+
{ role: 'moderator' },
|
|
271
|
+
],
|
|
272
|
+
NOT: {
|
|
273
|
+
status: 'banned',
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
})
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### 배열 필터
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
await prisma.user.findMany({
|
|
283
|
+
where: {
|
|
284
|
+
id: { in: ['id1', 'id2', 'id3'] },
|
|
285
|
+
role: { notIn: ['banned', 'suspended'] },
|
|
286
|
+
},
|
|
287
|
+
})
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## 관계 쿼리
|
|
293
|
+
|
|
294
|
+
### Include (전체 데이터)
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
const user = await prisma.user.findUnique({
|
|
298
|
+
where: { id: 'user-id' },
|
|
299
|
+
include: {
|
|
300
|
+
posts: true,
|
|
301
|
+
profile: true,
|
|
302
|
+
},
|
|
303
|
+
})
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Select (특정 필드)
|
|
307
|
+
|
|
308
|
+
```typescript
|
|
309
|
+
const user = await prisma.user.findUnique({
|
|
310
|
+
where: { id: 'user-id' },
|
|
311
|
+
select: {
|
|
312
|
+
id: true,
|
|
313
|
+
email: true,
|
|
314
|
+
posts: {
|
|
315
|
+
select: { id: true, title: true },
|
|
316
|
+
},
|
|
317
|
+
},
|
|
318
|
+
})
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### 관계 필터링
|
|
322
|
+
|
|
323
|
+
```typescript
|
|
324
|
+
// 관계 조건으로 필터
|
|
325
|
+
const usersWithPublishedPosts = await prisma.user.findMany({
|
|
326
|
+
where: {
|
|
327
|
+
posts: {
|
|
328
|
+
some: { published: true }, // 하나라도 만족
|
|
329
|
+
// every: { published: true }, // 모두 만족
|
|
330
|
+
// none: { published: true }, // 하나도 없음
|
|
331
|
+
},
|
|
332
|
+
},
|
|
333
|
+
})
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
## 트랜잭션
|
|
339
|
+
|
|
340
|
+
### Sequential 트랜잭션
|
|
341
|
+
|
|
342
|
+
```typescript
|
|
343
|
+
const [user, post] = await prisma.$transaction([
|
|
344
|
+
prisma.user.create({ data: { email: 'user@example.com' } }),
|
|
345
|
+
prisma.post.create({ data: { title: 'New Post', authorId: 'user-id' } }),
|
|
346
|
+
])
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### Interactive 트랜잭션
|
|
350
|
+
|
|
351
|
+
```typescript
|
|
352
|
+
const result = await prisma.$transaction(async (tx) => {
|
|
353
|
+
const user = await tx.user.findUnique({ where: { id: 'user-id' } })
|
|
354
|
+
|
|
355
|
+
if (!user) {
|
|
356
|
+
throw new Error('User not found')
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const post = await tx.post.create({
|
|
360
|
+
data: {
|
|
361
|
+
title: 'New Post',
|
|
362
|
+
authorId: user.id,
|
|
363
|
+
},
|
|
364
|
+
})
|
|
365
|
+
|
|
366
|
+
return { user, post }
|
|
367
|
+
})
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
---
|
|
371
|
+
|
|
372
|
+
## Hono와 함께 사용
|
|
373
|
+
|
|
374
|
+
```typescript
|
|
375
|
+
import { Hono } from 'hono'
|
|
376
|
+
import { zValidator } from '@hono/zod-validator'
|
|
377
|
+
import { z } from 'zod'
|
|
378
|
+
import { HTTPException } from 'hono/http-exception'
|
|
379
|
+
import { prisma } from '@/database/prisma'
|
|
380
|
+
|
|
381
|
+
const app = new Hono()
|
|
382
|
+
|
|
383
|
+
const createUserSchema = z.object({
|
|
384
|
+
email: z.email(),
|
|
385
|
+
name: z.string().min(1).optional(),
|
|
386
|
+
})
|
|
387
|
+
|
|
388
|
+
app.post('/users', zValidator('json', createUserSchema), async (c) => {
|
|
389
|
+
const data = c.req.valid('json')
|
|
390
|
+
|
|
391
|
+
// 중복 체크
|
|
392
|
+
const existing = await prisma.user.findUnique({
|
|
393
|
+
where: { email: data.email },
|
|
394
|
+
})
|
|
395
|
+
|
|
396
|
+
if (existing) {
|
|
397
|
+
throw new HTTPException(409, { message: 'Email already exists' })
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const user = await prisma.user.create({ data })
|
|
401
|
+
return c.json({ user }, 201)
|
|
402
|
+
})
|
|
403
|
+
|
|
404
|
+
app.get('/users/:id', async (c) => {
|
|
405
|
+
const id = c.req.param('id')
|
|
406
|
+
const user = await prisma.user.findUnique({
|
|
407
|
+
where: { id },
|
|
408
|
+
include: { posts: true },
|
|
409
|
+
})
|
|
410
|
+
|
|
411
|
+
if (!user) {
|
|
412
|
+
throw new HTTPException(404, { message: 'User not found' })
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
return c.json({ user })
|
|
416
|
+
})
|
|
417
|
+
|
|
418
|
+
export default app
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
---
|
|
422
|
+
|
|
423
|
+
## 관련 문서
|
|
424
|
+
|
|
425
|
+
- [Schema 정의](./schema.md)
|
|
426
|
+
- [Relations](./relations.md)
|
|
427
|
+
- [Transactions](./transactions.md)
|