"매 zero-runtime-cost 의 nominal typing 의 TypeScript". ts-brand 는 매 structural typing 의 default 의 TypeScript 에 매 nominal-style brand 의 추가 → 매 UserId 와 매 OrderId 가 매 둘 다 string 이지만 매 swap 의 compile error. 매 2026 의 standard pattern — 매 Effect-TS, Zod, neverthrow 의 모두 의 leverage.
매 핵심
매 problem (structural typing 의 limitation)
매 TypeScript: 매 type UserId = string; type OrderId = string → 매 둘 다 동일 의 interchangeable.
매 logical bug: 매 fn(userId: UserId) 의 orderId 의 pass 가 의 silent.
매 solution (Brand)
매 phantom type tag 의 use → 매 compile-time discrimination.
매 runtime cost = 0 (type-only).
매 ts-brand API
Brand<Base, Tag>: 매 branded type 의 create.
make<T>(): 매 cast helper.
매 alternatives: 매 own-rolling, Effect-TS Brand, type-fest Opaque.
💻 패턴
1. Basic Brand
import{Brand,make}from'ts-brand';typeUserId=Brand<string,'UserId'>;typeOrderId=Brand<string,'OrderId'>;constUserId=make<UserId>();constOrderId=make<OrderId>();constu: UserId=UserId('u-123');consto: OrderId=OrderId('o-456');functionfetchUser(id: UserId){/* ... */}fetchUser(u);// ✓
fetchUser(o);// ✗ Type error
fetchUser('raw');// ✗ Type error
2. Validated Brand (with runtime check)
typeEmail=Brand<string,'Email'>;functionparseEmail(s: string):Email{if(!/^[^@]+@[^@]+\.[^@]+$/.test(s))thrownewError(`invalid email: ${s}`);returnsasEmail;}// 매 user 의 input 의 parseEmail 의 통과 만 의 Email 의 acquire.
conste=parseEmail(req.body.email);
3. Brand + Zod (parse 시 brand)
import{z}from'zod';importtype{Brand}from'ts-brand';typeUserId=Brand<string,'UserId'>;constUserIdSchema=z.string().uuid().brand<'UserId'>();// ^? z.ZodBranded<z.ZodString, 'UserId'>
constid=UserIdSchema.parse('xxx-uuid');// type: string & z.BRAND<'UserId'>
// 매 Zod-native brand 의 ts-brand 와 의 compatible.
4. Brand removal (rare, escape hatch)
functionbrandOf<TextendsBrand<any,any>>(b: T):UnwrapBrand<T>{returnbasany;}typeUnwrapBrand<T>=TextendsBrand<inferU,any>?U : never;constraw: string=brandOf(u);// 매 e.g., logging