Initial commit

This commit is contained in:
Igor Martyniuk
2026-06-25 15:53:49 +03:00
commit 1bb0b7e68a
1517 changed files with 218469 additions and 0 deletions
+137
View File
@@ -0,0 +1,137 @@
# Branded Types in TypeScript
Branded types (also known as _opaque types_) are a TypeScript pattern used to make primitive types like `string` or `number` more specific and safer.
They allow you to distinguish between values that are structurally identical but conceptually different — for example, an `Email` vs a regular `string`.
## The Core Idea
We create a "brand" by intersecting a base type (`string`) with an object containing a unique marker:
```ts
type Branded<T, B extends string> = T & { readonly __brand: B };
```
- `T` → the base type (e.g., `string`)
- `B` → a unique label (e.g., `"Email"`)
This creates a new type that behaves like `T` but is **not interchangeable** with other branded types.
## Defining a Branded Type
```ts
type Email = Branded<string, "Email">;
```
Now `Email` is no longer just a `string` — it’s a _distinct type_.
## Creating Values Safely
To construct a branded value, you typically use a factory function:
```ts
function createEmail(value: string): Email {
if (!value.includes("@")) {
throw new Error("Invalid email");
}
return value as Email;
}
```
### Why use a function?
- Centralizes validation
- Prevents unsafe casting (`as Email`) scattered across the codebase
- Ensures only valid values become branded types
## Example
```ts
const email = createEmail("test@example.com");
function getEmail(email: Email) {
console.log(email);
}
getEmail(email);
```
## Invalid Usage
```ts
const email = createEmail(""); // Throws "Invalid email"
```
```ts
getEmail("plain string"); // Type error
```
Even though `"plain string"` is a `string`, it is **not** an `Email`.
## What Makes It Work?
This line is the key:
```ts
T & { __brand: B };
```
It creates an **intersection type**, meaning:
- At runtime -> it's still just a `string`
- At compile time -> TypeScript treats it as a unique type
So this fails:
```ts
type UserId = Branded<string, "UserId">;
const email: Email = createEmail("a@b.com");
const userId: UserId = email; // Type error
```
Even though both are strings, their brands differ.
## Important Limitation
Branded types exist **only at compile time**.
```ts
typeof em === "string"; // true
```
There is **no runtime difference** between `Email` and `string`.
## When to Use Branded Types
Use them when:
- You have multiple IDs (`UserId`, `OrderId`, etc.)
- You want stronger domain modeling
- You want to prevent mixing similar primitives
- You want safer APIs without runtime overhead
## Final Example (Complete)
```ts
type Branded<T, B extends string> = T & { readonly __brand: B };
type Email = Branded<string, "Email">;
function createEmail(value: string): Email {
if (!value.includes("@")) {
throw new Error("Invalid email");
}
return value as Email;
}
const email = createEmail("damn@example.com");
function getEmail(email: Email) {
console.log(email);
}
getEmail(email);
```
[Readmore](https://dev.to/themuneebh/typescript-branded-types-in-depth-overview-and-use-cases-60e)
+1
View File
@@ -0,0 +1 @@
export type Branded<T, B extends string> = T & { readonly __brand: B };
+2
View File
@@ -0,0 +1,2 @@
export * from "./types";
export * from "./branded";
+13
View File
@@ -0,0 +1,13 @@
export type IsoDateString = string;
export type TimePeriod = "am" | "pm";
export type InputTimeFormat = "ISO" | "HMS";
export type TimeHMS = `${number}:${number}:${number}`;
export type UserDateInputModeSegment = "day" | "month" | "year";
export type UserDeviceType = "mobile" | "tablet" | "desktop";
export type TimeMode = "12h" | "24h";