@kood/claude-code 0.6.6 → 0.7.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.
Files changed (170) hide show
  1. package/dist/index.js +7 -1
  2. package/package.json +1 -1
  3. package/templates/.claude/agents/analyst.md +5 -0
  4. package/templates/.claude/agents/architect.md +5 -0
  5. package/templates/.claude/agents/build-fixer.md +1 -0
  6. package/templates/.claude/agents/code-reviewer.md +1 -0
  7. package/templates/.claude/agents/critic.md +4 -0
  8. package/templates/.claude/agents/deep-executor.md +1 -0
  9. package/templates/.claude/agents/dependency-manager.md +2 -0
  10. package/templates/.claude/agents/deployment-validator.md +2 -0
  11. package/templates/.claude/agents/designer.md +2 -0
  12. package/templates/.claude/agents/document-writer.md +3 -0
  13. package/templates/.claude/agents/explore.md +1 -0
  14. package/templates/.claude/agents/git-operator.md +2 -0
  15. package/templates/.claude/agents/implementation-executor.md +2 -0
  16. package/templates/.claude/agents/ko-to-en-translator.md +3 -0
  17. package/templates/.claude/agents/lint-fixer.md +2 -0
  18. package/templates/.claude/agents/planner.md +3 -0
  19. package/templates/.claude/agents/pm.md +349 -0
  20. package/templates/.claude/agents/qa-tester.md +1 -0
  21. package/templates/.claude/agents/refactor-advisor.md +4 -0
  22. package/templates/.claude/agents/researcher.md +9 -1
  23. package/templates/.claude/agents/scientist.md +1 -0
  24. package/templates/.claude/agents/security-reviewer.md +1 -0
  25. package/templates/.claude/agents/tdd-guide.md +1 -0
  26. package/templates/.claude/agents/vision.md +1 -0
  27. package/templates/.claude/instructions/agent-patterns/agent-teams-usage.md +376 -0
  28. package/templates/.claude/instructions/sourcing/reliable-search.md +49 -2
  29. package/templates/.claude/scripts/agent-teams/check-availability.sh +238 -0
  30. package/templates/.claude/scripts/agent-teams/setup-tmux.sh +125 -0
  31. package/templates/.claude/skills/agent-teams-setup/SKILL.md +460 -0
  32. package/templates/.claude/skills/brainstorm/SKILL.md +1 -0
  33. package/templates/.claude/skills/bug-fix/SKILL.md +1 -0
  34. package/templates/.claude/skills/crawler/SKILL.md +2 -0
  35. package/templates/.claude/skills/docs-creator/SKILL.md +1 -0
  36. package/templates/.claude/skills/docs-fetch/SKILL.md +6 -4
  37. package/templates/.claude/skills/docs-refactor/SKILL.md +1 -0
  38. package/templates/.claude/skills/elon-musk/SKILL.md +1 -0
  39. package/templates/.claude/skills/execute/SKILL.md +1 -0
  40. package/templates/.claude/skills/feedback/SKILL.md +1 -0
  41. package/templates/.claude/skills/figma-to-code/SKILL.md +1 -0
  42. package/templates/.claude/skills/genius-thinking/SKILL.md +1 -0
  43. package/templates/.claude/skills/global-uiux-design/SKILL.md +1 -0
  44. package/templates/.claude/skills/korea-uiux-design/SKILL.md +1 -0
  45. package/templates/.claude/skills/nextjs-react-best-practices/SKILL.md +1 -0
  46. package/templates/.claude/skills/plan/SKILL.md +1 -0
  47. package/templates/.claude/skills/prd/SKILL.md +1 -0
  48. package/templates/.claude/skills/project-optimizer/AGENTS.md +275 -0
  49. package/templates/.claude/skills/project-optimizer/SKILL.md +375 -0
  50. package/templates/.claude/skills/project-optimizer/rules/arch-config-centralize.md +66 -0
  51. package/templates/.claude/skills/project-optimizer/rules/arch-hot-path.md +35 -0
  52. package/templates/.claude/skills/project-optimizer/rules/arch-interface-segregation.md +51 -0
  53. package/templates/.claude/skills/project-optimizer/rules/arch-module-boundary.md +42 -0
  54. package/templates/.claude/skills/project-optimizer/rules/build-cache.md +57 -0
  55. package/templates/.claude/skills/project-optimizer/rules/build-code-split.md +56 -0
  56. package/templates/.claude/skills/project-optimizer/rules/build-incremental.md +65 -0
  57. package/templates/.claude/skills/project-optimizer/rules/build-minify.md +61 -0
  58. package/templates/.claude/skills/project-optimizer/rules/build-tree-shake.md +60 -0
  59. package/templates/.claude/skills/project-optimizer/rules/code-complexity.md +65 -0
  60. package/templates/.claude/skills/project-optimizer/rules/code-dead-elimination.md +32 -0
  61. package/templates/.claude/skills/project-optimizer/rules/code-duplication.md +54 -0
  62. package/templates/.claude/skills/project-optimizer/rules/code-error-handling.md +75 -0
  63. package/templates/.claude/skills/project-optimizer/rules/code-naming.md +52 -0
  64. package/templates/.claude/skills/project-optimizer/rules/concurrency-defer-await.md +54 -0
  65. package/templates/.claude/skills/project-optimizer/rules/concurrency-parallel.md +90 -0
  66. package/templates/.claude/skills/project-optimizer/rules/concurrency-pipeline.md +68 -0
  67. package/templates/.claude/skills/project-optimizer/rules/concurrency-pool.md +68 -0
  68. package/templates/.claude/skills/project-optimizer/rules/deps-lightweight-alt.md +37 -0
  69. package/templates/.claude/skills/project-optimizer/rules/deps-peer-align.md +44 -0
  70. package/templates/.claude/skills/project-optimizer/rules/deps-security-audit.md +45 -0
  71. package/templates/.claude/skills/project-optimizer/rules/deps-unused-removal.md +25 -0
  72. package/templates/.claude/skills/project-optimizer/rules/deps-version-pin.md +40 -0
  73. package/templates/.claude/skills/project-optimizer/rules/dx-ci-speed.md +47 -0
  74. package/templates/.claude/skills/project-optimizer/rules/dx-dev-server.md +35 -0
  75. package/templates/.claude/skills/project-optimizer/rules/dx-lint-config.md +36 -0
  76. package/templates/.claude/skills/project-optimizer/rules/dx-test-coverage.md +34 -0
  77. package/templates/.claude/skills/project-optimizer/rules/dx-type-safety.md +49 -0
  78. package/templates/.claude/skills/project-optimizer/rules/io-batch-queries.md +67 -0
  79. package/templates/.claude/skills/project-optimizer/rules/io-cache-layer.md +67 -0
  80. package/templates/.claude/skills/project-optimizer/rules/io-connection-reuse.md +67 -0
  81. package/templates/.claude/skills/project-optimizer/rules/io-serialize-minimal.md +61 -0
  82. package/templates/.claude/skills/project-optimizer/rules/io-stream.md +75 -0
  83. package/templates/.claude/skills/project-optimizer/rules/memory-bounded-cache.md +65 -0
  84. package/templates/.claude/skills/project-optimizer/rules/memory-large-data.md +64 -0
  85. package/templates/.claude/skills/project-optimizer/rules/memory-lazy-init.md +78 -0
  86. package/templates/.claude/skills/project-optimizer/rules/memory-leak-prevention.md +79 -0
  87. package/templates/.claude/skills/project-optimizer/rules/memory-pool-reuse.md +70 -0
  88. package/templates/.claude/skills/ralph/SKILL.md +1 -0
  89. package/templates/.claude/skills/refactor/SKILL.md +1 -0
  90. package/templates/.claude/skills/research/SKILL.md +1 -0
  91. package/templates/.claude/skills/sql-optimizer/SKILL.md +438 -0
  92. package/templates/.claude/skills/sql-optimizer/orm-patterns.md +218 -0
  93. package/templates/.claude/skills/startup-validator/SKILL.md +1 -0
  94. package/templates/.claude/skills/tanstack-start-react-best-practices/AGENTS.md +53 -14
  95. package/templates/.claude/skills/tanstack-start-react-best-practices/SKILL.md +94 -27
  96. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/bundle-defer-third-party.md +42 -19
  97. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/client-optimistic-updates.md +109 -0
  98. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/client-suspense-query.md +74 -0
  99. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/client-use-hook.md +81 -0
  100. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/rerender-react-compiler.md +81 -0
  101. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/routing-beforeload-auth.md +121 -0
  102. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/routing-file-conventions.md +104 -0
  103. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/routing-link-navigation.md +119 -0
  104. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/routing-nested-layouts.md +155 -0
  105. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/routing-path-params.md +89 -0
  106. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/routing-pending-component.md +110 -0
  107. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/routing-preload-strategy.md +91 -0
  108. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/routing-router-context.md +120 -0
  109. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/routing-search-params.md +114 -0
  110. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/server-deferred-data.md +1 -1
  111. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/server-error-boundaries.md +79 -0
  112. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/server-middleware.md +85 -0
  113. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/server-serialization.md +56 -21
  114. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/server-streaming.md +84 -0
  115. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/server-validator.md +71 -0
  116. package/templates/.claude/skills/tauri-react-best-practices/AGENTS.md +527 -0
  117. package/templates/.claude/skills/tauri-react-best-practices/SKILL.md +571 -0
  118. package/templates/.claude/skills/tauri-react-best-practices/rules/bundle-barrel-imports.md +140 -0
  119. package/templates/.claude/skills/tauri-react-best-practices/rules/bundle-cargo-profile.md +96 -0
  120. package/templates/.claude/skills/tauri-react-best-practices/rules/bundle-frontend-treeshake.md +242 -0
  121. package/templates/.claude/skills/tauri-react-best-practices/rules/bundle-lazy-components.md +255 -0
  122. package/templates/.claude/skills/tauri-react-best-practices/rules/bundle-remove-unused-commands.md +160 -0
  123. package/templates/.claude/skills/tauri-react-best-practices/rules/deploy-ci-pipeline.md +269 -0
  124. package/templates/.claude/skills/tauri-react-best-practices/rules/deploy-signing.md +207 -0
  125. package/templates/.claude/skills/tauri-react-best-practices/rules/deploy-updater.md +226 -0
  126. package/templates/.claude/skills/tauri-react-best-practices/rules/ipc-async-commands.md +172 -0
  127. package/templates/.claude/skills/tauri-react-best-practices/rules/ipc-batch-commands.md +133 -0
  128. package/templates/.claude/skills/tauri-react-best-practices/rules/ipc-binary-response.md +198 -0
  129. package/templates/.claude/skills/tauri-react-best-practices/rules/ipc-channel-streaming.md +186 -0
  130. package/templates/.claude/skills/tauri-react-best-practices/rules/ipc-error-handling.md +250 -0
  131. package/templates/.claude/skills/tauri-react-best-practices/rules/ipc-type-safe.md +227 -0
  132. package/templates/.claude/skills/tauri-react-best-practices/rules/perf-derived-state.md +231 -0
  133. package/templates/.claude/skills/tauri-react-best-practices/rules/perf-functional-setstate.md +191 -0
  134. package/templates/.claude/skills/tauri-react-best-practices/rules/perf-index-maps.md +276 -0
  135. package/templates/.claude/skills/tauri-react-best-practices/rules/perf-lazy-state-init.md +196 -0
  136. package/templates/.claude/skills/tauri-react-best-practices/rules/plugin-lifecycle.md +265 -0
  137. package/templates/.claude/skills/tauri-react-best-practices/rules/plugin-mobile-compat.md +199 -0
  138. package/templates/.claude/skills/tauri-react-best-practices/rules/plugin-permission-scope.md +193 -0
  139. package/templates/.claude/skills/tauri-react-best-practices/rules/react-error-boundary.md +239 -0
  140. package/templates/.claude/skills/tauri-react-best-practices/rules/react-event-listener.md +151 -0
  141. package/templates/.claude/skills/tauri-react-best-practices/rules/react-file-src.md +155 -0
  142. package/templates/.claude/skills/tauri-react-best-practices/rules/react-invoke-hook.md +139 -0
  143. package/templates/.claude/skills/tauri-react-best-practices/rules/react-optimistic-update.md +211 -0
  144. package/templates/.claude/skills/tauri-react-best-practices/rules/security-capability-split.md +205 -0
  145. package/templates/.claude/skills/tauri-react-best-practices/rules/security-csp.md +207 -0
  146. package/templates/.claude/skills/tauri-react-best-practices/rules/security-least-privilege.md +106 -0
  147. package/templates/.claude/skills/tauri-react-best-practices/rules/security-no-wildcard.md +253 -0
  148. package/templates/.claude/skills/tauri-react-best-practices/rules/security-scope-paths.md +160 -0
  149. package/templates/.claude/skills/tauri-react-best-practices/rules/state-async-mutex.md +270 -0
  150. package/templates/.claude/skills/tauri-react-best-practices/rules/state-mutex-pattern.md +265 -0
  151. package/templates/.claude/skills/tauri-react-best-practices/rules/state-react-sync.md +375 -0
  152. package/templates/.claude/skills/tauri-react-best-practices/rules/state-single-container.md +275 -0
  153. package/templates/tanstack-start/docs/architecture.md +238 -167
  154. package/templates/tanstack-start/docs/library/tanstack-router/error-handling.md +777 -38
  155. package/templates/tanstack-start/docs/library/tanstack-router/hooks.md +549 -37
  156. package/templates/tanstack-start/docs/library/tanstack-router/index.md +895 -111
  157. package/templates/tanstack-start/docs/library/tanstack-router/navigation.md +641 -43
  158. package/templates/tanstack-start/docs/library/tanstack-router/route-context.md +889 -38
  159. package/templates/tanstack-start/docs/library/tanstack-router/search-params.md +891 -29
  160. package/templates/tanstack-start/docs/library/tanstack-start/auth-patterns.md +972 -36
  161. package/templates/tanstack-start/docs/library/tanstack-start/index.md +1525 -881
  162. package/templates/tanstack-start/docs/library/tanstack-start/middleware.md +1099 -20
  163. package/templates/tanstack-start/docs/library/tanstack-start/routing.md +796 -30
  164. package/templates/tanstack-start/docs/library/tanstack-start/server-functions.md +953 -35
  165. package/templates/tanstack-start/docs/library/tanstack-start/setup.md +371 -15
  166. package/templates/tauri/CLAUDE.md +189 -0
  167. package/templates/tauri/docs/guides/distribution.md +261 -0
  168. package/templates/tauri/docs/guides/getting-started.md +302 -0
  169. package/templates/tauri/docs/guides/mobile.md +288 -0
  170. package/templates/tauri/docs/library/tauri/index.md +510 -0
