| name | typescript-type-safety |
| description | TypeScript type safety including type guards and advanced type system features. **ALWAYS use when writing ANY TypeScript code (frontend AND backend)** to ensure strict type safety, avoid `any` types, and leverage the type system. Examples - "create function", "implement class", "define interface", "type guard", "discriminated union", "type narrowing", "conditional types", "handle unknown types". |
You are an expert in TypeScript's type system and type safety. You guide developers to write type-safe code that leverages TypeScript's powerful type system to catch errors at compile time.
For development workflow and quality gates (pre-commit checklist, bun commands), see project-workflow skill
When to Engage
You should proactively assist when:
- Working with
unknown types in any context
- Implementing type guards for context-specific types
- Using discriminated unions within bounded contexts
- Implementing advanced TypeScript patterns without over-abstraction
- User asks about type safety or TypeScript features
Modular Monolith Type Safety
Context-Specific Types
export interface AuthUser {
id: string;
email: string;
isActive: boolean;
}
export interface TaxCalculation {
ncmCode: string;
rate: number;
amount: number;
}
export interface BaseEntity<T> {
id: string;
data: T;
}
Core Type Safety Rules
1. NEVER Use any
function process(data: any) {
return data.value;
}
function process(data: unknown): string {
if (isProcessData(data)) {
return data.value;
}
throw new TypeError("Invalid data structure");
}
interface ProcessData {
value: string;
}
function isProcessData(data: unknown): data is ProcessData {
return (
typeof data === "object" &&
data !== null &&
"value" in data &&
typeof (data as ProcessData).value === "string"
);
}
2. Use Proper Type Guards
function isString(value: unknown): value is string {
return typeof value === "string";
}
function isNumber(value: unknown): value is number {
return typeof value === "number";
}
function isObject(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
interface User {
id: string;
email: string;
name: string;
}
function isUser(value: unknown): value is User {
if (!isObject(value)) return false;
return (
"id" in value &&
typeof value.id === "string" &&
"email" in value &&
typeof value.email === "string" &&
"name" in value &&
typeof value.name === "string"
);
}
function processUser(data: unknown): User {
if (!isUser(data)) {
throw new TypeError("Invalid user data");
}
return data;
}
Discriminated Unions
type PaymentMethod =
| {
type: "credit_card";
cardNumber: string;
expiryDate: string;
cvv: string;
}
| {
type: "paypal";
email: string;
}
| {
type: "bank_transfer";
accountNumber: string;
routingNumber: string;
};
function processPayment(method: PaymentMethod, amount: number): void {
switch (method.type) {
case "credit_card":
console.log(`Charging ${amount} to card ${method.cardNumber}`);
break;
case "paypal":
console.log(`Charging ${amount} to PayPal ${method.email}`);
break;
case "bank_transfer":
console.log(
`Charging ${amount} via bank transfer ${method.accountNumber}`
);
break;
default:
const _exhaustive: never = method;
throw new Error(`Unhandled payment method: ${_exhaustive}`);
}
}
Conditional Types
type ResponseData<T> = T extends { id: string }
? { success: true; data: T }
: never;
type User = { id: string; name: string };
type UserResponse = ResponseData<User>;
type Awaited<T> = T extends Promise<infer U> ? U : T;
type UserPromise = Promise<User>;
type UserType = Awaited<UserPromise>;
type ReturnType<T> = T extends (...args: unknown[]) => infer R ? R : never;
function getUser(): User {
return { id: "1", name: "John" };
}
type UserFromFunction = ReturnType<typeof getUser>;
Mapped Types
type Partial<T> = {
[P in keyof T]?: T[P];
};
type User = {
id: string;
email: string;
name: string;
};
type PartialUser = Partial<User>;
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
type ReadonlyUser = Readonly<User>;
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
type UserPreview = Pick<User, "id" | "name">;
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
type UserWithoutId = Omit<User, "id">;
Template Literal Types
type EventName = "user" | "order" | "payment";
type EventAction = "created" | "updated" | "deleted";
type Event = `${EventName}:${EventAction}`;
function emitEvent(event: Event): void {
console.log(`Emitting ${event}`);
}
emitEvent("user:created");
emitEvent("user:invalid");
Function Overloads
function getValue(key: "count"): number;
function getValue(key: "name"): string;
function getValue(key: "isActive"): boolean;
function getValue(key: string): unknown {
const values: Record<string, unknown> = {
count: 42,
name: "John",
isActive: true,
};
return values[key];
}
const count = getValue("count");
const name = getValue("name");
const isActive = getValue("isActive");
Const Assertions
const config = {
apiUrl: "https://api.example.com",
timeout: 5000,
retries: 3,
} as const;
const colors = ["red", "green", "blue"] as const;
type Color = (typeof colors)[number];
Utility Types
NonNullable
type NonNullable<T> = T extends null | undefined ? never : T;
type MaybeString = string | null | undefined;
type DefiniteString = NonNullable<MaybeString>;
Extract and Exclude
type Extract<T, U> = T extends U ? T : never;
type Exclude<T, U> = T extends U ? never : T;
type Status = "pending" | "approved" | "rejected" | "cancelled";
type PositiveStatus = Extract<Status, "approved" | "pending">;
type NegativeStatus = Exclude<Status, "approved" | "pending">;
Record
type Record<K extends keyof unknown, T> = {
[P in K]: T;
};
type UserRoles = "admin" | "user" | "guest";
type Permissions = Record<UserRoles, string[]>;
const permissions: Permissions = {
admin: ["read", "write", "delete"],
user: ["read", "write"],
guest: ["read"],
};
Type Narrowing
typeof Guards
function process(value: string | number): string {
if (typeof value === "string") {
return value.toUpperCase();
}
return value.toFixed(2);
}
instanceof Guards
class User {
constructor(public name: string) {}
}
class Admin extends User {
constructor(name: string, public level: number) {
super(name);
}
}
function greet(user: User | Admin): string {
if (user instanceof Admin) {
return `Hello Admin ${user.name}, level ${user.level}`;
}
return `Hello ${user.name}`;
}
in Operator
type Dog = { bark: () => void };
type Cat = { meow: () => void };
function makeSound(animal: Dog | Cat): void {
if ("bark" in animal) {
animal.bark();
} else {
animal.meow();
}
}
TypeScript Configuration
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"module": "ESNext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"types": ["bun-types"],
"target": "ES2022",
"lib": ["ES2022"]
}
}
Best Practices
Do:
- ✅ Use
unknown instead of any
- ✅ Implement type guards for runtime checks
- ✅ Leverage discriminated unions for polymorphism
- ✅ Enable strict mode in tsconfig.json
- ✅ Use const assertions for literal types
- ✅ Implement exhaustiveness checks in switch statements
Don't:
- ❌ Use
any type
- ❌ Use type assertions (as) without validation
- ❌ Disable strict mode
- ❌ Use
@ts-ignore or @ts-expect-error without good reason
- ❌ Mix types and interfaces unnecessarily
- ❌ Create overly complex type gymnastics
Common Type Errors and Solutions
Error: Object is possibly 'null' or 'undefined'
function getName(user: User | null): string {
return user.name;
}
function getName(user: User | null): string {
if (!user) {
throw new Error("User is null");
}
return user.name;
}
function getName(user: User | null): string | undefined {
return user?.name;
}
Error: Type 'X' is not assignable to type 'Y'
interface User {
id: string;
name: string;
}
const user = {
id: "1",
name: "John",
extra: "field",
};
const typedUser: User = user;
const exactUser: User = {
id: "1",
name: "John",
};
Remember
- Type safety catches bugs at compile time - Invest in good types
- unknown > any - Always use unknown for truly unknown types
- Type guards are your friends - Use them liberally
- Strict mode is mandatory - Never disable it