AgentSkillsCN

typescript-mastery

为 Buzz Stack 提供严格类型安全、泛型、区分联合类型与类型级别编程的高级 TypeScript 模式。

SKILL.md
--- frontmatter
name: typescript-mastery
description: Advanced TypeScript patterns for strict type safety, generics, discriminated unions, and type-level programming in Buzz Stack.
argument-hint: Describe the typing challenge - generics, type safety, composition, constraints, etc.

TypeScript Mastery

Overview

TypeScript strict mode is enforced in Buzz Stack. This skill covers advanced patterns for maximum type safety, reducing runtime errors and improving code clarity through the type system.

Why it matters:

  • any type leaks defeat the purpose of TypeScript
  • Generics enable reusable, type-safe code
  • Discriminated unions catch invalid states at compile time
  • Type-level patterns prevent entire classes of bugs

Core Concepts

1. Strict Mode & Implications

typescript
// tsconfig.json must have:
{
  "compilerOptions": {
    "strict": true, // ALL strict checks enabled
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictBindCallApply": true,
    "strictPropertyInitialization": true,
    "noImplicitThis": true,
    "alwaysStrict": true
  }
}

Effect:

  • No implicit any
  • No accessing undefined properties
  • No loose function typing
  • Properties must be initialized

2. Generic Constraints (Type-Safety for Reusability)

typescript
// ❌ WRONG: Generic without constraints
function getValue<T>(obj: T, key: string): unknown {
  return obj[key]; // TS can't guarantee key exists
}

// ✅ CORRECT: Constrain generic to key type
function getValue<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key]; // Now type-safe!
}

// Usage
interface User {
  id: string;
  name: string;
}

const user: User = { id: "1", name: "Alice" };
const name = getValue(user, "name"); // Type: string ✓
const age = getValue(user, "age"); // ❌ Compile error - age doesn't exist

3. Discriminated Unions (Type-Safe State)

typescript
// ❌ WRONG: Multiple optional fields (invalid states possible)
interface User {
  status?: "loading" | "success" | "error";
  data?: UserData;
  error?: Error;
}
// Invalid: status='loading' but data=undefined AND error=undefined

// ✅ CORRECT: Discriminated union (only valid states)
type UserState =
  | { status: "idle" }
  | { status: "loading" }
  | { status: "success"; data: UserData }
  | { status: "error"; error: Error };

// Usage
function handleUserState(state: UserState) {
  switch (state.status) {
    case "idle":
      // Can't access .data or .error here
      break;
    case "success":
      console.log(state.data); // ✓ Guaranteed to exist
      break;
    case "error":
      console.log(state.error); // ✓ Guaranteed to exist
      break;
  }
}

4. Conditional Types (Type-Level Programming)

typescript
// Extract the return type from a function type
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

// Usage
type MyFunc = (x: number) => string;
type MyReturn = ReturnType<MyFunc>; // string

// Real-world: Unwrap Promise types
type Awaited<T> = T extends Promise<infer U> ? U : T;

type ApiResponse = Promise<{ data: string }>;
type Unwrapped = Awaited<ApiResponse>; // { data: string }

5. Mapped Types (Generate Types Automatically)

typescript
// Create getters for all properties
type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};

// Usage
interface User {
  id: string;
  name: string;
  email: string;
}

type UserGetters = Getters<User>;
// Result:
// {
//   getId: () => string;
//   getName: () => string;
//   getEmail: () => string;
// }

// Real implementation
const userGetters: UserGetters = {
  getId: () => "1",
  getName: () => "Alice",
  getEmail: () => "alice@example.com",
};

6. Type Guards (Assertion Functions)

typescript
// ❌ WRONG: No type narrowing
function processValue(val: string | number) {
  if (typeof val === "string") {
    // TS still doesn't know val is string here
    console.log(val.length);
  }
}

// ✅ CORRECT: Type guard narrows type
function isString(val: unknown): val is string {
  return typeof val === "string";
}

function processValue(val: string | number) {
  if (isString(val)) {
    // Now TS knows val is string
    console.log(val.length);
  }
}

// For custom types
function isUser(obj: unknown): obj is User {
  return (
    typeof obj === "object" &&
    obj !== null &&
    "id" in obj &&
    "name" in obj &&
    typeof obj.id === "string" &&
    typeof obj.name === "string"
  );
}

Deep Patterns

Pattern 1: Result Type (Error Handling)

typescript
// Explicit error handling at type level
type Result<T, E> = { ok: true; value: T } | { ok: false; error: E };

// Usage
async function fetchUser(id: string): Promise<Result<User, HttpError>> {
  try {
    const response = await fetch(`/api/users/${id}`);
    if (!response.ok) {
      return { ok: false, error: new HttpError(response.status) };
    }
    const data = await response.json();
    return { ok: true, value: data };
  } catch (error) {
    return { ok: false, error };
  }
}