@@ -1,75 +1,993 @@
1
1
  # TanStack Start - Server Functions
2
2
 
3
- 서버에서만 실행되는 타입 안전한 함수.
3
+ > 타입 안전한 백엔드 API
4
4
 
5
- ## ⚠️ 필수: TanStack Query 사용
5
+ ---
6
6
 
7
- 클라이언트 호출 시 반드시 useQuery/useMutation 사용.
8
- - 자동 캐싱, 중복 요청 제거, 로딩/에러 상태 관리, invalidateQueries 동기화
7
+ <overview>
9
8
 
10
- ## 기본 패턴
9
+ ## Server Function이란?
10
+
11
+ Server Function은 서버에서만 실행되는 함수로, 클라이언트에서 타입 안전하게 호출할 수 있습니다.
12
+
13
+ | 특징 | 설명 |
14
+ |------|------|
15
+ | **타입 안전성** | 입출력 타입이 TypeScript로 자동 추론됨 |
16
+ | **직렬화** | JSON 자동 직렬화/역직렬화 |
17
+ | **검증** | Zod로 입력값 검증 (.inputValidator()) |
18
+ | **미들웨어** | 인증, 로깅, 권한 체크 등 (.middleware()) |
19
+ | **HTTP 메서드** | GET, POST, PUT, PATCH, DELETE 지원 |
20
+ | **클라이언트 호출** | TanStack Query (useQuery/useMutation) 필수 |
21
+
22
+ </overview>
23
+
24
+ ---
25
+
26
+ <http_methods>
27
+
28
+ ## HTTP 메서드별 사용 패턴
29
+
30
+ | 메서드 | 사용 | inputValidator | middleware |
31
+ |--------|------|---------------|-----------|
32
+ | **GET** | 데이터 조회 | 선택 | 인증 시 필수 |
33
+ | **POST** | 데이터 생성 | 필수 | 인증 시 필수 |
34
+ | **PUT** | 전체 수정 | 필수 | 인증 시 필수 |
35
+ | **PATCH** | 부분 수정 | 필수 | 인증 시 필수 |
36
+ | **DELETE** | 데이터 삭제 | 선택 | 인증 시 필수 |
37
+
38
+ </http_methods>
39
+
40
+ ---
41
+
42
+ <get_method>
43
+
44
+ ## GET: 데이터 조회
45
+
46
+ ### 기본 GET
11
47
 
