Claude Skill

typescript-ops

TypeScript type system, generics, utility types, strict mode, and ecosystem patterns. Use for: typescript, ts, type, generic, utility type, Partial, Pick, Omit, Record, Exclude, Extract, ReturnType, Parameters, keyof, typeof, infer, mapped type, conditional type, template literal

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download 0xdarkmatter-claude-mods-skills_typescript-ops-3dfaf0b.zip · 44 KB
Part of 0xdarkmatter/claude-mods — 94 skills

Install

skills CLI npx skills add https://github.com/0xDarkMatter/claude-mods/tree/main/skills/typescript-ops
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install 0xdarkmatter-claude-mods@llmmart
Git git clone https://github.com/0xDarkMatter/claude-mods.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole 0xdarkmatter/claude-mods collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

TypeScript Operations

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

Ecosystem facts verified as of 2026-08-08 (TypeScript 7, Zod 4, Valibot 1).

Staleness check: python scripts/check-typescript-facts.py --offline asserts the catalogued version-bearing facts (TypeScript major, zod, valibot) are still named in the prose and the dated currency note above is present; run --live to confirm each package's npm major still matches the documented major. Catalog: assets/typescript-facts.json.

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.

Modern Language Features (TypeScript 5.x → 6.0)

Feature Since What It Gives You
satisfies operator 4.9 Check a value against a type without widening it
Standard (TC39) decorators 5.0 @decorator on classes/methods without experimentalDecorators
const type parameters 5.0 function f<const T>(x: T) infers literal types without as const at call sites
using declarations 5.2 Explicit resource management (Symbol.dispose), auto-cleanup at scope exit
Inferred type predicates 5.5 arr.filter(x => x !== null) narrows without a hand-written x is T guard
verbatimModuleSyntax 5.0 Enforces import type for type-only imports — replaces importsNotUsedAsValues
// const type parameters (5.0) - literal inference without as const
function routes<const T extends readonly string[]>(paths: T): T { return paths; }
const r = routes(["/home", "/about"]); // readonly ["/home", "/about"], not string[]

// using declarations (5.2) - deterministic cleanup
function readConfig() {
    using file = openFile("config.json"); // file[Symbol.dispose]() runs at scope exit
    return parse(file.contents);
}

// Inferred type predicates (5.5) - no manual guard needed
const names = ["a", null, "b"].filter(x => x !== null); // string[], not (string | null)[]

TypeScript 6.0 (The Bridge Release)

TS 6.0 is the last release on the JavaScript-based compiler — it exists to bridge to the native (Go) compiler in TS 7, so its headline is stricter, modernised defaults:

  • strict: true is the default — a tsconfig that never set it now gets full strict checks
  • Defaults modernised: module: esnext, target: es2025; es2025 lib ships types for Temporal, Map.getOrInsert, RegExp.escape
  • Legacy options removed: moduleResolution: classic; module: amd/umd/system/none; minimum target is now ES2015 (es5 deprecated)
  • Interop always on: esModuleInterop / allowSyntheticDefaultImports can no longer be disabled
  • New --stableTypeOrdering flag eases 6.0 → 7.0 migration diffing

TypeScript 7 (Current Major — Native Compiler)

TS 7 is the Go-native rewrite (formerly tsgo), stable on npm since 2026-07-08. Same checking semantics, ~8–16× faster typechecks — but the package ships only a bin/tsc shim, no JavaScript compiler API: require('typescript') throws MODULE_NOT_FOUND on 7.0.x (the API returns in 7.1+). Consequences that gate adoption:

  • Repo tooling using the TS programmatic API (ts.createSourceFile, custom lint scripts, codemods, typescript-eslint) must go AST-free, switch parser, or pin an alias
  • Tools typechecking embedded languages (vue-tsc, svelte-check, Astro, MDX) are pinned to TS 6 until they port to the 7.1+ API — a real stack-selection input
  • A tsconfig already on TS 6 defaults adopts directly (the 6.0 bridge is skippable); baseUrl is a hard error (TS5102)
  • Running a typescript5 fallback alias alongside 7 makes bare npx tsc ambiguous — scripts must use explicit compiler paths during the soak

Deep dive: Load ./references/ts7-native-compiler.md for the no-JS-API workarounds, dual-install bin ambiguity, ecosystem lockout table, measured adoption benchmarks (12.1×), and the go/no-go checklist.

tsconfig Quick Reference

