Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Branding

For the problem that branding solves, see TypeScript problems Valof addresses.

Define branded types

Val takes the brand and the payload type:

import { Val } from "valof";

type UserId = Val<"UserId", string>;
type OrderId = Val<"OrderId", string>;

The brand is phantom. A UserId is still a string at runtime.

Name the brand after the type it brands: type UserId = Val<"UserId", string>. The brand-mismatch lint rule reports when the names do not match.

Construct values

Val.sealer supplies the constructor:

const UserId = Val.sealer<UserId>();
const OrderId = Val.sealer<OrderId>();

const userId = UserId("u_1");
let orderId: OrderId;

orderId = userId; // type error: UserId is not an OrderId
orderId = "o_1"; // type error: a plain string is not an OrderId

type User = Val<"User", { name: string }>;
const User = Val.sealer<User>();
const user = User({ name: "alice" });

// @ts-expect-error spread drops the brand
const changed: User = { ...user, name: "bob" };
const resealed = User({ ...user, name: "bob" });

Name the constructor after its type: const UserId = Val.sealer<UserId>(). TypeScript lets the type and value share a name. The companion-mismatch lint rule reports when they do not match.