12
48
  ```typescript
13
- // GET
49
+ // ✅ 간단한 조회
14
50
  export const getUsers = createServerFn({ method: 'GET' })
15
- .handler(async () => prisma.user.findMany())
51
+ .handler(async (): Promise<User[]> => {
52
+ return prisma.user.findMany()
53
+ })
54
+
55
+ // ✅ GET + 쿼리 파라미터 (선택적 검증)
56
+ export const getUserById = createServerFn({ method: 'GET' })
57
+ .inputValidator(z.object({
58
+ id: z.string().uuid(),
59
+ }))
60
+ .handler(async ({ data }): Promise<User | null> => {
61
+ return prisma.user.findUnique({
62
+ where: { id: data.id },
63
+ })
64
+ })
65
+
66
+ // ✅ GET + 필터링
67
+ const filterSchema = z.object({
68
+ status: z.enum(['active', 'inactive']).optional(),
69
+ limit: z.number().int().min(1).max(100).default(10),
70
+ })
71
+
72
+ export const searchUsers = createServerFn({ method: 'GET' })
73
+ .inputValidator(filterSchema)
74
+ .handler(async ({ data }): Promise<User[]> => {
75
+ return prisma.user.findMany({
76
+ where: {
77
+ ...(data.status && { status: data.status }),
78
+ },
79
+ take: data.limit,
80
+ })
81
+ })
82
+ ```
16
83
 
