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.

TypescriptData Integrity

Code

snippet
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"));