Branding & Flavoring
branding.ts
Definition : Trick that makes a type structurally distinct and strictly unassignable without explicit conversion, by adding a mandatory property (__brand
).
Usage :
- Primitive Type
- Explicit Conversion
Ex: Critical Values (
BearerToken
Vs.AccessToken
)
ts
export type Brand<T, U> = T & { __brand: U };
type PostId = string & { __brand: "PostId" };
type CommentId = Brand<string, "CommentId">;
// Strict Branding Factory
const makePostId = (id: string): PostId => id as PostId;
// TODO: const makeCommentId = (id: string): CommentId => id as CommentId;
const value = makePostId(42);
const postId: PostId = value; // Ok
const commentId: CommentId = value; // Error
flavoring.ts
Definition : Lighter variant of "Branding" that makes a type semantically distinct while remaining structurally compatible, thanks to an optional property (__flavor
).
Usage :
- Object Type
- Implicit Conversion
Ex: Semantic Differences (
User
Vs.UserRoles
)
ts
export type Flavor<T, U> = T & { __flavor?: U };
type Post = { author: string; content: string } & { __flavor?: "Post" };
type PostComment = Flavor<{ author: string; content: string }, "PostComment">;
const createPost = (post: Post) => {
/* ... */
};
const post: Post = { author: "John Doe", content: "Lorem ipsum dolor sit amet" };
createPost(post); // Ok
const postComment: PostComment = { author: "John Doe", content: "Lorem ipsum dolor sit amet" };
createPost(postComment); // Error
nominal.ts
ts
import { type Brand } from "./branding";
import { type Flavor } from "./flavoring";
export type Nominal<T, U> = T extends object ? Flavor<T, U> : Brand<T, U>;
type AccessToken = Nominal<string, "AccessToken">; // Brand
type User = Nominal<{ login: string; pswd: string; roles: string[] }, "User">; // Flavor
type UserRoles = Nominal<{ roles: string[] }, "UserRoles">; // Flavor