@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,177 @@
|
|
|
1
|
+
# Prisma - 스키마 정의
|
|
2
|
+
|
|
3
|
+
> **상위 문서**: [Prisma](./index.md)
|
|
4
|
+
|
|
5
|
+
## 기본 스키마
|
|
6
|
+
|
|
7
|
+
```prisma
|
|
8
|
+
// prisma/schema.prisma
|
|
9
|
+
datasource db {
|
|
10
|
+
provider = "postgresql"
|
|
11
|
+
url = env("DATABASE_URL")
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
generator client {
|
|
15
|
+
provider = "prisma-client"
|
|
16
|
+
output = "../generated/prisma"
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
model User {
|
|
20
|
+
id Int @id @default(autoincrement())
|
|
21
|
+
email String @unique
|
|
22
|
+
name String?
|
|
23
|
+
role Role @default(USER)
|
|
24
|
+
posts Post[]
|
|
25
|
+
profile ExtendedProfile?
|
|
26
|
+
createdAt DateTime @default(now())
|
|
27
|
+
updatedAt DateTime @updatedAt
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
model Post {
|
|
31
|
+
id Int @id @default(autoincrement())
|
|
32
|
+
title String
|
|
33
|
+
content String?
|
|
34
|
+
published Boolean @default(false)
|
|
35
|
+
author User @relation(fields: [authorId], references: [id])
|
|
36
|
+
authorId Int
|
|
37
|
+
categories Category[]
|
|
38
|
+
createdAt DateTime @default(now())
|
|
39
|
+
updatedAt DateTime @updatedAt
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
model Category {
|
|
43
|
+
id Int @id @default(autoincrement())
|
|
44
|
+
name String @unique
|
|
45
|
+
posts Post[]
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
model ExtendedProfile {
|
|
49
|
+
id Int @id @default(autoincrement())
|
|
50
|
+
biography String
|
|
51
|
+
user User @relation(fields: [userId], references: [id])
|
|
52
|
+
userId Int @unique
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
enum Role {
|
|
56
|
+
USER
|
|
57
|
+
ADMIN
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## 선택적 관계
|
|
62
|
+
|
|
63
|
+
```prisma
|
|
64
|
+
model Post {
|
|
65
|
+
id Int @id @default(autoincrement())
|
|
66
|
+
author User? @relation(fields: [authorId], references: [id])
|
|
67
|
+
authorId Int?
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## 커스텀 필드/모델명 매핑
|
|
72
|
+
|
|
73
|
+
```prisma
|
|
74
|
+
model User {
|
|
75
|
+
id Int @id @default(autoincrement()) @map("user_id")
|
|
76
|
+
email String @unique
|
|
77
|
+
|
|
78
|
+
@@map("users")
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## 관계 유형
|
|
83
|
+
|
|
84
|
+
### 1:1 관계
|
|
85
|
+
|
|
86
|
+
```prisma
|
|
87
|
+
model User {
|
|
88
|
+
id Int @id @default(autoincrement())
|
|
89
|
+
profile Profile?
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
model Profile {
|
|
93
|
+
id Int @id @default(autoincrement())
|
|
94
|
+
user User @relation(fields: [userId], references: [id])
|
|
95
|
+
userId Int @unique
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### 1:N 관계
|
|
100
|
+
|
|
101
|
+
```prisma
|
|
102
|
+
model User {
|
|
103
|
+
id Int @id @default(autoincrement())
|
|
104
|
+
posts Post[]
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
model Post {
|
|
108
|
+
id Int @id @default(autoincrement())
|
|
109
|
+
author User @relation(fields: [authorId], references: [id])
|
|
110
|
+
authorId Int
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### M:N 관계 (암묵적)
|
|
115
|
+
|
|
116
|
+
```prisma
|
|
117
|
+
model Post {
|
|
118
|
+
id Int @id @default(autoincrement())
|
|
119
|
+
categories Category[]
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
model Category {
|
|
123
|
+
id Int @id @default(autoincrement())
|
|
124
|
+
posts Post[]
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### M:N 관계 (명시적)
|
|
129
|
+
|
|
130
|
+
```prisma
|
|
131
|
+
model Post {
|
|
132
|
+
id Int @id @default(autoincrement())
|
|
133
|
+
categories CategoriesOnPosts[]
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
model Category {
|
|
137
|
+
id Int @id @default(autoincrement())
|
|
138
|
+
posts CategoriesOnPosts[]
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
model CategoriesOnPosts {
|
|
142
|
+
post Post @relation(fields: [postId], references: [id])
|
|
143
|
+
postId Int
|
|
144
|
+
category Category @relation(fields: [categoryId], references: [id])
|
|
145
|
+
categoryId Int
|
|
146
|
+
assignedAt DateTime @default(now())
|
|
147
|
+
|
|
148
|
+
@@id([postId, categoryId])
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## 인덱스
|
|
153
|
+
|
|
154
|
+
```prisma
|
|
155
|
+
model Post {
|
|
156
|
+
id Int @id @default(autoincrement())
|
|
157
|
+
title String
|
|
158
|
+
content String?
|
|
159
|
+
authorId Int
|
|
160
|
+
createdAt DateTime @default(now())
|
|
161
|
+
|
|
162
|
+
@@index([authorId])
|
|
163
|
+
@@index([createdAt])
|
|
164
|
+
@@index([title, authorId])
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## 복합 키
|
|
169
|
+
|
|
170
|
+
```prisma
|
|
171
|
+
model PostTag {
|
|
172
|
+
postId Int
|
|
173
|
+
tagId Int
|
|
174
|
+
|
|
175
|
+
@@id([postId, tagId])
|
|
176
|
+
}
|
|
177
|
+
```
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# Prisma - 설치 및 설정
|
|
2
|
+
|
|
3
|
+
> **상위 문서**: [Prisma](./index.md)
|
|
4
|
+
|
|
5
|
+
## 설치
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# npm
|
|
9
|
+
npm install @prisma/client@7
|
|
10
|
+
npm install -D prisma@7
|
|
11
|
+
|
|
12
|
+
# yarn
|
|
13
|
+
yarn add @prisma/client@7
|
|
14
|
+
yarn add -D prisma@7
|
|
15
|
+
|
|
16
|
+
# pnpm
|
|
17
|
+
pnpm add @prisma/client@7
|
|
18
|
+
pnpm add -D prisma@7
|
|
19
|
+
|
|
20
|
+
# bun
|
|
21
|
+
bun add @prisma/client@7
|
|
22
|
+
bun add prisma@7 --dev
|
|
23
|
+
|
|
24
|
+
# 초기화
|
|
25
|
+
npx prisma init
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## v6에서 v7 업그레이드
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# npm
|
|
32
|
+
npm install @prisma/client@7
|
|
33
|
+
npm install -D prisma@7
|
|
34
|
+
|
|
35
|
+
# yarn
|
|
36
|
+
yarn up prisma@7 @prisma/client@7
|
|
37
|
+
|
|
38
|
+
# pnpm
|
|
39
|
+
pnpm upgrade prisma@7 @prisma/client@7
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Generator 변경 (필수)
|
|
43
|
+
|
|
44
|
+
```prisma
|
|
45
|
+
// schema.prisma - 이전 (v6)
|
|
46
|
+
generator client {
|
|
47
|
+
provider = "prisma-client-js"
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// schema.prisma - v7
|
|
51
|
+
generator client {
|
|
52
|
+
provider = "prisma-client" // 새로운 provider
|
|
53
|
+
output = "../generated/prisma" // output 필수
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Prisma Client 설정
|
|
58
|
+
|
|
59
|
+
### 기본 설정
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
// lib/prisma.ts
|
|
63
|
+
import { PrismaClient } from './generated/prisma' // output 경로에서 import
|
|
64
|
+
|
|
65
|
+
const globalForPrisma = globalThis as unknown as {
|
|
66
|
+
prisma: PrismaClient | undefined
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const prisma =
|
|
70
|
+
globalForPrisma.prisma ??
|
|
71
|
+
new PrismaClient({
|
|
72
|
+
log: ['query'],
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### 로깅 설정
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
import { PrismaClient } from './generated/prisma'
|
|
82
|
+
|
|
83
|
+
const prisma = new PrismaClient({
|
|
84
|
+
log: [{ level: 'query', emit: 'event' }],
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
prisma.$on('query', (e) => {
|
|
88
|
+
console.log('Query:', e.query)
|
|
89
|
+
console.log('Params:', e.params)
|
|
90
|
+
console.log('Duration:', e.duration, 'ms')
|
|
91
|
+
})
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### QueryEvent 타입
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
type QueryEvent = {
|
|
98
|
+
timestamp: Date
|
|
99
|
+
query: string
|
|
100
|
+
params: string
|
|
101
|
+
duration: number
|
|
102
|
+
target: string
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## TanStack Start 통합
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
// lib/prisma.ts
|
|
110
|
+
import { PrismaClient } from './generated/prisma'
|
|
111
|
+
|
|
112
|
+
const globalForPrisma = globalThis as unknown as {
|
|
113
|
+
prisma: PrismaClient | undefined
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export const prisma =
|
|
117
|
+
globalForPrisma.prisma ?? new PrismaClient()
|
|
118
|
+
|
|
119
|
+
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
|
|
120
|
+
|
|
121
|
+
// Server Function에서 사용
|
|
122
|
+
import { createServerFn } from '@tanstack/react-start'
|
|
123
|
+
import { prisma } from '~/lib/prisma'
|
|
124
|
+
|
|
125
|
+
export const getUsers = createServerFn({ method: 'GET' })
|
|
126
|
+
.handler(async () => {
|
|
127
|
+
return prisma.user.findMany({
|
|
128
|
+
include: { posts: true },
|
|
129
|
+
})
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
export const createUser = createServerFn({ method: 'POST' })
|
|
133
|
+
.inputValidator((data: { email: string; name: string }) => data)
|
|
134
|
+
.handler(async ({ data }) => {
|
|
135
|
+
return prisma.user.create({ data })
|
|
136
|
+
})
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## 마이그레이션
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
# 개발 환경 마이그레이션
|
|
143
|
+
npx prisma migrate dev --name init
|
|
144
|
+
|
|
145
|
+
# 프로덕션 마이그레이션
|
|
146
|
+
npx prisma migrate deploy
|
|
147
|
+
|
|
148
|
+
# 스키마 동기화 (개발용)
|
|
149
|
+
npx prisma db push
|
|
150
|
+
|
|
151
|
+
# Prisma Client 생성
|
|
152
|
+
npx prisma generate
|
|
153
|
+
|
|
154
|
+
# Prisma Studio (GUI)
|
|
155
|
+
npx prisma studio
|
|
156
|
+
```
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# Prisma - 트랜잭션
|
|
2
|
+
|
|
3
|
+
> **상위 문서**: [Prisma](./index.md)
|
|
4
|
+
|
|
5
|
+
## 배열 기반 트랜잭션
|
|
6
|
+
|
|
7
|
+
여러 쿼리를 순차적으로 실행하고, 하나라도 실패하면 모두 롤백됩니다.
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
// 관계 데이터 삭제
|
|
11
|
+
const deletePosts = prisma.post.deleteMany({
|
|
12
|
+
where: { authorId: 7 },
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
const deleteUser = prisma.user.delete({
|
|
16
|
+
where: { id: 7 },
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
const transaction = await prisma.$transaction([deletePosts, deleteUser])
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## GDPR 데이터 삭제 예시
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
const id = 9 // 삭제할 사용자
|
|
26
|
+
|
|
27
|
+
const deletePosts = prisma.post.deleteMany({
|
|
28
|
+
where: { userId: id },
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
const deleteMessages = prisma.privateMessage.deleteMany({
|
|
32
|
+
where: { userId: id },
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
const deleteUser = prisma.user.delete({
|
|
36
|
+
where: { id: id },
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
// 모든 작업이 성공하거나 모두 실패
|
|
40
|
+
await prisma.$transaction([deletePosts, deleteMessages, deleteUser])
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## 모든 데이터 삭제
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
const deletePosts = prisma.post.deleteMany()
|
|
47
|
+
const deleteProfile = prisma.profile.deleteMany()
|
|
48
|
+
const deleteUsers = prisma.user.deleteMany()
|
|
49
|
+
|
|
50
|
+
// 순서대로 실행 (deleteUsers가 마지막)
|
|
51
|
+
await prisma.$transaction([deleteProfile, deletePosts, deleteUsers])
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## 인터랙티브 트랜잭션
|
|
55
|
+
|
|
56
|
+
더 복잡한 트랜잭션 로직이 필요할 때 사용합니다.
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
const result = await prisma.$transaction(async (tx) => {
|
|
60
|
+
// 1. 잔액 확인
|
|
61
|
+
const sender = await tx.account.findUnique({
|
|
62
|
+
where: { id: senderId },
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
if (!sender || sender.balance < amount) {
|
|
66
|
+
throw new Error('Insufficient balance')
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// 2. 보내는 계좌에서 차감
|
|
70
|
+
const updatedSender = await tx.account.update({
|
|
71
|
+
where: { id: senderId },
|
|
72
|
+
data: { balance: { decrement: amount } },
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
// 3. 받는 계좌에 입금
|
|
76
|
+
const updatedRecipient = await tx.account.update({
|
|
77
|
+
where: { id: recipientId },
|
|
78
|
+
data: { balance: { increment: amount } },
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
return { sender: updatedSender, recipient: updatedRecipient }
|
|
82
|
+
})
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## 트랜잭션 옵션
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
await prisma.$transaction(
|
|
89
|
+
async (tx) => {
|
|
90
|
+
// 트랜잭션 로직
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
maxWait: 5000, // 최대 대기 시간 (ms)
|
|
94
|
+
timeout: 10000, // 트랜잭션 타임아웃 (ms)
|
|
95
|
+
isolationLevel: 'Serializable', // 격리 수준
|
|
96
|
+
}
|
|
97
|
+
)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## 격리 수준
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
// 가능한 격리 수준
|
|
104
|
+
type IsolationLevel =
|
|
105
|
+
| 'ReadUncommitted'
|
|
106
|
+
| 'ReadCommitted'
|
|
107
|
+
| 'RepeatableRead'
|
|
108
|
+
| 'Serializable'
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## 타입 안전 Raw Query
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
import { selectUserTitles, updateUserName } from '@prisma/client/sql'
|
|
115
|
+
|
|
116
|
+
const [userList, updateUser] = await prisma.$transaction([
|
|
117
|
+
prisma.$queryRawTyped(selectUserTitles()),
|
|
118
|
+
prisma.$queryRawTyped(updateUserName(2)),
|
|
119
|
+
])
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## 트랜잭션 내 에러 처리
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
try {
|
|
126
|
+
await prisma.$transaction(async (tx) => {
|
|
127
|
+
await tx.user.create({ data: { email: 'alice@prisma.io' } })
|
|
128
|
+
|
|
129
|
+
// 의도적 에러 발생 시 전체 롤백
|
|
130
|
+
if (someCondition) {
|
|
131
|
+
throw new Error('Rollback transaction')
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
await tx.post.create({ data: { title: 'Hello', authorId: 1 } })
|
|
135
|
+
})
|
|
136
|
+
} catch (error) {
|
|
137
|
+
console.error('Transaction failed:', error)
|
|
138
|
+
// 트랜잭션 내 모든 변경사항이 롤백됨
|
|
139
|
+
}
|
|
140
|
+
```
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# TanStack Query
|
|
2
|
+
|
|
3
|
+
> **Version**: 5.x | React Data Fetching Library
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 🚀 Quick Reference (복사용)
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
// useQuery - 데이터 조회
|
|
11
|
+
const { data, isLoading, error } = useQuery({
|
|
12
|
+
queryKey: ['users'],
|
|
13
|
+
queryFn: () => getUsers(),
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
// useMutation - 데이터 변경
|
|
17
|
+
const queryClient = useQueryClient()
|
|
18
|
+
const mutation = useMutation({
|
|
19
|
+
mutationFn: createUser,
|
|
20
|
+
onSuccess: () => {
|
|
21
|
+
queryClient.invalidateQueries({ queryKey: ['users'] })
|
|
22
|
+
},
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
// mutation 실행
|
|
26
|
+
mutation.mutate({ email, name })
|
|
27
|
+
|
|
28
|
+
// Optimistic Update
|
|
29
|
+
const mutation = useMutation({
|
|
30
|
+
mutationFn: updateUser,
|
|
31
|
+
onMutate: async (newUser) => {
|
|
32
|
+
await queryClient.cancelQueries({ queryKey: ['users'] })
|
|
33
|
+
const previous = queryClient.getQueryData(['users'])
|
|
34
|
+
queryClient.setQueryData(['users'], (old) => [...old, newUser])
|
|
35
|
+
return { previous }
|
|
36
|
+
},
|
|
37
|
+
onError: (err, newUser, context) => {
|
|
38
|
+
queryClient.setQueryData(['users'], context.previous)
|
|
39
|
+
},
|
|
40
|
+
})
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### QueryClient 설정
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|
47
|
+
|
|
48
|
+
const queryClient = new QueryClient()
|
|
49
|
+
|
|
50
|
+
function App() {
|
|
51
|
+
return (
|
|
52
|
+
<QueryClientProvider client={queryClient}>
|
|
53
|
+
<YourApp />
|
|
54
|
+
</QueryClientProvider>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## 문서 구조
|
|
62
|
+
|
|
63
|
+
- [설치 및 설정](./setup.md) - QueryClient 설정
|
|
64
|
+
- [useQuery](./use-query.md) - 데이터 조회 훅
|
|
65
|
+
- [useMutation](./use-mutation.md) - 데이터 변경 훅
|
|
66
|
+
- [무효화](./invalidation.md) - Query 무효화 및 리페치
|
|
67
|
+
- [Optimistic Updates](./optimistic-updates.md) - 낙관적 업데이트
|
|
68
|
+
|
|
69
|
+
## 빠른 시작
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
yarn add @tanstack/react-query
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### QueryClient 설정
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
import {
|
|
79
|
+
QueryClient,
|
|
80
|
+
QueryClientProvider,
|
|
81
|
+
} from '@tanstack/react-query'
|
|
82
|
+
|
|
83
|
+
const queryClient = new QueryClient()
|
|
84
|
+
|
|
85
|
+
function App() {
|
|
86
|
+
return (
|
|
87
|
+
<QueryClientProvider client={queryClient}>
|
|
88
|
+
<YourApp />
|
|
89
|
+
</QueryClientProvider>
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## 핵심 개념
|
|
95
|
+
|
|
96
|
+
### useQuery - 데이터 조회
|
|
97
|
+
|
|
98
|
+
```tsx
|
|
99
|
+
import { useQuery } from '@tanstack/react-query'
|
|
100
|
+
|
|
101
|
+
function Todos() {
|
|
102
|
+
const { data, isLoading, error } = useQuery({
|
|
103
|
+
queryKey: ['todos'],
|
|
104
|
+
queryFn: getTodos,
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
if (isLoading) return <div>Loading...</div>
|
|
108
|
+
if (error) return <div>Error: {error.message}</div>
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<ul>
|
|
112
|
+
{data?.map((todo) => (
|
|
113
|
+
<li key={todo.id}>{todo.title}</li>
|
|
114
|
+
))}
|
|
115
|
+
</ul>
|
|
116
|
+
)
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### useMutation - 데이터 변경
|
|
121
|
+
|
|
122
|
+
```tsx
|
|
123
|
+
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
|
124
|
+
|
|
125
|
+
function AddTodo() {
|
|
126
|
+
const queryClient = useQueryClient()
|
|
127
|
+
|
|
128
|
+
const mutation = useMutation({
|
|
129
|
+
mutationFn: postTodo,
|
|
130
|
+
onSuccess: () => {
|
|
131
|
+
queryClient.invalidateQueries({ queryKey: ['todos'] })
|
|
132
|
+
},
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
<button onClick={() => mutation.mutate({ title: 'New Todo' })}>
|
|
137
|
+
Add Todo
|
|
138
|
+
</button>
|
|
139
|
+
)
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## 참고 자료
|
|
144
|
+
|
|
145
|
+
- [TanStack Query 공식 문서](https://tanstack.com/query/latest)
|
|
146
|
+
- [TanStack Query GitHub](https://github.com/tanstack/query)
|