17
- // POST + Zod Validation
84
+ ### GET + 인증
85
+
86
+ ```typescript
87
+ // ✅ 현재 사용자 조회
88
+ export const getMyProfile = createServerFn({ method: 'GET' })
89
+ .middleware([authMiddleware])
90
+ .handler(async ({ context }): Promise<User> => {
91
+ return prisma.user.findUnique({
92
+ where: { id: context.user.id },
93
+ })
94
+ })
95
+
96
+ // ✅ 사용자 전용 데이터
97
+ export const getMyPosts = createServerFn({ method: 'GET' })
98
+ .middleware([authMiddleware])
99
+ .handler(async ({ context }): Promise<Post[]> => {
100
+ return prisma.post.findMany({
101
+ where: { authorId: context.user.id },
102
+ orderBy: { createdAt: 'desc' },
103
+ })
104
+ })
105
+ ```
106
+
107
+ </get_method>
108
+
109
+ ---
110
+
111
+ <post_method>
112
+
113
+ ## POST: 데이터 생성
114
+
115
+ ### POST + inputValidator (필수)
116
+
117
+ ```typescript
118
+ // ✅ inputValidator 필수
18
119
  const createUserSchema = z.object({
19
- email: z.email(),
20
- name: z.string().min(1).max(100),
120
+ email: z.string().email(),
121
+ name: z.string().min(1).max(100).trim(),
122
+ age: z.number().int().min(0).max(150).optional(),
21
123
  })
22
124
 
23
125
  export const createUser = createServerFn({ method: 'POST' })
24
126
  .inputValidator(createUserSchema)
25
- .handler(async ({ data }) => prisma.user.create({ data }))
127
+ .handler(async ({ data }): Promise<User> => {
128
+ // data는 자동으로 검증됨 (타입 안전)
129
+ return prisma.user.create({
130
+ data,
131
+ })
132
+ })
26
133
  ```
27
134
 
28
- ## 컴포넌트에서 호출
135
+ ### POST + inputValidator + 인증
29
136
 
30
- ```tsx
31
- // ✅ useQuery (조회)
32
- const { data, isLoading } = useQuery({
33
- queryKey: ['posts'],
34
- queryFn: () => getServerPosts(),
137
+ ```typescript
138
+ // ✅ POST + 검증 + 인증
139
+ const createPostSchema = z.object({
140
+ title: z.string().min(1).max(200),
141
+ content: z.string().min(1).max(10000),
142
+ tags: z.array(z.string()).max(5).default([]),
143
+ })
144
+
145
+ export const createPost = createServerFn({ method: 'POST' })
146
+ .middleware([authMiddleware])
147
+ .inputValidator(createPostSchema)
148
+ .handler(async ({ data, context }): Promise<Post> => {
149
+ return prisma.post.create({
150
+ data: {
151
+ ...data,
152
+ authorId: context.user.id,
153
+ published: false,
154
+ },
155
+ })
156
+ })
157
+ ```
158
+
159
+ ### POST + 부수 효과
160
+
161
+ ```typescript
162
+ // ✅ 중복 체크 + 생성
163
+ const registerSchema = z.object({
164
+ email: z.string().email(),
165
+ password: z.string().min(8),
166
+ name: z.string().min(1),
167
+ })
168
+
169
+ export const registerUser = createServerFn({ method: 'POST' })
170
+ .inputValidator(registerSchema)
171
+ .handler(async ({ data }): Promise<{ user: User }> => {
172
+ // 중복 체크 (내부 헬퍼)
173
+ const exists = await prisma.user.findUnique({
174
+ where: { email: data.email },
175
+ })
176
+
177
+ if (exists) {
178
+ throw new Error('Email already registered')
179
+ }
180
+
181
+ // 비밀번호 해싱 (내부 헬퍼)
182
+ const hashedPassword = await hashPassword(data.password)
183
+
184
+ const user = await prisma.user.create({
185
+ data: {
186
+ ...data,
187
+ password: hashedPassword,
188
+ },
189
+ })
190
+
191
+ return { user }
192
+ })
193
+ ```
194
+
195
+ ### ❌ inputValidator 없이 POST (금지)
196
+
197
+ ```typescript
198
+ // ❌ 금지: 입력 검증 없음
199
+ export const badCreate = createServerFn({ method: 'POST' })
200
+ .handler(async ({ data }) => {
201
+ // data 타입 불안전, 검증 없음
202
+ return prisma.user.create({ data })
203
+ })
204
+ ```
205
+
206
+ </post_method>
207
+
208
+ ---
209
+
210
+ <put_patch_methods>
211
+
212
+ ## PUT/PATCH: 데이터 수정
213
+
214
+ ### PUT: 전체 수정
215
+
216
+ ```typescript
217
+ // ✅ PUT + inputValidator 필수
218
+ const updateUserSchema = z.object({
219
+ id: z.string().uuid(),
220
+ email: z.string().email(),
221
+ name: z.string().min(1).max(100),
222
+ age: z.number().int().min(0).optional(),
223
+ })
224
+
225
+ export const updateUser = createServerFn({ method: 'PUT' })
226
+ .middleware([authMiddleware])
227
+ .inputValidator(updateUserSchema)
228
+ .handler(async ({ data, context }): Promise<User> => {
229
+ // 권한 체크
230
+ const user = await prisma.user.findUnique({
231
+ where: { id: data.id },
232
+ })
233
+
234
+ if (user?.id !== context.user.id && context.user.role !== 'ADMIN') {
235
+ throw new Error('Unauthorized')
236
+ }
237
+
238
+ return prisma.user.update({
239
+ where: { id: data.id },
240
+ data: {
241
+ email: data.email,
242
+ name: data.name,
243
+ ...(data.age !== undefined && { age: data.age }),
244
+ },
245
+ })
246
+ })
247
+ ```
248
+
249
+ ### PATCH: 부분 수정
250
+
251
+ ```typescript
252
+ // ✅ PATCH + inputValidator 필수 (모든 필드 optional)
253
+ const patchUserSchema = z.object({
254
+ id: z.string().uuid(),
255
+ email: z.string().email().optional(),
256
+ name: z.string().min(1).max(100).optional(),
257
+ age: z.number().int().min(0).optional(),
258
+ })
259
+
260
+ export const patchUser = createServerFn({ method: 'PATCH' })
261
+ .middleware([authMiddleware])
262
+ .inputValidator(patchUserSchema)
263
+ .handler(async ({ data, context }): Promise<User> => {
264
+ const { id, ...updateData } = data
265
+
266
+ // 권한 체크
267
+ if (id !== context.user.id && context.user.role !== 'ADMIN') {
268
+ throw new Error('Unauthorized')
269
+ }
270
+
271
+ return prisma.user.update({
272
+ where: { id },
273
+ data: updateData,
274
+ })
275
+ })
276
+ ```
277
+
278
+ </put_patch_methods>
279
+
280
+ ---
281
+
282
+ <delete_method>
283
+
284
+ ## DELETE: 데이터 삭제
285
+
286
+ ```typescript
287
+ // ✅ DELETE + 권한 체크
288
+ const deletePostSchema = z.object({
289
+ id: z.string().uuid(),
290
+ })
291
+
292
+ export const deletePost = createServerFn({ method: 'DELETE' })
293
+ .middleware([authMiddleware])
294
+ .inputValidator(deletePostSchema)
295
+ .handler(async ({ data, context }): Promise<{ success: true }> => {
296
+ // 권한 체크
297
+ const post = await prisma.post.findUnique({
298
+ where: { id: data.id },
299
+ })
300
+
301
+ if (!post) {
302
+ throw new Error('Post not found')
303
+ }
304
+
305
+ if (post.authorId !== context.user.id && context.user.role !== 'ADMIN') {
306
+ throw new Error('Forbidden')
307
+ }
308
+
309
+ await prisma.post.delete({
310
+ where: { id: data.id },
311
+ })
312
+
313
+ return { success: true }
314
+ })
315
+
316
+ // ✅ 관리자 전용 삭제
317
+ export const adminDeleteUser = createServerFn({ method: 'DELETE' })
318
+ .middleware([adminMiddleware])
319
+ .inputValidator(z.object({ id: z.string().uuid() }))
320
+ .handler(async ({ data }): Promise<{ success: true }> => {
321
+ await prisma.user.delete({
322
+ where: { id: data.id },
323
+ })
324
+ return { success: true }
325
+ })
326
+ ```
327
+
328
+ </delete_method>
329
+
330
+ ---
331
+
332
+ <input_validator>
333
+
334
+ ## .inputValidator() - Zod 검증
335
+
336
+ ### 기본 검증
337
+
338
+ ```typescript
339
+ // ✅ 단순 객체
340
+ const emailSchema = z.object({
341
+ email: z.string().email(),
35
342
  })
