{"id":12165,"library":"ts-brand","title":"TypeScript Type Branding (ts-brand)","description":"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.","status":"active","version":"0.2.0","language":"javascript","source_language":"en","source_url":"https://github.com/kourge/ts-brand","tags":["javascript","typescript","opaque","branding","type"],"install":[{"cmd":"npm install ts-brand","lang":"bash","label":"npm"},{"cmd":"yarn add ts-brand","lang":"bash","label":"yarn"},{"cmd":"pnpm add ts-brand","lang":"bash","label":"pnpm"}],"dependencies":[],"imports":[{"note":"`ts-brand` exports only types. `require` attempts to load a value, leading to a runtime error or a TypeScript error if strict checks are enabled. This is a type-only import.","wrong":"const { Brand } = require('ts-brand');","symbol":"Brand","correct":"import { Brand } from 'ts-brand';"}],"quickstart":{"code":"import { Brand } from 'ts-brand';\n\n// Define the base API\ndeclare function getPost(postId: Post['id']): Promise<Post>;\ndeclare function getUser(userId: User['id']): Promise<User>;\n\ninterface User {\n  id: Brand<number, User>; // Branding 'number' with the User interface for strong uniqueness\n  name: string;\n}\n\ninterface Post {\n  id: Brand<number, Post>; // Branding 'number' with the Post interface\n  authorId: User['id']; // Correctly typed authorId as a User's branded ID\n  title: string;\n  body: string;\n}\n\n// A function that correctly retrieves the author of a post\nfunction authorOfPost(postId: Post['id']): Promise<User> {\n  return getPost(postId).then(post => getUser(post.authorId));\n}\n\n// Example of how a type error is caught (uncomment to see error)\n/*\nfunction buggyAuthorOfPost(postId: Post['id']): Promise<User> {\n  // This would correctly fail to compile because post.id is Post['id']\n  // and getUser expects User['id'], even though both are based on 'number'.\n  return getPost(postId).then(post => getUser(post.id));\n}\n*/\n\n// To use branded types, you typically cast a base type value.\nconst postIdValue: number = 123;\nconst userIdValue: number = 456;\n\nconst myPostId: Post['id'] = postIdValue as Post['id'];\nconst myUserId: User['id'] = userIdValue as User['id'];\n\n// This would compile:\nauthorOfPost(myPostId);\n\n// This would be a compile-time error:\n// getUser(myPostId); // Type 'Brand<number, Post>' is not assignable to type 'Brand<number, User>'.\n","lang":"typescript","description":"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."},"warnings":[{"fix":"Do not rely on branded types for runtime type checks. Implement runtime validation or assertion functions separately if needed.","message":"Branded types in `ts-brand` are a compile-time construct and have no runtime impact. At runtime, a branded type like `Brand<number, 'user'>` will simply be a `number`. Any runtime checks or conversions must be implemented manually.","severity":"gotcha","affected_versions":">=0.2.0"},{"fix":"Use a unique branding type, such as a string literal specific to the context (e.g., `'UserId'`), or for stronger guarantees, use the recursive branding pattern where the branding type refers to its own interface (e.g., `Brand<number, User>`).","message":"Two branded types are considered the same if they share the same base type and branding type. Care must be taken to ensure that the `Branding` type parameter is truly unique to avoid unintended type conflation across different parts of a codebase.","severity":"gotcha","affected_versions":">=0.2.0"},{"fix":"Review TypeScript's release notes for breaking changes and adjust type assertions or branded type definitions as necessary, especially when `strictNullChecks` or other strict compiler options are enabled. Consider using assertion functions to encapsulate type checks.","message":"When upgrading TypeScript, new strictness checks or changes in type inference might expose previously unflagged issues related to type branding or assertions.","severity":"gotcha","affected_versions":">=0.2.0"},{"fix":"Monitor the project's GitHub repository for updates and review changelogs carefully before upgrading to newer versions. Pin exact versions in your `package.json` to mitigate unexpected changes.","message":"Since `ts-brand` is at an early version (0.2.0), its API surface might evolve, and future minor or major releases could introduce breaking changes or significant modifications to existing types.","severity":"gotcha","affected_versions":">=0.2.0"}],"env_vars":null,"last_verified":"2026-04-19T00:00:00.000Z","next_check":"2026-07-18T00:00:00.000Z","problems":[{"fix":"Use a type assertion (`as Brand<number, 'User'>`) to explicitly tell TypeScript that the value conforms to the branded type after validation, or use a utility function that performs the assertion and returns the branded type. `const myUserId: User['id'] = rawNumber as User['id'];`","cause":"Attempting to assign a raw `number` (or other base type) directly to a branded type without an explicit type assertion.","error":"Type 'number' is not assignable to type 'Brand<number, \"User\">'."},{"fix":"This is the intended behavior of nominal typing. If you genuinely need to convert between distinct branded types, explicitly 'unbrand' to the base type and then 'rebrand' to the target type, typically with a type assertion and appropriate validation logic. E.g., `const convertedUserId: User['id'] = (postAuthorId as number) as User['id'];`","cause":"Attempting to assign a value of one branded type to a variable expecting a different branded type, even if their underlying base types are the same.","error":"Type 'Brand<number, Post>' is not assignable to type 'Brand<number, User>'."},{"fix":"Ensure you are using `import { Brand } from 'ts-brand';` within a TypeScript file (`.ts` or `.tsx`). If working in a mixed JavaScript/TypeScript project, remember that types are a compile-time construct and not available at runtime via `require`.","cause":"This typically occurs when trying to use `Brand` in a JavaScript file that doesn't import types, or when an incorrect CommonJS `require` syntax is used for a TypeScript type-only module.","error":"Cannot find name 'Brand'."}],"ecosystem":"npm"}