{
    "compilerOptions": {
        // Strict mode (default in TS 6; state it explicitly anyway)
        "strict": true,               // Enables all strict checks
        "noUncheckedIndexedAccess": true,  // arr[0] is T | undefined

        // Module system (TS 6 defaults to module: esnext; interop is always on)
        "module": "esnext",           // or "nodenext" for Node
        "moduleResolution": "bundler", // or "nodenext"

        // Output (TS 6 defaults target to es2025; min supported is es2015)
        "target": "es2022",
        "outDir": "dist",
        "declaration": true,          // Generate .d.ts
        "sourceMap": true,

        // Paths — tsconfig-relative; don't add baseUrl (TS 7 hard-errors on it, TS5102)
        "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 4)

import { z } from "zod";

// Define schema (Zod 4: string formats are top-level - z.email(), not z.string().email())
const UserSchema = z.object({
    id: z.number(),
    name: z.string().min(1),
    email: z.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 }

Zod 4 changes to know (if you learned Zod 3): string formats moved to the top level (z.email(), z.uuid(), z.url() — the z.string().email() method form is deprecated); error customisation unified under a single error param (invalid_type_error / required_error dropped); much faster parsing and a tree-shakeable zod/mini entry point.

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/ts7-native-compiler.md Adopting the TS 7 native (Go) compiler: no JS API, bin ambiguity, ecosystem lockout, benchmarked go/no-go
./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
Files (claude-mods)
  • assets
    • typescript-facts.json 1.7 KB
      {
        "_comment": "Canonical fast-moving facts the typescript-ops skill encodes. scripts/check-typescript-facts.py asserts SKILL.md + references name these consistently (--offline) and probes the npm registry for major-version drift (--live). Edit deliberately: a change here is a skill-content decision, not housekeeping.",
        "schema": "claude-mods.typescript-ops.facts/v1",
        "as_of": "2026-08-08",
        "registry": "https://registry.npmjs.org",
        "currency_note": "> Ecosystem facts verified as of 2026-08-08 (TypeScript 7, Zod 4, Valibot 1).",
        "packages": [
          {
            "name": "typescript",
            "documented_major": 7,
            "role": "language",
            "where": "SKILL.md (Modern Language Features 5.x -> 6.0: satisfies, standard decorators, const type params, using, inferred predicates; TS 6.0 bridge section; TS 7 native-compiler section); references/ts7-native-compiler.md (no JS API on 7.0.x, dual-install bin ambiguity, ecosystem lockout until 7.1, benchmarked adoption); references/config-strict.md (strict default in 6.0, removed module/moduleResolution options). Historical feature floors (TS 4.4+, 4.7+) remain as floors, not currency anchors."
          },
          {
            "name": "zod",
            "documented_major": 4,
            "role": "runtime validation (default choice)",
            "where": "SKILL.md (Runtime Validation (Zod 4): top-level z.email(), unified error param, zod/mini); references/ecosystem.md (z.uuid/z.email top-level formats, unified error param, discriminatedUnion, coerce)."
          },
          {
            "name": "valibot",
            "documented_major": 1,
            "role": "runtime validation (tree-shakeable alt)",
            "where": "references/ecosystem.md (stable since 1.0; v.pipe, v.InferOutput, v.safeParse, v.picklist)."
          }
        ]
      }
      
  • references
    • config-strict.md 13.4 KB
      # TypeScript Configuration and Strict Mode Reference
      
      ## Table of Contents
      
      1. [Strict Mode Flags](#strict-mode-flags)
      2. [Migration Strategy](#migration-strategy)
      3. [Module Configuration](#module-configuration)
      4. [Path Aliases](#path-aliases)
      5. [Project References](#project-references)
      6. [Monorepo Setup](#monorepo-setup)
      7. [Declaration Files](#declaration-files)
      
      ---
      
      ## Strict Mode Flags
      
      ### Enable the Full Strict Suite
      
      `"strict": true` is shorthand for enabling all individual strict flags at once. Always enable it. Since TypeScript 6.0 it is the **default** — a tsconfig that never set `strict` now gets the full suite, so a project upgrading to TS 6 may surface long-hidden errors; set `"strict": false` only as a deliberate, temporary migration step.
      
      ```json
      {
        "compilerOptions": {
          "strict": true,
      
          // Additional strictness beyond "strict": true
          "noUncheckedIndexedAccess": true,
          "noImplicitReturns": true,
          "noFallthroughCasesInSwitch": true,
          "exactOptionalPropertyTypes": true,
          "noPropertyAccessFromIndexSignature": true,
          "noImplicitOverride": true
        }
      }
      ```
      
      ### Understand Each Flag
      
      **strictNullChecks** - `null` and `undefined` are not assignable to other types. The most impactful flag.
      
      ```typescript
      // Without strictNullChecks: null assignable to anything
      // With strictNullChecks:
      function getLength(s: string): number {
        return s.length; // OK
      }
      getLength(null); // Error: Argument of type 'null' is not assignable to parameter of type 'string'
      
      // Forces explicit null handling:
      function getName(user: { name: string } | null): string {
        return user?.name ?? 'Anonymous';
      }
      ```
      
      **strictFunctionTypes** - Function parameters are checked contravariantly, not bivariantly.
      
      ```typescript
      type Animal = { name: string };
      type Dog = Animal & { breed: string };
      
      type AnimalCallback = (a: Animal) => void;
      type DogCallback    = (d: Dog) => void;
      
      let animalCb: AnimalCallback = (a) => console.log(a.name);
      let dogCb: DogCallback = (d) => console.log(d.breed);
      
      // With strictFunctionTypes, this is an error (unsafe in callback position):
      // dogCb = animalCb; // DogCallback expects d.breed but AnimalCallback only provides a.name
      ```
      
      **strictBindCallApply** - `.bind()`, `.call()`, `.apply()` are type-checked.
      
      ```typescript
      function add(a: number, b: number): number { return a + b; }
      
      add.call(null, 1, 2);   // OK
      add.call(null, '1', 2); // Error: Argument of type 'string' not assignable to 'number'
      add.bind(null, 1)(2);   // OK, typed as () => number after bind
      ```
      
      **strictPropertyInitialization** - Class properties must be assigned in the constructor.
      
      ```typescript
      class Service {
        name: string;       // Error: not definitely assigned
        id: string;         // Error: not definitely assigned
      
        // Fix options:
        optA: string = '';                              // default value
        optB!: string;                                  // definite assignment assertion (use sparingly)
        optC: string | undefined;                       // allow undefined
        constructor() { this.optA = this.optA; }       // assign in constructor
      }
      ```
      
      **noImplicitAny** - Variables whose type cannot be inferred default to `any` - this flag makes that an error.
      
      ```typescript
      function process(data) { // Error: 'data' implicitly has an 'any' type
        return data.value;
      }
      
      function process(data: { value: string }): string { // OK
        return data.value;
      }
      ```
      
      **noImplicitThis** - `this` usage without explicit annotation is an error.
      
      ```typescript
      function greet() {
        return this.name; // Error: 'this' implicitly has type 'any'
      }
      
      function greet(this: { name: string }): string {
        return this.name; // OK - this is typed
      }
      ```
      
      **useUnknownInCatchVariables** (part of strict in TS 4.4+) - Catch clause variables are `unknown`, not `any`.
      
      ```typescript
      try {
        riskyOperation();
      } catch (err) {
        // err is 'unknown' - must narrow before use
        if (err instanceof Error) {
          console.error(err.message); // OK
        } else {
          console.error(String(err)); // handle non-Error throws
        }
      }
      ```
      
      **noUncheckedIndexedAccess** - Index signatures include `undefined` in return type.
      
      ```typescript
      const map: Record<string, string> = {};
      const value = map['key']; // string | undefined (not just string)
      
      // Forces null checking:
      if (value !== undefined) {
        console.log(value.toUpperCase()); // OK
      }
      ```
      
      **exactOptionalPropertyTypes** - Distinguishes between `prop?: T` (absent or T) and `prop: T | undefined`.
      
      ```typescript
      interface A { name?: string; }
      
      // With exactOptionalPropertyTypes:
      const a: A = { name: undefined }; // Error: undefined is not the same as absent
      const b: A = {};                   // OK - name is absent
      const c: A = { name: 'Alice' };    // OK
      ```
      
      ---
      
      ## Migration Strategy
      
      ### Adopt Strict Mode Incrementally
      
      ```json
      // Phase 1: Start here - catches the worst issues
      {
        "compilerOptions": {
          "noImplicitAny": true,
          "strictNullChecks": true
        }
      }
      
      // Phase 2: Add remaining strict flags
      {
        "compilerOptions": {
          "strict": true
        }
      }
      
      // Phase 3: Tighten further
      {
        "compilerOptions": {
          "strict": true,
          "noUncheckedIndexedAccess": true,
          "exactOptionalPropertyTypes": true,
          "noImplicitReturns": true
        }
      }
      ```
      
      ### Use @ts-expect-error for Tracked Suppressions
      
      Prefer `@ts-expect-error` over `@ts-ignore`. The former causes a type error if the suppressed line no longer has an error, making it self-cleaning.
      
      ```typescript
      // @ts-ignore - silently does nothing if the error is later fixed (dead suppression)
      const x: string = 42;
      
      // @ts-expect-error - causes a type error when the suppressed error is fixed
      // @ts-expect-error: temporary until API is updated
      const y: string = legacyApi.getValue();
      ```
      
      ### Migration Checklist
      
      ```
      [ ] Enable noImplicitAny first - forces all untyped code to be explicit
      [ ] Add @ts-expect-error to suppress errors in files not yet migrated
      [ ] Enable strictNullChecks - fix null/undefined handling
      [ ] Enable strict: true - address remaining flags
      [ ] Track suppressions with: grep -r "@ts-expect-error" . --include="*.ts"
      [ ] Eliminate suppressions file by file
      [ ] Enable noUncheckedIndexedAccess as final step (highest refactor cost)
      ```
      
      ---
      
      ## Module Configuration
      
      ### Choose the Right module and moduleResolution
      
      ```json
      // For Node.js with CommonJS
      {
        "compilerOptions": {
          "module": "CommonJS",
          "moduleResolution": "Node"
        }
      }
      
      // For Node.js with ESM (Node 18+) - recommended for new Node projects
      {
        "compilerOptions": {
          "module": "Node16",       // or "NodeNext"
          "moduleResolution": "Node16"
        }
      }
      
      // For bundlers (Vite, webpack, esbuild, Rollup)
      {
        "compilerOptions": {
          "module": "ESNext",
          "moduleResolution": "Bundler"
        }
      }
      ```
      
      **TypeScript 6.0 removals**: `moduleResolution: "Classic"` and `module: "amd" / "umd" /
      "system" / "none"` were removed outright, and `esModuleInterop` /
      `allowSyntheticDefaultImports` can no longer be set to `false` (the safe interop behaviour
      is always on). Defaults are now `module: "esnext"` and `target: "es2025"`; the minimum
      `target` is ES2015. If a legacy tsconfig names any removed option, migrate to `Bundler`
      or `Node16`/`NodeNext` before upgrading.
      
      ### Understand ESM vs CJS Interop Issues
      
      With `Node16`/`NodeNext`, you must use explicit `.js` extensions in relative imports (even for `.ts` files).
      
      ```typescript
      // tsconfig.json: "module": "Node16"
      
      // WRONG - no extension
      import { helper } from './helper';
      
      // CORRECT - use .js extension (TypeScript resolves it to .ts)
      import { helper } from './helper.js';
      ```
      
      Set `"type": "module"` in `package.json` to use ESM, or use `.mts`/`.cts` file extensions to override per-file.
      
      ```json
      // package.json
      {
        "type": "module"
      }
      ```
      
      ---
      
      ## Path Aliases
      
      ### Configure Paths in tsconfig.json
      
      Write `paths` entries tsconfig-relative (`./src/*`) and omit `baseUrl` entirely:
      it has been unnecessary for `paths` since TS 4.1, and TypeScript 7 hard-errors on
      it (TS5102 — see `ts7-native-compiler.md`).
      
      ```json
      {
        "compilerOptions": {
          "paths": {
            "@/*":         ["./src/*"],
            "@components/*": ["./src/components/*"],
            "@utils/*":    ["./src/utils/*"],
            "@types/*":    ["./src/types/*"]
          }
        }
      }
      ```
      
      ### Use Paths with Vite
      
      ```typescript
      // vite.config.ts
      import { defineConfig } from 'vite';
      import { resolve } from 'path';
      
      export default defineConfig({
        resolve: {
          alias: {
            '@': resolve(__dirname, './src'),
            '@components': resolve(__dirname, './src/components'),
            '@utils': resolve(__dirname, './src/utils'),
          },
        },
      });
      ```
      
      ### Use Paths with Node (tsx / tsconfig-paths)
      
      ```bash
      # Option 1: tsx (recommended for scripts/CLIs)
      npx tsx --tsconfig tsconfig.json src/index.ts
      
      # Option 2: tsconfig-paths with ts-node
      npx ts-node -r tsconfig-paths/register src/index.ts
      
      # Option 3: tsconfig-paths at runtime (after compilation)
      node -r tsconfig-paths/register dist/index.js
      ```
      
      ```typescript
      // tsconfig-paths at runtime setup
      // bootstrap.js
      const { register } = require('tsconfig-paths');
      const tsConfig = require('./tsconfig.json');
      register({
        // tsconfig has no baseUrl (TS 7 errors on it) — anchor at the config's own dir
        baseUrl: __dirname,
        paths: tsConfig.compilerOptions.paths,
      });
      require('./dist/index.js');
      ```
      
      ---
      
      ## Project References
      
      ### Set Up Composite Projects
      
      Project references allow incremental builds and better IDE performance in large repos.
      
      ```json
      // packages/shared/tsconfig.json
      {
        "compilerOptions": {
          "composite": true,    // required for project references
          "declaration": true,  // required for project references
          "declarationMap": true,
          "outDir": "./dist",
          "rootDir": "./src"
        }
      }
      
      // packages/app/tsconfig.json
      {
        "compilerOptions": {
          "outDir": "./dist",
          "rootDir": "./src"
        },
        "references": [
          { "path": "../shared" }
        ]
      }
      ```
      
      ### Build with --build Mode
      
      ```bash
      # Build all referenced projects in dependency order
      tsc --build
      
      # Build and watch
      tsc --build --watch
      
      # Clean built outputs
      tsc --build --clean
      
      # Force rebuild
      tsc --build --force
      ```
      
      ---
      
      ## Monorepo Setup
      
      ### Define a Root tsconfig for Shared Settings
      
      ```json
      // tsconfig.base.json (root)
      {
        "compilerOptions": {
          "strict": true,
          "noUncheckedIndexedAccess": true,
          "noImplicitReturns": true,
          "esModuleInterop": true,
          "skipLibCheck": true,
          "forceConsistentCasingInFileNames": true,
          "resolveJsonModule": true,
          "declaration": true,
          "declarationMap": true,
          "sourceMap": true
        }
      }
      
      // packages/server/tsconfig.json
      {
        "extends": "../../tsconfig.base.json",
        "compilerOptions": {
          "module": "CommonJS",
          "moduleResolution": "Node",
          "target": "ES2022",
          "outDir": "./dist",
          "rootDir": "./src"
        },
        "include": ["src/**/*"],
        "references": [{ "path": "../shared" }]
      }
      
      // packages/web/tsconfig.json
      {
        "extends": "../../tsconfig.base.json",
        "compilerOptions": {
          "module": "ESNext",
          "moduleResolution": "Bundler",
          "target": "ES2022",
          "jsx": "react-jsx",
          "outDir": "./dist",
          "rootDir": "./src"
        },
        "include": ["src/**/*"],
        "references": [{ "path": "../shared" }]
      }
      ```
      
      ### Use a Root tsconfig for IDE Support
      
      ```json
      // tsconfig.json (root - IDE only, not for building)
      {
        "files": [],
        "references": [
          { "path": "./packages/shared" },
          { "path": "./packages/server" },
          { "path": "./packages/web" }
        ]
      }
      ```
      
      ---
      
      ## Declaration Files
      
      ### Write Ambient Declarations for Untyped Modules
      
      ```typescript
      // types/untyped-module.d.ts
      declare module 'some-legacy-package' {
        export interface Options {
          timeout?: number;
          retries?: number;
        }
      
        export function connect(url: string, options?: Options): Promise<void>;
        export function disconnect(): void;
      
        export default {
          connect,
          disconnect,
        };
      }
      
      // Wildcard module for assets (e.g., CSS, SVG)
      declare module '*.svg' {
        const content: string;
        export default content;
      }
      
      declare module '*.png' {
        const content: string;
        export default content;
      }
      
      declare module '*.css' {
        const styles: Record<string, string>;
        export default styles;
      }
      ```
      
      ### Augment Global Scope
      
      ```typescript
      // global.d.ts
      declare global {
        // Extend the Window interface
        interface Window {
          __APP_VERSION__: string;
          analytics: {
            track(event: string, props?: Record<string, unknown>): void;
          };
        }
      
        // Extend ProcessEnv for typed environment variables
        namespace NodeJS {
          interface ProcessEnv {
            NODE_ENV: 'development' | 'production' | 'test';
            DATABASE_URL: string;
            API_KEY: string;
            PORT?: string;
          }
        }
      }
      
      export {}; // This export makes the file a module, enabling declare global
      ```
      
      ### Use Triple-Slash Directives
      
      ```typescript
      // Reference a type definition file
      /// <reference types="node" />
      /// <reference types="jest" />
      
      // Reference a specific .d.ts file
      /// <reference path="../types/custom.d.ts" />
      
      // Reference a lib
      /// <reference lib="dom" />
      /// <reference lib="es2022" />
      ```
      
      ### Write a .d.ts for a Hand-Authored JavaScript Library
      
      ```typescript
      // src/math-helpers.js (source)
      function add(a, b) { return a + b; }
      function multiply(a, b) { return a * b; }
      module.exports = { add, multiply };
      
      // src/math-helpers.d.ts (declaration)
      export declare function add(a: number, b: number): number;
      export declare function multiply(a: number, b: number): number;
      ```
      
      ### Configure Declaration Output
      
      ```json
      {
        "compilerOptions": {
          "declaration": true,         // emit .d.ts files
          "declarationDir": "./types", // output directory for .d.ts (optional)
          "declarationMap": true,      // emit .d.ts.map for source navigation
          "emitDeclarationOnly": true  // only emit .d.ts, no JS (when bundler handles JS)
        }
      }
      ```
      
    • ecosystem.md 17.1 KB
      # TypeScript Ecosystem Reference
      
      ## Table of Contents
      
      1. [Runtime Validation](#runtime-validation)
      2. [Type-Safe API Clients](#type-safe-api-clients)
      3. [ORM Types](#orm-types)
      4. [Testing with Types](#testing-with-types)
      5. [Type-Safe Routing](#type-safe-routing)
      6. [Effect](#effect)
      7. [ts-pattern](#ts-pattern)
      8. [Type Challenges](#type-challenges)
      
      ---
      
      ## Runtime Validation
      
      ### Use Zod for Schema Validation with Type Inference
      
      Zod is the most widely adopted runtime validation library. Define a schema once; infer the TypeScript type from it. As of Zod 4, string formats are top-level functions (`z.email()`, `z.uuid()`, `z.url()`) — the Zod 3 method forms (`z.string().email()`) are deprecated.
      
      ```typescript
      import { z } from 'zod';
      
      // Define schema (Zod 4 syntax)
      const UserSchema = z.object({
        id: z.uuid(),
        name: z.string().min(1).max(100),
        email: z.email(),
        age: z.number().int().min(0).max(150).optional(),
        role: z.enum(['admin', 'user', 'moderator']),
        createdAt: z.coerce.date(),
      });
      
      // Infer TypeScript type from schema - single source of truth
      type User = z.infer<typeof UserSchema>;
      // { id: string; name: string; email: string; age?: number; role: 'admin' | 'user' | 'moderator'; createdAt: Date }
      
      // Parse and validate (throws ZodError on failure)
      const user = UserSchema.parse(rawData);
      
      // Safe parse (returns success/failure object, never throws)
      const result = UserSchema.safeParse(rawData);
      if (result.success) {
        console.log(result.data); // typed as User
      } else {
        console.error(result.error.flatten()); // ZodError with friendly message structure
      }
      ```
      
      ### Apply Zod Transforms and Refinements
      
      ```typescript
      const PasswordSchema = z
        .string()
        .min(8, 'Password must be at least 8 characters')
        .regex(/[A-Z]/, 'Password must contain an uppercase letter')
        .regex(/[0-9]/, 'Password must contain a number');
      
      // Transform: parse then convert
      const DateStringSchema = z.string().transform((s) => new Date(s));
      type DateValue = z.infer<typeof DateStringSchema>; // Date (output type after transform)
      // Input type is string; output type is Date
      
      // Refine: validate with custom logic
      const EvenNumberSchema = z.number().refine(
        (n) => n % 2 === 0,
        { message: 'Number must be even' }
      );
      
      // Custom error messages (Zod 4: one unified `error` param replaces
      // invalid_type_error / required_error / errorMap)
      const AgeSchema = z.number({ error: 'Age must be a number' }).int().min(0);
      
      // Discriminated union (Zod version)
      const ApiResponseSchema = z.discriminatedUnion('status', [
        z.object({ status: z.literal('success'), data: z.unknown() }),
        z.object({ status: z.literal('error'), message: z.string() }),
      ]);
      type ApiResponse = z.infer<typeof ApiResponseSchema>;
      ```
      
      ### Use Valibot as a Tree-Shakeable Alternative
      
      Valibot (stable since 1.0) covers the same ground as Zod but is tree-shakeable by design, resulting in much smaller bundles for edge/browser deployments. Zod 4's `zod/mini` entry point narrows the gap, but valibot's pipe composition remains the smallest-bundle option.
      
      ```typescript
      import * as v from 'valibot';
      
      const UserSchema = v.object({
        id: v.pipe(v.string(), v.uuid()),
        name: v.pipe(v.string(), v.minLength(1), v.maxLength(100)),
        email: v.pipe(v.string(), v.email()),
        role: v.picklist(['admin', 'user', 'moderator']),
      });
      
      type User = v.InferOutput<typeof UserSchema>;
      
      const result = v.safeParse(UserSchema, rawData);
      if (result.success) {
        console.log(result.output); // typed as User
      }
      ```
      
      ### Compare Zod vs Valibot
      
      | Concern | Zod | Valibot |
      |---------|-----|---------|
      | Bundle size | ~13 kB min+gz | ~0.5-2 kB (tree-shaken) |
      | API style | Method chaining | Pipe/function composition |
      | Ecosystem | Larger (more integrations) | Smaller but growing |
      | Best for | Node.js / full-stack | Edge / browser |
      | Async validation | `z.refine(async ...)` | `v.pipeAsync(...)` |
      
      ---
      
      ## Type-Safe API Clients
      
      ### Build a Type-Safe Fetch Wrapper
      
      ```typescript
      type ApiResponse<T> =
        | { ok: true; data: T; status: number }
        | { ok: false; error: string; status: number };
      
      async function apiFetch<T>(
        url: string,
        schema: { parse: (data: unknown) => T },
        init?: RequestInit
      ): Promise<ApiResponse<T>> {
        try {
          const response = await fetch(url, init);
          const json: unknown = await response.json();
      
          if (!response.ok) {
            return { ok: false, error: String(json), status: response.status };
          }
      
          const data = schema.parse(json);
          return { ok: true, data, status: response.status };
        } catch (err) {
          return { ok: false, error: err instanceof Error ? err.message : 'Unknown error', status: 0 };
        }
      }
      
      // Usage with Zod schema
      const UsersSchema = z.array(UserSchema);
      const result = await apiFetch('/api/users', UsersSchema);
      if (result.ok) {
        result.data; // User[]
      }
      ```
      
      ### Use openapi-typescript for Contract-First APIs
      
      ```bash
      # Generate TypeScript types from an OpenAPI spec
      npx openapi-typescript ./openapi.yaml -o ./src/types/api.d.ts
      # or from a URL
      npx openapi-typescript https://api.example.com/openapi.json -o ./src/types/api.d.ts
      ```
      
      ```typescript
      import type { paths, components } from './types/api.d.ts';
      
      // Use generated types in a typed client
      type GetUserParams = paths['/users/{id}']['get']['parameters'];
      type GetUserResponse = paths['/users/{id}']['get']['responses']['200']['content']['application/json'];
      type User = components['schemas']['User'];
      ```
      
      ### Add tRPC for End-to-End Type Safety
      
      ```typescript
      // server/router.ts
      import { initTRPC } from '@trpc/server';
      import { z } from 'zod';
      
      const t = initTRPC.create();
      
      export const appRouter = t.router({
        user: t.router({
          getById: t.procedure
            .input(z.object({ id: z.string() }))
            .query(async ({ input }) => {
              return await db.user.findUnique({ where: { id: input.id } });
            }),
      
          create: t.procedure
            .input(z.object({ name: z.string(), email: z.email() }))
            .mutation(async ({ input }) => {
              return await db.user.create({ data: input });
            }),
        }),
      });
      
      export type AppRouter = typeof appRouter;
      
      // client/trpc.ts
      import { createTRPCProxyClient, httpBatchLink } from '@trpc/client';
      import type { AppRouter } from '../server/router';
      
      const trpc = createTRPCProxyClient<AppRouter>({
        links: [httpBatchLink({ url: 'http://localhost:3000/trpc' })],
      });
      
      // Fully type-safe - input and output types inferred from router
      const user = await trpc.user.getById.query({ id: '123' });
      // user is typed as the return type of the resolver - no manual typing needed
      ```
      
      ---
      
      ## ORM Types
      
      ### Generate Types with Prisma
      
      Prisma generates complete TypeScript types from the schema file.
      
      ```prisma
      // prisma/schema.prisma
      model User {
        id        String   @id @default(cuid())
        email     String   @unique
        name      String?
        posts     Post[]
        createdAt DateTime @default(now())
      }
      ```
      
      ```typescript
      import { PrismaClient } from '@prisma/client';
      // Prisma generates:
      // - PrismaClient with typed query methods
      // - User, Post, etc. model types
      // - UserCreateInput, UserUpdateInput, UserWhereInput, etc.
      
      const db = new PrismaClient();
      
      // Fully typed queries
      const user = await db.user.findUniqueOrThrow({
        where: { email: 'alice@example.com' },
        include: { posts: true },
      });
      // user: User & { posts: Post[] }
      
      // Use generated input types
      import type { Prisma } from '@prisma/client';
      
      async function createUser(data: Prisma.UserCreateInput) {
        return db.user.create({ data });
      }
      
      // Select subsets for performance
      type UserPreview = Prisma.UserGetPayload<{
        select: { id: true; name: true; email: true };
      }>;
      ```
      
      ### Write Type-Safe Queries with Drizzle ORM
      
      Drizzle is a SQL-first ORM where types flow from the schema definition.
      
      ```typescript
      import { pgTable, text, integer, timestamp } from 'drizzle-orm/pg-core';
      import { drizzle } from 'drizzle-orm/node-postgres';
      import { eq } from 'drizzle-orm';
      
      const users = pgTable('users', {
        id: text('id').primaryKey(),
        name: text('name').notNull(),
        email: text('email').notNull().unique(),
        age: integer('age'),
        createdAt: timestamp('created_at').defaultNow(),
      });
      
      // Infer types directly from table definition
      type User = typeof users.$inferSelect;    // for SELECT results
      type NewUser = typeof users.$inferInsert; // for INSERT data
      
      const db = drizzle(pool);
      
      // Type-safe queries - IDE autocomplete on column names
      const allUsers = await db.select().from(users);
      // allUsers: User[]
      
      const alice = await db.select().from(users).where(eq(users.email, 'alice@example.com'));
      // alice: User[]
      
      await db.insert(users).values({ id: '1', name: 'Alice', email: 'alice@example.com' });
      // Type error if required fields are missing
      ```
      
      ### Query with Kysely for SQL-First Type Safety
      
      Kysely provides type-safe query building without code generation.
      
      ```typescript
      import { Kysely, PostgresDialect } from 'kysely';
      
      interface Database {
        users: { id: string; name: string; email: string; age: number | null };
        posts: { id: string; userId: string; title: string; content: string };
      }
      
      const db = new Kysely<Database>({ dialect: new PostgresDialect({ pool }) });
      
      const users = await db
        .selectFrom('users')
        .select(['id', 'name', 'email'])
        .where('age', '>', 18)
        .execute();
      // users: Array<{ id: string; name: string; email: string }>
      ```
      
      ---
      
      ## Testing with Types
      
      ### Use expectTypeOf in Vitest
      
      ```typescript
      import { expectTypeOf, test } from 'vitest';
      
      test('identity function preserves type', () => {
        function identity<T>(value: T): T { return value; }
      
        expectTypeOf(identity('hello')).toEqualTypeOf<string>();
        expectTypeOf(identity(42)).toEqualTypeOf<number>();
        expectTypeOf(identity).toBeFunction();
        expectTypeOf(identity).parameter(0).toBeString();
      });
      
      test('Result type narrows correctly', () => {
        type Result<T> = { ok: true; value: T } | { ok: false; error: string };
      
        function ok<T>(value: T): Result<T> { return { ok: true, value }; }
        function err<T>(error: string): Result<T> { return { ok: false, error }; }
      
        expectTypeOf(ok('data')).toEqualTypeOf<Result<string>>();
        expectTypeOf(err<number>('oops')).toEqualTypeOf<Result<number>>();
      });
      ```
      
      ### Use assertType for Compile-Time Checks
      
      ```typescript
      import { assertType, test } from 'vitest';
      
      test('types are correct', () => {
        // assertType<T>(value) asserts value matches type T at compile time
        // (no runtime effect - type-only check)
        assertType<string>('hello');
        assertType<number>(42);
      
        // @ts-expect-error assertions that should fail
        // @ts-expect-error
        assertType<string>(42); // fails: 42 is not string
      });
      ```
      
      ### Use tsd for Testing Declaration Files
      
      `tsd` is dedicated to testing `.d.ts` files. It checks that type definitions behave correctly.
      
      ```typescript
      // index.test-d.ts
      import { expectType, expectError, expectAssignable } from 'tsd';
      import { getUser, createUser } from './index.js';
      
      // Check return types
      expectType<Promise<User>>(getUser('123'));
      
      // Check that invalid calls produce errors
      expectError(getUser(123)); // Error: number not assignable to string
      
      // Check assignability (less strict than equality)
      expectAssignable<{ id: string }>(await getUser('1'));
      ```
      
      ```json
      // package.json
      {
        "scripts": {
          "test:types": "tsd"
        },
        "tsd": {
          "directory": "test"
        }
      }
      ```
      
      ---
      
      ## Type-Safe Routing
      
      ### Use Next.js Typed Routes
      
      Next.js 13+ supports experimental typed routes that validate `href` values.
      
      ```json
      // next.config.js
      {
        "experimental": {
          "typedRoutes": true
        }
      }
      ```
      
      ```typescript
      import Link from 'next/link';
      
      // TypeScript validates the href against your actual routes
      <Link href="/about">About</Link>            // OK if /about exists
      <Link href="/users/[id]">User</Link>        // Error: must pass actual id
      <Link href={{ pathname: '/users/[id]', params: { id: '1' } }}>User</Link> // OK
      ```
      
      ### Build Type-Safe Path Parameters
      
      ```typescript
      // Generic route parameter extractor
      type ExtractParams<T extends string> =
        T extends `${string}:${infer Param}/${infer Rest}`
          ? { [K in Param]: string } & ExtractParams<Rest>
          : T extends `${string}:${infer Param}`
          ? { [K in Param]: string }
          : Record<string, never>;
      
      type Prettify<T> = { [K in keyof T]: T[K] } & {};
      
      function createRoute<T extends string>(
        template: T
      ): { path: T; build(params: Prettify<ExtractParams<T>>): string } {
        return {
          path: template,
          build(params) {
            return Object.entries(params).reduce(
              (path, [key, value]) => path.replace(`:${key}`, value as string),
              template
            );
          },
        };
      }
      
      const userRoute = createRoute('/users/:userId/posts/:postId');
      const url = userRoute.build({ userId: '1', postId: '42' }); // '/users/1/posts/42'
      // TypeScript error if userId or postId is missing
      ```
      
      ---
      
      ## Effect
      
      ### Use Effect-TS for Typed Functional Error Handling
      
      Effect models computations as `Effect<Value, Error, Requirements>`. Errors are part of the type, not thrown.
      
      ```typescript
      import { Effect, pipe } from 'effect';
      
      // Define typed errors
      class UserNotFoundError {
        readonly _tag = 'UserNotFoundError';
        constructor(readonly id: string) {}
      }
      
      class DatabaseError {
        readonly _tag = 'DatabaseError';
        constructor(readonly message: string) {}
      }
      
      // Effect<User, UserNotFoundError | DatabaseError, never>
      // Value: User, Error: UserNotFoundError | DatabaseError, Requirements: none
      const getUser = (id: string): Effect.Effect<User, UserNotFoundError | DatabaseError> =>
        Effect.tryPromise({
          try: () => db.user.findUniqueOrThrow({ where: { id } }),
          catch: (e) =>
            e instanceof Error && e.message.includes('No User found')
              ? new UserNotFoundError(id)
              : new DatabaseError(String(e)),
        });
      
      // Compose effects with pipe
      const program = pipe(
        getUser('123'),
        Effect.map((user) => user.name),
        Effect.catchTag('UserNotFoundError', (e) =>
          Effect.succeed(`User ${e.id} not found`)
        ),
        // DatabaseError is still in the error channel - must be handled or propagated
      );
      
      // Run the effect
      const result = await Effect.runPromise(program);
      ```
      
      ---
      
      ## ts-pattern
      
      ### Match Exhaustively with ts-pattern
      
      `ts-pattern` provides pattern matching with full TypeScript type narrowing.
      
      ```typescript
      import { match, P } from 'ts-pattern';
      
      type ApiState =
        | { status: 'idle' }
        | { status: 'loading' }
        | { status: 'success'; data: User[] }
        | { status: 'error'; error: Error };
      
      function render(state: ApiState): string {
        return match(state)
          .with({ status: 'idle' },    () => 'Ready')
          .with({ status: 'loading' }, () => 'Loading...')
          .with({ status: 'success', data: P.select() }, (data) =>
            `Loaded ${data.length} users`
          )
          .with({ status: 'error', error: P.select() }, (error) =>
            `Error: ${error.message}`
          )
          .exhaustive(); // Compile error if a variant is unhandled
      }
      
      // Pattern guards
      const result = match(value)
        .with(P.number.gt(100), (n) => `Big: ${n}`)
        .with(P.number.lt(0),   (n) => `Negative: ${n}`)
        .with(P.number,          (n) => `Normal: ${n}`)
        .with(P.string,          (s) => `String: ${s}`)
        .otherwise(() => 'Unknown');
      
      // Nested matching
      const message = match(response)
        .with({ type: 'error', code: P.union(401, 403) }, () => 'Unauthorized')
        .with({ type: 'error', code: 404 },               () => 'Not Found')
        .with({ type: 'error' },                           () => 'Server Error')
        .with({ type: 'success' },                         () => 'OK')
        .exhaustive();
      ```
      
      ---
      
      ## Type Challenges
      
      ### Practice Advanced Types Effectively
      
      The `type-challenges` repository (github.com/type-challenges/type-challenges) provides 200+ graded exercises.
      
      ```typescript
      // Example: Implement Readonly<T> from scratch
      type MyReadonly<T> = {
        readonly [K in keyof T]: T[K];
      };
      
      // Example: Implement Pick<T, K>
      type MyPick<T, K extends keyof T> = {
        [P in K]: T[P];
      };
      
      // Example: Implement Exclude<T, U>
      type MyExclude<T, U> = T extends U ? never : T;
      
      // Example: Implement ReturnType<T>
      type MyReturnType<T> = T extends (...args: unknown[]) => infer R ? R : never;
      
      // Example: Deep Readonly
      type DeepReadonly<T> = keyof T extends never
        ? T
        : { readonly [K in keyof T]: DeepReadonly<T[K]> };
      ```
      
      ### Use the TypeScript Playground
      
      The TypeScript Playground (typescriptlang.org/play) supports:
      - Sharing type puzzles via URL
      - Viewing emitted JavaScript
      - Checking against multiple TS versions
      - Running code in browser
      
      ### Recommended Learning Resources
      
      | Resource | Focus |
      |----------|-------|
      | `type-challenges` on GitHub | Exercises from easy to extreme |
      | Matt Pocock's Total TypeScript | Tutorials and workshops |
      | TypeScript Deep Dive (basarat) | Comprehensive free book |
      | Official TS Handbook | Language reference |
      | tsdocs.dev | Browse type definitions for any npm package |
      | typescript-eslint.io | Type-aware lint rules |
      
      ### Set Up a Type Testing Playground Locally
      
      ```bash
      mkdir ts-playground && cd ts-playground
      npm init -y
      npm install -D typescript tsx @types/node
      
      cat > tsconfig.json << 'EOF'
      {
        "compilerOptions": {
          "strict": true,
          "noUncheckedIndexedAccess": true,
          "exactOptionalPropertyTypes": true,
          "target": "ES2022",
          "module": "ESNext",
          "moduleResolution": "Bundler"
        }
      }
      EOF
      
      # Write type experiments
      cat > playground.ts << 'EOF'
      type Test = /* your type here */;
      type Expect<T extends true> = T;
      type Equal<A, B> = A extends B ? B extends A ? true : false : false;
      
      type Case1 = Expect<Equal<Test, ExpectedType>>;
      EOF
      
      npx tsx playground.ts
      ```
      
    • generics-patterns.md 14.6 KB
      # TypeScript Generics Patterns Reference
      
      ## Table of Contents
      
      1. [Generic Functions](#generic-functions)
      2. [Generic Classes](#generic-classes)
      3. [Generic Interfaces](#generic-interfaces)
      4. [Conditional Types](#conditional-types)
      5. [Mapped Types](#mapped-types)
      6. [Template Literal Types in Generics](#template-literal-types-in-generics)
      7. [Variadic Tuple Types](#variadic-tuple-types)
      8. [Higher-Kinded Types](#higher-kinded-types)
      9. [Builder Pattern](#builder-pattern)
      10. [Common Generic Patterns](#common-generic-patterns)
      
      ---
      
      ## Generic Functions
      
      ### Infer Type Parameters From Arguments
      
      TypeScript infers type parameters from call-site arguments. Prefer inference over explicit type args.
      
      ```typescript
      // Inferred: T = string from the argument
      function identity<T>(value: T): T {
        return value;
      }
      const s = identity('hello'); // T inferred as string
      
      // Constraint: T must have a length property
      function longest<T extends { length: number }>(a: T, b: T): T {
        return a.length >= b.length ? a : b;
      }
      longest('alice', 'bob');         // OK - strings have length
      longest([1, 2, 3], [1, 2]);     // OK - arrays have length
      longest({ length: 5 }, { length: 3 }); // OK
      
      // Multiple type parameters with relationship constraint
      function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
        return obj[key];
      }
      
      const user = { id: 1, name: 'Alice', active: true };
      getProperty(user, 'name');   // string
      getProperty(user, 'active'); // boolean
      getProperty(user, 'foo');    // Error: 'foo' is not a key of typeof user
      ```
      
      ### Use Default Type Parameters
      
      ```typescript
      // Default type parameter when not specified
      function createArray<T = string>(length: number, fill: T): T[] {
        return Array(length).fill(fill);
      }
      
      const strings = createArray(3, 'x');   // string[] - T inferred
      const numbers = createArray(3, 0);     // number[] - T inferred
      const explicit = createArray<boolean>(3, true); // boolean[]
      
      // Useful in generic components/hooks
      interface PaginatedResponse<T = unknown> {
        data: T[];
        total: number;
        page: number;
      }
      ```
      
      ---
      
      ## Generic Classes
      
      ### Parameterize Class Behavior
      
      ```typescript
      class Stack<T> {
        private items: T[] = [];
      
        push(item: T): void {
          this.items.push(item);
        }
      
        pop(): T | undefined {
          return this.items.pop();
        }
      
        peek(): T | undefined {
          return this.items[this.items.length - 1];
        }
      
        get size(): number {
          return this.items.length;
        }
      }
      
      const numStack = new Stack<number>();
      numStack.push(1);
      numStack.push(2);
      const top = numStack.pop(); // number | undefined
      ```
      
      ### Recognize the Static Members Limitation
      
      Static members cannot reference a class's type parameters. The type parameter belongs to an instance.
      
      ```typescript
      class Container<T> {
        value: T;  // OK - instance member
      
        constructor(value: T) {
          this.value = value;
        }
      
        // static defaultValue: T; // Error: static members can't reference type parameters
      
        // Workaround: use a separate factory type or factory method
        static create<U>(value: U): Container<U> {
          return new Container(value);
        }
      }
      ```
      
      ---
      
      ## Generic Interfaces
      
      ### Implement the Repository Pattern
      
      ```typescript
      interface Repository<T, ID = string> {
        findById(id: ID): Promise<T | null>;
        findAll(filter?: Partial<T>): Promise<T[]>;
        save(entity: T): Promise<T>;
        delete(id: ID): Promise<void>;
      }
      
      interface User {
        id: string;
        name: string;
        email: string;
      }
      
      class UserRepository implements Repository<User, string> {
        async findById(id: string): Promise<User | null> { /* ... */ return null; }
        async findAll(filter?: Partial<User>): Promise<User[]> { /* ... */ return []; }
        async save(entity: User): Promise<User> { /* ... */ return entity; }
        async delete(id: string): Promise<void> { /* ... */ }
      }
      ```
      
      ### Implement the Factory Pattern
      
      ```typescript
      interface Factory<T, TArgs extends unknown[] = []> {
        create(...args: TArgs): T;
      }
      
      class ConnectionFactory implements Factory<Connection, [string, number]> {
        create(host: string, port: number): Connection {
          return new Connection(host, port);
        }
      }
      ```
      
      ---
      
      ## Conditional Types
      
      ### Distribute Over Union Types
      
      Conditional types distribute over naked type parameters in unions.
      
      ```typescript
      // Distributes: IsString<string | number> = IsString<string> | IsString<number>
      type IsString<T> = T extends string ? true : false;
      type Test = IsString<string | number>; // true | false = boolean
      
      // To prevent distribution, wrap in a tuple
      type IsStringExact<T> = [T] extends [string] ? true : false;
      type Test2 = IsStringExact<string | number>; // false
      ```
      
      ### Use infer to Extract Types
      
      ```typescript
      // Extract the element type from an array
      type UnpackArray<T> = T extends (infer U)[] ? U : T;
      type Item = UnpackArray<string[]>; // string
      type Same = UnpackArray<number>;   // number
      
      // Extract return type (equivalent to built-in ReturnType)
      type MyReturnType<T> = T extends (...args: unknown[]) => infer R ? R : never;
      
      // Extract the resolved value of a Promise
      type Awaited<T> = T extends Promise<infer U> ? Awaited<U> : T;
      type Value = Awaited<Promise<Promise<string>>>; // string
      
      // Extract first parameter type
      type FirstParam<T> = T extends (first: infer F, ...rest: unknown[]) => unknown ? F : never;
      type F = FirstParam<(a: string, b: number) => void>; // string
      
      // Extract constructor instance type
      type InstanceOf<T> = T extends new (...args: unknown[]) => infer I ? I : never;
      ```
      
      ### Nest Conditional Types for Complex Logic
      
      ```typescript
      type TypeName<T> =
        T extends string  ? 'string'  :
        T extends number  ? 'number'  :
        T extends boolean ? 'boolean' :
        T extends null    ? 'null'    :
        T extends undefined ? 'undefined' :
        T extends Function ? 'function' :
        'object';
      
      type A = TypeName<string>;   // 'string'
      type B = TypeName<() => void>; // 'function'
      type C = TypeName<{ a: 1 }>;  // 'object'
      
      // Filter a union: keep only string keys from a type
      type StringKeys<T> = {
        [K in keyof T]: T[K] extends string ? K : never;
      }[keyof T];
      
      interface Mixed { id: string; count: number; name: string; active: boolean; }
      type OnlyStringFields = StringKeys<Mixed>; // 'id' | 'name'
      ```
      
      ---
      
      ## Mapped Types
      
      ### Remap Keys with the as Clause
      
      ```typescript
      // Prefix all keys
      type Prefixed<T, P extends string> = {
        [K in keyof T as `${P}${Capitalize<string & K>}`]: T[K];
      };
      
      interface User { id: string; name: string; }
      type PrefixedUser = Prefixed<User, 'user'>; // { userId: string; userName: string }
      
      // Filter keys by value type
      type PickByValue<T, V> = {
        [K in keyof T as T[K] extends V ? K : never]: T[K];
      };
      
      interface Config { debug: boolean; port: number; host: string; verbose: boolean; }
      type BooleanConfig = PickByValue<Config, boolean>; // { debug: boolean; verbose: boolean }
      ```
      
      ### Apply Modifiers with + and -
      
      ```typescript
      // Add readonly and optional
      type Immutable<T> = {
        +readonly [K in keyof T]+?: T[K];
      };
      
      // Remove readonly and optional (make mutable and required)
      type Mutable<T> = {
        -readonly [K in keyof T]-?: T[K];
      };
      
      interface Optional {
        readonly id?: string;
        readonly name?: string;
      }
      
      type Concrete = Mutable<Optional>; // { id: string; name: string }
      ```
      
      ### Combine Mapped and Conditional Types
      
      ```typescript
      // Make only specific keys optional
      type MakeOptional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
      
      interface Post {
        id: string;
        title: string;
        content: string;
        publishedAt: Date;
      }
      
      type DraftPost = MakeOptional<Post, 'id' | 'publishedAt'>;
      // { title: string; content: string; id?: string; publishedAt?: Date }
      ```
      
      ---
      
      ## Template Literal Types in Generics
      
      ### Build Type-Safe Event Emitters
      
      ```typescript
      type EventMap = {
        userCreated: { userId: string };
        orderPlaced: { orderId: string; total: number };
        sessionExpired: { sessionId: string };
      };
      
      type EventListener<TMap, TEvent extends keyof TMap> =
        (event: TMap[TEvent]) => void;
      
      type Emitter<TMap> = {
        on<TEvent extends keyof TMap>(
          event: TEvent,
          listener: EventListener<TMap, TEvent>
        ): void;
        emit<TEvent extends keyof TMap>(event: TEvent, data: TMap[TEvent]): void;
      };
      
      declare const emitter: Emitter<EventMap>;
      
      emitter.on('userCreated', (e) => console.log(e.userId));   // OK
      emitter.on('orderPlaced', (e) => console.log(e.total));    // OK
      emitter.emit('userCreated', { userId: '123' });             // OK
      emitter.emit('userCreated', { orderId: '123' });            // Error: wrong shape
      ```
      
      ### Extract Route Parameters
      
      ```typescript
      type RouteParams<T extends string> =
        T extends `${string}:${infer Param}/${infer Rest}`
          ? { [K in Param | keyof RouteParams<Rest>]: string }
          : T extends `${string}:${infer Param}`
          ? { [K in Param]: string }
          : Record<string, never>;
      
      function buildRoute<T extends string>(
        template: T,
        params: RouteParams<T>
      ): string {
        return Object.entries(params).reduce(
          (path, [key, value]) => path.replace(`:${key}`, value as string),
          template
        );
      }
      
      const url = buildRoute('/users/:userId/posts/:postId', {
        userId: '1',
        postId: '42',
      }); // '/users/1/posts/42'
      ```
      
      ---
      
      ## Variadic Tuple Types
      
      ### Spread Tuples for Function Composition
      
      ```typescript
      // Concatenate two tuple types
      type Concat<T extends unknown[], U extends unknown[]> = [...T, ...U];
      type T1 = Concat<[1, 2], [3, 4]>; // [1, 2, 3, 4]
      
      // Strongly typed pipe/compose
      type Pipe<T extends ((...args: unknown[]) => unknown)[]> =
        T extends [infer First, ...infer Rest]
          ? First extends (...args: infer A) => infer R
            ? Rest extends []
              ? (...args: A) => R
              : Pipe<[(...args: A) => R, ...Extract<Rest, ((...args: unknown[]) => unknown)[]>]>
            : never
          : never;
      
      // Prepend and append to tuples
      type Prepend<T, Tuple extends unknown[]> = [T, ...Tuple];
      type Append<Tuple extends unknown[], T>  = [...Tuple, T];
      
      type WithFirst = Prepend<string, [number, boolean]>; // [string, number, boolean]
      type WithLast  = Append<[string, number], boolean>;   // [string, number, boolean]
      ```
      
      ### Build Type-Safe curry
      
      ```typescript
      type Head<T extends unknown[]> = T extends [infer H, ...unknown[]] ? H : never;
      type Tail<T extends unknown[]> = T extends [unknown, ...infer R] ? R : never;
      
      type Curry<TArgs extends unknown[], TReturn> =
        TArgs extends []
          ? TReturn
          : (arg: Head<TArgs>) => Curry<Tail<TArgs>, TReturn>;
      
      declare function curry<TArgs extends unknown[], TReturn>(
        fn: (...args: TArgs) => TReturn
      ): Curry<TArgs, TReturn>;
      
      const add = curry((a: number, b: number, c: number) => a + b + c);
      const add5 = add(5);          // Curry<[number, number], number>
      const add5and3 = add5(3);     // Curry<[number], number>
      const result = add5and3(2);   // number = 10
      ```
      
      ---
      
      ## Higher-Kinded Types
      
      ### Emulate HKT with Interface Lookup
      
      TypeScript doesn't natively support higher-kinded types, but they can be emulated with a registry pattern.
      
      ```typescript
      // Define a type-level registry for type constructors
      interface HKTRegistry {
        // Registered types go here via module augmentation
      }
      
      type HKT = keyof HKTRegistry;
      type Apply<F extends HKT, A> = HKTRegistry[F] extends { type: unknown }
        ? (HKTRegistry[F] & { arg: A })['type']
        : never;
      
      // Register Array as a type constructor
      declare module './hkt' {
        interface HKTRegistry {
          Array: { type: Array<this['arg']> };
        }
      }
      
      // Functor interface using HKT
      interface Functor<F extends HKT> {
        map<A, B>(fa: Apply<F, A>, f: (a: A) => B): Apply<F, B>;
      }
      ```
      
      ---
      
      ## Builder Pattern
      
      ### Track Builder State in the Type System
      
      ```typescript
      type BuilderState = {
        hasName: boolean;
        hasAge: boolean;
      };
      
      type Builder<State extends BuilderState, T = {}> = {
        setName(name: string): Builder<State & { hasName: true }, T & { name: string }>;
        setAge(age: number): Builder<State & { hasAge: true }, T & { age: number }>;
      } & (State['hasName'] extends true
        ? State['hasAge'] extends true
          ? { build(): T }
          : {}
        : {});
      
      declare function createBuilder(): Builder<{ hasName: false; hasAge: false }>;
      
      const builder = createBuilder();
      const user = builder.setName('Alice').setAge(30).build();
      // user: { name: string } & { age: number }
      
      // Compile errors:
      // builder.build() - Error: build() not available until required fields set
      // builder.setName('Alice').build() - Error: age not set
      ```
      
      ### Use Fluent Interface with Immutable Type Accumulation
      
      ```typescript
      class QueryBuilder<T extends Record<string, unknown> = Record<string, never>> {
        private conditions: string[] = [];
        private selectedFields: string[] = [];
      
        select<K extends string>(field: K): QueryBuilder<T & Record<K, unknown>> {
          this.selectedFields.push(field);
          return this as unknown as QueryBuilder<T & Record<K, unknown>>;
        }
      
        where(condition: string): this {
          this.conditions.push(condition);
          return this;
        }
      
        build(): { fields: string[]; conditions: string[] } {
          return { fields: this.selectedFields, conditions: this.conditions };
        }
      }
      
      const query = new QueryBuilder()
        .select('id')
        .select('name')
        .where('active = true')
        .build();
      ```
      
      ---
      
      ## Common Generic Patterns
      
      ### MaybePromise
      
      ```typescript
      type MaybePromise<T> = T | Promise<T>;
      
      async function normalize<T>(value: MaybePromise<T>): Promise<T> {
        return await value;
      }
      ```
      
      ### DeepPartial
      
      ```typescript
      type DeepPartial<T> = T extends (infer U)[]
        ? DeepPartial<U>[]
        : T extends object
        ? { [K in keyof T]?: DeepPartial<T[K]> }
        : T;
      ```
      
      ### PathOf and Get
      
      ```typescript
      // PathOf: all dot-notation paths into an object
      type PathOf<T> = T extends object
        ? { [K in keyof T]: K extends string
            ? T[K] extends object
              ? K | `${K}.${PathOf<T[K]>}`
              : K
            : never
          }[keyof T]
        : never;
      
      // Get: value at a path
      type Get<T, P extends string> =
        P extends `${infer K}.${infer Rest}`
          ? K extends keyof T ? Get<T[K], Rest> : never
          : P extends keyof T ? T[P] : never;
      ```
      
      ### Prettify (Flatten Intersection Types for Readability)
      
      ```typescript
      type Prettify<T> = { [K in keyof T]: T[K] } & {};
      
      type A = { id: string } & { name: string } & { age: number };
      type B = Prettify<A>; // { id: string; name: string; age: number }
      // B displays as a single object in IDE hover, much more readable
      ```
      
      ### RequireAtLeastOne
      
      ```typescript
      type RequireAtLeastOne<T, Keys extends keyof T = keyof T> =
        Omit<T, Keys> &
        { [K in Keys]-?: Required<Pick<T, K>> & Partial<Omit<T, K>> }[Keys];
      
      interface ContactOptions {
        email?: string;
        phone?: string;
        address?: string;
      }
      
      type Contact = RequireAtLeastOne<ContactOptions>;
      // Must provide at least one of email, phone, or address
      ```
      
      ### RequireExactlyOne
      
      ```typescript
      type RequireExactlyOne<T, Keys extends keyof T = keyof T> =
        Omit<T, Keys> &
        { [K in Keys]: Required<Pick<T, K>> & { [O in Exclude<Keys, K>]?: never } }[Keys];
      
      interface PaymentMethod {
        creditCard?: { number: string };
        bankTransfer?: { account: string };
        paypal?: { email: string };
      }
      
      type Payment = RequireExactlyOne<PaymentMethod>;
      // Must provide exactly one payment method
      ```
      
    • ts7-native-compiler.md 10.4 KB
      # TypeScript 7 Native Compiler Reference
      
      Adoption knowledge for `typescript@7` — the Go-native compiler (formerly `tsgo` /
      `@typescript/native-preview`), stable on npm as `typescript@7.0.2` since 2026-07-08.
      
      > **Point-in-time warning:** sections 3–6 describe the TS 7.0.x state as of
      > 2026-08-08. The JavaScript compiler API is slated to return in **7.1+**, and
      > ecosystem tools re-port on their own schedules — verify the current 7.x state
      > before applying any of the "lockout" or "fallback" guidance below.
      
      ## Table of Contents
      
      1. [What TS 7 Native Is](#what-ts-7-native-is)
      2. [No JavaScript Compiler API](#no-javascript-compiler-api)
      3. [Dual-Install Bin Ambiguity](#dual-install-bin-ambiguity)
      4. [Ecosystem Lockout Until 7.1](#ecosystem-lockout-until-71)
      5. [The Adoption Case: Measured](#the-adoption-case-measured)
      6. [Go/No-Go Checklist](#gono-go-checklist)
      
      ---
      
      ## What TS 7 Native Is
      
      TypeScript 7 is the Go-based rewrite of the compiler (project "Corsa", previewed
      as `tsgo`). The npm package `typescript@7` ships:
      
      - **A `bin/tsc` shim** that launches the platform-native Go binary (delivered via
        optional dependencies like `@typescript/typescript-win32-x64`)
      - **No `lib/typescript.js`** — the package is a CLI, not a library
      
      Type-checking semantics are intended to be identical to the JS compiler; the
      announcement claimed 7.7–11.9× faster typechecks (real-world repos measure in and
      above that range — see [the adoption case](#the-adoption-case-measured)).
      
      TS 6.0 is the JS-based bridge release: it exists to modernise config defaults
      (strict-by-default, `module: esnext`, legacy options removed) so a 6.0-clean
      tsconfig upgrades to 7 with little or no change. A repo whose tsconfig *already*
      matches the strict modern defaults can skip the bridge entirely and adopt 7
      directly — the bridge is for config migration, not a required step.
      
      One known hard break at the config level: **TS 7 errors on `baseUrl`**
      (TS5102). Repos using `baseUrl`-relative `paths` must rewrite them
      tsconfig-relative (`./src/*`, `../pkg/*`) — if they already are, deleting the
      `baseUrl` line changes nothing.
      
      ## No JavaScript Compiler API
      
      The single biggest adoption consequence, and the one that silently breaks repo
      tooling:
      
      ```javascript
      // Under typescript@7 (7.0.x):
      const ts = require("typescript");
      // Error: Cannot find module — MODULE_NOT_FOUND
      // There is no lib/typescript.js; the package is a bin shim over a Go binary.
      ```
      
      Anything that imports TypeScript **programmatically** — `ts.createSourceFile`,
      `ts.createProgram`, custom lint scripts, codemods, doc generators,
      `typescript-eslint` — has no compiler to import. Options, in order of preference:
      
      1. **Make the tool AST-free.** For lint-style checks, a dependency-free
         scrub-then-scan (strip strings/comments with regex, then scan the residue
         for the forbidden pattern) needs no compiler at all. A worked production
         example: a D1-access lint gate (`check-no-raw-d1.mjs`) written as a plain
         Node script precisely so the typecheck compiler could change out from under
         it without breaking the gate. If a script *can* be dependency-free, that is
         the most durable shape — don't "improve" it into `require('typescript')`.
      2. **Use an alternative parser** that doesn't depend on the TS package: swc,
         oxc, babel with the TypeScript plugin, or `@typescript-eslint`'s standalone
         parser pinned to a TS 5/6 peer.
      3. **Pin the API consumer to an alias.** Install
         `typescript5` (`npm:typescript@^5.9.3`) or the `@typescript/typescript6`
         alias alongside `typescript@7`, and point the API consumer at the alias.
         This works but drags in the [bin ambiguity](#dual-install-bin-ambiguity)
         below — treat it as a transition state, not an end state.
      
      Audit before adopting: `npm ls typescript` in every workspace. If `typescript`
      is a **leaf** (nothing depends on it), nothing consumes the API and the switch
      is safe. If typescript-eslint or similar appears above it, you're in the
      [lockout](#ecosystem-lockout-until-71) case.
      
      Note the boundary: bundlers and test runners that transpile via **esbuild or
      swc** (vite, vitest, wrangler, tsup) never invoke tsc or its API — they are
      unaffected. The API break only bites tools that *import* the `typescript`
      package.
      
      ## Dual-Install Bin Ambiguity
      
      > Point-in-time: this section applies while a `typescript5` fallback alias is
      > installed next to `typescript@7`. Once the alias is retired the ambiguity
      > disappears.
      
      Keeping a TS 5 fallback during a soak period means root `node_modules` holds
      **two packages that both ship a `tsc` bin** (`typescript@7` and the
      `typescript5` alias). npm's bin-link order is **not deterministic** — observed
      in production: after installing the alias, `npx tsc` in the repo root resolved
      to **5.9.3**, not 7. Consequences:
      
      - **Bare `tsc` / `npx tsc` must not be trusted** in any directory whose
        `node_modules` holds both packages. `npx tsc --version` tells you which one
        *won the link race*, not which one your scripts should run.
      - **Package scripts must invoke explicit bin paths** while the alias exists:
      
      ```jsonc
      // package.json — explicit compiler paths while typescript5 alias is installed
      {
          "scripts": {
              // Native TS 7 — the gate
              "typecheck":      "node node_modules/typescript/bin/tsc --noEmit && node node_modules/typescript/bin/tsc --noEmit -p web/tsconfig.json",
              // Classic TS 5 fallback — one-command cross-check during the soak
              "typecheck:tsc5": "node node_modules/typescript5/lib/tsc.js --noEmit && node node_modules/typescript5/lib/tsc.js --noEmit -p web/tsconfig.json"
          },
          "devDependencies": {
              "typescript": "^7.0.2",
              "typescript5": "npm:typescript@^5.9.3"
          }
      }
      ```
      
      - Workspaces with a **single** typescript install (e.g. a `web/` sub-package
        that only has TS 7) have an unambiguous `tsc` — bare invocations there are
        fine and don't need rewriting.
      - Prefer the vendored alias over `npx -p typescript@5.9.3` for the fallback: an
        escape hatch that reaches for the network/npx cache at fallback time can fail
        to resolve exactly when you need it offline.
      - **Retire the alias after the soak** (a few weeks of normal work with no
        native-compiler-attributable discrepancies): remove `typescript5` and the
        `*:tsc5` scripts, and scripts may return to bare `tsc` since the collision is
        gone. Leave a guard comment on the explicit paths until then so nobody
        "simplifies" them back early.
      
      ## Ecosystem Lockout Until 7.1
      
      > Point-in-time: this is the TS 7.0.x situation. The JS API returns in 7.1+;
      > each tool then ports on its own schedule. Check each tool's current status
      > before treating it as blocked.
      
      Toolchains that embed the TS programmatic API to typecheck **non-TS file
      formats** cannot run on 7.0.x at all:
      
      | Tool | Why it's pinned |
      |------|-----------------|
      | `vue-tsc` | Wraps the TS API to check `.vue` SFC template/script blocks |
      | `svelte-check` | Same pattern for `.svelte` files |
      | Astro (`astro check`) | TS API over `.astro` frontmatter/components |
      | MDX type-checking | TS API over embedded JSX in `.mdx` |
      | `typescript-eslint` type-aware rules | `ts.createProgram` under the hood |
      
      These stay on TS 6 (or a `typescript6` alias) until the API lands in 7.1+ *and*
      each tool ships a port. This makes compiler choice a **stack-selection input**:
      
      - **Single-language TS/TSX stacks** (React, plain Node/Workers, anything where
        tsc only ever sees `.ts`/`.tsx`): adopt TS 7 immediately — nothing in the
        check path needs the API.
      - **Embedded-language stacks** (Vue, Svelte, Astro, MDX-heavy docs): the
        typecheck gate is welded to the TS 6 API for now. Adopting TS 7 for *speed*
        means either waiting for 7.1+ ports or splitting the gate (native tsc for
        `.ts`, tool-pinned TS 6 for the embedded formats) — added complexity that
        usually isn't worth it before the ports exist.
      
      ## The Adoption Case: Measured
      
      > Point-in-time: numbers from one production adoption on 2026-07-09
      > (`typescript@7.0.2`); your repo will differ — measure your own.
      
      A ~60k-line Cloudflare Workers + React SPA repo (two tsconfigs) measured with
      hyperfine (warmup 1, 5 runs):
      
      | Typecheck | tsc 5.9.3 | tsc 7.0.2 | Speedup |
      |-----------|-----------|-----------|---------|
      | Worker config | 4.02 s | 0.53 s | 7.6× |
      | Web SPA config | 10.05 s | 0.63 s | 16.0× |
      | Combined | 14.07 s | 1.16 s | **12.1×** |
      
      Total adoption cost: **one config line** (removing `baseUrl` from the web
      tsconfig — its `paths` were already tsconfig-relative). The root tsconfig,
      already on TS 6-style strict defaults, needed zero changes; the 6.0 bridge was
      skipped entirely.
      
      Second-order win: at ~0.6 s the previously-too-slow SPA typecheck moved *into*
      the pre-land check gate — web-side type drift now surfaces at check time
      instead of at build time. A 12× compiler isn't just the same gate faster; it
      makes previously-rationed checks cheap enough to run always.
      
      Supply-chain footnote: first-stable adoption (`7.0.2` was one day old) sits
      inside the usual 7-day new-release cooldown for build deps. The mitigations
      that made an early land acceptable there: Microsoft-published, no install
      lifecycle scripts on the package or its platform binaries, and tsc running
      `--noEmit` (it never writes shipped artifacts). Weigh the same factors — or
      just wait out the cooldown.
      
      ## Go/No-Go Checklist
      
      Run through this before switching a repo's typecheck to `typescript@7`:
      
      1. **API audit** — `npm ls typescript` in every workspace. TypeScript must be a
         leaf, or every dependent must have a 7.0-era answer (AST-free rewrite,
         alternative parser, or pinned alias). Embedded-language tools
         (vue-tsc/svelte-check/Astro/MDX) → wait for 7.1+ ports.
      2. **Config audit** — remove `baseUrl` (TS5102 hard error); confirm `paths`
         are tsconfig-relative and resolution is unchanged under the old compiler
         first.
      3. **Benchmark before/after** — hyperfine with warmup on your actual configs.
         The speedup is the justification; record it.
      4. **Zero behaviour diff** — run old and new compilers over the same tree and
         compare emitted diagnostics. Identical error sets = go. Any discrepancy is
         an upstream bug report, not a "close enough".
      5. **Keep the fallback documented** — vendored `typescript5` alias +
         explicit-path scripts + a `check:tsc5` mirror for the soak period, with the
         retirement condition written down (N weeks, no native-attributable
         discrepancies → remove alias, return to bare `tsc`).
      6. **Full gate green** — the entire check pipeline (typecheck + lints + tests +
         build) on the new compiler before landing.
      
    • type-system.md 16.6 KB
      # TypeScript Type System Reference
      
      ## Table of Contents
      
      1. [Literal Types](#literal-types)
      2. [Discriminated Unions](#discriminated-unions)
      3. [Branded/Nominal Types](#brandednominal-types)
      4. [Template Literal Types](#template-literal-types)
      5. [Recursive Types](#recursive-types)
      6. [satisfies Operator](#satisfies-operator)
      7. [Type Assertions](#type-assertions)
      8. [Declaration Merging](#declaration-merging)
      9. [Type-Level Arithmetic](#type-level-arithmetic)
      10. [Variance](#variance)
      
      ---
      
      ## Literal Types
      
      ### Understand String, Number, and Boolean Literals
      
      Literal types restrict a value to one specific value rather than the broader primitive type.
      
      ```typescript
      // String literal
      type Direction = 'north' | 'south' | 'east' | 'west';
      type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
      
      // Number literal
      type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;
      type HttpSuccess = 200 | 201 | 204;
      
      // Boolean literal
      type Truthy = true;
      type Falsy = false;
      
      // Mixed literal union
      type Status = 'pending' | 'fulfilled' | 'rejected';
      type Result = 0 | 1 | -1;
      
      function move(direction: Direction): void {
        console.log(`Moving ${direction}`);
      }
      
      move('north');  // OK
      move('up');     // Error: Argument of type '"up"' is not assignable to parameter of type 'Direction'
      ```
      
      ### Use const Assertions to Preserve Literal Types
      
      Without `as const`, TypeScript widens literals to their primitive types. With it, literals are preserved.
      
      ```typescript
      // Without as const - types are widened
      const config = {
        endpoint: '/api',   // string
        retries: 3,         // number
        methods: ['GET', 'POST'], // string[]
      };
      
      // With as const - all literals preserved
      const CONFIG = {
        endpoint: '/api',   // '/api'
        retries: 3,         // 3
        methods: ['GET', 'POST'], // readonly ['GET', 'POST']
      } as const;
      
      type Endpoint = typeof CONFIG.endpoint;  // '/api'
      type Retry   = typeof CONFIG.retries;    // 3
      type Methods = typeof CONFIG.methods[number]; // 'GET' | 'POST'
      
      // as const on arrays
      const ROLES = ['admin', 'user', 'moderator'] as const;
      type Role = typeof ROLES[number]; // 'admin' | 'user' | 'moderator'
      
      // as const on function arguments
      function configure<T extends object>(opts: T): Readonly<T> {
        return Object.freeze(opts);
      }
      
      const opts = configure({ debug: true, port: 3000 } as const);
      // opts.debug is true (not boolean), opts.port is 3000 (not number)
      ```
      
      ### Derive Union Types from const Arrays
      
      ```typescript
      const HTTP_METHODS = ['GET', 'POST', 'PUT', 'DELETE'] as const;
      type HttpMethod = typeof HTTP_METHODS[number];
      
      // Enum alternative using as const object
      const Color = {
        Red: 'red',
        Green: 'green',
        Blue: 'blue',
      } as const;
      
      type Color = typeof Color[keyof typeof Color]; // 'red' | 'green' | 'blue'
      ```
      
      ---
      
      ## Discriminated Unions
      
      ### Build Discriminated Unions with a Shared Literal Property
      
      Every variant shares a common property (the discriminant) with a unique literal type.
      
      ```typescript
      type Shape =
        | { kind: 'circle'; radius: number }
        | { kind: 'rectangle'; width: number; height: number }
        | { kind: 'triangle'; base: number; height: number };
      
      function area(shape: Shape): number {
        switch (shape.kind) {
          case 'circle':
            return Math.PI * shape.radius ** 2;
          case 'rectangle':
            return shape.width * shape.height;
          case 'triangle':
            return 0.5 * shape.base * shape.height;
        }
      }
      ```
      
      ### Implement Exhaustiveness Checking with never
      
      When all union variants are handled, the remaining type is `never`. Passing `never` to a function that expects `never` causes a type error when a new variant is added.
      
      ```typescript
      function assertNever(value: never, message?: string): never {
        throw new Error(message ?? `Unhandled discriminated union member: ${JSON.stringify(value)}`);
      }
      
      type NetworkState =
        | { status: 'idle' }
        | { status: 'loading' }
        | { status: 'success'; data: string }
        | { status: 'error'; error: Error };
      
      function handleState(state: NetworkState): string {
        switch (state.status) {
          case 'idle':    return 'Waiting...';
          case 'loading': return 'Loading...';
          case 'success': return state.data;
          case 'error':   return state.error.message;
          default:        return assertNever(state); // Compile error if case is missing
        }
      }
      ```
      
      ### Model Result Types as Discriminated Unions
      
      ```typescript
      type Ok<T>  = { ok: true;  value: T };
      type Err<E> = { ok: false; error: E };
      type Result<T, E = Error> = Ok<T> | Err<E>;
      
      function divide(a: number, b: number): Result<number, string> {
        if (b === 0) return { ok: false, error: 'Division by zero' };
        return { ok: true, value: a / b };
      }
      
      const result = divide(10, 2);
      if (result.ok) {
        console.log(result.value); // number
      } else {
        console.error(result.error); // string
      }
      ```
      
      ---
      
      ## Branded/Nominal Types
      
      ### Create Branded Types to Prevent Type Confusion
      
      TypeScript uses structural typing: two types with the same shape are interchangeable. Branding adds a phantom property to make them nominally distinct.
      
      ```typescript
      type Brand<T, B extends string> = T & { readonly __brand: B };
      
      type UserId   = Brand<string, 'UserId'>;
      type OrderId  = Brand<string, 'OrderId'>;
      type Email    = Brand<string, 'Email'>;
      type Dollars  = Brand<number, 'Dollars'>;
      type Cents    = Brand<number, 'Cents'>;
      
      // Without branding these are all just 'string' - interchangeable and unsafe.
      // With branding they are distinct.
      function getUser(id: UserId): void { /* ... */ }
      function getOrder(id: OrderId): void { /* ... */ }
      
      declare const userId: UserId;
      declare const orderId: OrderId;
      
      getUser(userId);   // OK
      getUser(orderId);  // Error: Argument of type 'OrderId' is not assignable to parameter of type 'UserId'
      ```
      
      ### Write Validation Functions That Return Branded Types
      
      ```typescript
      function brandUserId(raw: string): UserId {
        return raw as UserId;
      }
      
      function parseEmail(raw: string): Email {
        if (!/^[^@]+@[^@]+\.[^@]+$/.test(raw)) {
          throw new Error(`Invalid email: ${raw}`);
        }
        return raw as Email;
      }
      
      function parseDollars(amount: number): Dollars {
        if (amount < 0) throw new Error('Dollars cannot be negative');
        return amount as Dollars;
      }
      
      // Use in domain logic - type system enforces correct usage
      function sendInvoice(to: Email, amount: Dollars): void { /* ... */ }
      ```
      
      ### Use Opaque Types via Unique Symbol (Advanced)
      
      For stricter encapsulation, use unique symbols as the brand key.
      
      ```typescript
      declare const _brand: unique symbol;
      
      type Opaque<T, Tag> = T & { readonly [_brand]: Tag };
      
      type PositiveInt = Opaque<number, 'PositiveInt'>;
      
      function toPositiveInt(n: number): PositiveInt {
        if (!Number.isInteger(n) || n <= 0) {
          throw new Error(`Expected positive integer, got ${n}`);
        }
        return n as PositiveInt;
      }
      ```
      
      ---
      
      ## Template Literal Types
      
      ### Build Type-Safe String Patterns
      
      Template literal types compose string literals at the type level.
      
      ```typescript
      type EventName<T extends string> = `on${Capitalize<T>}`;
      type ClickEvent = EventName<'click'>;   // 'onClick'
      type ChangeEvent = EventName<'change'>; // 'onChange'
      
      type CSSProperty = 'margin' | 'padding';
      type CSSUnit = 'px' | 'em' | 'rem' | '%';
      type CSSValue = `${number}${CSSUnit}`; // '10px', '1.5em', etc.
      
      // Route parameter extraction
      type RouteParam<T extends string> =
        T extends `${string}:${infer Param}/${infer Rest}`
          ? Param | RouteParam<Rest>
          : T extends `${string}:${infer Param}`
          ? Param
          : never;
      
      type Params = RouteParam<'/users/:userId/posts/:postId'>;
      // 'userId' | 'postId'
      ```
      
      ### Use String Manipulation Types
      
      ```typescript
      type Uppercased = Uppercase<'hello world'>;   // 'HELLO WORLD'
      type Lowercased = Lowercase<'HELLO WORLD'>;   // 'hello world'
      type Capitalized = Capitalize<'hello'>;       // 'Hello'
      type Uncapitalized = Uncapitalize<'Hello'>;   // 'hello'
      
      // Build getter/setter types
      type Getter<T extends string> = `get${Capitalize<T>}`;
      type Setter<T extends string> = `set${Capitalize<T>}`;
      
      type FieldName = 'name' | 'age' | 'email';
      type Getters = { [K in FieldName as Getter<K>]: string };
      // { getName: string; getAge: string; getEmail: string }
      
      // Build event handler types from object keys
      type EventHandlers<T> = {
        [K in keyof T as `on${Capitalize<string & K>}Change`]: (value: T[K]) => void;
      };
      
      interface FormFields { name: string; age: number; }
      type FormHandlers = EventHandlers<FormFields>;
      // { onNameChange: (value: string) => void; onAgeChange: (value: number) => void }
      ```
      
      ---
      
      ## Recursive Types
      
      ### Define the JSON Type
      
      ```typescript
      type JsonPrimitive = string | number | boolean | null;
      type JsonArray    = JsonValue[];
      type JsonObject   = { [key: string]: JsonValue };
      type JsonValue    = JsonPrimitive | JsonArray | JsonObject;
      
      // Usage
      const data: JsonValue = {
        name: 'Alice',
        scores: [1, 2, 3],
        address: { city: 'NYC', zip: null },
      };
      ```
      
      ### Implement Deep Readonly and Deep Partial
      
      ```typescript
      type DeepReadonly<T> = T extends (infer U)[]
        ? ReadonlyArray<DeepReadonly<U>>
        : T extends object
        ? { readonly [K in keyof T]: DeepReadonly<T[K]> }
        : T;
      
      type DeepPartial<T> = T extends (infer U)[]
        ? DeepPartial<U>[]
        : T extends object
        ? { [K in keyof T]?: DeepPartial<T[K]> }
        : T;
      
      interface Config {
        server: { host: string; port: number };
        database: { url: string; poolSize: number };
      }
      
      type ReadonlyConfig = DeepReadonly<Config>;
      // server.host and all nested props are readonly
      
      type PartialConfig = DeepPartial<Config>;
      // All nested props optional
      ```
      
      ### Build Path Types for Safe Object Access
      
      ```typescript
      type PathOf<T, Sep extends string = '.'> =
        T extends object
          ? {
              [K in keyof T]: K extends string
                ? T[K] extends object
                  ? K | `${K}${Sep}${PathOf<T[K], Sep>}`
                  : K
                : never;
            }[keyof T]
          : never;
      
      type ValueAt<T, P extends string> =
        P extends `${infer K}.${infer Rest}`
          ? K extends keyof T
            ? ValueAt<T[K], Rest>
            : never
          : P extends keyof T
          ? T[P]
          : never;
      
      interface User {
        id: string;
        profile: { name: string; address: { city: string } };
      }
      
      type UserPath = PathOf<User>;
      // 'id' | 'profile' | 'profile.name' | 'profile.address' | 'profile.address.city'
      
      type CityType = ValueAt<User, 'profile.address.city'>; // string
      ```
      
      ---
      
      ## satisfies Operator
      
      ### Validate Type Without Widening
      
      The `satisfies` operator checks that a value matches a type while preserving the most specific type.
      
      ```typescript
      // Problem without satisfies:
      type Palette = Record<string, [number, number, number] | string>;
      
      const palette1: Palette = {
        red: [255, 0, 0],
        green: '#00ff00',
      };
      // palette1.red is [number, number, number] | string - information lost
      
      // With satisfies:
      const palette2 = {
        red: [255, 0, 0],
        green: '#00ff00',
      } satisfies Palette;
      // palette2.red is [number, number, number] - specific type preserved
      // palette2.green is string - specific type preserved
      
      palette2.red.map(v => v * 2); // OK - TypeScript knows it's an array
      palette2.green.toUpperCase(); // OK - TypeScript knows it's a string
      ```
      
      ### Combine satisfies with as const
      
      ```typescript
      const routes = {
        home: '/',
        about: '/about',
        user: '/users/:id',
      } as const satisfies Record<string, `/${string}`>;
      
      // Routes values are literal types, not string
      type HomeRoute = typeof routes.home; // '/'
      ```
      
      ### Use satisfies for Configuration Objects
      
      ```typescript
      interface PluginConfig {
        name: string;
        version: string;
        hooks?: {
          beforeBuild?: () => void;
          afterBuild?: () => void;
        };
      }
      
      const myPlugin = {
        name: 'my-plugin',
        version: '1.0.0',
        hooks: {
          beforeBuild: () => console.log('building...'),
        },
      } satisfies PluginConfig;
      
      // myPlugin.name is 'my-plugin' not string
      // TypeScript checks shape against PluginConfig at definition site
      ```
      
      ---
      
      ## Type Assertions
      
      ### Understand When Assertions Are Safe
      
      Type assertions (`as T`) override TypeScript's type inference. They are safe only when you have external information the compiler cannot verify.
      
      ```typescript
      // SAFE: narrowing after a runtime check
      function processInput(input: unknown): string {
        if (typeof input === 'string') {
          return input; // narrowed, no assertion needed
        }
        // We know from domain logic this is always serializable
        return String(input);
      }
      
      // SAFE: DOM API returns Element | null, but we know the element exists
      const canvas = document.getElementById('canvas') as HTMLCanvasElement;
      
      // UNSAFE: asserting unrelated types
      const num = 42 as unknown as string; // compiles, crashes at runtime
      ```
      
      ### Use Double Assertion as Escape Hatch
      
      When TypeScript refuses an assertion because types don't overlap, cast through `unknown`.
      
      ```typescript
      // Only do this when you have proof the cast is correct
      function forceType<T>(value: unknown): T {
        return value as T;
      }
      
      // Explicit escape: cast through unknown
      const risky = someValue as unknown as TargetType;
      ```
      
      ### Prefer Type Guards Over Assertions
      
      ```typescript
      // BAD: assertion with no runtime check
      function getUser(data: unknown): User {
        return data as User; // unsafe, no verification
      }
      
      // GOOD: type guard with runtime verification
      function isUser(data: unknown): data is User {
        return (
          typeof data === 'object' &&
          data !== null &&
          'id' in data &&
          typeof (data as Record<string, unknown>).id === 'string' &&
          'name' in data &&
          typeof (data as Record<string, unknown>).name === 'string'
        );
      }
      
      function getUser(data: unknown): User {
        if (!isUser(data)) throw new Error('Invalid user data');
        return data; // safe, narrowed by type guard
      }
      ```
      
      ---
      
      ## Declaration Merging
      
      ### Merge Interfaces to Extend Third-Party Types
      
      ```typescript
      // Original interface from a library
      interface Request {
        method: string;
        url: string;
      }
      
      // Your augmentation - merges with above
      interface Request {
        user?: { id: string; role: string };
        requestId: string;
      }
      
      // Result: Request has method, url, user, requestId
      ```
      
      ### Augment Modules to Add Types to External Packages
      
      ```typescript
      // express-augment.d.ts
      import 'express';
      
      declare module 'express' {
        interface Request {
          user?: { id: string; role: 'admin' | 'user' };
          sessionId: string;
        }
      }
      ```
      
      ### Augment Global Scope
      
      ```typescript
      // global.d.ts
      declare global {
        interface Window {
          analytics: {
            track(event: string, properties?: Record<string, unknown>): void;
          };
        }
      
        interface Array<T> {
          // Add a custom method to all arrays
          groupBy<K extends string>(keyFn: (item: T) => K): Record<K, T[]>;
        }
      }
      
      export {}; // Required to make this a module (not a script)
      ```
      
      ---
      
      ## Type-Level Arithmetic
      
      ### Measure Tuple Lengths
      
      ```typescript
      type Length<T extends readonly unknown[]> = T['length'];
      
      type Three = Length<[1, 2, 3]>; // 3
      type Zero  = Length<[]>;         // 0
      ```
      
      ### Build a Recursive Counter
      
      ```typescript
      // Build a tuple of length N, then read its length
      type BuildTuple<N extends number, T extends unknown[] = []> =
        T['length'] extends N ? T : BuildTuple<N, [...T, unknown]>;
      
      type Add<A extends number, B extends number> =
        Length<[...BuildTuple<A>, ...BuildTuple<B>]>;
      
      type Sum = Add<3, 4>; // 7
      
      type Subtract<A extends number, B extends number> =
        BuildTuple<A> extends [...BuildTuple<B>, ...infer Rest]
          ? Length<Rest>
          : never;
      
      type Diff = Subtract<7, 3>; // 4
      ```
      
      ---
      
      ## Variance
      
      ### Understand Covariance and Contravariance
      
      - **Covariant**: A `Producer<Dog>` is assignable to `Producer<Animal>` (output position)
      - **Contravariant**: A `Consumer<Animal>` is assignable to `Consumer<Dog>` (input position)
      - **Invariant**: Neither assignment is safe
      
      ```typescript
      // Covariant: return type position
      type Producer<out T> = () => T;
      
      declare let animalProducer: Producer<Animal>;
      declare let dogProducer: Producer<Dog>;
      
      animalProducer = dogProducer; // OK - Dog is a subtype of Animal
      
      // Contravariant: parameter type position
      type Consumer<in T> = (value: T) => void;
      
      declare let animalConsumer: Consumer<Animal>;
      declare let dogConsumer: Consumer<Dog>;
      
      dogConsumer = animalConsumer; // OK - Consumer<Animal> handles any Animal including Dog
      animalConsumer = dogConsumer; // Error - Consumer<Dog> can't handle all Animals
      ```
      
      ### Apply in/out Variance Annotations (TypeScript 4.7+)
      
      ```typescript
      interface Animal { name: string; }
      interface Dog extends Animal { breed: string; }
      
      // Explicitly mark variance for clarity and performance
      interface ReadableStream<out T> {   // covariant - only produces T
        read(): T;
      }
      
      interface WritableStream<in T> {    // contravariant - only consumes T
        write(value: T): void;
      }
      
      interface Transform<in TInput, out TOutput> { // bivariant
        transform(input: TInput): TOutput;
      }
      ```
      
      ### Recognize Function Parameter Bivariance Trap
      
      ```typescript
      // strictFunctionTypes catches this
      type Callback = (event: MouseEvent) => void;
      type Handler  = (event: Event) => void;
      
      // With strictFunctionTypes: NOT assignable (correct)
      // Without strictFunctionTypes: assignable (unsafe)
      ```
      
    • utility-types.md 12.9 KB
      # TypeScript Utility Types Reference
      
      ## Table of Contents
      
      1. [Built-in Utility Types](#built-in-utility-types)
      2. [Custom Utility Types](#custom-utility-types)
      3. [Type-Safe Object Operations](#type-safe-object-operations)
      4. [Array/Tuple Utilities](#arraytuple-utilities)
      5. [Function Utilities](#function-utilities)
      
      ---
      
      ## Built-in Utility Types
      
      ### Object Shape Utilities
      
      ```typescript
      // Partial<T> - Make all properties optional
      interface User { id: string; name: string; email: string; }
      type UpdateUser = Partial<User>; // { id?: string; name?: string; email?: string }
      
      function updateUser(id: string, patch: Partial<User>): User { /* ... */ }
      
      // Required<T> - Make all properties required
      interface Config { host?: string; port?: number; debug?: boolean; }
      type StrictConfig = Required<Config>; // { host: string; port: number; debug: boolean }
      
      // Readonly<T> - Make all properties readonly
      type ImmutableUser = Readonly<User>; // { readonly id: string; readonly name: string; ... }
      const frozen: ImmutableUser = { id: '1', name: 'Alice', email: 'a@b.com' };
      // frozen.name = 'Bob'; // Error: cannot assign to 'name' because it is a read-only property
      
      // Record<K, T> - Create an object type with keys K and values T
      type UserMap    = Record<string, User>;
      type StatusMap  = Record<'active' | 'inactive' | 'banned', number>;
      type HttpStatus = Record<200 | 404 | 500, string>;
      
      // Pick<T, K> - Select a subset of properties
      type UserPreview = Pick<User, 'id' | 'name'>; // { id: string; name: string }
      type Credentials = Pick<User, 'email'>;         // { email: string }
      
      // Omit<T, K> - Exclude specific properties
      type PublicUser = Omit<User, 'email'>;   // { id: string; name: string }
      type NewUser    = Omit<User, 'id'>;      // { name: string; email: string }
      ```
      
      ### Union Manipulation Utilities
      
      ```typescript
      // Exclude<T, U> - Remove U from union T
      type NonBoolean  = Exclude<string | number | boolean, boolean>; // string | number
      type NonNullish  = Exclude<string | null | undefined, null | undefined>; // string
      type NonString   = Exclude<string | number | boolean, string>; // number | boolean
      
      // Extract<T, U> - Keep only members of T that are assignable to U
      type OnlyStrings = Extract<string | number | boolean, string>; // string
      type Primitives  = Extract<string | number | { id: string }, string | number>; // string | number
      
      // NonNullable<T> - Remove null and undefined from T
      type SafeString = NonNullable<string | null | undefined>; // string
      type SafeUser   = NonNullable<User | null | undefined>;   // User
      ```
      
      ### Function Utilities
      
      ```typescript
      function fetchData(url: string, timeout: number, headers: Record<string, string>): Promise<unknown> {
        return fetch(url);
      }
      
      // ReturnType<T> - Get the return type of a function type
      type FetchResult = ReturnType<typeof fetchData>;     // Promise<unknown>
      type StringLength = ReturnType<typeof String.prototype.indexOf>; // number
      
      // Parameters<T> - Get parameters as a tuple type
      type FetchParams = Parameters<typeof fetchData>;
      // [url: string, timeout: number, headers: Record<string, string>]
      
      // Call a function with stored parameters
      function withDefaults<T extends (...args: unknown[]) => unknown>(
        fn: T,
        defaults: Partial<Parameters<T>>
      ) { /* ... */ }
      
      // ConstructorParameters<T> - Get constructor parameter types
      class HttpClient {
        constructor(baseUrl: string, timeout: number) {}
      }
      type HttpArgs = ConstructorParameters<typeof HttpClient>; // [string, number]
      
      // InstanceType<T> - Get the type of a class instance
      type ClientInstance = InstanceType<typeof HttpClient>; // HttpClient
      
      // ThisParameterType<T> - Extract the type of 'this'
      function greet(this: { name: string }, greeting: string): string {
        return `${greeting}, ${this.name}`;
      }
      type GreetThis = ThisParameterType<typeof greet>; // { name: string }
      
      // OmitThisParameter<T> - Remove this parameter from function type
      type GreetFn = OmitThisParameter<typeof greet>; // (greeting: string) => string
      ```
      
      ### Awaited and String Utilities
      
      ```typescript
      // Awaited<T> - Recursively unwrap Promise
      type A = Awaited<Promise<string>>;          // string
      type B = Awaited<Promise<Promise<number>>>; // number
      type C = Awaited<string | Promise<number>>; // string | number
      
      async function loadData(): Promise<User[]> { return []; }
      type LoadResult = Awaited<ReturnType<typeof loadData>>; // User[]
      
      // String manipulation (compile-time only, no runtime effect)
      type UpperName = Uppercase<'hello'>;      // 'HELLO'
      type LowerName = Lowercase<'WORLD'>;      // 'world'
      type CapName   = Capitalize<'alice'>;     // 'Alice'
      type UnCapName = Uncapitalize<'Hello'>;   // 'hello'
      
      // Useful for generating method names
      type Methods<T extends string> = `get${Capitalize<T>}` | `set${Capitalize<T>}`;
      type NameMethods = Methods<'name' | 'age'>; // 'getName' | 'getAge' | 'setName' | 'setAge'
      ```
      
      ---
      
      ## Custom Utility Types
      
      ### DeepReadonly
      
      ```typescript
      type DeepReadonly<T> =
        T extends (infer U)[]
          ? ReadonlyArray<DeepReadonly<U>>
          : T extends object
          ? { readonly [K in keyof T]: DeepReadonly<T[K]> }
          : T;
      
      interface AppState {
        user: { id: string; profile: { name: string; bio: string } };
        settings: { theme: string; notifications: boolean[] };
      }
      
      type FrozenState = DeepReadonly<AppState>;
      declare const state: FrozenState;
      // state.user.profile.name = 'x'; // Error - deeply readonly
      ```
      
      ### DeepPartial
      
      ```typescript
      type DeepPartial<T> =
        T extends (infer U)[]
          ? DeepPartial<U>[]
          : T extends object
          ? { [K in keyof T]?: DeepPartial<T[K]> }
          : T;
      
      // Useful for deep merge / patch operations
      function deepMerge<T>(target: T, patch: DeepPartial<T>): T {
        if (typeof patch !== 'object' || patch === null) return patch as T;
        const result = { ...target };
        for (const key of Object.keys(patch) as (keyof T)[]) {
          const val = patch[key as keyof typeof patch];
          if (val !== undefined) {
            (result[key] as unknown) = typeof val === 'object' && val !== null
              ? deepMerge(result[key] as object, val as DeepPartial<object>)
              : val;
          }
        }
        return result;
      }
      ```
      
      ### Nullable and Optional
      
      ```typescript
      type Nullable<T> = T | null;
      type Optional<T> = T | undefined;
      type NullableOptional<T> = T | null | undefined;
      
      // Require at least one of specified keys
      type RequireAtLeastOne<T, Keys extends keyof T = keyof T> =
        Omit<T, Keys> &
        { [K in Keys]-?: Required<Pick<T, K>> & Partial<Omit<T, K>> }[Keys];
      
      // Require exactly one of specified keys
      type RequireExactlyOne<T, Keys extends keyof T = keyof T> =
        Omit<T, Keys> &
        { [K in Keys]: Required<Pick<T, K>> & { [O in Exclude<Keys, K>]?: never } }[Keys];
      ```
      
      ### Merge and UnionToIntersection
      
      ```typescript
      // Merge two types, second overrides first
      type Merge<T, U> = Omit<T, keyof U> & U;
      
      type A = { id: string; name: string; active: boolean };
      type B = { name: number; extra: string }; // name changes type
      type C = Merge<A, B>; // { id: string; active: boolean; name: number; extra: string }
      
      // Convert a union to an intersection
      type UnionToIntersection<U> =
        (U extends unknown ? (x: U) => void : never) extends (x: infer I) => void
          ? I
          : never;
      
      type IntersectedABC = UnionToIntersection<{ a: string } | { b: number } | { c: boolean }>;
      // { a: string } & { b: number } & { c: boolean }
      
      // Prettify - flatten intersection types for readable IDE output
      type Prettify<T> = { [K in keyof T]: T[K] } & {};
      ```
      
      ### Exact and StrictOmit
      
      ```typescript
      // Ensure no extra properties (useful in function params)
      type Exact<T, Shape> = T extends Shape
        ? Exclude<keyof T, keyof Shape> extends never
          ? T
          : never
        : never;
      
      // StrictOmit: errors if K is not in T (unlike Omit which silently ignores)
      type StrictOmit<T, K extends keyof T> = Omit<T, K>;
      ```
      
      ---
      
      ## Type-Safe Object Operations
      
      ### Type-Safe pick
      
      ```typescript
      function pick<T extends object, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
        return keys.reduce((acc, key) => {
          acc[key] = obj[key];
          return acc;
        }, {} as Pick<T, K>);
      }
      
      const user: User = { id: '1', name: 'Alice', email: 'a@b.com' };
      const preview = pick(user, ['id', 'name']); // { id: string; name: string }
      // TypeScript knows preview has only 'id' and 'name'
      ```
      
      ### Type-Safe omit
      
      ```typescript
      function omit<T extends object, K extends keyof T>(obj: T, keys: K[]): Omit<T, K> {
        const result = { ...obj };
        keys.forEach((key) => delete result[key]);
        return result as Omit<T, K>;
      }
      
      const publicUser = omit(user, ['email']); // { id: string; name: string }
      ```
      
      ### Type-Safe merge
      
      ```typescript
      function merge<T extends object, U extends object>(base: T, override: U): Merge<T, U> {
        return { ...base, ...override } as Merge<T, U>;
      }
      
      type Merge<T, U> = Omit<T, keyof U> & U;
      ```
      
      ### Type-Safe diff (keys present in T but not U)
      
      ```typescript
      type Diff<T, U> = Pick<T, Exclude<keyof T, keyof U>>;
      
      type OnlyInA = Diff<{ a: string; b: number; c: boolean }, { b: number; d: string }>;
      // { a: string; c: boolean }
      ```
      
      ---
      
      ## Array/Tuple Utilities
      
      ### Head, Tail, Last, Reverse
      
      ```typescript
      // First element of a tuple
      type Head<T extends unknown[]> =
        T extends [infer H, ...unknown[]] ? H : never;
      
      // All but first element
      type Tail<T extends unknown[]> =
        T extends [unknown, ...infer R] ? R : never;
      
      // Last element of a tuple
      type Last<T extends unknown[]> =
        T extends [...unknown[], infer L] ? L : never;
      
      // Reverse a tuple
      type Reverse<T extends unknown[], Acc extends unknown[] = []> =
        T extends [infer Head, ...infer Rest]
          ? Reverse<Rest, [Head, ...Acc]>
          : Acc;
      
      type H = Head<[string, number, boolean]>; // string
      type T = Tail<[string, number, boolean]>; // [number, boolean]
      type L = Last<[string, number, boolean]>; // boolean
      type R = Reverse<[1, 2, 3]>;             // [3, 2, 1]
      ```
      
      ### Flatten Types
      
      ```typescript
      // Flatten one level
      type Flatten<T extends unknown[]> =
        T extends (infer U)[] ? U : T;
      
      type F = Flatten<string[][]>; // string[]
      
      // Flatten nested arrays recursively
      type DeepFlatten<T> =
        T extends (infer U)[]
          ? U extends unknown[]
            ? DeepFlatten<U>
            : U
          : T;
      
      type Deep = DeepFlatten<string[][][]>; // string
      ```
      
      ### Zip Two Tuples
      
      ```typescript
      type Zip<T extends unknown[], U extends unknown[]> =
        T extends [infer TH, ...infer TR]
          ? U extends [infer UH, ...infer UR]
            ? [[TH, UH], ...Zip<TR, UR>]
            : []
          : [];
      
      type Zipped = Zip<[1, 2, 3], ['a', 'b', 'c']>;
      // [[1, 'a'], [2, 'b'], [3, 'c']]
      ```
      
      ### Length and Indices
      
      ```typescript
      type Length<T extends readonly unknown[]> = T['length'];
      
      // Generate numeric union of indices
      type Indices<T extends readonly unknown[]> =
        Exclude<keyof T, keyof []>;
      
      type Len = Length<[1, 2, 3]>; // 3
      type Idx = Indices<['a', 'b', 'c']>; // '0' | '1' | '2'
      ```
      
      ---
      
      ## Function Utilities
      
      ### Promisify Type
      
      ```typescript
      // Convert a callback-style function type to one returning a Promise
      type Promisify<T extends (...args: unknown[]) => unknown> =
        T extends (...args: infer A) => infer R
          ? R extends Promise<unknown>
            ? T
            : (...args: A) => Promise<Awaited<R>>
          : never;
      
      type SyncFn = (x: number) => string;
      type AsyncFn = Promisify<SyncFn>; // (x: number) => Promise<string>
      ```
      
      ### Curry Type
      
      ```typescript
      type Head<T extends unknown[]> = T extends [infer H, ...unknown[]] ? H : never;
      type Tail<T extends unknown[]> = T extends [unknown, ...infer R] ? R : never;
      
      type Curried<TArgs extends unknown[], TReturn> =
        TArgs extends []
          ? TReturn
          : (arg: Head<TArgs>) => Curried<Tail<TArgs>, TReturn>;
      
      declare function curry<TArgs extends unknown[], TReturn>(
        fn: (...args: TArgs) => TReturn
      ): Curried<TArgs, TReturn>;
      
      const add = curry((a: number, b: number) => a + b);
      const inc = add(1);     // Curried<[number], number> = (b: number) => number
      const two = inc(1);     // number
      ```
      
      ### Overload Helper
      
      ```typescript
      // Extract all overload signatures as a union
      type Overloads<T extends (...args: unknown[]) => unknown> =
        T extends {
          (...args: infer A1): infer R1;
          (...args: infer A2): infer R2;
          (...args: infer A3): infer R3;
          (...args: infer A4): infer R4;
        }
          ? ((...args: A1) => R1) | ((...args: A2) => R2) | ((...args: A3) => R3) | ((...args: A4) => R4)
          : T extends {
              (...args: infer A1): infer R1;
              (...args: infer A2): infer R2;
              (...args: infer A3): infer R3;
            }
          ? ((...args: A1) => R1) | ((...args: A2) => R2) | ((...args: A3) => R3)
          : T extends { (...args: infer A1): infer R1; (...args: infer A2): infer R2 }
          ? ((...args: A1) => R1) | ((...args: A2) => R2)
          : T;
      ```
      
      ### Memoize with Type Safety
      
      ```typescript
      type AnyFn = (...args: unknown[]) => unknown;
      
      function memoize<T extends AnyFn>(fn: T): T {
        const cache = new Map<string, ReturnType<T>>();
        return ((...args: Parameters<T>): ReturnType<T> => {
          const key = JSON.stringify(args);
          if (cache.has(key)) return cache.get(key) as ReturnType<T>;
          const result = fn(...args) as ReturnType<T>;
          cache.set(key, result);
          return result;
        }) as T;
      }
      
      const expensiveCalc = memoize((a: number, b: number): number => a * b);
      expensiveCalc(2, 3); // 6 - computed
      expensiveCalc(2, 3); // 6 - cached
      ```
      
  • scripts
    • check-typescript-facts.py 11 KB
      #!/usr/bin/env python3
      """Staleness verifier for typescript-ops: the version-bearing facts the skill
      encodes must stay real and cited.
      
      typescript-ops anchors its currency to a few version-bearing facts — the
      TypeScript major the prose assumes (feature floors like "TS 4.4+",
      "TypeScript 4.7+"), and the runtime-validation stack (zod, valibot). That is
      exactly the fact that drifts silently (SKILL-RESOURCE-PROTOCOL.md §7): a
      package leaves its documented major upstream, or the prose stops naming a
      package the catalog still commits to, and nobody notices for months. Two
      modes guard it:
      
        --offline (default, safe for PR CI): structural consistency, no network.
          * assets/typescript-facts.json parses; every entry has name + documented_major
          * every catalogued package is still named somewhere in the skill prose
            (SKILL.md / references/*.md) — the catalog can't drift from the docs
          * SKILL.md still carries a dated "as of 20XX" currency note
        --live (scheduled freshness.yml, never a PR gate): does each package's
          latest published major on npm still match the documented major? A newer
          major = the skill is behind reality (drift). Exit 7 if npm is unreachable.
      
      Usage:   check-typescript-facts.py [--offline | --live] [--catalog FILE] [--skill DIR] [--json] [--timeout S]
      Input:   argv flags only (no stdin).
      Output:  stdout = findings (plain rows, or a --json envelope). Data only.
      Stderr:  the verdict line, notices, errors.
      Exit:    0 ok, 2 usage, 3 catalog/skill missing, 4 catalog unparseable,
               7 npm unreachable (live, advisory — never a real failure),
               10 drift found (offline: uncited/undocumented/no currency note;
                               live: published major newer than documented major)
      
      Examples:
        check-typescript-facts.py --offline                 # PR CI: catalog ⇆ prose consistency
        check-typescript-facts.py --live                     # weekly: every package's major still matches npm
        check-typescript-facts.py --offline --json | jq '.data[]'
      """
      from __future__ import annotations
      
      import argparse
      import json
      import os
      import re
      import sys
      import urllib.error
      import urllib.parse
      import urllib.request
      from pathlib import Path
      
      EX_OK = 0
      EX_USAGE = 2
      EX_NOTFOUND = 3
      EX_UNPARSEABLE = 4
      EX_UNAVAILABLE = 7
      EX_DRIFT = 10
      
      HERE = Path(__file__).resolve().parent
      DEFAULT_CATALOG = HERE.parent / "assets" / "typescript-facts.json"
      DEFAULT_SKILL = HERE.parent
      DEFAULT_REGISTRY = "https://registry.npmjs.org"
      SCHEMA = "claude-mods.typescript-ops.facts/v1"
      CURRENCY_RE = re.compile(r"as of 20\d\d")
      
      
      def eprint(*a) -> None:
          print(*a, file=sys.stderr)
      
      
      class Term:
          """Minimal ANSI helper (term.sh is bash-only; per TERMINAL-DESIGN.md §9 the
          Python port is inline). Honors FORCE_COLOR / NO_COLOR / TERM_ASCII and the
          bound stream's TTY + encoding so piped data stays plain ASCII."""
      
          _C = {"green": "\033[32m", "red": "\033[31m", "dim": "\033[2m", "off": "\033[0m"}
      
          def __init__(self, stream=sys.stderr) -> None:
              enc = (getattr(stream, "encoding", "") or "").lower()
              self.ascii = os.environ.get("TERM_ASCII") == "1" or "utf" not in enc
              if os.environ.get("FORCE_COLOR"):
                  self.color = True
              elif (os.environ.get("NO_COLOR") is not None
                    or os.environ.get("TERM") == "dumb"
                    or not getattr(stream, "isatty", lambda: False)()):
                  self.color = False
              else:
                  self.color = True
      
          def c(self, name: str, text: str) -> str:
              return f"{self._C.get(name, '')}{text}{self._C['off']}" if self.color else text
      
          def mark(self, ok: bool) -> str:
              g = ("+" if self.ascii else "✓") if ok else ("x" if self.ascii else "✗")
              return self.c("green" if ok else "red", g)
      
      
      def load_catalog(path: Path) -> tuple[list[dict], str]:
          """Returns (packages, registry). Each package has name + documented_major."""
          if not path.is_file():
              eprint(f"error: package catalog not found: {path}")
              raise SystemExit(EX_NOTFOUND)
          try:
              data = json.loads(path.read_text(encoding="utf-8"))
              pkgs = data["packages"]
              if not isinstance(pkgs, list) or not pkgs:
                  raise ValueError("'packages' must be a non-empty array")
              for p in pkgs:
                  if not isinstance(p, dict) or "name" not in p or "documented_major" not in p:
                      raise ValueError(f"package entry missing name/documented_major: {p!r}")
                  dm = p["documented_major"]
                  if not isinstance(dm, int) or isinstance(dm, bool) or dm < 0:
                      raise ValueError(f"documented_major must be a non-negative int: {p!r}")
              registry = data.get("registry") or DEFAULT_REGISTRY
              return pkgs, registry
          except (json.JSONDecodeError, KeyError, TypeError, ValueError) as exc:
              eprint(f"error: could not parse catalog {path}: {exc}")
              raise SystemExit(EX_UNPARSEABLE)
      
      
      def read_corpus(skill_dir: Path) -> tuple[str, str]:
          """Returns (skill_md_text, all_prose_text) across SKILL.md + references/*.md."""
          doc = skill_dir / "SKILL.md"
          if not doc.is_file():
              eprint(f"error: SKILL.md not found under {skill_dir}")
              raise SystemExit(EX_NOTFOUND)
          skill_md = doc.read_text(encoding="utf-8")
          parts = [skill_md]
          for ref in sorted((skill_dir / "references").glob("*.md")):
              parts.append(ref.read_text(encoding="utf-8"))
          return skill_md, "\n".join(parts)
      
      
      def check_offline(pkgs: list[dict], skill_dir: Path) -> list[dict]:
          skill_md, corpus = read_corpus(skill_dir)
          findings: list[dict] = []
          for p in pkgs:
              name = p["name"]
              # case-sensitive exact substring: npm names are case-sensitive (zod != Zod)
              if name not in corpus:
                  findings.append({"package": name, "issue": "catalogued but not named in skill prose"})
          if not CURRENCY_RE.search(skill_md):
              findings.append({"package": "(SKILL.md)", "issue": "no dated 'as of 20XX' currency note"})
          return findings
      
      
      def npm_latest(registry: str, name: str, timeout: float) -> tuple[str, object]:
          """Return ('ok', version_str) | ('gone', None) | ('unreachable', info)."""
          url = registry.rstrip("/") + "/" + urllib.parse.quote(name, safe="") + "/latest"
          req = urllib.request.Request(url, method="GET",
                                       headers={"User-Agent": "claude-mods-typescript-ops-check/1",
                                                "Accept": "application/json"})
          try:
              with urllib.request.urlopen(req, timeout=timeout) as resp:
                  data = json.loads(resp.read().decode("utf-8"))
                  return ("ok", data.get("version", ""))
          except urllib.error.HTTPError as exc:
              if exc.code in (404, 410):
                  return ("gone", None)
              return ("unreachable", exc.code)  # 5xx etc: transient, not a content finding
          except (urllib.error.URLError, TimeoutError, OSError, json.JSONDecodeError) as exc:
              return ("unreachable", str(getattr(exc, "reason", exc)))
      
      
      def major_of(version: str) -> int | None:
          m = re.match(r"\D*(\d+)", version or "")
          return int(m.group(1)) if m else None
      
      
      def check_live(pkgs: list[dict], registry: str, timeout: float) -> tuple[list[dict], list[dict]]:
          drift: list[dict] = []
          unreachable: list[dict] = []
          for p in pkgs:
              name = p["name"]
              doc = p["documented_major"]
              status, info = npm_latest(registry, name, timeout)
              if status == "gone":
                  drift.append({"package": name, "issue": "no longer resolves on npm (404)"})
              elif status != "ok":
                  unreachable.append({"package": name, "issue": f"unreachable: {info}"})
              else:
                  live_major = major_of(str(info))
                  if live_major is None:
                      unreachable.append({"package": name, "issue": f"could not parse version {info!r}"})
                  elif live_major > doc:
                      drift.append({"package": name,
                                    "issue": f"npm@{info} major {live_major} > documented major {doc}"})
          return drift, unreachable
      
      
      def main(argv: list[str]) -> int:
          p = argparse.ArgumentParser(
              prog="check-typescript-facts.py",
              description="Verify typescript-ops' version-bearing facts stay cited (offline) and current on npm (live).",
          )
          mode = p.add_mutually_exclusive_group()
          mode.add_argument("--offline", action="store_true", help="structural consistency, no network (default)")
          mode.add_argument("--live", action="store_true", help="check every package's latest major still matches npm")
          p.add_argument("--catalog", default=str(DEFAULT_CATALOG), help="facts catalog JSON")
          p.add_argument("--skill", default=str(DEFAULT_SKILL), help="skill directory (SKILL.md + references/)")
          p.add_argument("--timeout", type=float, default=10.0, help="per-request timeout seconds (live)")
          p.add_argument("--json", action="store_true", help="emit a JSON envelope")
          try:
              args = p.parse_args(argv)
          except SystemExit as exc:
              return EX_USAGE if exc.code not in (0, None) else (exc.code or EX_OK)
      
          pkgs, registry = load_catalog(Path(args.catalog))
          live = args.live and not args.offline
          t = Term(sys.stderr)
      
          if live:
              drift, unreachable = check_live(pkgs, registry, args.timeout)
              findings = drift + unreachable
              if args.json:
                  print(json.dumps({
                      "data": findings,
                      "meta": {"mode": "live", "packages_checked": len(pkgs),
                               "drift": len(drift), "unreachable": len(unreachable),
                               "registry": registry, "schema": SCHEMA},
                  }, indent=2))
              else:
                  for f in drift:
                      print(f"DRIFT  {f['package']}: {f['issue']}")
                  for f in unreachable:
                      print(f"UNREACH  {f['package']}: {f['issue']}")
              # §7: confirmed drift -> 10; else transient/unreachable -> 7 (advisory); else 0.
              if drift:
                  eprint(f"{t.mark(False)} ts-facts/live: {len(drift)} package(s) drifted from documented major "
                         f"{t.c('dim', '(' + registry + ')')}")
                  return EX_DRIFT
              if unreachable:
                  eprint(f"{t.mark(False)} ts-facts/live: npm unreachable for "
                         f"{len(unreachable)}/{len(pkgs)} {t.c('dim', '(advisory - retry next run)')}")
                  return EX_UNAVAILABLE
              eprint(f"{t.mark(True)} ts-facts/live: all {len(pkgs)} package(s) match documented major on npm")
              return EX_OK
      
          # offline (default)
          findings = check_offline(pkgs, Path(args.skill))
          if args.json:
              print(json.dumps({
                  "data": findings,
                  "meta": {"mode": "offline", "packages_checked": len(pkgs),
                           "drift": len(findings), "consistent": not findings, "schema": SCHEMA},
              }, indent=2))
          else:
              for f in findings:
                  print(f"DRIFT  {f['package']}: {f['issue']}")
          ok = not findings
          eprint(f"{t.mark(ok)} ts-facts/offline: {len(pkgs)} package(s) checked, "
                 f"{len(findings)} inconsistency {t.c('dim', '(catalog vs skill prose)')}")
          return EX_DRIFT if findings else EX_OK
      
      
      if __name__ == "__main__":
          sys.exit(main(sys.argv[1:]))
      
  • tests
    • run.sh 5.2 KB
      #!/usr/bin/env bash
      # Self-test for the typescript-ops skill.
      #
      # Offline-deterministic (no network, no TypeScript compiler required). Asserts
      # structural integrity (frontmatter, references present + linked) and — the
      # load-bearing check — the staleness verifier contract (SKILL-RESOURCE-PROTOCOL
      # §7, §10): the catalogued version-bearing facts (TypeScript major, zod,
      # valibot) stay named in the prose and the dated currency note stays present.
      # Resolves paths relative to itself so it works in the repo and once installed
      # to ~/.claude/skills/typescript-ops/.
      #
      # Usage:   bash tests/run.sh
      # Exit:    0 all pass, 1 one or more failures
      
      set -uo pipefail
      
      HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
      SKILL="$(dirname "$HERE")"
      DOC="$SKILL/SKILL.md"
      REF="$SKILL/references"
      
      PASS=0; FAIL=0
      ok() { PASS=$((PASS+1)); printf '  PASS  %s\n' "$1"; }
      no() { FAIL=$((FAIL+1)); printf '  FAIL  %s\n' "$1"; }
      has() { case "$2" in *"$1"*) ok "$3";; *) no "$3 (missing '$1')";; esac; }
      
      echo "=== typescript-ops self-test ==="
      
      # ── SKILL.md frontmatter ───────────────────────────────────────────────────
      echo "-- frontmatter --"
      [[ -f "$DOC" ]] && ok "SKILL.md present" || { no "SKILL.md missing"; echo "=== $PASS passed, $FAIL failed ==="; exit 1; }
      [[ "$(sed -n '1p' "$DOC")" == "---" ]] && ok "frontmatter fence opens at line 1" || no "no opening frontmatter fence"
      doc="$(cat "$DOC")"
      has 'name: typescript-ops' "$doc" "frontmatter declares name: typescript-ops"
      has 'description:'        "$doc" "frontmatter has description"
      has 'license: MIT'        "$doc" "frontmatter declares license"
      has 'author: claude-mods' "$doc" "frontmatter declares metadata.author"
      
      # ── references: the 6 documented files exist ───────────────────────────────
      echo "-- references present --"
      EXPECT=(type-system utility-types generics-patterns config-strict ts7-native-compiler ecosystem)
      for r in "${EXPECT[@]}"; do
        f="$REF/$r.md"
        [[ -f "$f" ]] && ok "$r.md present" || no "$r.md missing"
      done
      
      # ── every references/ citation in SKILL.md resolves (no ghost refs) ────────
      # typescript-ops cites its references as inline code spans (`./references/x.md`),
      # not markdown links — extract those spans and confirm each file exists.
      echo "-- cited references resolve --"
      linked=0; broken=0
      while IFS= read -r rel; do
        [[ -z "$rel" ]] && continue
        linked=$((linked+1))
        [[ -f "$SKILL/$rel" ]] || { no "SKILL.md cites missing file: $rel"; broken=$((broken+1)); }
      done < <(grep -oE '`(\./)?references/[^`#]+\.md`' "$DOC" | sed -E 's/^`//; s/`$//; s/^\.\///' | sort -u)
      [[ "$linked" -gt 0 ]] && ok "SKILL.md cites its references ($linked unique)" || no "SKILL.md cites no references"
      [[ "$broken" -eq 0 ]] && ok "all cited reference files resolve" || no "$broken reference citation(s) broken"
      
      # ── dated currency note present (verifier depends on it) ───────────────────
      echo "-- currency note --"
      grep -qE 'as of 20[0-9]{2}' "$DOC" && ok "dated 'as of 20XX' currency note present" || no "no dated currency note"
      
      # ── staleness verifier: offline contract (SKILL-RESOURCE-PROTOCOL §7) ───────
      echo "-- check-typescript-facts.py (offline) --"
      VERIFIER="$SKILL/scripts/check-typescript-facts.py"
      CATALOG="$SKILL/assets/typescript-facts.json"
      ec() { local want="$1" lbl="$2"; shift 2; "$@" >/dev/null 2>&1; local got=$?
             [[ "$got" == "$want" ]] && ok "$lbl (exit $got)" || no "$lbl (want $want got $got)"; }
      # Pick a python that actually executes — skips the Windows Store python3 stub.
      PY=""
      for c in python python3 py; do
        if command -v "$c" >/dev/null 2>&1 && "$c" -c "" >/dev/null 2>&1; then PY="$c"; break; fi
      done
      [[ -f "$VERIFIER" ]] && ok "verifier present" || no "verifier missing"
      [[ -f "$CATALOG"  ]] && ok "facts catalog present" || no "catalog missing"
      has "scripts/check-typescript-facts.py" "$doc" "verifier cited from SKILL.md"
      if [[ -n "$PY" ]]; then
        TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
        ec 0 "py_compile"            "$PY" -m py_compile "$VERIFIER"
        ec 0 "--help"                "$PY" "$VERIFIER" --help
        ec 0 "--offline consistent"  "$PY" "$VERIFIER" --offline
        ec 2 "bad flag -> 2"         "$PY" "$VERIFIER" --bogus
        ec 2 "conflicting modes -> 2" "$PY" "$VERIFIER" --offline --live
        jout="$("$PY" "$VERIFIER" --offline --json 2>/dev/null)"
        has 'claude-mods.typescript-ops.facts/v1' "$jout" "--json envelope schema"
        ec 3 "missing catalog -> 3"  "$PY" "$VERIFIER" --offline --catalog "$TMP/nope.json"
        printf '{"packages":"x"}' > "$TMP/bad.json"
        ec 4 "malformed catalog -> 4" "$PY" "$VERIFIER" --offline --catalog "$TMP/bad.json"
        printf '{"packages":[{"name":"zzznotreal","documented_major":3}]}' > "$TMP/drift.json"
        ec 10 "uncited package -> 10" "$PY" "$VERIFIER" --offline --catalog "$TMP/drift.json"
      else
        no "no working python to exercise the verifier"
      fi
      
      # ── summary ────────────────────────────────────────────────────────────────
      echo "=== $PASS passed, $FAIL failed ==="
      [[ "$FAIL" -eq 0 ]] || exit 1
      
  • SKILL.md 13 KB
    ---
    name: typescript-ops
    description: "TypeScript type system, generics, utility types, strict mode, and ecosystem patterns. Use for: typescript, ts, type, generic, utility type, Partial, Pick, Omit, Record, Exclude, Extract, ReturnType, Parameters, keyof, typeof, infer, mapped type, conditional type, template literal type, discriminated union, type guard, type assertion, type narrowing, tsconfig, strict mode, declaration file, zod, valibot, typescript 7, tsgo, native compiler."
    license: MIT
    allowed-tools: "Read Write Bash"
    metadata:
      author: claude-mods
      related-skills: react-ops, testing-ops
    ---
    
    # TypeScript Operations
    
    Comprehensive TypeScript skill covering the type system, generics, and production patterns.
    
    > Ecosystem facts verified as of 2026-08-08 (TypeScript 7, Zod 4, Valibot 1).
    
    **Staleness check:** `python scripts/check-typescript-facts.py --offline` asserts the
    catalogued version-bearing facts (TypeScript major, zod, valibot) are still named in the
    prose and the dated currency note above is present; run `--live` to confirm each package's
    npm major still matches the documented major. Catalog: `assets/typescript-facts.json`.
    
    ## 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
    
    ```typescript
    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
    
    ```typescript
    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
    
    ```typescript
    // 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
    
    ```typescript
    // 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
    
    ```typescript
    // 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.
    
    ## Modern Language Features (TypeScript 5.x → 6.0)
    
    | Feature | Since | What It Gives You |
    |---------|-------|-------------------|
    | `satisfies` operator | 4.9 | Check a value against a type without widening it |
    | Standard (TC39) decorators | 5.0 | `@decorator` on classes/methods without `experimentalDecorators` |
    | `const` type parameters | 5.0 | `function f<const T>(x: T)` infers literal types without `as const` at call sites |
    | `using` declarations | 5.2 | Explicit resource management (`Symbol.dispose`), auto-cleanup at scope exit |
    | Inferred type predicates | 5.5 | `arr.filter(x => x !== null)` narrows without a hand-written `x is T` guard |
    | `verbatimModuleSyntax` | 5.0 | Enforces `import type` for type-only imports — replaces `importsNotUsedAsValues` |
    
    ```typescript
    // const type parameters (5.0) - literal inference without as const
    function routes<const T extends readonly string[]>(paths: T): T { return paths; }
    const r = routes(["/home", "/about"]); // readonly ["/home", "/about"], not string[]
    
    // using declarations (5.2) - deterministic cleanup
    function readConfig() {
        using file = openFile("config.json"); // file[Symbol.dispose]() runs at scope exit
        return parse(file.contents);
    }
    
    // Inferred type predicates (5.5) - no manual guard needed
    const names = ["a", null, "b"].filter(x => x !== null); // string[], not (string | null)[]
    ```
    
    ### TypeScript 6.0 (The Bridge Release)
    
    TS 6.0 is the last release on the JavaScript-based compiler — it exists to bridge to the
    native (Go) compiler in TS 7, so its headline is stricter, modernised defaults:
    
    - **`strict: true` is the default** — a tsconfig that never set it now gets full strict checks
    - **Defaults modernised**: `module: esnext`, `target: es2025`; `es2025` lib ships types for Temporal, `Map.getOrInsert`, `RegExp.escape`
    - **Legacy options removed**: `moduleResolution: classic`; `module: amd/umd/system/none`; minimum `target` is now ES2015 (`es5` deprecated)
    - **Interop always on**: `esModuleInterop` / `allowSyntheticDefaultImports` can no longer be disabled
    - New `--stableTypeOrdering` flag eases 6.0 → 7.0 migration diffing
    
    ### TypeScript 7 (Current Major — Native Compiler)
    
    TS 7 is the Go-native rewrite (formerly `tsgo`), stable on npm since 2026-07-08.
    Same checking semantics, ~8–16× faster typechecks — but the package ships **only a
    `bin/tsc` shim, no JavaScript compiler API**: `require('typescript')` throws
    `MODULE_NOT_FOUND` on 7.0.x (the API returns in 7.1+). Consequences that gate
    adoption:
    
    - Repo tooling using the TS programmatic API (`ts.createSourceFile`, custom lint
      scripts, codemods, typescript-eslint) must go AST-free, switch parser, or pin an alias
    - Tools typechecking embedded languages (vue-tsc, svelte-check, Astro, MDX) are
      pinned to TS 6 until they port to the 7.1+ API — a real stack-selection input
    - A tsconfig already on TS 6 defaults adopts directly (the 6.0 bridge is skippable);
      `baseUrl` is a hard error (TS5102)
    - Running a `typescript5` fallback alias alongside 7 makes bare `npx tsc` ambiguous —
      scripts must use explicit compiler paths during the soak
    
    **Deep dive**: Load `./references/ts7-native-compiler.md` for the no-JS-API workarounds,
    dual-install bin ambiguity, ecosystem lockout table, measured adoption benchmarks (12.1×),
    and the go/no-go checklist.
    
    ## tsconfig Quick Reference
    
    ```jsonc
    {
        "compilerOptions": {
            // Strict mode (default in TS 6; state it explicitly anyway)
            "strict": true,               // Enables all strict checks
            "noUncheckedIndexedAccess": true,  // arr[0] is T | undefined
    
            // Module system (TS 6 defaults to module: esnext; interop is always on)
            "module": "esnext",           // or "nodenext" for Node
            "moduleResolution": "bundler", // or "nodenext"
    
            // Output (TS 6 defaults target to es2025; min supported is es2015)
            "target": "es2022",
            "outDir": "dist",
            "declaration": true,          // Generate .d.ts
            "sourceMap": true,
    
            // Paths — tsconfig-relative; don't add baseUrl (TS 7 hard-errors on it, TS5102)
            "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
    
    ```typescript
    // 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 4)
    
    ```typescript
    import { z } from "zod";
    
    // Define schema (Zod 4: string formats are top-level - z.email(), not z.string().email())
    const UserSchema = z.object({
        id: z.number(),
        name: z.string().min(1),
        email: z.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 }
    ```
    
    **Zod 4 changes to know** (if you learned Zod 3): string formats moved to the top level
    (`z.email()`, `z.uuid()`, `z.url()` — the `z.string().email()` method form is deprecated);
    error customisation unified under a single `error` param (`invalid_type_error` /
    `required_error` dropped); much faster parsing and a tree-shakeable `zod/mini` entry point.
    
    ## 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/ts7-native-compiler.md` | Adopting the TS 7 native (Go) compiler: no JS API, bin ambiguity, ecosystem lockout, benchmarked go/no-go |
    | `./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
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related