36
343
 
37
- // useMutation (변경)
344
+ export const sendEmail = createServerFn({ method: 'POST' })
345
+ .inputValidator(emailSchema)
346
+ .handler(async ({ data }) => {
347
+ // data.email은 string (타입 안전)
348
+ await sendEmailService(data.email)
349
+ return { sent: true }
350
+ })
351
+ ```
352
+
353
+ ### 복잡한 검증
354
+
355
+ ```typescript
356
+ // ✅ 중첩 객체, 배열, transform
357
+ const createProjectSchema = z.object({
358
+ name: z.string().min(1).max(100).trim(),
359
+ description: z.string().min(10).max(1000).optional(),
360
+ tags: z.array(z.string().min(1)).max(10),
361
+ settings: z.object({
362
+ isPublic: z.boolean().default(false),
363
+ allowComments: z.boolean().default(true),
364
+ }),
365
+ startDate: z.coerce.date(),
366
+ }).strict() // 추가 필드 금지
367
+
368
+ export const createProject = createServerFn({ method: 'POST' })
369
+ .inputValidator(createProjectSchema)
370
+ .handler(async ({ data }) => {
371
+ // data 완전 검증됨
372
+ return prisma.project.create({ data })
373
+ })
374
+ ```
375
+
376
+ ### 조건부 검증
377
+
378
+ ```typescript
379
+ // ✅ Zod refine/superRefine
380
+ const passwordSchema = z.object({
381
+ password: z.string().min(8),
382
+ confirmPassword: z.string(),
383
+ }).refine((data) => data.password === data.confirmPassword, {
384
+ message: 'Passwords do not match',
385
+ path: ['confirmPassword'],
386
+ })
387
+
388
+ export const resetPassword = createServerFn({ method: 'POST' })
389
+ .inputValidator(passwordSchema)
390
+ .handler(async ({ data }) => {
391
+ // 비밀번호 업데이트
392
+ return { success: true }
393
+ })
394
+ ```
395
+
396
+ </input_validator>
397
+
398
+ ---
399
+
400
+ <middleware>
401
+
402
+ ## .middleware() - 인증 및 권한 체크
403
+
404
+ ### 기본 미들웨어
405
+
406
+ ```typescript
407
+ // ✅ 인증 미들웨어
408
+ const authMiddleware = createMiddleware({ type: 'function' })
409
+ .server(async ({ next, request }) => {
410
+ const session = await getSession(request)
411
+ if (!session?.user) {
412
+ throw redirect({ to: '/login' })
413
+ }
414
+ return next({ context: { user: session.user } })
415
+ })
416
+
417
+ // 적용
418
+ export const getMyPosts = createServerFn({ method: 'GET' })
419
+ .middleware([authMiddleware])
420
+ .handler(async ({ context }): Promise<Post[]> => {
421
+ return prisma.post.findMany({
422
+ where: { authorId: context.user.id },
423
+ })
424
+ })
425
+ ```
426
+
427
+ ### 권한 체크 미들웨어
428
+
429
+ ```typescript
430
+ // ✅ 관리자 미들웨어
431
+ const adminMiddleware = createMiddleware({ type: 'function' })
432
+ .server(async ({ next, request }) => {
433
+ const session = await getSession(request)
434
+ if (session?.user?.role !== 'ADMIN') {
435
+ throw new Error('Forbidden: Admin only')
436
+ }
437
+ return next({ context: { user: session.user } })
438
+ })
439
+
440
+ // ✅ 역할 기반 미들웨어
441
+ const roleMiddleware = (allowedRoles: string[]) =>
442
+ createMiddleware({ type: 'function' })
443
+ .server(async ({ next, request }) => {
444
+ const session = await getSession(request)
445
+ if (!session?.user || !allowedRoles.includes(session.user.role)) {
446
+ throw new Error('Forbidden')
447
+ }
448
+ return next({ context: { user: session.user } })
449
+ })
450
+
451
+ // 사용
452
+ export const deleteAnyUser = createServerFn({ method: 'DELETE' })
453
+ .middleware([adminMiddleware])
454
+ .inputValidator(z.object({ id: z.string() }))
455
+ .handler(async ({ data }) => {
456
+ return prisma.user.delete({ where: { id: data.id } })
457
+ })
458
+ ```
459
+
460
+ ### 미들웨어 체이닝
461
+
462
+ ```typescript
463
+ // ✅ 여러 미들웨어 조합
464
+ export const protectedFn = createServerFn({ method: 'POST' })
465
+ .middleware([
466
+ authMiddleware, // 1. 인증
467
+ adminMiddleware, // 2. 권한
468
+ ])
469
+ .inputValidator(someSchema)
470
+ .handler(async ({ data, context }) => {
471
+ // context.user는 존재 (인증됨) + ADMIN 역할 확인됨
472
+ return { success: true }
473
+ })
474
+ ```
475
+
476
+ </middleware>
477
+
478
+ ---
479
+
480
+ <client_calling>
481
+
482
+ ## 클라이언트에서 호출 (TanStack Query 필수)
483
+
484
+ ### useQuery (GET)
485
+
486
+ ```tsx
487
+ // ✅ 데이터 조회
488
+ const UsersPage = (): JSX.Element => {
489
+ const { data, isLoading, error } = useQuery({
490
+ queryKey: ['users'],
491
+ queryFn: () => getUsers(),
492
+ })
493
+
494
+ if (isLoading) return <div>Loading...</div>
495
+ if (error) return <div>Error: {error.message}</div>
496
+
497
+ return (
498
+ <ul>
499
+ {data?.map((user) => (
500
+ <li key={user.id}>{user.name}</li>
501
+ ))}
502
+ </ul>
503
+ )
504
+ }
505
+ ```
506
+
507
+ ### useMutation (POST/PUT/DELETE)
508
+
509
+ ```tsx
510
+ // ✅ 데이터 생성/수정/삭제
511
+ const CreateUserForm = (): JSX.Element => {
512
+ const queryClient = useQueryClient()
513
+
514
+ const mutation = useMutation({
515
+ mutationFn: (payload: { email: string; name: string }) =>
516
+ createUser(payload),
517
+ onSuccess: () => {
518
+ // 캐시 무효화 → 자동 리페치
519
+ queryClient.invalidateQueries({ queryKey: ['users'] })
520
+ },
521
+ onError: (error) => {
522
+ console.error('Creation failed:', error.message)
523
+ },
524
+ })
525
+
526
+ const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
527
+ e.preventDefault()
528
+ const formData = new FormData(e.currentTarget)
529
+
530
+ mutation.mutate({
531
+ email: formData.get('email') as string,
532
+ name: formData.get('name') as string,
533
+ })
534
+ }
535
+
536
+ return (
537
+ <form onSubmit={handleSubmit}>
538
+ <input
539
+ name="email"
540
+ type="email"
541
+ required
542
+ disabled={mutation.isPending}
543
+ />
544
+ <input
545
+ name="name"
546
+ required
547
+ disabled={mutation.isPending}
548
+ />
549
+ <button type="submit" disabled={mutation.isPending}>
550
+ {mutation.isPending ? 'Creating...' : 'Create'}
551
+ </button>
552
+ {mutation.error && (
553
+ <p style={{ color: 'red' }}>Error: {mutation.error.message}</p>
554
+ )}
555
+ </form>
556
+ )
557
+ }
558
+ ```
559
+
560
+ ### Optimistic Updates
561
+
562
+ ```tsx
563
+ // ✅ 낙관적 업데이트
38
564
  const mutation = useMutation({
39
- mutationFn: createPost,
40
- onSuccess: () => queryClient.invalidateQueries({ queryKey: ['posts'] }),
565
+ mutationFn: (newPost) => createPost(newPost),
566
+ onMutate: async (newPost) => {
567
+ // 기존 쿼리 취소
568
+ await queryClient.cancelQueries({ queryKey: ['posts'] })
569
+
570
+ // 이전 데이터 저장
571
+ const previousPosts = queryClient.getQueryData(['posts'])
572
+
573
+ // 낙관적 업데이트
574
+ queryClient.setQueryData(
575
+ ['posts'],
576
+ (old: Post[] = []) => [...old, newPost],
577
+ )
578
+
579
+ return { previousPosts }
580
+ },
581
+ onError: (err, newPost, context) => {
582
+ // 실패 시 롤백
583
+ queryClient.setQueryData(['posts'], context?.previousPosts)
584
+ },
585
+ onSuccess: () => {
586
+ queryClient.invalidateQueries({ queryKey: ['posts'] })
587
+ },
41
588
  })