// Forced handling
const result = await fetchUser("123");
if (result.ok) {
  console.log(result.value.name); // ✓ Guaranteed User
} else {
  console.log(result.error.message); // ✓ Guaranteed Error
}

Pattern 2: Builder Pattern with Types

typescript
class QueryBuilder<T = never> {
  private filters: string[] = [];
  private limits: number | undefined;

  where<U extends T>(
    field: Extract<keyof T, string>,
    value: T[U],
  ): QueryBuilder<T> {
    this.filters.push(`${field}=${value}`);
    return this;
  }

  limit(n: number): QueryBuilder<T> {
    this.limits = n;
    return this;
  }

  build(): string {
    return `SELECT * WHERE ${this.filters.join(" AND ")} LIMIT ${this.limits}`;
  }
}

// Usage with type checking
interface User {
  id: string;
  name: string;
  email: string;
}

const query = new QueryBuilder<User>()
  .where("name", "Alice") // ✓ 'name' exists on User
  .where("age", 30) // ❌ 'age' doesn't exist on User
  .limit(10)
  .build();

Pattern 3: Generic Service Layer (Type-Safe Orchestration)

typescript
// Service that works with ANY data type
abstract class Service<T> {
  protected cache = new Map<string, T>();

  async get(id: string): Promise<T | null> {
    const cached = this.cache.get(id);
    if (cached) return cached;

    const item = await this.fetch(id);
    if (item) {
      this.cache.set(id, item);
    }
    return item;
  }

  protected abstract fetch(id: string): Promise<T | null>;
}

// Concrete implementation
interface User {
  id: string;
  name: string;
}

class UserService extends Service<User> {
  protected async fetch(id: string): Promise<User | null> {
    const response = await fetch(`/api/users/${id}`);
    if (!response.ok) return null;
    return response.json();
  }
}

// Strongly typed
const userService = new UserService();
const user = await userService.get("123"); // Type: User | null ✓

Pattern 4: Utility Types (Use Existing, Don't Reinvent)

typescript
// Pick - select specific properties
type UserInfo = Pick<User, "id" | "name">;

// Omit - exclude specific properties
type UserWithoutEmail = Omit<User, "email">;

// Record - create object with known keys
type Permissions = Record<"read" | "write" | "delete", boolean>;
// Result: { read: boolean; write: boolean; delete: boolean; }

// Partial - make all properties optional
type PartialUser = Partial<User>;

// Required - make all properties required
type FullUser = Required<Partial<User>>;

// ReadOnly - prevent mutations
type ReadonlyUser = Readonly<User>;

// Extract - get matching types
type StringKeys = Extract<keyof User, string>;

// Exclude - remove matching types
type NonStringKeys = Exclude<keyof User, string>;

Anti-Patterns to Avoid

typescript
// ❌ Wrong: Using any
function processData(data: any) {
  return data.toUpperCase(); // No error, runs quietly
}

// ✅ Correct: Specific types
function processData(data: string) {
  return data.toUpperCase();
}

// ❌ Wrong: Multiple optional properties
interface Response {
  data?: unknown;
  error?: unknown;
}

// ✅ Correct: Discriminated union
type Response = { data: unknown } | { error: unknown };

// ❌ Wrong: Generic without constraints
function getValue<T>(obj: T, key: string) {
  return obj[key]; // Unsafe
}

// ✅ Correct: Constrain the generic
function getValue<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

Compilation Strategy

  1. Enable strict mode (non-negotiable for type safety)
  2. Use noUnusedLocals and noUnusedParameters (catch dead code)
  3. Use exactOptionalPropertyTypes (undefined vs. optional)
  4. Use noImplicitOverride (catch override mistakes)
  5. Use noUncheckedIndexedAccess (access safety)
typescript
// tsconfig.json - COMPLETE STRICT SETUP
{
  "compilerOptions": {
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true,
    "noImplicitOverride": true,
    "skipLibCheck": false // Don't skip checking node_modules
  }
}

Real-World Application in Buzz Stack

  • React props: Discriminated component states
  • Service layer: Generics for type-safe caching
  • API contracts: Result<T, E> for error handling
  • Form handling: Type-safe form state with discriminators
  • Hook composition: Generic hooks with constraints
  • Utility functions: Extract common patterns with mapped types

Key Questions When Writing Types

  1. Is this type accurate? (Does it match reality?)
  2. Could invalid state be created? (Discriminate unions)
  3. Is the generic properly constrained? (Use extends)
  4. Can I make this reusable? (Generics, mapped types)
  5. Am I using any? (Stop. Find another way.)

Resources