typescript-ops

SKILL.md

TypeScript Operations

Comprehensive TypeScript skill covering the type system, generics, and production patterns.

Type Narrowing Decision Tree

How to narrow a type?
├─ Primitive type check
│  └─ typeof: typeof x === "string"
├─ Instance check
│  └─ instanceof: x instanceof Date
├─ Property existence
│  └─ in: "email" in user
├─ Discriminated union
│  └─ switch on literal field: switch (event.type)
├─ Null/undefined check
│  └─ Truthiness: if (x) or if (x != null)
├─ Custom logic
│  └─ Type predicate: function isUser(x: unknown): x is User
└─ Assertion (you know better than TS)
   └─ as: value as string (escape hatch, avoid when possible)

Type Guard Example

interface Dog { bark(): void; breed: string }
interface Cat { meow(): void; color: string }

function isDog(pet: Dog | Cat): pet is Dog {
    return "bark" in pet;
}

function handlePet(pet: Dog | Cat) {
    if (isDog(pet)) {
        pet.bark(); // TS knows it's Dog here
    } else {
        pet.meow(); // TS knows it's Cat here
    }
}

Discriminated Unions

type Result<T> =
    | { status: "success"; data: T }
    | { status: "error"; error: string }
    | { status: "loading" };

function handle<T>(result: Result<T>) {
    switch (result.status) {
        case "success": return result.data;     // data is available
        case "error":   throw new Error(result.error); // error is available
        case "loading": return null;
    }
    // Exhaustiveness check: result is `never` here
    const _exhaustive: never = result;
}

Utility Types Cheat Sheet

Utility What It Does Example
Partial<T> All props optional Partial<User> for update payloads
Required<T> All props required Required<Config> for validated config
Readonly<T> All props readonly Readonly<State> for immutable state
Pick<T, K> Select specific props Pick<User, "id" | "name">
Omit<T, K> Remove specific props Omit<User, "password">
Record<K, V> Object with typed keys/values Record<string, number>
Exclude<U, E> Remove types from union Exclude<Status, "deleted">
Extract<U, E> Keep types from union Extract<Event, { type: "click" }>
NonNullable<T> Remove null/undefined NonNullable<string | null>
ReturnType<F> Function return type ReturnType<typeof fetchUser>
Parameters<F> Function params as tuple Parameters<typeof createUser>
Awaited<T> Unwrap Promise type Awaited<Promise<User>> = User

Generic Patterns

Constrained Generics

// Basic constraint
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
    return obj[key];
}

// Multiple constraints
function merge<T extends object, U extends object>(a: T, b: U): T & U {
    return { ...a, ...b };
}

// Default generic type
type ApiResponse<T = unknown> = {
    data: T;
    status: number;
};

Conditional Types

// Basic conditional
type IsString<T> = T extends string ? true : false;

// infer keyword - extract inner type
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type UnwrapArray<T> = T extends (infer U)[] ? U : T;

// Distributive conditional (distributes over union)
type ToArray<T> = T extends any ? T[] : never;
// ToArray<string | number> = string[] | number[]

// Prevent distribution with wrapping
type ToArrayNonDist<T> = [T] extends [any] ? T[] : never;
// ToArrayNonDist<string | number> = (string | number)[]

Mapped Types

// Make all properties optional and nullable
type Nullable<T> = { [K in keyof T]: T[K] | null };

// Add prefix to keys
type Prefixed<T, P extends string> = {
    [K in keyof T as `${P}${Capitalize<string & K>}`]: T[K];
};
// Prefixed<{ name: string }, "get"> = { getName: string }

// Filter keys by value type
type StringKeys<T> = {
    [K in keyof T as T[K] extends string ? K : never]: T[K];
};

Deep dive: Load ./references/generics-patterns.md for advanced type-level programming, recursive types, template literal types.

tsconfig Quick Reference

{
    "compilerOptions": {
        // Strict mode (always enable)
        "strict": true,               // Enables all strict checks
        "noUncheckedIndexedAccess": true,  // arr[0] is T | undefined

        // Module system
        "module": "esnext",           // or "nodenext" for Node
        "moduleResolution": "bundler", // or "nodenext"
        "esModuleInterop": true,

        // Output
        "target": "es2022",
        "outDir": "dist",
        "declaration": true,          // Generate .d.ts
        "sourceMap": true,

        // Paths
        "baseUrl": ".",
        "paths": { "@/*": ["src/*"] },

        // Strictness extras
        "noUnusedLocals": true,
        "noUnusedParameters": true,
        "noFallthroughCasesInSwitch": true,
        "forceConsistentCasingInFileNames": true
    },
    "include": ["src"],
    "exclude": ["node_modules", "dist"]
}

Deep dive: Load ./references/config-strict.md for strict mode migration, monorepo config, project references.

Common Gotchas

Gotcha Why Fix
any leaks any disables type checking for everything it touches Use unknown + narrowing instead
as assertions hide bugs Assertion doesn't check at runtime Use type guards or validation (Zod)
enum quirks Numeric enums are not type-safe, reverse mappings confuse Use as const objects or string literal unions
object vs Record vs {} {} matches any non-null value, object is non-primitive Use Record<string, unknown> for "any object"
Array index access arr[999] returns T not T | undefined by default Enable noUncheckedIndexedAccess
Optional vs undefined { x?: string } allows missing key, { x: string | undefined } requires key Be explicit about which you mean
! non-null assertion Silences null checks, no runtime effect Use ?? defaultValue or proper null check
Structural typing surprise { a: 1, b: 2 } assignable to { a: number } Use branded types for nominal typing

Branded / Nominal Types

// Prevent accidentally mixing types that are structurally identical
type UserId = string & { readonly __brand: "UserId" };
type OrderId = string & { readonly __brand: "OrderId" };

function createUserId(id: string): UserId { return id as UserId; }

function getUser(id: UserId) { /* ... */ }

const userId = createUserId("u-123");
const orderId = "o-456" as OrderId;

getUser(userId);   // OK
getUser(orderId);  // Error: OrderId not assignable to UserId

Runtime Validation (Zod)

import { z } from "zod";

// Define schema
const UserSchema = z.object({
    id: z.number(),
    name: z.string().min(1),
    email: z.string().email(),
    role: z.enum(["admin", "user"]),
    settings: z.object({
        theme: z.enum(["light", "dark"]).default("light"),
    }).optional(),
});

// Infer type from schema
type User = z.infer<typeof UserSchema>;

// Validate
const user = UserSchema.parse(untrustedData);       // throws on invalid
const result = UserSchema.safeParse(untrustedData);  // returns { success, data/error }

Reference Files

Load these for deep-dive topics. Each is self-contained.

Reference When to Load
./references/type-system.md Advanced types, branded types, type-level programming, satisfies operator
./references/generics-patterns.md Generic constraints, conditional types, mapped types, template literals, recursive types
./references/utility-types.md All built-in utility types with examples, custom utility types
./references/config-strict.md tsconfig deep dive, strict mode migration, project references, monorepo setup
./references/ecosystem.md Zod/Valibot, type-safe API clients, ORM types, testing with Vitest

See Also

  • testing-ops - Cross-language testing strategies
  • ci-cd-ops - TypeScript CI pipelines, type checking in CI
Weekly Installs
1
GitHub Stars
10
First Seen
7 days ago
Installed on
zencoder1
amp1
cline1
openclaw1
opencode1
cursor1