589
+ ```
590
+
591
+ ### ❌ 직접 호출 금지
592
+
593
+ ```tsx
594
+ // ❌ 잘못된 패턴 (캐싱 없음, 동기화 안됨)
595
+ const BadComponent = (): JSX.Element => {
596
+ const [users, setUsers] = useState<User[]>([])
597
+ const [loading, setLoading] = useState(false)
42
598
 
43
- // ❌ 직접 호출 금지 (캐싱 없음, 동기화 안됨)
599
+ useEffect(() => {
600
+ setLoading(true)
601
+ getUsers() // ❌ 직접 호출 (TanStack Query 미사용)
602
+ .then(setUsers)
603
+ .catch(console.error)
604
+ .finally(() => setLoading(false))
605
+ }, [])
606
+
607
+ return <div>{/* ... */}</div>
608
+ }
609
+ ```
610
+
611
+ </client_calling>
612
+
613
+ ---
614
+
615
+ <helper_functions>
616
+
617
+ ## 헬퍼 함수 규칙
618
+
619
+ ### ❌ 잘못된 구조
620
+
621
+ ```typescript
622
+ // functions/user-functions.ts
623
+
624
+ // ❌ 헬퍼 함수를 export (금지!)
625
+ export const validateUserEmail = async (email: string): Promise<void> => {
626
+ const exists = await prisma.user.findUnique({ where: { email } })
627
+ if (exists) throw new Error('Email already exists')
628
+ }
629
+
630
+ // Server Function
631
+ export const createUser = createServerFn({ method: 'POST' })
632
+ .inputValidator(createUserSchema)
633
+ .handler(async ({ data }) => {
634
+ await validateUserEmail(data.email) // ❌ export된 헬퍼
635
+ return prisma.user.create({ data })
636
+ })
44
637
  ```
45
638
 
46
- ## 함수 분리 규칙
639
+ ### 올바른 구조
47
640
 
48
641
  ```typescript
