@kood/claude-code 0.3.6 → 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.
Files changed (34) hide show
  1. package/dist/index.js +1 -1
  2. package/package.json +1 -1
  3. package/templates/nextjs/CLAUDE.md +228 -0
  4. package/templates/nextjs/docs/design.md +558 -0
  5. package/templates/nextjs/docs/guides/conventions.md +343 -0
  6. package/templates/nextjs/docs/guides/getting-started.md +367 -0
  7. package/templates/nextjs/docs/guides/routes.md +342 -0
  8. package/templates/nextjs/docs/library/better-auth/index.md +541 -0
  9. package/templates/nextjs/docs/library/nextjs/app-router.md +269 -0
  10. package/templates/nextjs/docs/library/nextjs/caching.md +351 -0
  11. package/templates/nextjs/docs/library/nextjs/index.md +291 -0
  12. package/templates/nextjs/docs/library/nextjs/middleware.md +391 -0
  13. package/templates/nextjs/docs/library/nextjs/route-handlers.md +382 -0
  14. package/templates/nextjs/docs/library/nextjs/server-actions.md +366 -0
  15. package/templates/nextjs/docs/library/prisma/cloudflare-d1.md +76 -0
  16. package/templates/nextjs/docs/library/prisma/config.md +77 -0
  17. package/templates/nextjs/docs/library/prisma/crud.md +90 -0
  18. package/templates/nextjs/docs/library/prisma/index.md +73 -0
  19. package/templates/nextjs/docs/library/prisma/relations.md +69 -0
  20. package/templates/nextjs/docs/library/prisma/schema.md +98 -0
  21. package/templates/nextjs/docs/library/prisma/setup.md +49 -0
  22. package/templates/nextjs/docs/library/prisma/transactions.md +50 -0
  23. package/templates/nextjs/docs/library/tanstack-query/index.md +66 -0
  24. package/templates/nextjs/docs/library/tanstack-query/invalidation.md +54 -0
  25. package/templates/nextjs/docs/library/tanstack-query/optimistic-updates.md +77 -0
  26. package/templates/nextjs/docs/library/tanstack-query/use-mutation.md +63 -0
  27. package/templates/nextjs/docs/library/tanstack-query/use-query.md +70 -0
  28. package/templates/nextjs/docs/library/zod/complex-types.md +61 -0
  29. package/templates/nextjs/docs/library/zod/index.md +56 -0
  30. package/templates/nextjs/docs/library/zod/transforms.md +51 -0
  31. package/templates/nextjs/docs/library/zod/validation.md +70 -0
  32. package/templates/tanstack-start/CLAUDE.md +7 -3
  33. package/templates/tanstack-start/docs/guides/hooks.md +28 -0
  34. package/templates/tanstack-start/docs/guides/routes.md +29 -10
