Files
2nd/10_Wiki/Topics/Architecture/DeepReadonly.md
T
koriweb d8a80f6272 chore(wiki): dangling 링크 canonical 정규화 (768파일/1200건)
이름만 다른(표기 변형) [[위키링크]]를 대상 문서의 canonical 제목으로 치환해
끊겼던 1,200개 링크를 연결. 제목/파일명 정규화 일치만 적용하고 별칭 매칭은
과병합 위험으로 제외(애매성 가드). 원본은 _link_reconcile_backup/ 에 백업.
도구: Datacollect/scripts/link_reconcile_apply.mjs

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 12:24:15 +09:00

5.6 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-deepreadonly DeepReadonly 10_Wiki/Topics verified self
deep-readonly
recursive-readonly
immutable-type
none A 0.9 applied
typescript
type-utility
immutability
2026-05-10 pending
language framework
typescript typescript-5.x

DeepReadonly

매 한 줄

"매 Readonly<T> 의 surface-only — 매 nested mutation 가 still possible". 매 DeepReadonly 가 매 recursively 의 every property 의 readonly 의 mark — 매 redux state, config object, frozen domain model 의 essential. TS 5.x 의 매 const type parameter + DeepReadonly 가 매 powerful combo.

매 핵심

매 vs Readonly

  • Readonly<T> — 매 top-level only. 매 obj.nested.mutate = X 가 still allowed.
  • DeepReadonly<T> — 매 every level 의 recursive freeze.

매 type-only vs runtime

  • DeepReadonly 의 매 compile-time type guarantee — 매 runtime 의 mutation 의 X protect.
  • 매 runtime freeze 의 Object.freeze (shallow) 또는 deepFreeze helper 사용.
  • TypeScript 5.0+ as const 의 매 literal-level deep readonly 의 produce.

매 사용처

  1. Redux/Zustand state shape — 매 mutation prevent.
  2. Configuration schemas (env config, feature flags).
  3. API response DTOs after parse.
  4. Domain entities in DDD value objects.
  5. Test fixtures (prevent accidental modification).

💻 패턴

Basic DeepReadonly

type DeepReadonly<T> = T extends (...args: any[]) => any
  ? T
  : T extends object
    ? { readonly [K in keyof T]: DeepReadonly<T[K]> }
    : T;

interface User {
  id: string;
  profile: { name: string; tags: string[] };
}
const u: DeepReadonly<User> = { id: '1', profile: { name: 'a', tags: ['x'] } };
// u.profile.name = 'b'; // ❌ Cannot assign
// u.profile.tags.push('y'); // ❌ readonly array

Handles Map / Set / Array

type DeepReadonly<T> =
  T extends (infer U)[] ? readonly DeepReadonly<U>[]
  : T extends Map<infer K, infer V> ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>
  : T extends Set<infer U> ? ReadonlySet<DeepReadonly<U>>
  : T extends Promise<infer U> ? Promise<DeepReadonly<U>>
  : T extends object ? { readonly [K in keyof T]: DeepReadonly<T[K]> }
  : T;

Brand-aware (preserve nominal types)

type Brand<T, B> = T & { readonly __brand: B };
type DeepReadonly<T> = T extends Brand<infer U, infer B>
  ? Brand<DeepReadonly<U>, B>
  : T extends object
    ? { readonly [K in keyof T]: DeepReadonly<T[K]> }
    : T;

Runtime deepFreeze pair

export function deepFreeze<T>(obj: T): DeepReadonly<T> {
  if (obj && typeof obj === 'object' && !Object.isFrozen(obj)) {
    Object.freeze(obj);
    for (const key of Object.keys(obj)) deepFreeze((obj as any)[key]);
  }
  return obj as DeepReadonly<T>;
}

as const + DeepReadonly

const config = {
  features: { newCheckout: true, beta: ['user1', 'user2'] },
  limits: { rpm: 100 },
} as const;
// 매 `as const` 가 매 every literal 의 deeply readonly + literal-typed 로 만듦.
type Config = typeof config;
// Config['features']['beta'] === readonly ['user1', 'user2']

Mutable inverse (DeepWritable)

type DeepWritable<T> = T extends (...args: any[]) => any
  ? T
  : T extends object
    ? { -readonly [K in keyof T]: DeepWritable<T[K]> }
    : T;

function clone<T>(x: DeepReadonly<T>): DeepWritable<T> {
  return structuredClone(x as T) as DeepWritable<T>;
}

Zod + DeepReadonly

import { z } from 'zod';
const UserSchema = z.object({ id: z.string(), tags: z.array(z.string()) }).readonly();
type User = DeepReadonly<z.infer<typeof UserSchema>>;

Function args (Redux reducer)

function reducer<S, A>(state: DeepReadonly<S>, action: A): S {
  // state.x = ... // ❌ compile error
  return { ...(state as S), updated: true } as S;
}

매 결정 기준

상황 Approach
Literal config as const
Generic state shape DeepReadonly<T> utility
Runtime guarantee 필요 deepFreeze + DeepReadonly
Performance hot path 매 type-only DeepReadonly (no runtime cost)
Library author 가 expose DeepReadonly + readonly arrays in public API

기본값: type-only DeepReadonly + as const for literals; runtime deepFreeze only for security-sensitive boundaries.

🔗 Graph

🤖 LLM 활용

언제: 매 utility type 의 design / extension, 매 type error explanation, 매 readonly violation 의 codemod 작성. 언제 X: 매 runtime data validation (Zod 사용). 매 hot-path performance tuning (TS types 가 erased — runtime cost 0).

안티패턴

  • 함수 type 의 readonly 적용: 매 (...args) => any 가 readonly 의 의미 X — special-case 필요.
  • Date / RegExp 의 recurse: 매 built-in instances 가 깨짐 — exclude 의 type guard.
  • DeepReadonly + cast away: state as Mutable 가 매 type safety 의 destroy.
  • Runtime mutation through cast: 매 (state as any).x = 1 — 매 type lie 의 propagate.
  • Naive keyof T on union: distributive conditional 의 사용.

🧪 검증 / 중복

  • Verified (TypeScript 5.x docs, type-fest library, ts-toolbelt).
  • 신뢰도 A.

🕓 Changelog

날짜 변경
2026-05-08 Phase 1
2026-05-10 Manual cleanup — DeepReadonly utility type variants and patterns