49
- // 내부 헬퍼 (export 금지!)
50
- const validateUserData = async (email: string) => { ... }
642
+ // functions/user-functions.ts
643
+
644
+ // ✅ 헬퍼는 export 금지 (내부용)
645
+ const validateUserEmail = async (email: string): Promise<void> => {
646
+ const exists = await prisma.user.findUnique({ where: { email } })
647
+ if (exists) throw new Error('Email already exists')
648
+ }
51
649
 
52
- // Server Function (export 가능)
650
+ // Server Function export
53
651
  export const createUser = createServerFn({ method: 'POST' })
54
652
  .inputValidator(createUserSchema)
55
653
  .handler(async ({ data }) => {
56
- await validateUserData(data.email)
654
+ await validateUserEmail(data.email) // ✅ 내부 헬퍼만 사용
57
655
  return prisma.user.create({ data })
58
656
  })
59
657
 
60
- // index.ts: Server Function만 export
61
- export { createUser } from './mutations'
62
- // ❌ export { validateUserData } 금지
658
+ // index.ts
659
+ export { createUser } from './user-functions'
660
+ // ❌ export { validateUserEmail } 금지
661
+ ```
662
+
663
+ </helper_functions>
664
+
665
+ ---
666
+
667
+ <environment_security>
668
+
669
+ ## 환경 변수 보안
670
+
671
+ ### ❌ 잘못된 패턴
672
+
673
+ ```tsx
674
+ // routes/config.tsx
675
+
676
+ // ❌ loader에서 환경변수 직접 사용 (클라이언트에 노출)
677
+ export const Route = createFileRoute('/config')({
678
+ loader: () => {
679
+ const apiSecret = process.env.API_SECRET // ❌ 클라이언트에 노출됨!
680
+ return { apiSecret }
681
+ },
682
+ component: ConfigPage,
683
+ })
684
+ ```
685
+
686
+ ### ✅ 올바른 패턴
687
+
688
+ ```typescript
689
+ // functions/config.ts
690
+
691
+ // ✅ Server Function에서만 환경변수 사용
692
+ export const getSecretConfig = createServerFn({ method: 'GET' })
693
+ .middleware([authMiddleware])
694
+ .handler(async (): Promise<{ apiSecret: string }> => {
695
+ return {
696
+ apiSecret: process.env.API_SECRET, // ✅ 서버에서만 실행
697
+ }
698
+ })
699
+
700
+ // routes/config.tsx
701
+ export const Route = createFileRoute('/config')({
702
+ loader: async () => {
703
+ const config = await getSecretConfig()
704
+ return config
705
+ },
706
+ component: ConfigPage,
707
+ })
708
+ ```
709
+
710
+ </environment_security>
711
+
712
+ ---
713
+
714
+ <static_server_functions>
715
+
716
+ ## 정적 Server Functions (Experimental)
717
+
718
+ ```typescript
719
+ // ✅ 정적 함수 (빌드 타임에 최적화)
720
+ import { staticFunctionMiddleware } from '@tanstack/react-start'
721
+
722
+ export const getStaticPosts = createServerFn({ method: 'GET' })
723
+ .middleware([staticFunctionMiddleware])
724
+ .handler(async (): Promise<Post[]> => {
725
+ return prisma.post.findMany({
726
+ where: { published: true },
727
+ })
728
+ })
729
+ ```
730
+
731
+ **주의:** 이 기능은 실험적이며, TanStack Start 버전에 따라 변경될 수 있습니다.
732
+
733
+ </static_server_functions>
734
+
735
+ ---
736
+
737
+ <return_types>
738
+
739
+ ## 명시적 Return Type
740
+
741
+ ```typescript
742
+ // ✅ 명시적 return type 필수
743
+ export const getUser = createServerFn({ method: 'GET' })
744
+ .inputValidator(z.object({ id: z.string() }))
745
+ .handler(async ({ data }): Promise<User | null> => {
746
+ return prisma.user.findUnique({ where: { id: data.id } })
747
+ })
748
+
749
+ // ✅ 복잡한 타입
750
+ export const getStats = createServerFn({ method: 'GET' })
751
+ .middleware([authMiddleware])
752
+ .handler(async (): Promise<{
753
+ totalUsers: number
754
+ totalPosts: number
755
+ lastUpdated: Date
756
+ }> => {
757
+ return {
758
+ totalUsers: await prisma.user.count(),
759
+ totalPosts: await prisma.post.count(),
760
+ lastUpdated: new Date(),
761
+ }
762
+ })
763
+
764
+ // ❌ 암시적 타입 (금지)
765
+ export const badFn = createServerFn({ method: 'GET' })
766
+ .handler(async () => {
767
+ // return 타입이 추론됨 (불명확)
768
+ return { data: 'something' }
769
+ })
770
+ ```
771
+
772
+ </return_types>
773
+
774
+ ---
775
+
776
+ <file_organization>
777
+
778
+ ## 파일 구조 권장사항
779
+
780
+ ### 권장 구조
781
+
782
+ ```
783
+ src/utils/
784
+ ├── users.functions.ts # Server Function 래퍼 (createServerFn)
785
+ ├── users.server.ts # 서버 전용 헬퍼 (DB 쿼리, 내부 로직)
786
+ └── schemas.ts # 공유 검증 스키마 (클라이언트에서도 안전)
787
+ ```
788
+
789
+ | 파일 | 역할 | import 가능 위치 |
790
+ |------|------|-----------------|
791
+ | `.functions.ts` | createServerFn 래퍼 export | 어디서든 안전 |
792
+ | `.server.ts` | 서버 전용 코드 | Server Function handler 내부에서만 |
793
+ | `.ts` (접미사 없음) | 클라이언트 안전 코드 (타입, 스키마, 상수) | 어디서든 안전 |
794
+
795
+ ### 예시
796
+
797
+ ```typescript
798
+ // users.server.ts - 서버 전용 헬퍼
799
+ import { db } from '~/db'
800
+
801
+ export async function findUserById(id: string) {
802
+ return db.query.users.findFirst({ where: eq(users.id, id) })
803
+ }
804
+
805
+ // users.functions.ts - Server Functions
806
+ import { createServerFn } from '@tanstack/react-start'
807
+ import { findUserById } from './users.server'
808
+
809
+ export const getUser = createServerFn({ method: 'GET' })
810
+ .inputValidator((data: { id: string }) => data)
811
+ .handler(async ({ data }) => {
812
+ return findUserById(data.id)
813
+ })
63
814
  ```
64
815
 
65
- ## 보안
816
+ ### Static Import 안전성
817
+
818
+ Server Function은 클라이언트 컴포넌트에서 **정적으로 import 해도 안전**합니다:
819
+
820
+ ```tsx
821
+ // ✅ 안전 - 빌드 프로세스가 환경별로 처리
822
+ import { getUser } from '~/utils/users.functions'
823
+
824
+ function UserProfile({ id }) {
825
+ const { data } = useQuery({
826
+ queryKey: ['user', id],
827
+ queryFn: () => getUser({ data: { id } }),
828
+ })
829
+ }
830
+ ```
66
831
 
67
832
  ```tsx