@@ -0,0 +1,69 @@
1
+ # Prisma - 관계 쿼리
2
+
3
+ ## 중첩 생성
4
+
5
+ ```typescript
6
+ const user = await prisma.user.create({
7
+ data: {
8
+ email: 'user@prisma.io',
9
+ posts: { create: [{ title: 'Post 1' }, { title: 'Post 2' }] },
10
+ },
11
+ include: { posts: true },
12
+ })
13
+ ```
14
+
15
+ ## 관계 연결
16
+
17
+ ```typescript
18
+ // connect - 기존 연결
19
+ author: { connect: { id: 1 } }
20
+
21
+ // connectOrCreate - 있으면 연결, 없으면 생성
22
+ categories: { connectOrCreate: { where: { name: 'Tech' }, create: { name: 'Tech' } } }
23
+
24
+ // disconnect - 관계 해제
25
+ author: { disconnect: true }
26
+ ```
27
+
28
+ ## 관계 포함 조회
29
+
30
+ ```typescript
31
+ // include
32
+ const users = await prisma.user.findMany({ include: { posts: true, profile: true } })
33
+
34
+ // 중첩
35
+ include: { posts: { include: { categories: true } } }
36
+
37
+ // 필터 + 정렬
38
+ include: { posts: { where: { published: true }, orderBy: { createdAt: 'desc' }, take: 5 } }
39
+ ```
40
+
41
+ ## 관계로 필터링
42
+
43
+ ```typescript
44
+ // some - 하나라도 만족
45
+ where: { posts: { some: { published: true } } }
46
+
47
+ // every - 모두 만족
48
+ where: { posts: { every: { published: true } } }
49
+
50
+ // none - 만족 없음
51
+ where: { posts: { none: { published: false } } }
52
+ ```
53
+
54
+ ## 카운트
55
+
56
+ ```typescript
57
+ include: { _count: { select: { posts: true } } }
58
+ // 결과: { _count: { posts: 5 } }
59
+ ```
60
+
61
+ ## 중첩 수정/삭제
62
+
63
+ ```typescript
64
+ // updateMany
65
+ posts: { updateMany: { where: { published: false }, data: { published: true } } }
66
+
67
+ // deleteMany
68
+ posts: { deleteMany: { where: { published: false } } }
69
+ ```
@@ -0,0 +1,98 @@
1
+ # Prisma - 스키마 정의 (Multi-File)
2
+
3
+ ## ⚠️ 필수 규칙
4
+
5
+ 1. **Multi-File 구조** 사용
6
+ 2. **모든 요소에 한글 주석** (파일, 모델, 필드, enum)
7
+
8
+ ## 구조
9
+
10
+ ```
11
+ prisma/schema/
12
+ ├── +base.prisma # datasource, generator
13
+ ├── +enum.prisma # 모든 enum
14
+ ├── user.prisma # User 모델
15
+ └── post.prisma # Post 모델
16
+ ```
17
+
18
+ ## 예시
19
+
20
+ ```prisma
21
+ // +base.prisma
22
+ datasource db {
23
+ provider = "postgresql"
24
+ url = env("DATABASE_URL")
25
+ }
26
+
27
+ generator client {
28
+ provider = "prisma-client"
29
+ output = "../generated/prisma"
30
+ }
31
+
32
+ // +enum.prisma
33
+ enum Role {
34
+ USER // 일반 사용자
35
+ ADMIN // 관리자
36
+ }
37
+
38
+ // user.prisma
39
+ model User {
40
+ id Int @id @default(autoincrement())
41
+ email String @unique // 로그인 이메일
42
+ name String? // 표시 이름
43
+ role Role @default(USER)
44
+ posts Post[] // 작성 게시글 (1:N)
45
+ profile Profile? // 프로필 (1:1)
46
+ createdAt DateTime @default(now())
47
+ updatedAt DateTime @updatedAt
48
+ }
49
+ ```
50
+
51
+ ## 관계 유형
52
+
53
+ ```prisma
54
+ // 1:1
55
+ model Profile {
56
+ id Int @id @default(autoincrement())
57
+ user User @relation(fields: [userId], references: [id])
58
+ userId Int @unique
59
+ }
60
+
61
+ // 1:N
62
+ model Post {
63
+ author User @relation(fields: [authorId], references: [id])
64
+ authorId Int
65
+ }
66
+
67
+ // M:N (암묵적)
68
+ model Post { categories Category[] }
69
+ model Category { posts Post[] }
70
+
71
+ // M:N (명시적)
72
+ model CategoriesOnPosts {
73
+ post Post @relation(fields: [postId], references: [id])
74
+ postId Int
75
+ category Category @relation(fields: [categoryId], references: [id])
76
+ categoryId Int
77
+ @@id([postId, categoryId])
78
+ }
79
+ ```
80
+
81
+ ## 기타
82
+
83
+ ```prisma
84
+ // 선택적 관계
85
+ author User? @relation(fields: [authorId], references: [id])
86
+ authorId Int?
87
+
88
+ // 인덱스
89
+ @@index([authorId])
90
+ @@index([createdAt])
91
+
92
+ // 복합 키
93
+ @@id([postId, tagId])
94
+
95
+ // 매핑
96
+ @map("user_id")
97
+ @@map("users")
98
+ ```
@@ -0,0 +1,49 @@
1
+ # Prisma - 설치 및 설정
2
+
3
+ ## 설치
4
+
5
+ ```bash
6
+ yarn add @prisma/client@7
7
+ yarn add -D prisma@7
8
+ npx prisma init
9
+ ```
10
+
11
+ ## v6 → v7 업그레이드
12
+
13
+ ```prisma
14
+ // v6 (이전)
15
+ generator client {
16
+ provider = "prisma-client-js"
17
+ }
18
+
19
+ // v7 (필수)
20
+ generator client {
21
+ provider = "prisma-client"
22
+ output = "../generated/prisma"
23
+ }
24
+ ```
25
+
26
+ ## Prisma Client
27
+
28
+ ```typescript
29
+ // lib/prisma.ts
30
+ import { PrismaClient } from './generated/prisma'
31
+
32
+ const globalForPrisma = globalThis as unknown as { prisma: PrismaClient | undefined }
33
+ export const prisma = globalForPrisma.prisma ?? new PrismaClient({ log: ['query'] })
34
+ if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
35
+ ```
36
+
37
+ ## TanStack Start 연동
38
+
39
+ ```typescript
40
+ import { createServerFn } from '@tanstack/react-start'
41
+ import { prisma } from '@/lib/prisma'
42
+
43
+ export const getUsers = createServerFn({ method: 'GET' })
44
+ .handler(async () => prisma.user.findMany({ include: { posts: true } }))
45
+
46
+ export const createUser = createServerFn({ method: 'POST' })
47
+ .inputValidator((data: { email: string; name: string }) => data)
48
+ .handler(async ({ data }) => prisma.user.create({ data }))
49
+ ```
@@ -0,0 +1,50 @@
1
+ # Prisma - 트랜잭션
2
+
3
+ ## 배열 기반 트랜잭션
4
+
5
+ 하나라도 실패하면 모두 롤백.
6
+
7
+ ```typescript
8
+ const deletePosts = prisma.post.deleteMany({ where: { authorId: 7 } })
9
+ const deleteUser = prisma.user.delete({ where: { id: 7 } })
10
+ await prisma.$transaction([deletePosts, deleteUser])
11
+ ```
12
+
13
+ ## 인터랙티브 트랜잭션
14
+
15
+ 복잡한 로직, 조건부 처리.
16
+
17
+ ```typescript
18
+ const result = await prisma.$transaction(async (tx) => {
19
+ const sender = await tx.account.findUnique({ where: { id: senderId } })
20
+ if (!sender || sender.balance < amount) throw new Error('Insufficient balance')
21
+
22
+ await tx.account.update({ where: { id: senderId }, data: { balance: { decrement: amount } } })
23
+ await tx.account.update({ where: { id: recipientId }, data: { balance: { increment: amount } } })
24
+
25
+ return { success: true }
26
+ })
27
+ ```
28
+
29
+ ## 옵션
30
+
31
+ ```typescript
32
+ await prisma.$transaction(async (tx) => { ... }, {
33
+ maxWait: 5000, // 최대 대기 (ms)
34
+ timeout: 10000, // 타임아웃 (ms)
35
+ isolationLevel: 'Serializable', // ReadUncommitted | ReadCommitted | RepeatableRead | Serializable
36
+ })
37
+ ```
38
+
39
+ ## 에러 처리
40
+
41
+ ```typescript
42
+ try {
43
+ await prisma.$transaction(async (tx) => {
44
+ await tx.user.create({ data: { email } })
45
+ if (someCondition) throw new Error('Rollback')
46
+ })
47
+ } catch (error) {
48
+ // 전체 롤백됨
49
+ }
50
+ ```
@@ -0,0 +1,66 @@
1
+ # TanStack Query
2
+
3
+ > 5.x | React Data Fetching
4
+
5
+ @use-query.md
6
+ @use-mutation.md
7
+ @invalidation.md
8
+ @optimistic-updates.md
9
+
10
+ ---
11
+
12
+ <quick_reference>
13
+
14
+ ```typescript
15
+ // useQuery
16
+ const { data, isLoading, error } = useQuery({
17
+ queryKey: ['users'],
18
+ queryFn: () => getUsers(),
19
+ })
20
+
21
+ // useMutation + invalidation
22
+ const queryClient = useQueryClient()
23
+ const mutation = useMutation({
24
+ mutationFn: createUser,
25
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: ['users'] }),
26
+ })
27
+ mutation.mutate({ email, name })
28
+
29
+ // Optimistic Update
30
+ const mutation = useMutation({
31
+ mutationFn: updateUser,
32
+ onMutate: async (newUser) => {
33
+ await queryClient.cancelQueries({ queryKey: ['users'] })
34
+ const previous = queryClient.getQueryData(['users'])
35
+ queryClient.setQueryData(['users'], (old) => [...old, newUser])
36
+ return { previous }
37
+ },
38
+ onError: (err, newUser, context) => {
39
+ queryClient.setQueryData(['users'], context.previous)
40
+ },
41
+ })
42
+
43
+ // 설정
44
+ const queryClient = new QueryClient({
45
+ defaultOptions: {
46
+ queries: {
47
+ staleTime: 1000 * 60 * 5, // 5분
48
+ gcTime: 1000 * 60 * 30, // 30분
49
+ retry: 3,
50
+ },
51
+ },
52
+ })
53
+
54
+ const App = () => (
55
+ <QueryClientProvider client={queryClient}>
56
+ <YourApp />
57
+ </QueryClientProvider>
58
+ )
59
+
60
+ // Query Keys
61
+ ['todos'] // 단순
62
+ ['todo', { id: 5 }] // 파라미터
63
+ ['todos', 'list', { filters }] // 계층적
64
+ ```
65
+
66
+ </quick_reference>
@@ -0,0 +1,54 @@
1
+ # TanStack Query - Query 무효화
2
+
3
+ <patterns>
4
+
5
+ ```typescript
6
+ const queryClient = useQueryClient()
7
+
8
+ // 단일
9
+ queryClient.invalidateQueries({ queryKey: ['todos'] })
10
+
11
+ // 다중
12
+ await Promise.all([
13
+ queryClient.invalidateQueries({ queryKey: ['todos'] }),
14
+ queryClient.invalidateQueries({ queryKey: ['reminders'] }),
15
+ ])
16
+
17
+ // 전체
18
+ queryClient.invalidateQueries()
19
+
20
+ // 옵션
21
+ queryClient.invalidateQueries({
22
+ queryKey: ['posts'],
23
+ exact: true, // 정확한 키 매칭만
24
+ refetchType: 'active', // 'active' | 'inactive' | 'all' | 'none'
25
+ })
26
+
27
+ // Query Key 매칭
28
+ queryClient.invalidateQueries({ queryKey: ['todos'] }) // prefix 매칭
29
+ queryClient.invalidateQueries({ queryKey: ['todos', 'list'], exact: true }) // 정확한 매칭
30
+
31
+ // Mutation과 함께
32
+ useMutation({
33
+ mutationFn: addTodo,
34
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
35
+ onSettled: () => queryClient.invalidateQueries({ queryKey: ['todos'] }), // 성공/실패 무관
36
+ })
37
+
38
+ // 직접 업데이트 vs 무효화
39
+ queryClient.setQueryData(['todos'], (old) => [...old, newTodo]) // 더 빠름
40
+ queryClient.invalidateQueries({ queryKey: ['todos'] }) // 서버 데이터 보장
41
+ ```
42
+
43
+ </patterns>
44
+
45
+ <options>
46
+
47
+ | refetchType | 설명 |
48
+ |-------------|------|
49
+ | active | 렌더링 중인 쿼리만 재조회 (기본) |
50
+ | inactive | 비활성 쿼리만 |
51
+ | all | 모든 매칭 쿼리 |
52
+ | none | 무효화만, 재조회 안함 |
53
+
54
+ </options>
@@ -0,0 +1,77 @@
1
+ # TanStack Query - Optimistic Updates
2
+
3
+ <patterns>
4
+
5
+ ```tsx
6
+ // 추가
7
+ useMutation({
8
+ mutationFn: addTodo,
9
+ onMutate: async (newTodo) => {
10
+ await queryClient.cancelQueries({ queryKey: ['todos'] })
11
+ const previousTodos = queryClient.getQueryData(['todos'])
12
+ queryClient.setQueryData(['todos'], (old) => [...old, newTodo])
13
+ return { previousTodos }
14
+ },
15
+ onError: (err, newTodo, context) => {
16
+ queryClient.setQueryData(['todos'], context.previousTodos)
17
+ },
18
+ onSettled: () => {
19
+ queryClient.invalidateQueries({ queryKey: ['todos'] })
20
+ },
21
+ })
22
+
23
+ // 삭제
24
+ useMutation({
25
+ mutationFn: deleteTodo,
26
+ onMutate: async (todoId) => {
27
+ await queryClient.cancelQueries({ queryKey: ['todos'] })
28
+ const previousTodos = queryClient.getQueryData(['todos'])
29
+ queryClient.setQueryData(['todos'], (old) =>
30
+ old.filter((todo) => todo.id !== todoId)
31
+ )
32
+ return { previousTodos }
33
+ },
34
+ onError: (err, todoId, context) => {
35
+ queryClient.setQueryData(['todos'], context.previousTodos)
36
+ },
37
+ onSettled: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
38
+ })
39
+
40
+ // 토글
41
+ useMutation({
42
+ mutationFn: toggleTodo,
43
+ onMutate: async (todoId) => {
44
+ await queryClient.cancelQueries({ queryKey: ['todos'] })
45
+ const previousTodos = queryClient.getQueryData(['todos'])
46
+ queryClient.setQueryData(['todos'], (old) =>
47
+ old.map((todo) =>
48
+ todo.id === todoId ? { ...todo, completed: !todo.completed } : todo
49
+ )
50
+ )
51
+ return { previousTodos }
52
+ },
53
+ onError: (err, todoId, context) => {
54
+ queryClient.setQueryData(['todos'], context.previousTodos)
55
+ },
56
+ onSettled: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
57
+ })
58
+
59
+ // 단일 항목
60
+ useMutation({
61
+ mutationFn: updateTodo,
62
+ onMutate: async (newTodo) => {
63
+ await queryClient.cancelQueries({ queryKey: ['todos', newTodo.id] })
64
+ const previousTodo = queryClient.getQueryData(['todos', newTodo.id])
65
+ queryClient.setQueryData(['todos', newTodo.id], newTodo)
66
+ return { previousTodo, newTodo }
67
+ },
68
+ onError: (err, newTodo, context) => {
69
+ queryClient.setQueryData(['todos', context.newTodo.id], context.previousTodo)
70
+ },
71
+ onSettled: (newTodo) => {
72
+ queryClient.invalidateQueries({ queryKey: ['todos', newTodo.id] })
73
+ },
74
+ })
75
+ ```
76
+
77
+ </patterns>
@@ -0,0 +1,63 @@
1
+ # TanStack Query - useMutation
2
+
3
+ <patterns>
4
+
5
+ ```tsx
6
+ // 기본
7
+ const queryClient = useQueryClient()
8
+ const mutation = useMutation({
9
+ mutationFn: postTodo,
10
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
11
+ })
12
+ mutation.mutate({ title: 'New Todo' })
13
+
14
+ // 콜백
15
+ useMutation({
16
+ mutationFn: updateTodo,
17
+ onMutate: async (newTodo) => {
18
+ // mutation 시작 전 (optimistic update용)
19
+ return { previousData } // context로 전달
20
+ },
21
+ onSuccess: (data, variables, context) => {},
22
+ onError: (error, variables, context) => {},
23
+ onSettled: (data, error, variables, context) => {
24
+ queryClient.invalidateQueries({ queryKey: ['todos'] })
25
+ },
26
+ })
27
+
28
+ // mutate vs mutateAsync
29
+ mutation.mutate(data, {
30
+ onSuccess: (result) => console.log(result),
31
+ onError: (error) => console.log(error),
32
+ })
33
+
34
+ try {
35
+ const result = await mutation.mutateAsync(data)
36
+ } catch (error) { ... }
37
+
38
+ // 캐시 업데이트
39
+ useMutation({
40
+ mutationFn: patchTodo,
41
+ onSuccess: (data) => {
42
+ queryClient.invalidateQueries({ queryKey: ['todos'] })
43
+ queryClient.setQueryData(['todo', { id: data.id }], data)
44
+ },
45
+ })
46
+ ```
47
+
48
+ </patterns>
49
+
50
+ <returns>
51
+
52
+ | 속성 | 설명 |
53
+ |------|------|
54
+ | data | mutation 결과 |
55
+ | error | 에러 객체 |
56
+ | isPending | 실행 중 |
57
+ | isSuccess/isError | 상태 |
58
+ | mutate | 실행 (비동기) |
59
+ | mutateAsync | 실행 (Promise) |
60
+ | reset | 상태 초기화 |
61
+ | variables | 전달된 변수 |
62
+
63
+ </returns>
@@ -0,0 +1,70 @@
1
+ # TanStack Query - useQuery
2
+
3
+ <patterns>
4
+
5
+ ```tsx
6
+ // 기본
7
+ const { data, isLoading, error } = useQuery({
8
+ queryKey: ['todos'],
9
+ queryFn: getTodos,
10
+ })
11
+ if (isLoading) return <div>Loading...</div>
12
+ if (error) return <div>Error: {error.message}</div>
13
+
14
+ // 옵션
15
+ useQuery({
16
+ queryKey: ['todos'],
17
+ queryFn: fetchTodos,
18
+ staleTime: 1000 * 60 * 5, // fresh 유지 시간
19
+ gcTime: 1000 * 60 * 30, // 가비지 컬렉션 시간
20
+ refetchOnWindowFocus: true, // 포커스 시 리페치
21
+ refetchInterval: 1000 * 60, // 자동 리페치 간격
22
+ retry: 3, // 재시도 횟수
23
+ enabled: !!userId, // 조건부 실행
24
+ initialData: [], // 초기 데이터
25
+ select: (data) => data.filter(t => t.done), // 데이터 변환
26
+ })
27
+
28
+ // 파라미터
29
+ useQuery({
30
+ queryKey: ['todo', todoId],
31
+ queryFn: () => fetchTodoById(todoId),
32
+ enabled: !!todoId,
33
+ })
34
+
35
+ // 의존적 쿼리
36
+ const { data: user } = useQuery({ queryKey: ['user', userId], queryFn: ... })
37
+ const { data: posts } = useQuery({
38
+ queryKey: ['posts', user?.id],
39
+ queryFn: () => fetchPostsByUserId(user!.id),
40
+ enabled: !!user?.id,
41
+ })
42
+
43
+ // 병렬
44
+ const usersQuery = useQuery({ queryKey: ['users'], queryFn: fetchUsers })
45
+ const postsQuery = useQuery({ queryKey: ['posts'], queryFn: fetchPosts })
46
+
47
+ // 동적 병렬
48
+ const userQueries = useQueries({
49
+ queries: userIds.map((id) => ({
50
+ queryKey: ['user', id],
51
+ queryFn: () => fetchUserById(id),
52
+ })),
53
+ })
54
+ ```
55
+
56
+ </patterns>
57
+
58
+ <returns>
59
+
60
+ | 속성 | 설명 |
61
+ |------|------|
62
+ | data | 쿼리 결과 |
63
+ | error | 에러 객체 |
64
+ | isLoading | 첫 로딩 중 |
65
+ | isFetching | 백그라운드 페칭 중 |
66
+ | isError/isSuccess | 상태 |
67
+ | refetch | 수동 리페치 |
68
+ | status | 'pending' \| 'error' \| 'success' |
69
+
70
+ </returns>
@@ -0,0 +1,61 @@
1
+ # Zod - 복합 타입
2
+
3
+ <patterns>
4
+
5
+ ```typescript
6
+ // 객체
7
+ const UserSchema = z.object({
8
+ name: z.string(),
9
+ email: z.email(),
10
+ age: z.number().optional(),
11
+ })
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)
19
+
20
+ z.strictObject({ name: z.string() }) // v4: 추가 키 에러
21
+ z.looseObject({ name: z.string() }) // v4: 추가 키 통과
22
+
23
+ // 배열/튜플
24
+ z.array(z.string())
25
+ z.array(z.number()).min(1).max(10).length(5).nonempty()
26
+ z.tuple([z.string(), z.number()]) // [string, number]
27
+
28
+ // 유니온
29
+ z.union([z.string(), z.number()])
30
+ z.string().or(z.number())
31
+
32
+ z.discriminatedUnion('type', [
33
+ z.object({ type: z.literal('circle'), radius: z.number() }),
34
+ z.object({ type: z.literal('rectangle'), width: z.number(), height: z.number() }),
35
+ ])
36
+
37
+ // Enum
38
+ const Status = z.enum(['pending', 'done', 'cancelled'])
39
+ type Status = z.infer<typeof Status> // 'pending' | 'done' | 'cancelled'
40
+
41
+ enum Fruits { Apple, Banana }
42
+ z.nativeEnum(Fruits)
43
+
44
+ // Record/Map/Set
45
+ z.record(z.string(), z.object({ name: z.string() })) // { [key: string]: { name: string } }
46
+ z.map(z.string(), z.number()) // Map<string, number>
47
+ z.set(z.number()) // Set<number>
48
+
49
+ // 재귀
50
+ type Json = string | number | boolean | null | { [key: string]: Json } | Json[]
51
+
52
+ const jsonSchema: z.ZodType<Json> = z.lazy(() =>
53
+ z.union([
54
+ z.string(), z.number(), z.boolean(), z.null(),
55
+ z.array(jsonSchema),
56
+ z.record(jsonSchema)
57
+ ])
58
+ )
59
+ ```
60
+
61
+ </patterns>