Branded Types for Validated Primitives
Combine runtime parsers with branded TypeScript types so validated emails, user IDs and slugs cannot be mixed up by accident.
Code
declare const brand: unique symbol;
export type Brand<Value, Name extends string> = Value & { readonly [brand]: Name };
export type Email = Brand<string, "Email">;
export type UserId = Brand<string, "UserId">;
export type Slug = Brand<string, "Slug">;
const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/;
const USER_ID_PATTERN = /^usr_[a-z0-9]{8,}$/;
const SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
export function parseEmail(value: string): Email {
if (typeof value !== "string") throw new TypeError("email must be a string");
const normalized = value.trim().toLowerCase();
if (normalized.length > 254 || !EMAIL_PATTERN.test(normalized)) {
throw new Error(`invalid email address "${value}"`);
}
return normalized as Email;
}
export function parseUserId(value: string): UserId {
if (typeof value !== "string") throw new TypeError("user id must be a string");
const normalized = value.trim();
if (!USER_ID_PATTERN.test(normalized)) throw new Error(`invalid user id "${value}"`);
return normalized as UserId;
}
export function parseSlug(value: string): Slug {
if (typeof value !== "string") throw new TypeError("slug must be a string");
const normalized = value.trim().toLowerCase();
if (normalized.length === 0 || normalized.length > 80 || !SLUG_PATTERN.test(normalized)) {
throw new Error(`invalid slug "${value}"`);
}
return normalized as Slug;
}
export function isEmail(value: string): value is Email {
return value.length <= 254 && EMAIL_PATTERN.test(value.trim().toLowerCase());
}
function sendWelcome(email: Email, userId: UserId): string {
return `queued welcome for ${userId} at ${email}`;
}
const email = parseEmail(" [email protected] ");
const userId = parseUserId("usr_1234abcd");
console.log(email, userId);
console.log(isEmail("not-an-email"));
console.log(sendWelcome(email, userId));
console.log(parseSlug("release-notes-2026"));Report a problem with this exampleOpens a pre-filled issue on GitHub.