@kood/claude-code 0.1.5 → 0.1.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 +21 -243
- package/package.json +1 -1
- package/templates/hono/CLAUDE.md +10 -6
- package/templates/hono/docs/deployment/index.md +5 -0
- package/templates/hono/docs/library/hono/index.md +6 -0
- package/templates/hono/docs/library/prisma/index.md +3 -0
- package/templates/npx/CLAUDE.md +8 -2
- package/templates/tanstack-start/CLAUDE.md +105 -259
- 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/accessibility.md +56 -326
- package/templates/tanstack-start/docs/design/color.md +37 -179
- package/templates/tanstack-start/docs/design/components.md +77 -311
- package/templates/tanstack-start/docs/design/index.md +24 -87
- package/templates/tanstack-start/docs/design/safe-area.md +51 -250
- package/templates/tanstack-start/docs/design/spacing.md +57 -276
- package/templates/tanstack-start/docs/design/tailwind-setup.md +45 -359
- package/templates/tanstack-start/docs/design/typography.md +40 -284
- package/templates/tanstack-start/docs/guides/best-practices.md +3 -8
- package/templates/tanstack-start/docs/guides/env-setup.md +3 -3
- 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 +13 -113
- package/templates/tanstack-start/docs/library/prisma/transactions.md +20 -110
- package/templates/tanstack-start/docs/library/tanstack-query/index.md +12 -99
- 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/setup.md +11 -73
- 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 +41 -172
- package/templates/tanstack-start/docs/library/tanstack-start/setup.md +6 -39
- package/templates/tanstack-start/docs/library/zod/basic-types.md +33 -145
- package/templates/tanstack-start/docs/library/zod/complex-types.md +32 -156
- package/templates/tanstack-start/docs/library/zod/index.md +22 -150
- 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/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,186 +1,74 @@
|
|
|
1
1
|
# Zod - 기본 타입
|
|
2
2
|
|
|
3
|
-
> **상위 문서**: [Zod](./index.md)
|
|
4
|
-
|
|
5
3
|
## 문자열
|
|
6
4
|
|
|
7
5
|
```typescript
|
|
8
6
|
z.string()
|
|
9
|
-
z.string().min(1)
|
|
10
|
-
z.string().
|
|
11
|
-
z.string().
|
|
12
|
-
z.string().regex(/^\d+$/) // 정규식
|
|
13
|
-
z.string().trim() // 공백 제거
|
|
14
|
-
z.string().toLowerCase() // 소문자 변환
|
|
15
|
-
z.string().toUpperCase() // 대문자 변환
|
|
7
|
+
z.string().min(1).max(100).length(5)
|
|
8
|
+
z.string().regex(/^\d+$/)
|
|
9
|
+
z.string().trim().toLowerCase().toUpperCase()
|
|
16
10
|
```
|
|
17
11
|
|
|
18
|
-
### v4
|
|
12
|
+
### v4 최상위 포맷 API
|
|
19
13
|
|
|
20
14
|
```typescript
|
|
21
|
-
|
|
22
|
-
z.
|
|
23
|
-
z.
|
|
24
|
-
|
|
25
|
-
z.
|
|
26
|
-
z.
|
|
27
|
-
z.
|
|
28
|
-
z.
|
|
29
|
-
z.cuid2() // CUID2 형식
|
|
30
|
-
z.ulid() // ULID 형식
|
|
31
|
-
z.emoji() // 이모지 (단일 문자)
|
|
32
|
-
z.ipv4() // IPv4 주소
|
|
33
|
-
z.ipv6() // IPv6 주소
|
|
34
|
-
z.cidrv4() // IPv4 범위
|
|
35
|
-
z.cidrv6() // IPv6 범위
|
|
36
|
-
|
|
37
|
-
// ISO 날짜/시간 형식
|
|
38
|
-
z.iso.date() // ISO 날짜 (2024-01-15)
|
|
39
|
-
z.iso.time() // ISO 시간 (14:30:00)
|
|
40
|
-
z.iso.datetime() // ISO 날짜시간
|
|
41
|
-
z.iso.duration() // ISO 기간 (P1D, PT1H)
|
|
15
|
+
z.email() z.url() z.uuid() z.base64()
|
|
16
|
+
z.nanoid() z.cuid() z.cuid2() z.ulid()
|
|
17
|
+
z.ipv4() z.ipv6() z.cidrv4() z.cidrv6()
|
|
18
|
+
|
|
19
|
+
z.iso.date() // 2024-01-15
|
|
20
|
+
z.iso.time() // 14:30:00
|
|
21
|
+
z.iso.datetime() // ISO 날짜시간
|
|
22
|
+
z.iso.duration() // P1D, PT1H
|
|
42
23
|
```
|
|
43
24
|
|
|
44
|
-
### 템플릿 리터럴
|
|
25
|
+
### 템플릿 리터럴 (v4)
|
|
45
26
|
|
|
46
27
|
```typescript
|
|
47
|
-
const
|
|
48
|
-
// `
|
|
49
|
-
|
|
50
|
-
const cssUnits = z.enum(["px", "em", "rem", "%"])
|
|
51
|
-
const css = z.templateLiteral([z.number(), cssUnits])
|
|
52
|
-
// `${number}px` | `${number}em` | `${number}rem` | `${number}%`
|
|
53
|
-
|
|
54
|
-
const email = z.templateLiteral([
|
|
55
|
-
z.string().min(1),
|
|
56
|
-
"@",
|
|
57
|
-
z.string().max(64),
|
|
58
|
-
])
|
|
59
|
-
// `${string}@${string}` (min/max refinements도 적용됨!)
|
|
28
|
+
const css = z.templateLiteral([z.number(), z.enum(["px", "em", "rem"])])
|
|
29
|
+
// `${number}px` | `${number}em` | `${number}rem`
|
|
60
30
|
```
|
|
61
31
|
|
|
62
32
|
## 숫자
|
|
63
33
|
|
|
64
34
|
```typescript
|
|
65
35
|
z.number()
|
|
66
|
-
z.number().min(0)
|
|
67
|
-
z.number().
|
|
68
|
-
z.number().
|
|
69
|
-
z.number().positive() // 양수만
|
|
70
|
-
z.number().negative() // 음수만
|
|
71
|
-
z.number().nonnegative() // 0 이상
|
|
72
|
-
z.number().nonpositive() // 0 이하
|
|
73
|
-
z.number().finite() // 유한 숫자
|
|
74
|
-
z.number().safe() // 안전한 정수 범위
|
|
36
|
+
z.number().min(0).max(100).int()
|
|
37
|
+
z.number().positive().negative().nonnegative().nonpositive()
|
|
38
|
+
z.number().finite().safe()
|
|
75
39
|
```
|
|
76
40
|
|
|
77
41
|
## 불리언
|
|
78
42
|
|
|
79
43
|
```typescript
|
|
80
44
|
z.boolean()
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
### 문자열 불리언 (v4 신규)
|
|
84
|
-
|
|
85
|
-
환경변수 스타일 불리언 변환:
|
|
86
|
-
|
|
87
|
-
```typescript
|
|
88
|
-
const strbool = z.stringbool()
|
|
89
|
-
|
|
90
|
-
strbool.parse("true") // => true
|
|
91
|
-
strbool.parse("1") // => true
|
|
92
|
-
strbool.parse("yes") // => true
|
|
93
|
-
strbool.parse("on") // => true
|
|
94
|
-
strbool.parse("enabled") // => true
|
|
95
|
-
|
|
96
|
-
strbool.parse("false") // => false
|
|
97
|
-
strbool.parse("0") // => false
|
|
98
|
-
strbool.parse("no") // => false
|
|
99
|
-
strbool.parse("off") // => false
|
|
100
|
-
strbool.parse("disabled") // => false
|
|
101
|
-
|
|
102
|
-
// 커스텀 truthy/falsy 값
|
|
103
|
-
z.stringbool({
|
|
104
|
-
truthy: ["yes", "true"],
|
|
105
|
-
falsy: ["no", "false"]
|
|
106
|
-
})
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
## 날짜
|
|
110
|
-
|
|
111
|
-
```typescript
|
|
112
|
-
z.date()
|
|
113
|
-
z.date().min(new Date('2020-01-01'))
|
|
114
|
-
z.date().max(new Date('2030-12-31'))
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
## BigInt
|
|
118
|
-
|
|
119
|
-
```typescript
|
|
120
|
-
z.bigint()
|
|
121
|
-
z.bigint().positive()
|
|
122
|
-
z.bigint().negative()
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
## 리터럴
|
|
126
|
-
|
|
127
|
-
```typescript
|
|
128
|
-
z.literal('hello') // 정확히 'hello'만
|
|
129
|
-
z.literal(42) // 정확히 42만
|
|
130
|
-
z.literal(true) // 정확히 true만
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
## Null, Undefined, Void
|
|
134
|
-
|
|
135
|
-
```typescript
|
|
136
|
-
z.null() // null만 허용
|
|
137
|
-
z.undefined() // undefined만 허용
|
|
138
|
-
z.void() // undefined 허용 (반환 타입용)
|
|
139
|
-
```
|
|
140
45
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
```typescript
|
|
144
|
-
z.any() // 모든 타입 허용
|
|
145
|
-
z.unknown() // unknown 타입
|
|
146
|
-
z.never() // 값 없음 (유니온에서 사용)
|
|
46
|
+
// v4 문자열 불리언 (환경변수용)
|
|
47
|
+
z.stringbool() // "true"/"yes"/"1" → true, "false"/"no"/"0" → false
|
|
147
48
|
```
|
|
148
49
|
|
|
149
|
-
|
|
150
|
-
> ```typescript
|
|
151
|
-
> const mySchema = z.object({ a: z.any(), b: z.unknown() })
|
|
152
|
-
> // v3: { a?: any; b?: unknown }
|
|
153
|
-
> // v4: { a: any; b: unknown }
|
|
154
|
-
> ```
|
|
155
|
-
|
|
156
|
-
## Optional & Nullable
|
|
50
|
+
## 날짜/BigInt/리터럴
|
|
157
51
|
|
|
158
52
|
```typescript
|
|
159
|
-
z.
|
|
160
|
-
z.
|
|
161
|
-
z.
|
|
53
|
+
z.date().min(new Date('2020-01-01')).max(new Date('2030-12-31'))
|
|
54
|
+
z.bigint().positive().negative()
|
|
55
|
+
z.literal('hello') z.literal(42) z.literal(true)
|
|
162
56
|
```
|
|
163
57
|
|
|
164
|
-
##
|
|
58
|
+
## 특수 타입
|
|
165
59
|
|
|
166
60
|
```typescript
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
role: z.enum(['admin', 'user']).default('user'),
|
|
170
|
-
})
|
|
171
|
-
|
|
172
|
-
Schema.parse({}) // { name: 'Anonymous', role: 'user' }
|
|
61
|
+
z.null() z.undefined() z.void()
|
|
62
|
+
z.any() z.unknown() z.never()
|
|
173
63
|
```
|
|
174
64
|
|
|
175
|
-
##
|
|
65
|
+
## 수정자
|
|
176
66
|
|
|
177
67
|
```typescript
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
type NumberType = z.infer<typeof numberSchema> // number | undefined
|
|
68
|
+
z.string().optional() // string | undefined
|
|
69
|
+
z.string().nullable() // string | null
|
|
70
|
+
z.string().nullish() // string | null | undefined
|
|
71
|
+
z.string().default('Anonymous')
|
|
183
72
|
|
|
184
|
-
|
|
185
|
-
type DateType = z.infer<typeof dateSchema> // Date | null
|
|
73
|
+
type T = z.infer<typeof schema>
|
|
186
74
|
```
|
|
@@ -1,204 +1,80 @@
|
|
|
1
1
|
# Zod - 복합 타입
|
|
2
2
|
|
|
3
|
-
> **상위 문서**: [Zod](./index.md)
|
|
4
|
-
|
|
5
3
|
## 객체
|
|
6
4
|
|
|
7
5
|
```typescript
|
|
8
6
|
const UserSchema = z.object({
|
|
9
7
|
name: z.string(),
|
|
10
|
-
email: z.email(),
|
|
11
|
-
age: z.number().optional(),
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
// 검증
|
|
15
|
-
const result = UserSchema.parse({
|
|
16
|
-
name: 'john',
|
|
17
|
-
email: 'john@example.com',
|
|
18
|
-
age: 25,
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
// 안전한 검증
|
|
22
|
-
const safeResult = UserSchema.safeParse(data)
|
|
23
|
-
if (safeResult.success) {
|
|
24
|
-
console.log(safeResult.data)
|
|
25
|
-
} else {
|
|
26
|
-
console.log(safeResult.error)
|
|
27
|
-
}
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
### 객체 메서드
|
|
31
|
-
|
|
32
|
-
```typescript
|
|
33
|
-
// 부분 객체 (모든 필드가 optional)
|
|
34
|
-
const PartialUser = UserSchema.partial()
|
|
35
|
-
|
|
36
|
-
// 필수 객체 (모든 필드가 required)
|
|
37
|
-
const RequiredUser = UserSchema.required()
|
|
38
|
-
|
|
39
|
-
// 필드 선택
|
|
40
|
-
const UserName = UserSchema.pick({ name: true })
|
|
41
|
-
|
|
42
|
-
// 필드 제외
|
|
43
|
-
const UserWithoutEmail = UserSchema.omit({ email: true })
|
|
44
|
-
|
|
45
|
-
// 확장
|
|
46
|
-
const ExtendedUser = UserSchema.extend({
|
|
47
|
-
role: z.enum(['admin', 'user']),
|
|
8
|
+
email: z.email(),
|
|
9
|
+
age: z.number().optional(),
|
|
48
10
|
})
|
|
49
11
|
|
|
50
|
-
//
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
12
|
+
// 메서드
|
|
13
|
+
UserSchema.partial() // 모든 필드 optional
|
|
14
|
+
UserSchema.required() // 모든 필드 required
|
|
15
|
+
UserSchema.pick({ name: true }) // 특정 필드만
|
|
16
|
+
UserSchema.omit({ email: true }) // 특정 필드 제외
|
|
17
|
+
UserSchema.extend({ role: z.enum(['admin', 'user']) })
|
|
18
|
+
UserSchema.merge(AnotherSchema)
|
|
55
19
|
|
|
56
|
-
|
|
57
|
-
// v3
|
|
58
|
-
z.object({ name: z.string() }).strict() // 추가 키 에러
|
|
59
|
-
z.object({ name: z.string() }).passthrough() // 추가 키 통과
|
|
60
|
-
|
|
61
|
-
// v4 - 새로운 API
|
|
20
|
+
// v4 Strict/Loose
|
|
62
21
|
z.strictObject({ name: z.string() }) // 추가 키 에러
|
|
63
22
|
z.looseObject({ name: z.string() }) // 추가 키 통과
|
|
64
23
|
```
|
|
65
24
|
|
|
66
|
-
##
|
|
25
|
+
## 배열/튜플
|
|
67
26
|
|
|
68
27
|
```typescript
|
|
69
28
|
z.array(z.string())
|
|
70
|
-
z.array(z.number()).min(1)
|
|
71
|
-
z.array(z.number()).max(10) // 최대 10개
|
|
72
|
-
z.array(z.number()).length(5) // 정확히 5개
|
|
73
|
-
z.array(z.number()).nonempty() // 비어있지 않음
|
|
74
|
-
```
|
|
29
|
+
z.array(z.number()).min(1).max(10).length(5).nonempty()
|
|
75
30
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
```typescript
|
|
79
|
-
const tuple = z.tuple([
|
|
80
|
-
z.string(), // 첫 번째 요소
|
|
81
|
-
z.number(), // 두 번째 요소
|
|
82
|
-
])
|
|
83
|
-
|
|
84
|
-
type Tuple = z.infer<typeof tuple>
|
|
85
|
-
// [string, number]
|
|
31
|
+
z.tuple([z.string(), z.number()]) // [string, number]
|
|
86
32
|
```
|
|
87
33
|
|
|
88
34
|
## 유니온
|
|
89
35
|
|
|
90
36
|
```typescript
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const StringOrNumber = z.string().or(z.number())
|
|
94
|
-
|
|
95
|
-
type StringOrNumber = z.infer<typeof StringOrNumber>
|
|
96
|
-
// string | number
|
|
97
|
-
```
|
|
37
|
+
z.union([z.string(), z.number()])
|
|
38
|
+
z.string().or(z.number())
|
|
98
39
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
```typescript
|
|
102
|
-
const Shape = z.discriminatedUnion('type', [
|
|
40
|
+
// Discriminated Union
|
|
41
|
+
z.discriminatedUnion('type', [
|
|
103
42
|
z.object({ type: z.literal('circle'), radius: z.number() }),
|
|
104
43
|
z.object({ type: z.literal('rectangle'), width: z.number(), height: z.number() }),
|
|
105
44
|
])
|
|
106
|
-
|
|
107
|
-
type Shape = z.infer<typeof Shape>
|
|
108
|
-
// { type: 'circle'; radius: number } | { type: 'rectangle'; width: number; height: number }
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
### v4 향상된 Discriminated Union
|
|
112
|
-
|
|
113
|
-
```typescript
|
|
114
|
-
// 유니온 및 파이프 discriminator 지원
|
|
115
|
-
const MyResult = z.discriminatedUnion("status", [
|
|
116
|
-
// 단순 리터럴
|
|
117
|
-
z.object({ status: z.literal("aaa"), data: z.string() }),
|
|
118
|
-
// 유니온 discriminator
|
|
119
|
-
z.object({ status: z.union([z.literal("bbb"), z.literal("ccc")]) }),
|
|
120
|
-
// 파이프 discriminator
|
|
121
|
-
z.object({ status: z.literal("fail").transform(val => val.toUpperCase()) }),
|
|
122
|
-
])
|
|
123
|
-
|
|
124
|
-
// 중첩 discriminated union
|
|
125
|
-
const BaseError = z.object({ status: z.literal("failed"), message: z.string() })
|
|
126
|
-
|
|
127
|
-
const MyResult2 = z.discriminatedUnion("status", [
|
|
128
|
-
z.object({ status: z.literal("success"), data: z.string() }),
|
|
129
|
-
z.discriminatedUnion("code", [
|
|
130
|
-
BaseError.extend({ code: z.literal(400) }),
|
|
131
|
-
BaseError.extend({ code: z.literal(401) }),
|
|
132
|
-
BaseError.extend({ code: z.literal(500) })
|
|
133
|
-
])
|
|
134
|
-
])
|
|
135
45
|
```
|
|
136
46
|
|
|
137
47
|
## Enum
|
|
138
48
|
|
|
139
49
|
```typescript
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
FishEnum.parse('Salmon') // => "Salmon"
|
|
144
|
-
FishEnum.parse('Swordfish') // => ❌ throws
|
|
145
|
-
|
|
146
|
-
type Fish = z.infer<typeof FishEnum>
|
|
147
|
-
// 'Salmon' | 'Tuna' | 'Trout'
|
|
50
|
+
const Status = z.enum(['pending', 'done', 'cancelled'])
|
|
51
|
+
type Status = z.infer<typeof Status> // 'pending' | 'done' | 'cancelled'
|
|
148
52
|
|
|
149
53
|
// Native enum
|
|
150
|
-
enum Fruits {
|
|
151
|
-
|
|
152
|
-
Banana,
|
|
153
|
-
}
|
|
154
|
-
const FruitSchema = z.nativeEnum(Fruits)
|
|
54
|
+
enum Fruits { Apple, Banana }
|
|
55
|
+
z.nativeEnum(Fruits)
|
|
155
56
|
```
|
|
156
57
|
|
|
157
|
-
## Record
|
|
58
|
+
## Record/Map/Set
|
|
158
59
|
|
|
159
60
|
```typescript
|
|
160
|
-
|
|
161
|
-
name: z.string(),
|
|
162
|
-
}))
|
|
163
|
-
|
|
164
|
-
type UserStore = z.infer<typeof UserStore>
|
|
61
|
+
z.record(z.string(), z.object({ name: z.string() }))
|
|
165
62
|
// { [key: string]: { name: string } }
|
|
166
63
|
|
|
167
|
-
//
|
|
168
|
-
|
|
169
|
-
userStore['77d2586b-9e8e-4ecf-8b21-ea7e0530eadd'] = {
|
|
170
|
-
name: 'Carlotta',
|
|
171
|
-
} // passes
|
|
172
|
-
|
|
173
|
-
userStore['77d2586b-9e8e-4ecf-8b21-ea7e0530eadd'] = {
|
|
174
|
-
whatever: 'Ice cream sundae',
|
|
175
|
-
} // TypeError
|
|
64
|
+
z.map(z.string(), z.number()) // Map<string, number>
|
|
65
|
+
z.set(z.number()) // Set<number>
|
|
176
66
|
```
|
|
177
67
|
|
|
178
|
-
##
|
|
68
|
+
## 재귀 스키마
|
|
179
69
|
|
|
180
70
|
```typescript
|
|
181
|
-
|
|
182
|
-
const stringNumberMap = z.map(z.string(), z.number())
|
|
183
|
-
type StringNumberMap = z.infer<typeof stringNumberMap>
|
|
184
|
-
// Map<string, number>
|
|
185
|
-
|
|
186
|
-
// Set
|
|
187
|
-
const numberSet = z.set(z.number())
|
|
188
|
-
type NumberSet = z.infer<typeof numberSet>
|
|
189
|
-
// Set<number>
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
## 재귀 스키마 (JSON)
|
|
193
|
-
|
|
194
|
-
```typescript
|
|
195
|
-
const literalSchema = z.union([z.string(), z.number(), z.boolean(), z.null()])
|
|
196
|
-
type Literal = z.infer<typeof literalSchema>
|
|
197
|
-
type Json = Literal | { [key: string]: Json } | Json[]
|
|
71
|
+
type Json = string | number | boolean | null | { [key: string]: Json } | Json[]
|
|
198
72
|
|
|
199
73
|
const jsonSchema: z.ZodType<Json> = z.lazy(() =>
|
|
200
|
-
z.union([
|
|
74
|
+
z.union([
|
|
75
|
+
z.string(), z.number(), z.boolean(), z.null(),
|
|
76
|
+
z.array(jsonSchema),
|
|
77
|
+
z.record(jsonSchema)
|
|
78
|
+
])
|
|
201
79
|
)
|
|
202
|
-
|
|
203
|
-
jsonSchema.parse({ foo: [1, 2, { bar: 'baz' }] })
|
|
204
80
|
```
|
|
@@ -1,21 +1,25 @@
|
|
|
1
1
|
# Zod
|
|
2
2
|
|
|
3
|
-
> **
|
|
3
|
+
> **v4** | TypeScript Schema Validation
|
|
4
|
+
|
|
5
|
+
@basic-types.md
|
|
6
|
+
@complex-types.md
|
|
7
|
+
@transforms.md
|
|
8
|
+
@validation.md
|
|
4
9
|
|
|
5
10
|
---
|
|
6
11
|
|
|
7
|
-
##
|
|
12
|
+
## Quick Reference
|
|
8
13
|
|
|
9
14
|
```typescript
|
|
10
15
|
// 기본 스키마
|
|
11
16
|
const schema = z.object({
|
|
12
|
-
email: z.email(), // v4 (string().email()
|
|
17
|
+
email: z.email(), // v4! (string().email() 아님)
|
|
13
18
|
name: z.string().min(1).trim(),
|
|
14
|
-
website: z.url().optional(), // v4 (string().url()
|
|
19
|
+
website: z.url().optional(), // v4! (string().url() 아님)
|
|
15
20
|
age: z.number().int().positive(),
|
|
16
21
|
})
|
|
17
22
|
|
|
18
|
-
// 타입 추출
|
|
19
23
|
type Input = z.infer<typeof schema>
|
|
20
24
|
|
|
21
25
|
// 파싱
|
|
@@ -28,159 +32,27 @@ export const createUser = createServerFn({ method: 'POST' })
|
|
|
28
32
|
.handler(async ({ data }) => prisma.user.create({ data }))
|
|
29
33
|
```
|
|
30
34
|
|
|
31
|
-
### v4 새 API
|
|
35
|
+
### ⚠️ v4 새 API
|
|
32
36
|
|
|
33
37
|
```typescript
|
|
34
|
-
// ✅ v4
|
|
35
|
-
z.email()
|
|
36
|
-
z.
|
|
37
|
-
z.uuid()
|
|
38
|
-
z.iso.date()
|
|
39
|
-
z.iso.datetime()
|
|
38
|
+
// ✅ v4
|
|
39
|
+
z.email() z.url() z.uuid()
|
|
40
|
+
z.iso.date() z.iso.datetime()
|
|
40
41
|
|
|
41
|
-
// ❌ v3
|
|
42
|
-
z.string().email()
|
|
43
|
-
z.string().url()
|
|
42
|
+
// ❌ v3 deprecated
|
|
43
|
+
z.string().email() z.string().url()
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
## 문서 구조
|
|
49
|
-
|
|
50
|
-
- [기본 타입](./basic-types.md) - 문자열, 숫자, 불리언, 날짜
|
|
51
|
-
- [복합 타입](./complex-types.md) - 객체, 배열, 튜플, 유니온, Enum
|
|
52
|
-
- [변환](./transforms.md) - Transform, Coerce, Preprocess
|
|
53
|
-
- [검증](./validation.md) - Refinement, Superrefine, 커스텀 검증
|
|
54
|
-
|
|
55
|
-
## 빠른 시작
|
|
56
|
-
|
|
57
|
-
```bash
|
|
58
|
-
yarn add zod
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
### 기본 사용법
|
|
46
|
+
### v4 주요 변경
|
|
62
47
|
|
|
63
48
|
```typescript
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
// 스키마 생성
|
|
67
|
-
const mySchema = z.string()
|
|
68
|
-
|
|
69
|
-
// parsing (실패 시 에러 throw)
|
|
70
|
-
mySchema.parse('tuna') // => "tuna"
|
|
71
|
-
mySchema.parse(12) // => throws ZodError
|
|
72
|
-
|
|
73
|
-
// safe parsing (에러를 던지지 않음)
|
|
74
|
-
mySchema.safeParse('tuna') // => { success: true; data: "tuna" }
|
|
75
|
-
mySchema.safeParse(12) // => { success: false; error: ZodError }
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
### 타입 추론
|
|
79
|
-
|
|
80
|
-
```typescript
|
|
81
|
-
import { z } from 'zod'
|
|
82
|
-
|
|
83
|
-
const Player = z.object({
|
|
84
|
-
username: z.string(),
|
|
85
|
-
xp: z.number(),
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
// 스키마에서 타입 추출
|
|
89
|
-
type Player = z.infer<typeof Player>
|
|
90
|
-
|
|
91
|
-
// 타입 안전하게 사용
|
|
92
|
-
const player: Player = { username: 'billie', xp: 100 }
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
## v4 주요 변경사항
|
|
96
|
-
|
|
97
|
-
### 에러 커스터마이징 통합
|
|
98
|
-
|
|
99
|
-
```typescript
|
|
100
|
-
// v3 (deprecated)
|
|
101
|
-
z.string().min(5, { message: "Too short." })
|
|
102
|
-
z.string({ invalid_type_error: "Not a string", required_error: "Required" })
|
|
103
|
-
|
|
104
|
-
// v4 - error 파라미터로 통합
|
|
49
|
+
// 에러 커스텀: message → error
|
|
105
50
|
z.string().min(5, { error: "Too short." })
|
|
106
|
-
z.string({
|
|
107
|
-
error: (issue) => issue.input === undefined
|
|
108
|
-
? "This field is required"
|
|
109
|
-
: "Not a string"
|
|
110
|
-
})
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
### 새로운 최상위 문자열 포맷 API
|
|
114
|
-
|
|
115
|
-
```typescript
|
|
116
|
-
// v3 (deprecated)
|
|
117
|
-
z.string().email()
|
|
118
|
-
z.string().uuid()
|
|
119
|
-
|
|
120
|
-
// v4 - 최상위 API (더 나은 tree-shaking)
|
|
121
|
-
z.email()
|
|
122
|
-
z.uuid()
|
|
123
|
-
z.url()
|
|
124
|
-
z.base64()
|
|
125
|
-
z.nanoid()
|
|
126
|
-
z.cuid()
|
|
127
|
-
z.cuid2()
|
|
128
|
-
z.ulid()
|
|
129
|
-
z.ipv4()
|
|
130
|
-
z.ipv6()
|
|
131
|
-
z.iso.date()
|
|
132
|
-
z.iso.time()
|
|
133
|
-
z.iso.datetime()
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
### Strict/Loose 객체
|
|
137
|
-
|
|
138
|
-
```typescript
|
|
139
|
-
// v3
|
|
140
|
-
z.object({ name: z.string() }).strict()
|
|
141
|
-
z.object({ name: z.string() }).passthrough()
|
|
142
|
-
|
|
143
|
-
// v4
|
|
144
|
-
z.strictObject({ name: z.string() })
|
|
145
|
-
z.looseObject({ name: z.string() })
|
|
146
|
-
```
|
|
147
51
|
|
|
148
|
-
|
|
52
|
+
// Strict/Loose 객체
|
|
53
|
+
z.strictObject({ name: z.string() }) // 추가 키 에러
|
|
54
|
+
z.looseObject({ name: z.string() }) // 추가 키 통과
|
|
149
55
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
z.string()
|
|
153
|
-
.refine(val => val.includes("@"))
|
|
154
|
-
.min(5) // ❌ Property 'min' does not exist on type ZodEffects
|
|
155
|
-
|
|
156
|
-
// v4 - 스키마 내부에 저장되어 체이닝 가능
|
|
157
|
-
z.string()
|
|
158
|
-
.refine(val => val.includes("@"))
|
|
159
|
-
.min(5) // ✅
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
## TanStack Start와 함께 사용
|
|
163
|
-
|
|
164
|
-
```typescript
|
|
165
|
-
import { createServerFn } from '@tanstack/react-start'
|
|
166
|
-
import { zodValidator } from '@tanstack/react-start/validators'
|
|
167
|
-
import { z } from 'zod'
|
|
168
|
-
|
|
169
|
-
const createUserSchema = z.object({
|
|
170
|
-
email: z.email(), // v4 새 API
|
|
171
|
-
name: z.string().min(1).max(100).trim(),
|
|
172
|
-
bio: z.string().max(500).optional(),
|
|
173
|
-
})
|
|
174
|
-
|
|
175
|
-
export const createUser = createServerFn({ method: 'POST' })
|
|
176
|
-
.inputValidator(zodValidator(createUserSchema))
|
|
177
|
-
.handler(async ({ data }) => {
|
|
178
|
-
// data는 타입 안전함
|
|
179
|
-
return prisma.user.create({ data })
|
|
180
|
-
})
|
|
56
|
+
// Refinement 체이닝 가능
|
|
57
|
+
z.string().refine(val => val.includes("@")).min(5) // ✅ v4
|
|
181
58
|
```
|
|
182
|
-
|
|
183
|
-
## 참고 자료
|
|
184
|
-
|
|
185
|
-
- [Zod 공식 문서](https://zod.dev)
|
|
186
|
-
- [Zod GitHub](https://github.com/colinhacks/zod)
|