S SmartDocs
シリーズ: Typescript typescript 93 行 · 更新日 2025-12-27

10-branded-types.ts

Typescript/src/03-advanced/10-branded-types.ts

// ============================================
// 10. Branded Types and Opaque Types
// ============================================

// Branded Types
type Brand<T, B> = T & { __brand: B };

type UserId = Brand<string, "UserId">;
type ProductId = Brand<string, "ProductId">;

function getUser(id: UserId): { id: UserId; name: string } {
  return { id, name: "Alice" };
}

function getProduct(id: ProductId): { id: ProductId; name: string } {
  return { id, name: "Product" };
}

const userId: UserId = "123" as UserId;
const productId: ProductId = "456" as ProductId;

getUser(userId);     // OK
// getUser(productId);  // Error: Type 'ProductId' is not assignable to type 'UserId'

// Opaque Types
declare const __opaque: unique symbol;
type Opaque<T, K> = T & { readonly [__opaque]: K };

type Email = Opaque<string, "Email">;
type Password = Opaque<string, "Password">;

function createEmail(value: string): Email {
  if (!value.includes("@")) {
    throw new Error("Invalid email");
  }
  return value as Email;
}

function createPassword(value: string): Password {
  if (value.length < 8) {
    throw new Error("Password too short");
  }
  return value as Password;
}

const email: Email = createEmail("user@example.com");
const password: Password = createPassword("secure123");

// email and password are distinct types
// email = password; // Error: Type 'Password' is not assignable to type 'Email'

// Nominal Typing
type Meters = Brand<number, "Meters">;
type Feet = Brand<number, "Feet">;

function convertToFeet(meters: Meters): Feet {
  return (meters * 3.28084) as Feet;
}

const distance: Meters = 10 as Meters;
const inFeet = convertToFeet(distance); // Type-safe conversion

// Type-Safe IDs
type EntityId<T> = Brand<string, T>;

type UserId2 = EntityId<"User">;
type PostId = EntityId<"Post">;
type CommentId = EntityId<"Comment">;

interface User2 {
  id: UserId2;
  name: string;
}

interface Post {
  id: PostId;
  title: string;
  authorId: UserId2;
}

function getUser2(id: UserId2): User2 | undefined {
  // Implementation
  return undefined;
}

const userId2: UserId2 = "user-123" as UserId2;
const postId: PostId = "post-456" as PostId;

getUser2(userId2);  // OK
// getUser2(postId);  // Error: Type 'PostId' is not assignable to type 'UserId2'

export { getUser, createEmail, createPassword, convertToFeet, getUser2 };

関連記事