@kood/claude-code 0.1.6 → 0.1.9
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 +109 -216
- package/package.json +8 -2
- package/templates/hono/CLAUDE.md +59 -328
- package/templates/hono/docs/architecture/architecture.md +93 -747
- package/templates/hono/docs/deployment/cloudflare.md +59 -513
- package/templates/hono/docs/deployment/docker.md +41 -356
- package/templates/hono/docs/deployment/index.md +54 -190
- package/templates/hono/docs/deployment/railway.md +36 -306
- package/templates/hono/docs/deployment/vercel.md +49 -434
- package/templates/hono/docs/library/ai-sdk/index.md +53 -290
- package/templates/hono/docs/library/ai-sdk/openrouter.md +19 -387
- package/templates/hono/docs/library/ai-sdk/providers.md +28 -394
- package/templates/hono/docs/library/ai-sdk/streaming.md +52 -353
- package/templates/hono/docs/library/ai-sdk/structured-output.md +63 -395
- package/templates/hono/docs/library/ai-sdk/tools.md +62 -431
- package/templates/hono/docs/library/hono/env-setup.md +24 -313
- package/templates/hono/docs/library/hono/error-handling.md +34 -295
- package/templates/hono/docs/library/hono/index.md +29 -121
- package/templates/hono/docs/library/hono/middleware.md +21 -188
- package/templates/hono/docs/library/hono/rpc.md +40 -341
- package/templates/hono/docs/library/hono/validation.md +35 -195
- package/templates/hono/docs/library/pino/index.md +42 -333
- package/templates/hono/docs/library/prisma/cloudflare-d1.md +64 -367
- package/templates/hono/docs/library/prisma/config.md +19 -260
- package/templates/hono/docs/library/prisma/index.md +67 -320
- package/templates/hono/docs/library/zod/index.md +53 -257
- package/templates/npx/CLAUDE.md +62 -274
- package/templates/npx/docs/references/patterns.md +160 -0
- package/templates/tanstack-start/CLAUDE.md +100 -256
- package/templates/tanstack-start/docs/architecture/architecture.md +44 -589
- package/templates/tanstack-start/docs/deployment/cloudflare.md +37 -424
- package/templates/tanstack-start/docs/deployment/index.md +57 -286
- package/templates/tanstack-start/docs/deployment/nitro.md +36 -318
- package/templates/tanstack-start/docs/deployment/railway.md +40 -409
- package/templates/tanstack-start/docs/deployment/vercel.md +43 -465
- package/templates/tanstack-start/docs/design/components.md +77 -311
- package/templates/tanstack-start/docs/design/index.md +113 -69
- package/templates/tanstack-start/docs/design/safe-area.md +51 -250
- package/templates/tanstack-start/docs/design/tailwind-setup.md +45 -359
- package/templates/tanstack-start/docs/guides/conventions.md +103 -0
- package/templates/tanstack-start/docs/guides/env-setup.md +34 -340
- package/templates/tanstack-start/docs/guides/getting-started.md +22 -209
- package/templates/tanstack-start/docs/guides/hooks.md +166 -0
- package/templates/tanstack-start/docs/guides/routes.md +166 -0
- package/templates/tanstack-start/docs/guides/services.md +143 -0
- package/templates/tanstack-start/docs/library/better-auth/2fa.md +27 -115
- package/templates/tanstack-start/docs/library/better-auth/advanced.md +22 -105
- package/templates/tanstack-start/docs/library/better-auth/index.md +17 -66
- package/templates/tanstack-start/docs/library/better-auth/plugins.md +11 -88
- package/templates/tanstack-start/docs/library/better-auth/session.md +12 -92
- package/templates/tanstack-start/docs/library/better-auth/setup.md +9 -91
- package/templates/tanstack-start/docs/library/prisma/cloudflare-d1.md +30 -358
- package/templates/tanstack-start/docs/library/prisma/config.md +27 -327
- package/templates/tanstack-start/docs/library/prisma/crud.md +46 -174
- package/templates/tanstack-start/docs/library/prisma/index.md +23 -113
- package/templates/tanstack-start/docs/library/prisma/relations.md +31 -153
- package/templates/tanstack-start/docs/library/prisma/schema.md +40 -217
- package/templates/tanstack-start/docs/library/prisma/setup.md +12 -112
- package/templates/tanstack-start/docs/library/prisma/transactions.md +20 -110
- package/templates/tanstack-start/docs/library/tanstack-query/index.md +26 -97
- package/templates/tanstack-start/docs/library/tanstack-query/invalidation.md +28 -107
- package/templates/tanstack-start/docs/library/tanstack-query/optimistic-updates.md +44 -146
- package/templates/tanstack-start/docs/library/tanstack-query/use-mutation.md +33 -127
- package/templates/tanstack-start/docs/library/tanstack-query/use-query.md +49 -149
- package/templates/tanstack-start/docs/library/tanstack-start/auth-patterns.md +19 -112
- package/templates/tanstack-start/docs/library/tanstack-start/index.md +33 -80
- package/templates/tanstack-start/docs/library/tanstack-start/middleware.md +28 -106
- package/templates/tanstack-start/docs/library/tanstack-start/routing.md +21 -118
- package/templates/tanstack-start/docs/library/tanstack-start/server-functions.md +34 -246
- package/templates/tanstack-start/docs/library/tanstack-start/setup.md +6 -39
- package/templates/tanstack-start/docs/library/zod/complex-types.md +32 -156
- package/templates/tanstack-start/docs/library/zod/index.md +31 -144
- package/templates/tanstack-start/docs/library/zod/transforms.md +20 -129
- package/templates/tanstack-start/docs/library/zod/validation.md +39 -155
- package/templates/hono/docs/commands/git.md +0 -145
- package/templates/hono/docs/mcp/context7.md +0 -106
- package/templates/hono/docs/mcp/index.md +0 -176
- package/templates/hono/docs/mcp/sequential-thinking.md +0 -101
- package/templates/hono/docs/mcp/serena.md +0 -269
- package/templates/hono/docs/mcp/sgrep.md +0 -105
- package/templates/hono/docs/skills/gemini-review/SKILL.md +0 -220
- package/templates/hono/docs/skills/gemini-review/references/checklists.md +0 -136
- package/templates/hono/docs/skills/gemini-review/references/prompt-templates.md +0 -303
- package/templates/npx/docs/commands/git.md +0 -145
- package/templates/npx/docs/mcp/index.md +0 -60
- package/templates/npx/docs/skills/gemini-review/SKILL.md +0 -220
- package/templates/npx/docs/skills/gemini-review/references/checklists.md +0 -134
- package/templates/npx/docs/skills/gemini-review/references/prompt-templates.md +0 -301
- package/templates/tanstack-start/docs/commands/git.md +0 -145
- package/templates/tanstack-start/docs/design/accessibility.md +0 -433
- package/templates/tanstack-start/docs/design/color.md +0 -235
- package/templates/tanstack-start/docs/design/spacing.md +0 -341
- package/templates/tanstack-start/docs/design/typography.md +0 -324
- package/templates/tanstack-start/docs/guides/best-practices.md +0 -950
- package/templates/tanstack-start/docs/guides/husky-lint-staged.md +0 -303
- package/templates/tanstack-start/docs/guides/prettier.md +0 -189
- package/templates/tanstack-start/docs/guides/project-templates.md +0 -710
- package/templates/tanstack-start/docs/library/tanstack-query/setup.md +0 -107
- package/templates/tanstack-start/docs/library/zod/basic-types.md +0 -186
- package/templates/tanstack-start/docs/mcp/context7.md +0 -204
- package/templates/tanstack-start/docs/mcp/index.md +0 -177
- package/templates/tanstack-start/docs/mcp/sequential-thinking.md +0 -180
- package/templates/tanstack-start/docs/mcp/serena.md +0 -269
- package/templates/tanstack-start/docs/mcp/sgrep.md +0 -174
- package/templates/tanstack-start/docs/skills/gemini-review/SKILL.md +0 -220
- package/templates/tanstack-start/docs/skills/gemini-review/references/checklists.md +0 -144
- package/templates/tanstack-start/docs/skills/gemini-review/references/prompt-templates.md +0 -292
|
@@ -1,149 +1,49 @@
|
|
|
1
1
|
# Prisma - 설치 및 설정
|
|
2
2
|
|
|
3
|
-
> **상위 문서**: [Prisma](./index.md)
|
|
4
|
-
|
|
5
3
|
## 설치
|
|
6
4
|
|
|
7
5
|
```bash
|
|
8
|
-
# npm
|
|
9
|
-
npm install @prisma/client@7
|
|
10
|
-
npm install -D prisma@7
|
|
11
|
-
|
|
12
|
-
# yarn
|
|
13
6
|
yarn add @prisma/client@7
|
|
14
7
|
yarn add -D prisma@7
|
|
15
|
-
|
|
16
|
-
# bun
|
|
17
|
-
bun add @prisma/client@7
|
|
18
|
-
bun add prisma@7 --dev
|
|
19
|
-
|
|
20
|
-
# 초기화
|
|
21
8
|
npx prisma init
|
|
22
9
|
```
|
|
23
10
|
|
|
24
|
-
## v6
|
|
25
|
-
|
|
26
|
-
```bash
|
|
27
|
-
# npm
|
|
28
|
-
npm install @prisma/client@7
|
|
29
|
-
npm install -D prisma@7
|
|
30
|
-
|
|
31
|
-
# yarn
|
|
32
|
-
yarn up prisma@7 @prisma/client@7
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
### Generator 변경 (필수)
|
|
11
|
+
## v6 → v7 업그레이드
|
|
36
12
|
|
|
37
13
|
```prisma
|
|
38
|
-
//
|
|
14
|
+
// v6 (이전)
|
|
39
15
|
generator client {
|
|
40
16
|
provider = "prisma-client-js"
|
|
41
17
|
}
|
|
42
18
|
|
|
43
|
-
//
|
|
19
|
+
// v7 (필수)
|
|
44
20
|
generator client {
|
|
45
|
-
provider = "prisma-client"
|
|
46
|
-
output = "../generated/prisma"
|
|
21
|
+
provider = "prisma-client"
|
|
22
|
+
output = "../generated/prisma"
|
|
47
23
|
}
|
|
48
24
|
```
|
|
49
25
|
|
|
50
|
-
## Prisma Client
|
|
51
|
-
|
|
52
|
-
### 기본 설정
|
|
26
|
+
## Prisma Client
|
|
53
27
|
|
|
54
28
|
```typescript
|
|
55
29
|
// lib/prisma.ts
|
|
56
|
-
import { PrismaClient } from './generated/prisma' // output 경로에서 import
|
|
57
|
-
|
|
58
|
-
const globalForPrisma = globalThis as unknown as {
|
|
59
|
-
prisma: PrismaClient | undefined
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export const prisma =
|
|
63
|
-
globalForPrisma.prisma ??
|
|
64
|
-
new PrismaClient({
|
|
65
|
-
log: ['query'],
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
### 로깅 설정
|
|
72
|
-
|
|
73
|
-
```typescript
|
|
74
30
|
import { PrismaClient } from './generated/prisma'
|
|
75
31
|
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
prisma.$on('query', (e) => {
|
|
81
|
-
console.log('Query:', e.query)
|
|
82
|
-
console.log('Params:', e.params)
|
|
83
|
-
console.log('Duration:', e.duration, 'ms')
|
|
84
|
-
})
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
### QueryEvent 타입
|
|
88
|
-
|
|
89
|
-
```typescript
|
|
90
|
-
type QueryEvent = {
|
|
91
|
-
timestamp: Date
|
|
92
|
-
query: string
|
|
93
|
-
params: string
|
|
94
|
-
duration: number
|
|
95
|
-
target: string
|
|
96
|
-
}
|
|
32
|
+
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient | undefined }
|
|
33
|
+
export const prisma = globalForPrisma.prisma ?? new PrismaClient({ log: ['query'] })
|
|
34
|
+
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
|
|
97
35
|
```
|
|
98
36
|
|
|
99
|
-
## TanStack Start
|
|
37
|
+
## TanStack Start 연동
|
|
100
38
|
|
|
101
39
|
```typescript
|
|
102
|
-
// lib/prisma.ts
|
|
103
|
-
import { PrismaClient } from './generated/prisma'
|
|
104
|
-
|
|
105
|
-
const globalForPrisma = globalThis as unknown as {
|
|
106
|
-
prisma: PrismaClient | undefined
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export const prisma =
|
|
110
|
-
globalForPrisma.prisma ?? new PrismaClient()
|
|
111
|
-
|
|
112
|
-
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
|
|
113
|
-
|
|
114
|
-
// Server Function에서 사용
|
|
115
40
|
import { createServerFn } from '@tanstack/react-start'
|
|
116
41
|
import { prisma } from '@/lib/prisma'
|
|
117
42
|
|
|
118
43
|
export const getUsers = createServerFn({ method: 'GET' })
|
|
119
|
-
.handler(async () => {
|
|
120
|
-
return prisma.user.findMany({
|
|
121
|
-
include: { posts: true },
|
|
122
|
-
})
|
|
123
|
-
})
|
|
44
|
+
.handler(async () => prisma.user.findMany({ include: { posts: true } }))
|
|
124
45
|
|
|
125
46
|
export const createUser = createServerFn({ method: 'POST' })
|
|
126
47
|
.inputValidator((data: { email: string; name: string }) => data)
|
|
127
|
-
.handler(async ({ data }) => {
|
|
128
|
-
return prisma.user.create({ data })
|
|
129
|
-
})
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
## 마이그레이션
|
|
133
|
-
|
|
134
|
-
```bash
|
|
135
|
-
# 개발 환경 마이그레이션
|
|
136
|
-
npx prisma migrate dev --name init
|
|
137
|
-
|
|
138
|
-
# 프로덕션 마이그레이션
|
|
139
|
-
npx prisma migrate deploy
|
|
140
|
-
|
|
141
|
-
# 스키마 동기화 (개발용)
|
|
142
|
-
npx prisma db push
|
|
143
|
-
|
|
144
|
-
# Prisma Client 생성
|
|
145
|
-
npx prisma generate
|
|
146
|
-
|
|
147
|
-
# Prisma Studio (GUI)
|
|
148
|
-
npx prisma studio
|
|
48
|
+
.handler(async ({ data }) => prisma.user.create({ data }))
|
|
149
49
|
```
|
|
@@ -1,140 +1,50 @@
|
|
|
1
1
|
# Prisma - 트랜잭션
|
|
2
2
|
|
|
3
|
-
> **상위 문서**: [Prisma](./index.md)
|
|
4
|
-
|
|
5
3
|
## 배열 기반 트랜잭션
|
|
6
4
|
|
|
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
|
-
## 모든 데이터 삭제
|
|
5
|
+
하나라도 실패하면 모두 롤백.
|
|
44
6
|
|
|
45
7
|
```typescript
|
|
46
|
-
const deletePosts = prisma.post.deleteMany()
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
// 순서대로 실행 (deleteUsers가 마지막)
|
|
51
|
-
await prisma.$transaction([deleteProfile, deletePosts, deleteUsers])
|
|
8
|
+
const deletePosts = prisma.post.deleteMany({ where: { authorId: 7 } })
|
|
9
|
+
const deleteUser = prisma.user.delete({ where: { id: 7 } })
|
|
10
|
+
await prisma.$transaction([deletePosts, deleteUser])
|
|
52
11
|
```
|
|
53
12
|
|
|
54
13
|
## 인터랙티브 트랜잭션
|
|
55
14
|
|
|
56
|
-
|
|
15
|
+
복잡한 로직, 조건부 처리.
|
|
57
16
|
|
|
58
17
|
```typescript
|
|
59
18
|
const result = await prisma.$transaction(async (tx) => {
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
})
|
|
19
|
+
const sender = await tx.account.findUnique({ where: { id: senderId } })
|
|
20
|
+
if (!sender || sender.balance < amount) throw new Error('Insufficient balance')
|
|
74
21
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
where: { id: recipientId },
|
|
78
|
-
data: { balance: { increment: amount } },
|
|
79
|
-
})
|
|
22
|
+
await tx.account.update({ where: { id: senderId }, data: { balance: { decrement: amount } } })
|
|
23
|
+
await tx.account.update({ where: { id: recipientId }, data: { balance: { increment: amount } } })
|
|
80
24
|
|
|
81
|
-
return {
|
|
25
|
+
return { success: true }
|
|
82
26
|
})
|
|
83
27
|
```
|
|
84
28
|
|
|
85
|
-
##
|
|
29
|
+
## 옵션
|
|
86
30
|
|
|
87
31
|
```typescript
|
|
88
|
-
await prisma.$transaction(
|
|
89
|
-
|
|
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
|
-
])
|
|
32
|
+
await prisma.$transaction(async (tx) => { ... }, {
|
|
33
|
+
maxWait: 5000, // 최대 대기 (ms)
|
|
34
|
+
timeout: 10000, // 타임아웃 (ms)
|
|
35
|
+
isolationLevel: 'Serializable', // ReadUncommitted | ReadCommitted | RepeatableRead | Serializable
|
|
36
|
+
})
|
|
120
37
|
```
|
|
121
38
|
|
|
122
|
-
##
|
|
39
|
+
## 에러 처리
|
|
123
40
|
|
|
124
41
|
```typescript
|
|
125
42
|
try {
|
|
126
43
|
await prisma.$transaction(async (tx) => {
|
|
127
|
-
await tx.user.create({ data: { email
|
|
128
|
-
|
|
129
|
-
// 의도적 에러 발생 시 전체 롤백
|
|
130
|
-
if (someCondition) {
|
|
131
|
-
throw new Error('Rollback transaction')
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
await tx.post.create({ data: { title: 'Hello', authorId: 1 } })
|
|
44
|
+
await tx.user.create({ data: { email } })
|
|
45
|
+
if (someCondition) throw new Error('Rollback')
|
|
135
46
|
})
|
|
136
47
|
} catch (error) {
|
|
137
|
-
|
|
138
|
-
// 트랜잭션 내 모든 변경사항이 롤백됨
|
|
48
|
+
// 전체 롤백됨
|
|
139
49
|
}
|
|
140
50
|
```
|
|
@@ -1,28 +1,29 @@
|
|
|
1
1
|
# TanStack Query
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> 5.x | React Data Fetching Library
|
|
4
|
+
|
|
5
|
+
@use-query.md
|
|
6
|
+
@use-mutation.md
|
|
7
|
+
@invalidation.md
|
|
8
|
+
@optimistic-updates.md
|
|
4
9
|
|
|
5
10
|
---
|
|
6
11
|
|
|
7
|
-
##
|
|
12
|
+
## Quick Reference
|
|
8
13
|
|
|
9
14
|
```typescript
|
|
10
|
-
// useQuery
|
|
15
|
+
// useQuery
|
|
11
16
|
const { data, isLoading, error } = useQuery({
|
|
12
17
|
queryKey: ['users'],
|
|
13
18
|
queryFn: () => getUsers(),
|
|
14
19
|
})
|
|
15
20
|
|
|
16
|
-
// useMutation
|
|
21
|
+
// useMutation + invalidation
|
|
17
22
|
const queryClient = useQueryClient()
|
|
18
23
|
const mutation = useMutation({
|
|
19
24
|
mutationFn: createUser,
|
|
20
|
-
onSuccess: () => {
|
|
21
|
-
queryClient.invalidateQueries({ queryKey: ['users'] })
|
|
22
|
-
},
|
|
25
|
+
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['users'] }),
|
|
23
26
|
})
|
|
24
|
-
|
|
25
|
-
// mutation 실행
|
|
26
27
|
mutation.mutate({ email, name })
|
|
27
28
|
|
|
28
29
|
// Optimistic Update
|
|
@@ -40,47 +41,20 @@ const mutation = useMutation({
|
|
|
40
41
|
})
|
|
41
42
|
```
|
|
42
43
|
|
|
43
|
-
###
|
|
44
|
+
### 설정
|
|
44
45
|
|
|
45
46
|
```tsx
|
|
46
47
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|
47
48
|
|
|
48
|
-
const queryClient = new QueryClient(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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()
|
|
49
|
+
const queryClient = new QueryClient({
|
|
50
|
+
defaultOptions: {
|
|
51
|
+
queries: {
|
|
52
|
+
staleTime: 1000 * 60 * 5, // 5분
|
|
53
|
+
gcTime: 1000 * 60 * 30, // 30분 (이전 cacheTime)
|
|
54
|
+
retry: 3,
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
})
|
|
84
58
|
|
|
85
59
|
function App() {
|
|
86
60
|
return (
|
|
@@ -91,56 +65,11 @@ function App() {
|
|
|
91
65
|
}
|
|
92
66
|
```
|
|
93
67
|
|
|
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
|
-
})
|
|
68
|
+
### Query Keys 패턴
|
|
134
69
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
70
|
+
```typescript
|
|
71
|
+
['todos'] // 단순
|
|
72
|
+
['todo', { id: 5 }] // 파라미터
|
|
73
|
+
['todos', 'list', { filters }] // 계층적
|
|
74
|
+
['todos', 'detail', todoId]
|
|
141
75
|
```
|
|
142
|
-
|
|
143
|
-
## 참고 자료
|
|
144
|
-
|
|
145
|
-
- [TanStack Query 공식 문서](https://tanstack.com/query/latest)
|
|
146
|
-
- [TanStack Query GitHub](https://github.com/tanstack/query)
|
|
@@ -1,146 +1,67 @@
|
|
|
1
1
|
# TanStack Query - Query 무효화
|
|
2
2
|
|
|
3
|
-
> **상위 문서**: [TanStack Query](./index.md)
|
|
4
|
-
|
|
5
3
|
## 기본 무효화
|
|
6
4
|
|
|
7
5
|
```typescript
|
|
8
6
|
const queryClient = useQueryClient()
|
|
9
7
|
|
|
10
|
-
// 단일
|
|
11
|
-
|
|
8
|
+
// 단일
|
|
9
|
+
queryClient.invalidateQueries({ queryKey: ['todos'] })
|
|
12
10
|
|
|
13
|
-
// 다중
|
|
11
|
+
// 다중
|
|
14
12
|
await Promise.all([
|
|
15
13
|
queryClient.invalidateQueries({ queryKey: ['todos'] }),
|
|
16
14
|
queryClient.invalidateQueries({ queryKey: ['reminders'] }),
|
|
17
15
|
])
|
|
18
16
|
|
|
19
|
-
//
|
|
17
|
+
// 전체
|
|
20
18
|
queryClient.invalidateQueries()
|
|
21
19
|
```
|
|
22
20
|
|
|
23
|
-
##
|
|
21
|
+
## 옵션
|
|
24
22
|
|
|
25
23
|
```typescript
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
throwOnError: false,
|
|
34
|
-
cancelRefetch: true,
|
|
35
|
-
}
|
|
36
|
-
)
|
|
24
|
+
queryClient.invalidateQueries({
|
|
25
|
+
queryKey: ['posts'],
|
|
26
|
+
exact: true, // 정확한 키 매칭만
|
|
27
|
+
refetchType: 'active', // 'active'(기본) | 'inactive' | 'all' | 'none'
|
|
28
|
+
})
|
|
37
29
|
```
|
|
38
30
|
|
|
39
|
-
|
|
31
|
+
| refetchType | 설명 |
|
|
32
|
+
|-------------|------|
|
|
33
|
+
| active | 렌더링 중인 쿼리만 재조회 (기본) |
|
|
34
|
+
| inactive | 비활성 쿼리만 |
|
|
35
|
+
| all | 모든 매칭 쿼리 |
|
|
36
|
+
| none | 무효화만, 재조회 안함 |
|
|
40
37
|
|
|
41
|
-
|
|
42
|
-
- `inactive`: 비활성 쿼리만 재조회
|
|
43
|
-
- `all`: 모든 매칭 쿼리 재조회
|
|
44
|
-
- `none`: 무효화만 하고 재조회 안 함
|
|
45
|
-
|
|
46
|
-
## Query Key로 무효화
|
|
38
|
+
## Query Key 매칭
|
|
47
39
|
|
|
48
40
|
```typescript
|
|
49
|
-
//
|
|
41
|
+
// prefix 매칭 (todos로 시작하는 모든 쿼리)
|
|
50
42
|
queryClient.invalidateQueries({ queryKey: ['todos'] })
|
|
51
43
|
|
|
52
|
-
//
|
|
53
|
-
queryClient.invalidateQueries({
|
|
54
|
-
queryKey: ['todos', 'list'],
|
|
55
|
-
exact: true,
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
// ['todos', { type: 'done' }] 무효화
|
|
59
|
-
queryClient.invalidateQueries({
|
|
60
|
-
queryKey: ['todos', { type: 'done' }],
|
|
61
|
-
})
|
|
44
|
+
// 정확한 매칭
|
|
45
|
+
queryClient.invalidateQueries({ queryKey: ['todos', 'list'], exact: true })
|
|
62
46
|
```
|
|
63
47
|
|
|
64
|
-
## Mutation과 함께
|
|
65
|
-
|
|
66
|
-
### onSuccess에서 무효화
|
|
48
|
+
## Mutation과 함께
|
|
67
49
|
|
|
68
50
|
```tsx
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const mutation = useMutation({
|
|
75
|
-
mutationFn: addTodo,
|
|
76
|
-
onSuccess: async () => {
|
|
77
|
-
// 단일 쿼리 무효화
|
|
78
|
-
await queryClient.invalidateQueries({ queryKey: ['todos'] })
|
|
79
|
-
|
|
80
|
-
// 여러 쿼리 무효화
|
|
81
|
-
await Promise.all([
|
|
82
|
-
queryClient.invalidateQueries({ queryKey: ['todos'] }),
|
|
83
|
-
queryClient.invalidateQueries({ queryKey: ['reminders'] }),
|
|
84
|
-
])
|
|
85
|
-
},
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
return (
|
|
89
|
-
<button onClick={() => mutation.mutate(newTodo)}>
|
|
90
|
-
Add Todo
|
|
91
|
-
</button>
|
|
92
|
-
)
|
|
93
|
-
}
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
### onSettled에서 무효화
|
|
97
|
-
|
|
98
|
-
```tsx
|
|
99
|
-
const mutation = useMutation({
|
|
100
|
-
mutationFn: updateTodo,
|
|
101
|
-
onSettled: () => {
|
|
102
|
-
// 성공/실패 관계없이 무효화
|
|
103
|
-
queryClient.invalidateQueries({ queryKey: ['todos'] })
|
|
104
|
-
},
|
|
51
|
+
useMutation({
|
|
52
|
+
mutationFn: addTodo,
|
|
53
|
+
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
|
|
54
|
+
// 또는 onSettled (성공/실패 무관)
|
|
55
|
+
onSettled: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
|
|
105
56
|
})
|
|
106
57
|
```
|
|
107
58
|
|
|
108
59
|
## 캐시 직접 업데이트 vs 무효화
|
|
109
60
|
|
|
110
61
|
```tsx
|
|
111
|
-
//
|
|
62
|
+
// 직접 업데이트 (더 빠름)
|
|
112
63
|
queryClient.setQueryData(['todos'], (old) => [...old, newTodo])
|
|
113
64
|
|
|
114
|
-
//
|
|
115
|
-
queryClient.invalidateQueries({ queryKey: ['todos'] })
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
## 조건부 무효화
|
|
119
|
-
|
|
120
|
-
```tsx
|
|
121
|
-
const mutation = useMutation({
|
|
122
|
-
mutationFn: updateTodo,
|
|
123
|
-
onSuccess: (data, variables) => {
|
|
124
|
-
// 특정 조건에서만 무효화
|
|
125
|
-
if (variables.category === 'important') {
|
|
126
|
-
queryClient.invalidateQueries({ queryKey: ['todos', 'important'] })
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// 항상 개별 항목은 무효화
|
|
130
|
-
queryClient.invalidateQueries({ queryKey: ['todo', variables.id] })
|
|
131
|
-
},
|
|
132
|
-
})
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
## prefetch와 조합
|
|
136
|
-
|
|
137
|
-
```tsx
|
|
138
|
-
// 무효화 전에 새 데이터 프리페치
|
|
139
|
-
await queryClient.prefetchQuery({
|
|
140
|
-
queryKey: ['todos'],
|
|
141
|
-
queryFn: fetchTodos,
|
|
142
|
-
})
|
|
143
|
-
|
|
144
|
-
// 이후 무효화 시 이미 새 데이터가 있음
|
|
65
|
+
// 무효화 (서버 데이터 보장)
|
|
145
66
|
queryClient.invalidateQueries({ queryKey: ['todos'] })
|
|
146
67
|
```
|