@kood/claude-code 0.1.7 → 0.1.10
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 +137 -3
- package/package.json +8 -2
- package/templates/hono/CLAUDE.md +53 -326
- 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 +49 -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 +24 -122
- 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 +64 -320
- package/templates/hono/docs/library/zod/index.md +53 -257
- package/templates/npx/CLAUDE.md +58 -276
- package/templates/npx/docs/references/patterns.md +160 -0
- package/templates/tanstack-start/CLAUDE.md +0 -4
- package/templates/tanstack-start/docs/architecture/architecture.md +44 -589
- package/templates/tanstack-start/docs/design/index.md +119 -12
- 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/tanstack-query/index.md +18 -2
- package/templates/tanstack-start/docs/library/zod/index.md +16 -1
- package/templates/tanstack-start/docs/design/accessibility.md +0 -163
- package/templates/tanstack-start/docs/design/color.md +0 -93
- package/templates/tanstack-start/docs/design/spacing.md +0 -122
- package/templates/tanstack-start/docs/design/typography.md +0 -80
- 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 -48
- package/templates/tanstack-start/docs/library/zod/basic-types.md +0 -74
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
# Zod v4 - Schema Validation
|
|
2
2
|
|
|
3
|
-
> TypeScript-first 스키마
|
|
3
|
+
> TypeScript-first 스키마 검증
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
##
|
|
8
|
-
|
|
9
|
-
이 문서는 **Zod v4** 기준입니다. v3과 API가 다릅니다.
|
|
7
|
+
## 버전 주의
|
|
10
8
|
|
|
11
9
|
```typescript
|
|
12
10
|
// ✅ v4 문법
|
|
@@ -17,7 +15,6 @@ z.uuid()
|
|
|
17
15
|
// ❌ v3 문법 (사용 금지)
|
|
18
16
|
z.string().email()
|
|
19
17
|
z.string().url()
|
|
20
|
-
z.string().uuid()
|
|
21
18
|
```
|
|
22
19
|
|
|
23
20
|
---
|
|
@@ -32,76 +29,40 @@ npm install zod
|
|
|
32
29
|
|
|
33
30
|
## 기본 타입
|
|
34
31
|
|
|
35
|
-
### Primitives
|
|
36
|
-
|
|
37
32
|
```typescript
|
|
38
33
|
import { z } from 'zod'
|
|
39
34
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
// 불리언
|
|
47
|
-
const booleanSchema = z.boolean()
|
|
48
|
-
|
|
49
|
-
// BigInt
|
|
50
|
-
const bigintSchema = z.bigint()
|
|
51
|
-
|
|
52
|
-
// Date
|
|
53
|
-
const dateSchema = z.date()
|
|
54
|
-
|
|
55
|
-
// Undefined / Null
|
|
56
|
-
const undefinedSchema = z.undefined()
|
|
57
|
-
const nullSchema = z.null()
|
|
35
|
+
z.string()
|
|
36
|
+
z.number()
|
|
37
|
+
z.boolean()
|
|
38
|
+
z.date()
|
|
39
|
+
z.undefined()
|
|
40
|
+
z.null()
|
|
58
41
|
```
|
|
59
42
|
|
|
60
|
-
### 문자열
|
|
43
|
+
### 문자열 (v4)
|
|
61
44
|
|
|
62
45
|
```typescript
|
|
63
|
-
// ✅ v4 전용 메서드
|
|
64
46
|
z.email() // 이메일
|
|
65
47
|
z.url() // URL
|
|
66
48
|
z.uuid() // UUID
|
|
67
|
-
z.
|
|
68
|
-
z.
|
|
69
|
-
z.ip() // IP 주소
|
|
70
|
-
|
|
71
|
-
// 문자열 + 체이닝
|
|
72
|
-
z.string().min(1) // 최소 길이
|
|
73
|
-
z.string().max(100) // 최대 길이
|
|
74
|
-
z.string().length(10) // 정확한 길이
|
|
75
|
-
z.string().trim() // 앞뒤 공백 제거
|
|
76
|
-
z.string().toLowerCase() // 소문자 변환
|
|
77
|
-
z.string().toUpperCase() // 대문자 변환
|
|
78
|
-
z.string().startsWith('a') // 접두사
|
|
79
|
-
z.string().endsWith('z') // 접미사
|
|
80
|
-
z.string().includes('test') // 포함
|
|
49
|
+
z.string().min(1).max(100) // 길이
|
|
50
|
+
z.string().trim() // 공백 제거
|
|
81
51
|
z.string().regex(/^[a-z]+$/) // 정규식
|
|
82
52
|
```
|
|
83
53
|
|
|
84
|
-
### 숫자
|
|
54
|
+
### 숫자
|
|
85
55
|
|
|
86
56
|
```typescript
|
|
87
|
-
z.number().int()
|
|
88
|
-
z.number().positive()
|
|
89
|
-
z.number().
|
|
90
|
-
z.number().nonnegative() // 0 이상
|
|
91
|
-
z.number().nonpositive() // 0 이하
|
|
92
|
-
z.number().min(1) // 최소값
|
|
93
|
-
z.number().max(100) // 최대값
|
|
94
|
-
z.number().multipleOf(5) // 배수
|
|
95
|
-
z.number().finite() // 유한수
|
|
96
|
-
z.number().safe() // 안전한 정수 범위
|
|
57
|
+
z.number().int() // 정수
|
|
58
|
+
z.number().positive() // 양수
|
|
59
|
+
z.number().min(1).max(100)
|
|
97
60
|
```
|
|
98
61
|
|
|
99
62
|
---
|
|
100
63
|
|
|
101
64
|
## 객체
|
|
102
65
|
|
|
103
|
-
### 기본 객체
|
|
104
|
-
|
|
105
66
|
```typescript
|
|
106
67
|
const userSchema = z.object({
|
|
107
68
|
id: z.string(),
|
|
@@ -111,137 +72,45 @@ const userSchema = z.object({
|
|
|
111
72
|
})
|
|
112
73
|
|
|
113
74
|
type User = z.infer<typeof userSchema>
|
|
114
|
-
// { id: string; email: string; name: string; age: number }
|
|
115
75
|
```
|
|
116
76
|
|
|
117
77
|
### Optional / Nullable
|
|
118
78
|
|
|
119
79
|
```typescript
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
nullable: z.string().nullable(), // string | null
|
|
124
|
-
nullish: z.string().nullish(), // string | null | undefined
|
|
125
|
-
})
|
|
80
|
+
z.string().optional() // string | undefined
|
|
81
|
+
z.string().nullable() // string | null
|
|
82
|
+
z.string().nullish() // string | null | undefined
|
|
126
83
|
```
|
|
127
84
|
|
|
128
85
|
### 기본값
|
|
129
86
|
|
|
130
87
|
```typescript
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
role: z.enum(['user', 'admin']).default('user'),
|
|
134
|
-
active: z.boolean().default(true),
|
|
135
|
-
})
|
|
88
|
+
z.string().default('Anonymous')
|
|
89
|
+
z.enum(['user', 'admin']).default('user')
|
|
136
90
|
```
|
|
137
91
|
|
|
138
|
-
### Partial /
|
|
92
|
+
### Partial / Pick / Omit
|
|
139
93
|
|
|
140
94
|
```typescript
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
// 모든 필드 optional
|
|
147
|
-
const partialSchema = userSchema.partial()
|
|
148
|
-
// { name?: string; email?: string }
|
|
149
|
-
|
|
150
|
-
// 모든 필드 required
|
|
151
|
-
const requiredSchema = partialSchema.required()
|
|
152
|
-
// { name: string; email: string }
|
|
153
|
-
|
|
154
|
-
// 특정 필드만 partial
|
|
155
|
-
const mixedSchema = userSchema.partial({ email: true })
|
|
156
|
-
// { name: string; email?: string }
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
### Pick / Omit
|
|
160
|
-
|
|
161
|
-
```typescript
|
|
162
|
-
const userSchema = z.object({
|
|
163
|
-
id: z.string(),
|
|
164
|
-
name: z.string(),
|
|
165
|
-
email: z.email(),
|
|
166
|
-
password: z.string(),
|
|
167
|
-
})
|
|
168
|
-
|
|
169
|
-
// 특정 필드만 선택
|
|
170
|
-
const publicSchema = userSchema.pick({ id: true, name: true })
|
|
171
|
-
// { id: string; name: string }
|
|
172
|
-
|
|
173
|
-
// 특정 필드 제외
|
|
174
|
-
const createSchema = userSchema.omit({ id: true })
|
|
175
|
-
// { name: string; email: string; password: string }
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
### Extend / Merge
|
|
179
|
-
|
|
180
|
-
```typescript
|
|
181
|
-
const baseSchema = z.object({
|
|
182
|
-
id: z.string(),
|
|
183
|
-
createdAt: z.date(),
|
|
184
|
-
})
|
|
185
|
-
|
|
186
|
-
// 확장
|
|
187
|
-
const userSchema = baseSchema.extend({
|
|
188
|
-
name: z.string(),
|
|
189
|
-
email: z.email(),
|
|
190
|
-
})
|
|
191
|
-
|
|
192
|
-
// 병합
|
|
193
|
-
const anotherSchema = z.object({
|
|
194
|
-
updatedAt: z.date(),
|
|
195
|
-
})
|
|
196
|
-
const mergedSchema = baseSchema.merge(anotherSchema)
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
---
|
|
200
|
-
|
|
201
|
-
## 배열
|
|
202
|
-
|
|
203
|
-
```typescript
|
|
204
|
-
// 기본 배열
|
|
205
|
-
z.array(z.string())
|
|
206
|
-
|
|
207
|
-
// 길이 제한
|
|
208
|
-
z.array(z.string()).min(1)
|
|
209
|
-
z.array(z.string()).max(10)
|
|
210
|
-
z.array(z.string()).length(5)
|
|
211
|
-
z.array(z.string()).nonempty()
|
|
212
|
-
|
|
213
|
-
// 중복 제거 (unique는 없음, transform 사용)
|
|
214
|
-
z.array(z.string()).transform((arr) => [...new Set(arr)])
|
|
95
|
+
userSchema.partial() // 모든 필드 optional
|
|
96
|
+
userSchema.partial({ email: true }) // 특정 필드만
|
|
97
|
+
userSchema.pick({ id: true, name: true })
|
|
98
|
+
userSchema.omit({ password: true })
|
|
215
99
|
```
|
|
216
100
|
|
|
217
101
|
---
|
|
218
102
|
|
|
219
103
|
## Enum / Union
|
|
220
104
|
|
|
221
|
-
### Enum
|
|
222
|
-
|
|
223
105
|
```typescript
|
|
224
|
-
//
|
|
225
|
-
|
|
226
|
-
type Status = z.infer<typeof statusSchema>
|
|
227
|
-
// 'pending' | 'active' | 'completed'
|
|
228
|
-
|
|
229
|
-
// TypeScript enum 사용
|
|
230
|
-
enum Role {
|
|
231
|
-
User = 'user',
|
|
232
|
-
Admin = 'admin',
|
|
233
|
-
}
|
|
234
|
-
const roleSchema = z.nativeEnum(Role)
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
### Union
|
|
106
|
+
// Enum
|
|
107
|
+
z.enum(['pending', 'active', 'completed'])
|
|
238
108
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
const stringOrNumber = z.union([z.string(), z.number()])
|
|
109
|
+
// Union
|
|
110
|
+
z.union([z.string(), z.number()])
|
|
242
111
|
|
|
243
|
-
// Discriminated
|
|
244
|
-
|
|
112
|
+
// Discriminated Union
|
|
113
|
+
z.discriminatedUnion('type', [
|
|
245
114
|
z.object({ type: z.literal('success'), data: z.unknown() }),
|
|
246
115
|
z.object({ type: z.literal('error'), message: z.string() }),
|
|
247
116
|
])
|
|
@@ -252,12 +121,9 @@ const responseSchema = z.discriminatedUnion('type', [
|
|
|
252
121
|
## Coerce (타입 변환)
|
|
253
122
|
|
|
254
123
|
```typescript
|
|
255
|
-
// Query parameter 등에서 유용
|
|
256
|
-
z.coerce.string() // any → string
|
|
257
124
|
z.coerce.number() // string → number
|
|
258
|
-
z.coerce.boolean() // 'true'
|
|
125
|
+
z.coerce.boolean() // 'true' → boolean
|
|
259
126
|
z.coerce.date() // string → Date
|
|
260
|
-
z.coerce.bigint() // string → bigint
|
|
261
127
|
```
|
|
262
128
|
|
|
263
129
|
---
|
|
@@ -265,67 +131,27 @@ z.coerce.bigint() // string → bigint
|
|
|
265
131
|
## Transform
|
|
266
132
|
|
|
267
133
|
```typescript
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
// 타입 변환
|
|
272
|
-
const numberToString = z.number().transform((n) => String(n))
|
|
273
|
-
|
|
274
|
-
// 복합 변환
|
|
275
|
-
const userSchema = z.object({
|
|
276
|
-
email: z.email().transform((e) => e.toLowerCase()),
|
|
277
|
-
name: z.string().transform((n) => n.trim()),
|
|
278
|
-
tags: z.string().transform((s) => s.split(',').map((t) => t.trim())),
|
|
279
|
-
})
|
|
134
|
+
z.email().transform((e) => e.toLowerCase())
|
|
135
|
+
z.string().transform((s) => s.split(','))
|
|
280
136
|
```
|
|
281
137
|
|
|
282
138
|
---
|
|
283
139
|
|
|
284
140
|
## Refine (커스텀 검증)
|
|
285
141
|
|
|
286
|
-
### 단일 필드
|
|
287
|
-
|
|
288
142
|
```typescript
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
)
|
|
293
|
-
```
|
|
294
|
-
|
|
295
|
-
### 객체 전체
|
|
143
|
+
// 단일 필드
|
|
144
|
+
z.string().refine((val) => val.length >= 8, {
|
|
145
|
+
message: '8자 이상 필요',
|
|
146
|
+
})
|
|
296
147
|
|
|
297
|
-
|
|
298
|
-
|
|
148
|
+
// 객체 전체
|
|
149
|
+
z.object({
|
|
299
150
|
password: z.string().min(8),
|
|
300
151
|
confirmPassword: z.string(),
|
|
301
|
-
}).refine(
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
message: '비밀번호가 일치하지 않습니다',
|
|
305
|
-
path: ['confirmPassword'], // 에러 위치 지정
|
|
306
|
-
}
|
|
307
|
-
)
|
|
308
|
-
```
|
|
309
|
-
|
|
310
|
-
### SuperRefine (고급)
|
|
311
|
-
|
|
312
|
-
```typescript
|
|
313
|
-
const schema = z.string().superRefine((val, ctx) => {
|
|
314
|
-
if (val.length < 8) {
|
|
315
|
-
ctx.addIssue({
|
|
316
|
-
code: z.ZodIssueCode.too_small,
|
|
317
|
-
minimum: 8,
|
|
318
|
-
type: 'string',
|
|
319
|
-
inclusive: true,
|
|
320
|
-
message: '8자 이상 입력하세요',
|
|
321
|
-
})
|
|
322
|
-
}
|
|
323
|
-
if (!/[A-Z]/.test(val)) {
|
|
324
|
-
ctx.addIssue({
|
|
325
|
-
code: z.ZodIssueCode.custom,
|
|
326
|
-
message: '대문자를 포함하세요',
|
|
327
|
-
})
|
|
328
|
-
}
|
|
152
|
+
}).refine((data) => data.password === data.confirmPassword, {
|
|
153
|
+
message: '비밀번호 불일치',
|
|
154
|
+
path: ['confirmPassword'],
|
|
329
155
|
})
|
|
330
156
|
```
|
|
331
157
|
|
|
@@ -333,68 +159,40 @@ const schema = z.string().superRefine((val, ctx) => {
|
|
|
333
159
|
|
|
334
160
|
## 에러 처리
|
|
335
161
|
|
|
336
|
-
### safeParse
|
|
337
|
-
|
|
338
162
|
```typescript
|
|
339
|
-
const schema = z.object({
|
|
340
|
-
email: z.email(),
|
|
341
|
-
age: z.number().min(0),
|
|
342
|
-
})
|
|
343
|
-
|
|
344
163
|
const result = schema.safeParse(input)
|
|
345
164
|
|
|
346
165
|
if (result.success) {
|
|
347
|
-
console.log(result.data)
|
|
166
|
+
console.log(result.data)
|
|
348
167
|
} else {
|
|
349
168
|
console.log(result.error.flatten())
|
|
350
|
-
// {
|
|
351
|
-
// formErrors: [],
|
|
352
|
-
// fieldErrors: {
|
|
353
|
-
// email: ['Invalid email'],
|
|
354
|
-
// age: ['Number must be greater than or equal to 0']
|
|
355
|
-
// }
|
|
356
|
-
// }
|
|
169
|
+
// { fieldErrors: { email: ['Invalid email'] } }
|
|
357
170
|
}
|
|
358
171
|
```
|
|
359
172
|
|
|
360
|
-
### 커스텀 에러
|
|
173
|
+
### 커스텀 에러
|
|
361
174
|
|
|
362
175
|
```typescript
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
name: z.string().min(1, { message: '이름을 입력하세요' }),
|
|
366
|
-
age: z.number()
|
|
367
|
-
.min(0, { message: '나이는 0 이상이어야 합니다' })
|
|
368
|
-
.max(150, { message: '나이는 150 이하여야 합니다' }),
|
|
369
|
-
})
|
|
176
|
+
z.email({ message: '올바른 이메일 입력' })
|
|
177
|
+
z.string().min(1, { message: '필수 입력' })
|
|
370
178
|
```
|
|
371
179
|
|
|
372
180
|
---
|
|
373
181
|
|
|
374
|
-
## Hono와 함께
|
|
182
|
+
## Hono와 함께
|
|
375
183
|
|
|
376
184
|
```typescript
|
|
377
|
-
import { Hono } from 'hono'
|
|
378
185
|
import { zValidator } from '@hono/zod-validator'
|
|
379
|
-
import { z } from 'zod'
|
|
380
186
|
|
|
381
|
-
const
|
|
382
|
-
|
|
383
|
-
// ✅ v4 문법 사용
|
|
384
|
-
const createUserSchema = z.object({
|
|
187
|
+
const schema = z.object({
|
|
385
188
|
email: z.email(),
|
|
386
189
|
name: z.string().min(1).trim(),
|
|
387
|
-
website: z.url().optional(),
|
|
388
190
|
})
|
|
389
191
|
|
|
390
|
-
app.post(
|
|
391
|
-
'
|
|
392
|
-
zValidator('json', createUserSchema, (result, c) => {
|
|
192
|
+
app.post('/users',
|
|
193
|
+
zValidator('json', schema, (result, c) => {
|
|
393
194
|
if (!result.success) {
|
|
394
|
-
return c.json(
|
|
395
|
-
{ errors: result.error.flatten().fieldErrors },
|
|
396
|
-
400
|
|
397
|
-
)
|
|
195
|
+
return c.json({ errors: result.error.flatten().fieldErrors }, 400)
|
|
398
196
|
}
|
|
399
197
|
}),
|
|
400
198
|
(c) => {
|
|
@@ -409,5 +207,3 @@ app.post(
|
|
|
409
207
|
## 관련 문서
|
|
410
208
|
|
|
411
209
|
- [Hono 검증](../hono/validation.md)
|
|
412
|
-
- [복잡한 타입](./complex-types.md)
|
|
413
|
-
- [Transform](./transforms.md)
|