TypeScript Type Branding (ts-brand)

0.2.0 · active · verified Sun Apr 19

ts-brand is a TypeScript library that enables nominal typing through 'type branding,' a technique that intersects a base type with an object type containing a non-existent property. This allows developers to create distinct types (e.g., `PostId`, `UserId`) from a common primitive (like `number`), preventing accidental assignment bugs at compile time even though they share the same runtime representation. The library is currently at version 0.2.0, indicating it's an early-stage but active project. It ships with TypeScript types inherently, as its functionality is entirely type-system based. A key differentiator is its emphasis on ensuring brand uniqueness, offering methods like using recursive interface types as branding to prevent accidental conflation of brands across different definitions, which is a common pitfall in simpler branding implementations. The project's release cadence appears to be driven by contributions, without a fixed schedule.

Common errors

Warnings

Install

Imports

Quickstart

This quickstart demonstrates how to define and use branded types for nominal typing, preventing common ID-mismatch bugs at compile time. It shows the `Brand` type in action, including recursive branding for enhanced uniqueness.

import { Brand } from 'ts-brand';

// Define the base API
declare function getPost(postId: Post['id']): Promise<Post>;
declare function getUser(userId: User['id']): Promise<User>;

interface User {
  id: Brand<number, User>; // Branding 'number' with the User interface for strong uniqueness
  name: string;
}

interface Post {
  id: Brand<number, Post>; // Branding 'number' with the Post interface
  authorId: User['id']; // Correctly typed authorId as a User's branded ID
  title: string;
  body: string;
}

// A function that correctly retrieves the author of a post
function authorOfPost(postId: Post['id']): Promise<User> {
  return getPost(postId).then(post => getUser(post.authorId));
}

// Example of how a type error is caught (uncomment to see error)
/*
function buggyAuthorOfPost(postId: Post['id']): Promise<User> {
  // This would correctly fail to compile because post.id is Post['id']
  // and getUser expects User['id'], even though both are based on 'number'.
  return getPost(postId).then(post => getUser(post.id));
}
*/

// To use branded types, you typically cast a base type value.
const postIdValue: number = 123;
const userIdValue: number = 456;

const myPostId: Post['id'] = postIdValue as Post['id'];
const myUserId: User['id'] = userIdValue as User['id'];

// This would compile:
authorOfPost(myPostId);

// This would be a compile-time error:
// getUser(myPostId); // Type 'Brand<number, Post>' is not assignable to type 'Brand<number, User>'.

view raw JSON →