--- id: wiki-2026-0508-api-응답-및-상태-모델링-state-modeling-a title: API Response & State Modeling category: 10_Wiki/Topics status: verified canonical_id: self aliases: [State Modeling, Discriminated Union, Result type, Tagged Union, exhaustive check] duplicate_of: none source_trust_level: B confidence_score: 0.9 verification_status: applied tags: [typescript, api-design, state-modeling, discriminated-union, result-type, exhaustive-check] raw_sources: [] last_reinforced: 2026-05-09 github_commit: pending tech_stack: language: TypeScript framework: TypeScript / Zod / TS-Result --- # API Response & State Modeling ## 📌 한 줄 통찰 > **"매 invalid state 의 unrepresentable"**. Discriminated union + Result type. 매 compile-time exhaustive check. 매 runtime bug 의 prevent. ## 📖 핵심 ### 매 problem (without modeling) ```ts // ❌ Optional everything type Response = { data?: User; error?: string; loading?: boolean; }; // 매 combination 의 가능? // loading=true, data=set ? // error=set, data=set ? // 매 inconsistent state. ``` ### Solution: Discriminated Union (Tagged Union) ```ts type Response = | { type: 'idle' } | { type: 'loading' } | { type: 'success'; data: User } | { type: 'error'; message: string }; // 매 state 의 explicit. 매 invalid 의 impossible. ``` ### Result type ```ts type Result = | { ok: true; value: T } | { ok: false; error: E }; // Usage async function fetchUser(id: string): Promise> { try { const user = await api.users.get(id); return { ok: true, value: user }; } catch (e) { return { ok: false, error: e as Error }; } } // Caller const result = await fetchUser('123'); if (result.ok) { console.log(result.value.name); // type: User } else { console.error(result.error); } ``` ### Exhaustive check ```ts function render(state: Response) { switch (state.type) { case 'idle': return ; case 'loading': return ; case 'success': return ; case 'error': return ; default: const _: never = state; // compile error if state added throw new Error('unreachable'); } } ``` → 매 새 state 의 추가 시 compiler 의 exhaustive check. ### 매 application #### React state ```tsx const [state, setState] = useState({ type: 'idle' }); // 매 fetching setState({ type: 'loading' }); const result = await fetchUser(id); if (result.ok) { setState({ type: 'success', data: result.value }); } else { setState({ type: 'error', message: result.error.message }); } ``` #### TanStack Query (built-in) ```tsx const { data, error, isLoading, isSuccess } = useQuery({...}); if (isLoading) return ; if (error) return ; return ; ``` → 매 query state 의 already discriminated. #### State machine (XState) ```ts import { createMachine } from 'xstate'; const machine = createMachine({ id: 'fetch', initial: 'idle', states: { idle: { on: { FETCH: 'loading' } }, loading: { on: { SUCCESS: 'success', ERROR: 'error' } }, success: { on: { REFETCH: 'loading' } }, error: { on: { RETRY: 'loading' } }, }, }); ``` ### 매 API response shape #### Success / Error envelope ```ts type APIResponse = | { status: 'ok'; data: T } | { status: 'error'; code: string; message: string }; ``` #### Pagination ```ts type Paginated = { data: T[]; meta: { total: number; page: number; perPage: number }; }; ``` #### Nullable vs Optional ```ts // Nullable: 명시적 null type User = { name: string; bio: string | null }; // Optional: 매 absent type Update = { name?: string; bio?: string }; ``` → 매 different semantic. ### Validation (runtime, Zod) ```ts import { z } from 'zod'; const UserSchema = z.object({ id: z.string().uuid(), email: z.string().email(), age: z.number().int().min(0), }); type User = z.infer; // 매 API response 의 parse + validate const result = UserSchema.safeParse(await response.json()); if (result.success) { // result.data: User } else { console.error(result.error); } ``` → 매 type + runtime 의 둘 다. ### Branded types (extra safety) ```ts type UserId = string & { __brand: 'UserId' }; type OrderId = string & { __brand: 'OrderId' }; function getUser(id: UserId) { ... } const id = '123' as UserId; getUser(id); // OK getUser('123'); // ❌ string ≠ UserId ``` ## 💻 Code Pattern ### Result + chain ```ts class Result { constructor( public readonly ok: boolean, public readonly value?: T, public readonly error?: E ) {} map(fn: (v: T) => U): Result { return this.ok ? Result.ok(fn(this.value!)) : Result.err(this.error!); } flatMap(fn: (v: T) => Result): Result { return this.ok ? fn(this.value!) : Result.err(this.error!); } static ok(value: T): Result { return new Result(true, value); } static err(error: E): Result { return new Result(false, undefined, error); } } // Usage const result = await fetchUser('123'); const name = result.map(u => u.name).map(s => s.toUpperCase()); ``` ### Effect-TS (advanced FP) ```ts import { Effect, pipe } from 'effect'; const program = pipe( Effect.tryPromise(() => api.users.get('123')), Effect.map(u => u.name), Effect.catchAll(e => Effect.succeed('Unknown')), ); const result = await Effect.runPromise(program); ``` ### React hook with state ```tsx function useFetchUser(id: string) { const [state, setState] = useState<{ status: 'idle' | 'loading' | 'success' | 'error'; data?: User; error?: Error; }>({ status: 'idle' }); useEffect(() => { setState({ status: 'loading' }); fetchUser(id) .then(data => setState({ status: 'success', data })) .catch(error => setState({ status: 'error', error })); }, [id]); return state; } ``` ## 🤔 결정 기준 | 상황 | 추천 | |---|---| | Simple state | Discriminated union | | Async result | Result type | | Complex state | XState machine | | API response | Envelope + Zod validation | | Type identity | Branded type | | FP heavy | Effect-TS | **기본값**: Discriminated union + Result type + Zod validation. ## 🔗 Graph - 부모: [[TypeScript]] · [[State Management]] · [[API-Design]] - 변형: [[Discriminated-Union]] · [[Tagged-Union]] · [[Result Type]] - 응용: [[XState]] · [[Effect TS]] - Adjacent: [[Branded-Types]] · [[Exhaustive-Check]] ## 🤖 LLM 활용 **언제**: 매 TypeScript app 의 state design. 매 API contract 의 type-safe. **언제 X**: 매 simple primitive (boolean enough). 매 prototype. ## ❌ 안티패턴 - **Optional everything**: invalid state. - **`any` for response**: type 의 가치 X. - **No exhaustive check**: 매 새 state 의 missed. - **Throw + catch 만**: Result type 의 더 explicit. - **No runtime validation**: 매 wrong API response 의 silent. ## 🧪 검증 / 중복 - Verified (TypeScript handbook, neverthrow library docs). - 신뢰도 B. - Related: [[Tagged_Union_Discriminated_Types]] · [[TS_Schema_Validation_Comparison]]. ## 🕓 Changelog | 날짜 | 변경 | |---|---| | 2026-05-08 | Phase 1 | | 2026-05-09 | Manual cleanup — discriminated union + Result + state machine + code |