68
- // ❌ loader에서 환경변수 직접 사용 (노출됨)
69
- loader: () => { const secret = process.env.SECRET }
833
+ // ❌ 동적 import 피하기 (번들러 문제 발생 가능)
834
+ const { getUser } = await import('~/utils/users.functions')
835
+ ```
836
+
837
+ </file_organization>
838
+
839
+ ---
840
+
841
+ <error_redirects>
842
+
843
+ ## 에러 처리 및 리다이렉트
844
+
845
+ ### 기본 에러
846
+
847
+ ```typescript
848
+ export const riskyFunction = createServerFn().handler(async () => {
849
+ if (Math.random() > 0.5) {
850
+ throw new Error('Something went wrong!')
851
+ }
852
+ return { success: true }
853
+ })
854
+
855
+ // 에러가 클라이언트로 직렬화됨
856
+ try {
857
+ await riskyFunction()
858
+ } catch (error) {
859
+ console.log(error.message) // "Something went wrong!"
860
+ }
861
+ ```
862
+
863
+ ### 리다이렉트
864
+
865
+ ```typescript
866
+ import { createServerFn } from '@tanstack/react-start'
867
+ import { redirect } from '@tanstack/react-router'
868
+
869
+ export const requireAuth = createServerFn().handler(async () => {
870
+ const user = await getCurrentUser()
871
+
872
+ if (!user) {
873
+ throw redirect({ to: '/login' })
874
+ }
70
875
 
71
- // ✅ Server Function 사용
72
- const fn = createServerFn().handler(() => {
73
- const secret = process.env.SECRET // 서버에서만
876
+ return user
74
877
  })
75
878
  ```
879
+
880
+ ### Not Found
881
+
882
+ ```typescript
883
+ import { createServerFn } from '@tanstack/react-start'
884
+ import { notFound } from '@tanstack/react-router'
885
+
886
+ export const getPost = createServerFn()
887
+ .inputValidator((data: { id: string }) => data)
888
+ .handler(async ({ data }) => {
889
+ const post = await db.findPost(data.id)
890
+
891
+ if (!post) {
892
+ throw notFound()
893
+ }
894
+
895
+ return post
896
+ })
897
+ ```
898
+
899
+ </error_redirects>
900
+
901
+ ---
902
+
903
+ <server_context>
904
+
905
+ ## Server Context 유틸리티
906
+
907
+ Server Function 내에서 요청/응답을 직접 제어할 수 있습니다:
908
+
909
+ ```typescript
910
+ import { createServerFn } from '@tanstack/react-start'
911
+ import {
912
+ getRequest,
913
+ getRequestHeader,
914
+ setResponseHeaders,
915
+ setResponseStatus,
916
+ } from '@tanstack/react-start/server'
917
+
918
+ export const getCachedData = createServerFn({ method: 'GET' }).handler(
919
+ async () => {
920
+ // 요청 정보 접근
921
+ const request = getRequest()
922
+ const authHeader = getRequestHeader('Authorization')
923
+
924
+ // 응답 헤더 설정 (캐싱 등)
925
+ setResponseHeaders(
926
+ new Headers({
927
+ 'Cache-Control': 'public, max-age=300',
928
+ 'CDN-Cache-Control': 'max-age=3600, stale-while-revalidate=600',
929
+ }),
930
+ )
931
+
932
+ // 상태 코드 설정 (선택)
933
+ setResponseStatus(200)
934
+
935
+ return fetchData()
936
+ },
937
+ )
938
+ ```
939
+
940
+ ### 사용 가능한 유틸리티
941
+
942
+ | 유틸리티 | 설명 |
943
+ |---------|------|
944
+ | `getRequest()` | 전체 Request 객체 접근 |
945
+ | `getRequestHeader(name)` | 특정 요청 헤더 읽기 |
946
+ | `setResponseHeader(name, value)` | 단일 응답 헤더 설정 |
947
+ | `setResponseHeaders(headers)` | Headers 객체로 복수 헤더 설정 |
948
+ | `setResponseStatus(code)` | HTTP 상태 코드 설정 |
949
+
950
+ </server_context>
951
+
952
+ ---
953
+
954
+ <form_data>
955
+
956
+ ## FormData 처리
957
+
958
+ ```typescript
959
+ export const submitForm = createServerFn({ method: 'POST' })
960
+ .inputValidator((data) => {
961
+ if (!(data instanceof FormData)) {
962
+ throw new Error('Expected FormData')
963
+ }
964
+
965
+ return {
966
+ name: data.get('name')?.toString() || '',
967
+ email: data.get('email')?.toString() || '',
968
+ }
969
+ })
970
+ .handler(async ({ data }) => {
971
+ // data.name, data.email 사용
972
+ return { success: true }
973
+ })
974
+ ```
975
+
976
+ </form_data>
977
+
978
+ ---
979
+
980
+ <version_info>
981
+
982
+ **Version:** v1.159.4 (2026-02-09 기준)
983
+
984
+ **주요 변경사항:**
985
+ - `.inputValidator()` replaces `.validator()` (deprecated)
986
+ - Enhanced middleware system with context
987
+ - Improved type safety for async handlers
988
+ - Server context utilities (getRequest, setResponseHeaders 등)
989
+ - Static import safety (빌드 시 환경별 처리)
990
+
991
+ **패키지:** `@tanstack/react-start`
992
+
993
+ </version_info>