Files
2nd/10_Wiki/Topics/Architecture/Zod.md
T
Antigravity Agent f8b21af4be Wiki cleanup: error-doc removal, dedup merge, link normalization
10_Wiki/Topics 대규모 정리:
- 오류 캡처/미완성 stub 문서 227개 제거
- 교차폴더 중복 43클러스터 병합 (63파일 → redirect)
- 링크명 정규화: 깨진 링크 수정·redirect 직결·개념 매핑 ~2,400건
- 카테고리 MOC 6개 신규 생성
- Graph 섹션 미해결 related-keyword 링크 10,058건 제거

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 23:52:15 +09:00

5.9 KiB

id, title, category, status, canonical_id, aliases, duplicate_of, source_trust_level, confidence_score, verification_status, tags, raw_sources, last_reinforced, github_commit, tech_stack
id title category status canonical_id aliases duplicate_of source_trust_level confidence_score verification_status tags raw_sources last_reinforced github_commit tech_stack
wiki-2026-0508-zod Zod 10_Wiki/Topics verified self
Zod Schema
Zod 4
ts-schema-validation
none A 0.9 applied
typescript
validation
schema
runtime-types
2026-05-10 pending
language framework
typescript zod

Zod

매 한 줄

"매 schema 한 번 정의 매 runtime validation + static type 매 동시". 매 Colin McDonnell 매 2020 출시 매 TypeScript-first schema validator — 매 2026 매 Zod 4 매 stable, tRPC / OpenAI SDK / Vercel AI SDK / Next.js Server Actions 매 사실상 standard. 매 대안: Valibot (smaller bundle), ArkType (faster).

매 핵심

매 핵심 idea

  • 매 single source of truth: schema 의 의 매 type 도 (z.infer<>) 도 의 의.
  • 매 parse → validated value (typed); failure → ZodError (or safeParse returns Result-like).
  • Tree-shakeable, zero deps.

매 주요 method

  • z.object, z.array, z.union, z.discriminatedUnion, z.record, z.tuple.
  • .refine, .transform, .pipe, .brand.
  • .optional, .nullable, .nullish, .default, .catch.
  • z.infer<typeof schema>, z.input, z.output.

매 응용

  1. tRPC: input/output schema → end-to-end type safety.
  2. React Hook Form + zodResolver.
  3. OpenAI structured outputs / function calling: schema → JSON Schema.
  4. Server Actions / API route input validation.
  5. Env variable validation (zod-env, t3-env).

💻 패턴

Basic schema + infer

import { z } from "zod";

const User = z.object({
  id: z.string().uuid(),
  email: z.string().email(),
  age: z.number().int().min(0).max(150),
  role: z.enum(["admin", "user", "guest"]),
});
type User = z.infer<typeof User>;

const result = User.safeParse(input);
if (!result.success) console.error(result.error.flatten());
else console.log(result.data); // typed

Discriminated union

const Event = z.discriminatedUnion("type", [
  z.object({ type: z.literal("click"), x: z.number(), y: z.number() }),
  z.object({ type: z.literal("keydown"), key: z.string() }),
  z.object({ type: z.literal("scroll"), delta: z.number() }),
]);
type Event = z.infer<typeof Event>; // narrow 매 자동

Refine + transform + pipe

const Password = z.string()
  .min(8)
  .refine((s) => /[A-Z]/.test(s) && /\d/.test(s), "Need uppercase + digit");

const TrimmedEmail = z.string()
  .transform((s) => s.trim().toLowerCase())
  .pipe(z.string().email());

Recursive schema (Zod 4)

type Comment = { text: string; replies: Comment[] };
const Comment: z.ZodType<Comment> = z.lazy(() =>
  z.object({ text: z.string(), replies: z.array(Comment) }),
);

Branded types (nominal)

const UserId = z.string().uuid().brand<"UserId">();
type UserId = z.infer<typeof UserId>;
function loadUser(id: UserId) { /* … */ }
loadUser(crypto.randomUUID() as any); // 매 must parse 먼저

Form integration (React Hook Form)

import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";

const Schema = z.object({ email: z.string().email(), age: z.coerce.number().min(18) });
type Form = z.infer<typeof Schema>;

function SignUp() {
  const { register, handleSubmit, formState: { errors } } = useForm<Form>({
    resolver: zodResolver(Schema),
  });
  return (
    <form onSubmit={handleSubmit((d) => console.log(d))}>
      <input {...register("email")} />
      {errors.email && <span>{errors.email.message}</span>}
    </form>
  );
}

Env validation

// env.ts — process.env 매 typed
import { z } from "zod";

export const env = z.object({
  NODE_ENV: z.enum(["development", "production", "test"]),
  DATABASE_URL: z.string().url(),
  OPENAI_API_KEY: z.string().startsWith("sk-"),
  PORT: z.coerce.number().default(3000),
}).parse(process.env);

LLM structured output

import OpenAI from "openai";
import { zodResponseFormat } from "openai/helpers/zod";

const Recipe = z.object({
  name: z.string(),
  ingredients: z.array(z.object({ item: z.string(), qty: z.string() })),
  steps: z.array(z.string()),
});
const openai = new OpenAI();
const r = await openai.chat.completions.parse({
  model: "gpt-5",
  messages: [{ role: "user", content: "Recipe for cacio e pepe" }],
  response_format: zodResponseFormat(Recipe, "recipe"),
});
const recipe = r.choices[0].message.parsed!; // typed

매 결정 기준

상황 Approach
TS-only project Zod (default)
Bundle size critical (<5KB) Valibot
Performance critical (10M validations/s) ArkType
JSON Schema 의 매 first-class TypeBox
Java / Python interop schema JSON Schema + ajv / pydantic

기본값: Zod 4. 매 모든 boundary (network, env, storage) 매 parse.

🔗 Graph

🤖 LLM 활용

언제: API boundary parsing, env validation, LLM output parsing, form schema. 언제 X: Hot path (>1M ops/s) 매 ArkType 고려, embedded / no TS.

안티패턴

  • Inferring on .parse not on schema: z.infer<typeof S> 매 사용.
  • any cast 후 parse skip: 매 boundary 매 항상 parse.
  • Massive nested .refine chains: 매 readability — .pipe 분리.
  • Re-parsing same data multiple times: 매 once at boundary, 매 그 후 typed.
  • Throwing in nested transforms: z.NEVER + ctx.addIssue 사용.
  • Optional vs nullable 혼동: optional = | undefined, nullable = | null.

🧪 검증 / 중복

  • Verified (Zod 4 official docs, colinhacks/zod GitHub, tRPC/Vercel AI SDK integration).
  • 신뢰도 A.

🕓 Changelog

날짜 변경
2026-05-08 Phase 1
2026-05-10 Manual cleanup — Zod 4, schema patterns, LLM structured output integration