Challenge 1: Brand Two ID Types — Possible Solution ==================================================================== type Brand = T & { readonly __brand: TBrand }; type UserId = Brand; type ProductId = Brand; function toUserId(input: string): UserId { if (input.trim().length === 0) { throw new Error("UserId cannot be empty"); } return input as UserId; } function toProductId(input: string): ProductId { if (input.trim().length === 0) { throw new Error("ProductId cannot be empty"); } return input as ProductId; } function getUser(id: UserId): void { console.log(`Fetching user ${id}`); } // --- Usage --- const userId = toUserId("user-42"); const productId = toProductId("product-7"); getUser(userId); // ✅ compiles — userId is genuinely a UserId // getUser(productId); // ❌ Argument of type 'ProductId' is not assignable // to parameter of type 'UserId' — even though // both are strings at runtime. // getUser("user-42"); // ❌ a plain string also isn't a UserId — must go // through toUserId() first. WHY THIS WORKS -------------- - UserId and ProductId both compile down to `string` at runtime — the `__brand` property never actually exists on the value — but the type checker treats them as structurally different types because their brand's literal string ("UserId" vs "ProductId") differs. - toUserId() and toProductId() are the ONLY functions that produce these branded types — a plain string, or a ProductId, cannot be passed where a UserId is expected without going through toUserId() (or an explicit, clearly-suspicious `as UserId` cast). - This is exactly the bug the chapter opened with (`getUser(orderId)` compiling silently) — branding turns what used to be a silent runtime mix-up into an immediate compile-time error.