@kood/claude-code 0.3.5 → 0.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/templates/nextjs/CLAUDE.md +228 -0
- package/templates/nextjs/docs/design.md +558 -0
- package/templates/nextjs/docs/guides/conventions.md +343 -0
- package/templates/nextjs/docs/guides/getting-started.md +367 -0
- package/templates/nextjs/docs/guides/routes.md +342 -0
- package/templates/nextjs/docs/library/better-auth/index.md +541 -0
- package/templates/nextjs/docs/library/nextjs/app-router.md +269 -0
- package/templates/nextjs/docs/library/nextjs/caching.md +351 -0
- package/templates/nextjs/docs/library/nextjs/index.md +291 -0
- package/templates/nextjs/docs/library/nextjs/middleware.md +391 -0
- package/templates/nextjs/docs/library/nextjs/route-handlers.md +382 -0
- package/templates/nextjs/docs/library/nextjs/server-actions.md +366 -0
- package/templates/nextjs/docs/library/prisma/cloudflare-d1.md +76 -0
- package/templates/nextjs/docs/library/prisma/config.md +77 -0
- package/templates/nextjs/docs/library/prisma/crud.md +90 -0
- package/templates/nextjs/docs/library/prisma/index.md +73 -0
- package/templates/nextjs/docs/library/prisma/relations.md +69 -0
- package/templates/nextjs/docs/library/prisma/schema.md +98 -0
- package/templates/nextjs/docs/library/prisma/setup.md +49 -0
- package/templates/nextjs/docs/library/prisma/transactions.md +50 -0
- package/templates/nextjs/docs/library/tanstack-query/index.md +66 -0
- package/templates/nextjs/docs/library/tanstack-query/invalidation.md +54 -0
- package/templates/nextjs/docs/library/tanstack-query/optimistic-updates.md +77 -0
- package/templates/nextjs/docs/library/tanstack-query/use-mutation.md +63 -0
- package/templates/nextjs/docs/library/tanstack-query/use-query.md +70 -0
- package/templates/nextjs/docs/library/zod/complex-types.md +61 -0
- package/templates/nextjs/docs/library/zod/index.md +56 -0
- package/templates/nextjs/docs/library/zod/transforms.md +51 -0
- package/templates/nextjs/docs/library/zod/validation.md +70 -0
- package/templates/tanstack-start/CLAUDE.md +7 -3
- package/templates/tanstack-start/docs/architecture.md +38 -7
- package/templates/tanstack-start/docs/guides/hooks.md +28 -0
- package/templates/tanstack-start/docs/guides/routes.md +29 -10
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Zod
|
|
2
|
+
|
|
3
|
+
> v4 | TypeScript Schema Validation
|
|
4
|
+
|
|
5
|
+
@complex-types.md
|
|
6
|
+
@transforms.md
|
|
7
|
+
@validation.md
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
<quick_reference>
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
// 기본
|
|
15
|
+
const schema = z.object({
|
|
16
|
+
email: z.email(), // v4!
|
|
17
|
+
name: z.string().min(1).trim(),
|
|
18
|
+
website: z.url().optional(), // v4!
|
|
19
|
+
age: z.number().int().positive(),
|
|
20
|
+
})
|
|
21
|
+
type Input = z.infer<typeof schema>
|
|
22
|
+
|
|
23
|
+
schema.parse(data) // 실패 시 throw
|
|
24
|
+
schema.safeParse(data) // { success, data/error }
|
|
25
|
+
|
|
26
|
+
// TanStack Start
|
|
27
|
+
export const createUser = createServerFn({ method: 'POST' })
|
|
28
|
+
.inputValidator(schema)
|
|
29
|
+
.handler(async ({ data }) => prisma.user.create({ data }))
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
</quick_reference>
|
|
33
|
+
|
|
34
|
+
<v4_changes>
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
// ✅ v4 새 API
|
|
38
|
+
z.email() z.url() z.uuid()
|
|
39
|
+
z.iso.date() z.iso.datetime() z.iso.duration()
|
|
40
|
+
z.stringbool() // "true"/"yes"/"1" → true
|
|
41
|
+
|
|
42
|
+
// ❌ v3 deprecated
|
|
43
|
+
z.string().email() z.string().url()
|
|
44
|
+
|
|
45
|
+
// 변경사항
|
|
46
|
+
z.string().min(5, { error: "Too short." }) // message → error
|
|
47
|
+
z.strictObject({ name: z.string() }) // 추가 키 에러
|
|
48
|
+
z.looseObject({ name: z.string() }) // 추가 키 통과
|
|
49
|
+
z.string().refine(val => val.includes("@")).min(5) // refinement 체이닝
|
|
50
|
+
|
|
51
|
+
// 템플릿 리터럴
|
|
52
|
+
const css = z.templateLiteral([z.number(), z.enum(["px", "em", "rem"])])
|
|
53
|
+
// `${number}px` | `${number}em` | `${number}rem`
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
</v4_changes>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Zod - 변환
|
|
2
|
+
|
|
3
|
+
<patterns>
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
// Transform
|
|
7
|
+
const stringToLength = z.string().transform((val) => val.length)
|
|
8
|
+
stringToLength.parse('hello') // => 5
|
|
9
|
+
|
|
10
|
+
type In = z.input<typeof stringToLength> // string
|
|
11
|
+
type Out = z.output<typeof stringToLength> // number
|
|
12
|
+
|
|
13
|
+
// Pipe (검증 후 변환)
|
|
14
|
+
const stringToNumber = z.string()
|
|
15
|
+
.transform((val) => parseInt(val, 10))
|
|
16
|
+
.pipe(z.number().min(0).max(100))
|
|
17
|
+
|
|
18
|
+
stringToNumber.parse('50') // => 50
|
|
19
|
+
stringToNumber.parse('150') // throws
|
|
20
|
+
|
|
21
|
+
// Coerce (강제 변환)
|
|
22
|
+
z.coerce.string() // 모든 값 → 문자열
|
|
23
|
+
z.coerce.number() // 모든 값 → 숫자
|
|
24
|
+
z.coerce.boolean() // 모든 값 → 불리언
|
|
25
|
+
z.coerce.date() // 모든 값 → 날짜
|
|
26
|
+
z.coerce.bigint() // 모든 값 → BigInt
|
|
27
|
+
|
|
28
|
+
z.coerce.number().parse("42") // => 42
|
|
29
|
+
z.coerce.date().parse("2021-01-01") // => Date
|
|
30
|
+
|
|
31
|
+
// v4: 입력 타입이 unknown으로 변경
|
|
32
|
+
type In = z.input<typeof z.coerce.string()> // unknown
|
|
33
|
+
|
|
34
|
+
// v4 환경변수 불리언
|
|
35
|
+
z.stringbool() // "true"/"yes"/"1" → true, "false"/"no"/"0" → false
|
|
36
|
+
|
|
37
|
+
// Preprocess
|
|
38
|
+
const trimmed = z.preprocess(
|
|
39
|
+
(val) => typeof val === 'string' ? val.trim() : val,
|
|
40
|
+
z.string()
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
// 입력/출력 타입 분리
|
|
44
|
+
const Schema = z.object({
|
|
45
|
+
createdAt: z.string().transform((str) => new Date(str)),
|
|
46
|
+
})
|
|
47
|
+
type SchemaInput = z.input<typeof Schema> // { createdAt: string }
|
|
48
|
+
type SchemaOutput = z.output<typeof Schema> // { createdAt: Date }
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
</patterns>
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Zod - 검증
|
|
2
|
+
|
|
3
|
+
<patterns>
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
// Refinement (v4: message → error)
|
|
7
|
+
const PasswordSchema = z.string()
|
|
8
|
+
.min(8)
|
|
9
|
+
.refine((val) => /[A-Z]/.test(val), { error: '대문자 필수' })
|
|
10
|
+
.refine((val) => /[0-9]/.test(val), { error: '숫자 필수' })
|
|
11
|
+
|
|
12
|
+
z.string().refine(val => val.includes("@")).min(5) // v4: refinement 후 체이닝
|
|
13
|
+
|
|
14
|
+
// Async
|
|
15
|
+
const schema = z.string().refine(async (val) => val.length <= 8)
|
|
16
|
+
await schema.parseAsync('hello')
|
|
17
|
+
|
|
18
|
+
// Superrefine
|
|
19
|
+
z.object({
|
|
20
|
+
password: z.string(),
|
|
21
|
+
confirmPassword: z.string(),
|
|
22
|
+
}).superRefine((data, ctx) => {
|
|
23
|
+
if (data.password !== data.confirmPassword) {
|
|
24
|
+
ctx.addIssue({
|
|
25
|
+
code: z.ZodIssueCode.custom,
|
|
26
|
+
message: '비밀번호 불일치',
|
|
27
|
+
path: ['confirmPassword'],
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
}) // v4: ctx.path 사용 불가
|
|
31
|
+
|
|
32
|
+
// 커스텀
|
|
33
|
+
const px = z.custom<`${number}px`>((val) =>
|
|
34
|
+
typeof val === 'string' && /^\d+px$/.test(val)
|
|
35
|
+
)
|
|
36
|
+
px.parse('42px') // ✅
|
|
37
|
+
px.parse('42vw') // throws
|
|
38
|
+
|
|
39
|
+
// 에러 처리
|
|
40
|
+
const result = schema.safeParse(data)
|
|
41
|
+
if (!result.success) {
|
|
42
|
+
result.error.errors.forEach((err) => {
|
|
43
|
+
console.log(err.path, err.message, err.code)
|
|
44
|
+
})
|
|
45
|
+
const flat = result.error.flatten() // { fieldErrors: { email: ['Invalid email'] } }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// TanStack Start
|
|
49
|
+
export const createUser = createServerFn({ method: 'POST' })
|
|
50
|
+
.inputValidator(zodValidator(createUserSchema))
|
|
51
|
+
.handler(async ({ data }) => prisma.user.create({ data }))
|
|
52
|
+
|
|
53
|
+
// 환경 변수
|
|
54
|
+
const env = z.object({
|
|
55
|
+
NODE_ENV: z.enum(['development', 'production', 'test']),
|
|
56
|
+
DATABASE_URL: z.string().url(),
|
|
57
|
+
API_SECRET: z.string().min(32),
|
|
58
|
+
}).parse(process.env)
|
|
59
|
+
|
|
60
|
+
// Middleware
|
|
61
|
+
const workspaceMiddleware = createMiddleware({ type: 'function' })
|
|
62
|
+
.inputValidator(zodValidator(z.object({ workspaceId: z.string() })))
|
|
63
|
+
.server(({ next, data }) => next())
|
|
64
|
+
|
|
65
|
+
// Zod Mini (v4)
|
|
66
|
+
import * as z from "zod/mini"
|
|
67
|
+
z.string().check(z.minLength(5), z.maxLength(10), z.trim())
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
</patterns>
|
|
@@ -88,8 +88,9 @@
|
|
|
88
88
|
src/
|
|
89
89
|
├── routes/ # __root.tsx, index.tsx, $slug.tsx
|
|
90
90
|
│ └── [path]/
|
|
91
|
-
│ ├── -components/ # 페이지 전용
|
|
92
|
-
│
|
|
91
|
+
│ ├── -components/ # 페이지 전용 (필수)
|
|
92
|
+
│ ├── -hooks/ # 페이지 전용 Custom Hooks (필수)
|
|
93
|
+
│ └── -functions/ # 페이지 전용 Server Functions (필수)
|
|
93
94
|
├── functions/ # 공통 Server Functions
|
|
94
95
|
├── components/ui/
|
|
95
96
|
├── middleware/
|
|
@@ -97,7 +98,10 @@ src/
|
|
|
97
98
|
└── lib/
|
|
98
99
|
```
|
|
99
100
|
|
|
100
|
-
|
|
101
|
+
**필수 규칙:**
|
|
102
|
+
- 페이지당 `-components/`, `-hooks/`, `-functions/` 폴더 필수 (줄 수 무관)
|
|
103
|
+
- Custom Hook은 페이지 크기와 무관하게 **반드시** `-hooks/` 폴더에 분리
|
|
104
|
+
- 공통 함수 → `@/functions/`, 라우트 전용 → `routes/[경로]/-functions/`
|
|
101
105
|
</structure>
|
|
102
106
|
|
|
103
107
|
---
|
|
@@ -159,27 +159,58 @@ routes/<route-name>/
|
|
|
159
159
|
| **-components/** | 100-200줄 | 페이지 전용 컴포넌트 분리 |
|
|
160
160
|
| **-sections/** | 200줄+ | 논리적 섹션 분리 |
|
|
161
161
|
| **-tabs/** | 탭 UI | 탭 콘텐츠 분리 |
|
|
162
|
-
| **
|
|
162
|
+
| **route.tsx** | 레이아웃 | 하위 경로 공통 레이아웃 |
|
|
163
163
|
|
|
164
164
|
#### Layout Routes 패턴
|
|
165
165
|
|
|
166
|
+
> ⚠️ **route.tsx로 레이아웃 구성**
|
|
167
|
+
>
|
|
168
|
+
> `route.tsx`는 하위 경로의 공통 레이아웃 역할을 합니다.
|
|
169
|
+
> `index.tsx`는 Route Group `()`으로 묶어야 합니다.
|
|
170
|
+
>
|
|
171
|
+
> **필수:** `route.tsx`는 반드시 `component`를 export해야 합니다.
|
|
172
|
+
>
|
|
173
|
+
> | ❌ 금지 | ✅ 필수 |
|
|
174
|
+
> |--------|--------|
|
|
175
|
+
> | `export const Route = createFileRoute(...)({})` | `export const Route = createFileRoute(...)({ component: ... })` |
|
|
176
|
+
|
|
166
177
|
```
|
|
167
178
|
routes/
|
|
168
|
-
├── (auth)/
|
|
169
|
-
│ ├── route.tsx
|
|
170
|
-
│ ├──
|
|
171
|
-
│ └──
|
|
179
|
+
├── (auth)/
|
|
180
|
+
│ ├── route.tsx # 레이아웃 (<Outlet />)
|
|
181
|
+
│ ├── (main)/
|
|
182
|
+
│ │ └── index.tsx # /auth (목록/메인)
|
|
183
|
+
│ ├── login/
|
|
184
|
+
│ │ └── index.tsx # /auth/login
|
|
185
|
+
│ └── register/
|
|
186
|
+
│ └── index.tsx # /auth/register
|
|
172
187
|
```
|
|
173
188
|
|
|
174
189
|
```typescript
|
|
175
|
-
//
|
|
176
|
-
export const Route = createFileRoute('/(auth)
|
|
190
|
+
// ❌ 금지: component 없음
|
|
191
|
+
export const Route = createFileRoute('/(auth)')({
|
|
192
|
+
beforeLoad: async () => ({ user: await getUser() }),
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
// ✅ 필수: component 반드시 포함
|
|
196
|
+
// routes/(auth)/route.tsx - 레이아웃
|
|
197
|
+
export const Route = createFileRoute('/(auth)')({
|
|
177
198
|
component: () => (
|
|
178
199
|
<div className="auth-container">
|
|
179
200
|
<Outlet />
|
|
180
201
|
</div>
|
|
181
202
|
),
|
|
182
203
|
})
|
|
204
|
+
|
|
205
|
+
// routes/(auth)/(main)/index.tsx - 메인 페이지
|
|
206
|
+
export const Route = createFileRoute('/(auth)/')({
|
|
207
|
+
component: AuthMainPage,
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
// routes/(auth)/login/index.tsx
|
|
211
|
+
export const Route = createFileRoute('/(auth)/login')({
|
|
212
|
+
component: LoginPage,
|
|
213
|
+
})
|
|
183
214
|
```
|
|
184
215
|
|
|
185
216
|
### 2. Services Layer
|
|
@@ -10,6 +10,34 @@
|
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
+
<mandatory_separation>
|
|
14
|
+
|
|
15
|
+
## ⚠️ 필수 규칙: Custom Hook 분리
|
|
16
|
+
|
|
17
|
+
**모든 페이지는 Custom Hook을 `-hooks/` 폴더에 분리해야 합니다.**
|
|
18
|
+
|
|
19
|
+
- 페이지 크기(줄 수)와 **무관**하게 반드시 분리
|
|
20
|
+
- 10줄짜리 간단한 페이지도 Hook 분리 필수
|
|
21
|
+
- 페이지 컴포넌트는 오직 UI 렌더링만 담당
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
// ✅ 올바른 구조
|
|
25
|
+
routes/users/
|
|
26
|
+
├── index.tsx // UI만
|
|
27
|
+
├── -hooks/
|
|
28
|
+
│ └── use-users.ts // 모든 로직
|
|
29
|
+
├── -components/
|
|
30
|
+
└── -functions/
|
|
31
|
+
|
|
32
|
+
// ❌ 잘못된 구조
|
|
33
|
+
routes/users/
|
|
34
|
+
└── index.tsx // UI + 로직 혼재
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
</mandatory_separation>
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
13
41
|
<hook_order>
|
|
14
42
|
|
|
15
43
|
## Hook 내부 순서 (필수)
|
|
@@ -19,12 +19,16 @@ routes/
|
|
|
19
19
|
├── users/
|
|
20
20
|
│ ├── index.tsx # /users (List)
|
|
21
21
|
│ ├── $id.tsx # /users/:id (Detail)
|
|
22
|
-
│ ├── -components/ # 페이지 전용 컴포넌트
|
|
23
|
-
│ ├── -
|
|
24
|
-
│
|
|
22
|
+
│ ├── -components/ # 페이지 전용 컴포넌트 (필수)
|
|
23
|
+
│ ├── -hooks/ # 페이지 전용 Hook (필수)
|
|
24
|
+
│ ├── -functions/ # 페이지 전용 Server Functions (필수)
|
|
25
|
+
│ └── -sections/ # 섹션 분리 (선택, 복잡한 경우만)
|
|
25
26
|
└── posts/
|
|
26
27
|
├── index.tsx
|
|
27
|
-
|
|
28
|
+
├── $slug.tsx
|
|
29
|
+
├── -components/ # 필수
|
|
30
|
+
├── -hooks/ # 필수
|
|
31
|
+
└── -functions/ # 필수
|
|
28
32
|
```
|
|
29
33
|
|
|
30
34
|
| 접두사 | 용도 | 라우트 생성 |
|
|
@@ -33,6 +37,11 @@ routes/
|
|
|
33
37
|
| `$` | 동적 파라미터 | ✅ 생성 |
|
|
34
38
|
| `_` | Pathless Layout | ✅ 생성 (경로 없음) |
|
|
35
39
|
|
|
40
|
+
**⚠️ 필수 규칙:**
|
|
41
|
+
- 모든 페이지에 `-components/`, `-hooks/`, `-functions/` 폴더 **필수**
|
|
42
|
+
- 페이지 크기(줄 수)와 **무관**하게 Custom Hook은 반드시 `-hooks/` 폴더에 분리
|
|
43
|
+
- `-sections/`는 200줄 이상 복잡한 페이지에서만 선택적 사용
|
|
44
|
+
|
|
36
45
|
</route_structure>
|
|
37
46
|
|
|
38
47
|
---
|
|
@@ -257,15 +266,25 @@ export const UserCard = ({
|
|
|
257
266
|
|
|
258
267
|
---
|
|
259
268
|
|
|
260
|
-
<
|
|
269
|
+
<folder_structure_rules>
|
|
270
|
+
|
|
271
|
+
## 폴더 구조 규칙
|
|
272
|
+
|
|
273
|
+
**⚠️ 모든 페이지에 필수:**
|
|
274
|
+
- `-components/`: 페이지 전용 컴포넌트
|
|
275
|
+
- `-hooks/`: 페이지 전용 Custom Hook (줄 수 무관)
|
|
276
|
+
- `-functions/`: 페이지 전용 Server Functions
|
|
277
|
+
|
|
278
|
+
**선택적 사용:**
|
|
279
|
+
- `-sections/`: 200줄+ 복잡한 페이지에서만 사용
|
|
261
280
|
|
|
262
|
-
| 페이지 크기 |
|
|
281
|
+
| 페이지 크기 | 필수 | 선택 |
|
|
263
282
|
|------------|------|------|
|
|
264
|
-
| ~100줄 |
|
|
265
|
-
| 100-200줄 | `-components/`
|
|
266
|
-
| 200줄+ | `-
|
|
283
|
+
| ~100줄 | `-components/`, `-hooks/`, `-functions/` | - |
|
|
284
|
+
| 100-200줄 | `-components/`, `-hooks/`, `-functions/` | - |
|
|
285
|
+
| 200줄+ | `-components/`, `-hooks/`, `-functions/` | `-sections/` |
|
|
267
286
|
|
|
268
|
-
</
|
|
287
|
+
</folder_structure_rules>
|
|
269
288
|
|
|
270
289
|
---
|
|
271
290
|
|