- name
- typescript-expert
- description
- Solves TypeScript/JS problems and refactors TS for type safety: narrowing, branded types, deep-instantiation errors, strict tsconfig, module resolution, JS-to-TS migration.
- metadata
- {"portable_source":"https://github.com/ericlitman/open-pstack","portable_commit":"1b03678171f6f400ae2cc9dc4e7a4a6a13e4bb43","category":"framework","risk":"critical","date_added":"2026-02-27","version":"2.2.2","tags":"typescript, javascript, tooling"}
- when_to_use
- type performance, monorepo config, satisfies, branded types, remove as casts
# TypeScript Expert
## When invoked
1. Analyze project setup comprehensively:
**Prefer built-in file-reading and search capabilities for performance. Shell commands are fallbacks.**
```bash
# Core versions and configuration
bunx tsc --version
node -v
# Detect tooling ecosystem (prefer parsing package.json)
node -e "const p=require('./package.json');console.log(Object.keys({...p.devDependencies,...p.dependencies}||{}).join('\n'))" 2>/dev/null | grep -E 'biome|eslint|prettier|vitest|jest|turborepo|nx' || echo "No tooling detected"
# Check for monorepo (fixed precedence)
(test -f pnpm-workspace.yaml || test -f lerna.json || test -f nx.json || test -f turbo.json) && echo "Monorepo detected"
```
**After detection, adapt approach:**
- Match import style (absolute vs relative)
- Respect existing baseUrl/paths configuration
- Prefer existing project scripts over raw tools
- In monorepos, consider project references before broad tsconfig changes
2. Identify the specific problem category and complexity level
3. Apply the appropriate solution strategy from the expertise below
4. Validate thoroughly:
```bash
# Fast fail approach (avoid long-lived processes)
bun run typecheck || bunx tsc --noEmit
bun run test || bunx vitest run --reporter=basic --no-watch
# Only if needed and build affects outputs/config
bun run build
```
**Safety note:** Avoid watch/serve processes in validation. Use one-shot diagnostics only.
## Advanced Type System Expertise
### Type-Level Programming Patterns
**Branded Types for Domain Modeling** — nominal types (`type UserId = Brand<string, 'UserId'>`) prevent accidentally mixing domain primitives that share a base type. Use for critical domain primitives, API boundaries, currency/units. Rule: `references/rules/arch-branded-types.md`; cheatsheet: `references/typescript-cheatsheet.md` (§ Branded Types). Resource: https://egghead.io/blog/using-branded-types-in-typescript
**Advanced Conditional Types** — recursive type manipulation (e.g. `DeepReadonly<T>`) and template-literal event-source APIs. Use for library APIs, type-safe event systems, compile-time validation. Watch for type instantiation depth errors (limit recursion to 10 levels). See `references/typescript-cheatsheet.md` (§ Conditional Types, § Mapped Types, § Template Literal Types) and `references/rules/compile-avoid-deep-recursion.md`.
**Type Inference Techniques** — use `satisfies` (TS 5.0+) for constraint validation while preserving literal types; use `as const` assertions for maximum inference on literal arrays/objects. Rules: `references/rules/arch-satisfies-over-annotation.md`, `references/rules/arch-const-assertion.md`.
### Refactoring Rules
Refactoring or modernizing TypeScript for type safety (replacing `as` casts with narrowing, discriminated unions over enums, typed error handling, TS 4.x-5.x features, compile speed) draws on a 43-rule library in `references/rules/`. Open only the rules that apply, one file per rule. `references/rules/_sections.md` defines the categories and `_template.md` the shape of a new rule.
| Priority | Category | Prefix |
|----------|----------|--------|
| 1 | Type Architecture (CRITICAL) | `arch-` |
| 2 | Type Narrowing & Guards (CRITICAL) | `narrow-` |
| 3 | Modern TypeScript (HIGH) | `modern-` |
| 4 | Generic Patterns (HIGH) | `generic-` |
| 5 | Compiler Performance (MEDIUM-HIGH) | `compile-` |
| 6 | Error Safety (MEDIUM) | `error-` |
| 7 | Runtime Patterns (MEDIUM) | `perf-` |
| 8 | Quirks & Pitfalls (LOW-MEDIUM) | `quirk-` |
#### 1. Type Architecture (CRITICAL)
- [`arch-discriminated-unions`](references/rules/arch-discriminated-unions.md) — Use discriminated unions over string enums for exhaustive pattern matching
- [`arch-branded-types`](references/rules/arch-branded-types.md) — Use branded types for domain identifiers to prevent value mix-ups
- [`arch-satisfies-over-annotation`](references/rules/arch-satisfies-over-annotation.md) — Use `satisfies` for config objects to preserve literal types
- [`arch-interfaces-over-intersections`](references/rules/arch-interfaces-over-intersections.md) — Extend interfaces instead of intersecting types for better error messages
- [`arch-const-assertion`](references/rules/arch-const-assertion.md) — Use `as const` for immutable literal inference
- [`arch-readonly-by-default`](references/rules/arch-readonly-by-default.md) — Default to readonly types for function parameters and return values
- [`arch-avoid-partial-abuse`](references/rules/arch-avoid-partial-abuse.md) — Avoid `Partial<T>` abuse for builder patterns
#### 2. Type Narrowing & Guards (CRITICAL)
- [`narrow-custom-type-guards`](references/rules/narrow-custom-type-guards.md) — Write custom type guards instead of type assertions
- [`narrow-assertion-functions`](references/rules/narrow-assertion-functions.md) — Use assertion functions for precondition checks
- [`narrow-exhaustive-switch`](references/rules/narrow-exhaustive-switch.md) — Enforce exhaustive switch with `never`
- [`narrow-in-operator`](references/rules/narrow-in-operator.md) — Narrow with the `in` operator for interface unions
- [`narrow-eliminate-as-casts`](references/rules/narrow-eliminate-as-casts.md) — Eliminate `as` casts with proper narrowing chains
- [`narrow-typeof-chains`](references/rules/narrow-typeof-chains.md) — Use `typeof` narrowing before property access
#### 3. Modern TypeScript (HIGH)
- [`modern-using-keyword`](references/rules/modern-using-keyword.md) — Use the `using` keyword for resource cleanup
- [`modern-const-type-parameters`](references/rules/modern-const-type-parameters.md) — Use const type parameters for literal inference
- [`modern-template-literal-types`](references/rules/modern-template-literal-types.md) — Use template literal types for string patterns
- [`modern-noinfer-utility`](references/rules/modern-noinfer-utility.md) — Use `NoInfer` to control type parameter inference
- [`modern-accessor-keyword`](references/rules/modern-accessor-keyword.md) — Use `accessor` for auto-generated getters and setters
- [`modern-verbatim-module-syntax`](references/rules/modern-verbatim-module-syntax.md) — Enable `verbatimModuleSyntax` for explicit import types
#### 4. Generic Patterns (HIGH)
- [`generic-infer-over-annotate`](references/rules/generic-infer-over-annotate.md) — Let TypeScript infer instead of explicit annotation
- [`generic-constrain-dont-overconstrain`](references/rules/generic-constrain-dont-overconstrain.md) — Constrain generics minimally
- [`generic-avoid-distributive-surprises`](references/rules/generic-avoid-distributive-surprises.md) — Control distributive conditional types
- [`generic-mapped-type-utilities`](references/rules/generic-mapped-type-utilities.md) — Build custom mapped types for repeated transformations
- [`generic-return-type-inference`](references/rules/generic-return-type-inference.md) — Preserve return type inference in generic functions
#### 5. Compiler Performance (MEDIUM-HIGH)
- [`compile-explicit-return-types`](references/rules/compile-explicit-return-types.md) — Add explicit return types to exported functions
- [`compile-avoid-deep-recursion`](references/rules/compile-avoid-deep-recursion.md) — Avoid deeply recursive type definitions
- [`compile-project-references`](references/rules/compile-project-references.md) — Use project references for monorepo builds
- [`compile-base-types-over-unions`](references/rules/compile-base-types-over-unions.md) — Use base types instead of large union types
#### 6. Error Safety (MEDIUM)
- [`error-result-type`](references/rules/error-result-type.md) — Use Result types instead of thrown exceptions
- [`error-exhaustive-error-handling`](references/rules/error-exhaustive-error-handling.md) — Use exhaustive checks for typed error variants
- [`error-typed-catch`](references/rules/error-typed-catch.md) — Type catch clause variables as `unknown`
- [`error-never-for-unreachable`](references/rules/error-never-for-unreachable.md) — Use `never` to mark unreachable code paths
- [`error-discriminated-error-unions`](references/rules/error-discriminated-error-unions.md) — Model domain errors as discriminated unions
#### 7. Runtime Patterns (MEDIUM)
- [`perf-union-literals-over-enums`](references/rules/perf-union-literals-over-enums.md) — Use union literals instead of enums
- [`perf-avoid-delete-operator`](references/rules/perf-avoid-delete-operator.md) — Avoid the `delete` operator on objects
- [`perf-object-freeze-const`](references/rules/perf-object-freeze-const.md) — Use `Object.freeze` with `as const` for true immutability
- [`perf-object-keys-narrowing`](references/rules/perf-object-keys-narrowing.md) — Avoid `Object.keys` type widening
- [`perf-map-set-over-object`](references/rules/perf-map-set-over-object.md) — Use `Map` and `Set` over plain objects for dynamic collections
#### 8. Quirks & Pitfalls (LOW-MEDIUM)
- [`quirk-excess-property-checks`](references/rules/quirk-excess-property-checks.md) — Understand excess property checks on object literals
- [`quirk-empty-object-type`](references/rules/quirk-empty-object-type.md) — Avoid the `{}` type — it means non-nullish
- [`quirk-type-widening-let`](references/rules/quirk-type-widening-let.md) — Prevent type widening with `let` declarations
- [`quirk-variance-annotations`](references/rules/quirk-variance-annotations.md) — Use variance annotations for generic interfaces
- [`quirk-structural-typing-escapes`](references/rules/quirk-structural-typing-escapes.md) — Guard against structural typing escape hatches
Behavior-preserving restructuring stays with the `refactor-code` skill; it loads these rules for type-architecture work.
### Performance Optimization Strategies
**Type Checking Performance**
```bash
bunx tsc --extendedDiagnostics --incremental false | grep -E "Check time|Files:|Lines:|Nodes:"
```
Common fixes for "Type instantiation is excessively deep": replace type intersections with interfaces (`references/rules/arch-interfaces-over-intersections.md`), split large union types (>100 members; `references/rules/compile-base-types-over-unions.md`), avoid circular generic constraints, use type aliases to break recursion. Add explicit return types to exported functions (`references/rules/compile-explicit-return-types.md`).
**Build Performance Patterns**
- Enable `skipLibCheck: true` for library type checking only (often significantly improves performance on large projects, but avoid masking app typing issues)
- Use `incremental: true` with `.tsbuildinfo` cache
- Configure `include`/`exclude` precisely
- For monorepos: Use project references with `composite: true` (`references/rules/compile-project-references.md`)
## Real-World Problem Resolution
### Complex Error Patterns
**"The inferred type of X cannot be named"**
- Cause: Missing type export or circular dependency
- Fix priority: export the required type explicitly; use `ReturnType<typeof function>` helper; break circular dependencies with type-only imports
- Resource: https://github.com/microsoft/TypeScript/issues/47663
**Missing type declarations** — add an ambient `.d.ts` module declaration for untyped packages. See `references/typescript-cheatsheet.md` (§ Module Declarations). For more detail: [Declaration Files Guide](https://www.typescriptlang.org/docs/handbook/declaration-files/introduction.html)
**"Excessive stack depth comparing types"**
- Cause: Circular or deeply recursive types
- Fix priority: limit recursion depth with conditional types; use `interface` extends instead of type intersection; simplify generic constraints
```typescript
// Bad: Infinite recursion
type InfiniteArray<T> = T | InfiniteArray<T>[];
// Good: Limited recursion
type NestedArray<T, D extends number = 5> =
D extends 0 ? T : T | NestedArray<T, [-1, 0, 1, 2, 3, 4][D]>[];
```
**Module Resolution Mysteries** — "Cannot find module" despite the file existing:
1. Check `moduleResolution` matches your bundler
2. Verify `baseUrl` and `paths` alignment
3. For monorepos: Ensure workspace protocol (`workspace:*`)
4. Try clearing cache: `rm -rf node_modules/.cache .tsbuildinfo`
**Path Mapping at Runtime** — TypeScript paths only work at compile time, not runtime:
- ts-node: use `ts-node -r tsconfig-paths/register`
- Node ESM: use loader alternatives or avoid TS paths at runtime
- Production: pre-compile with resolved paths
### Migration Expertise
**JavaScript to TypeScript Migration** — incremental strategy: enable `allowJs`/`checkJs` in the existing tsconfig, rename files gradually (`.js` → `.ts`), add types file by file, enable strict mode features one by one. See `references/full-guide.md` (§ Migration Playbook) for the full command sequence and optional automated helpers (`ts-migrate`, `typesync`).
**Tool Migration Decisions**
| From | To | When | Migration Effort |
|------|-----|------|-----------------|
| ESLint + Prettier | Biome | Need much faster speed, okay with fewer rules | Low (1 day) |
| TSC for linting | Type-check only | Have 100+ files, need faster feedback | Medium (2-3 days) |
| Lerna | Nx/Turborepo | Need caching, parallel builds | High (1 week) |
| CJS | ESM | Node 18+, modern tooling | High (varies) |
### Monorepo Management
**Nx vs Turborepo Decision Matrix**
- Choose **Turborepo** if: Simple structure, need speed, <20 packages
- Choose **Nx** if: Complex dependencies, need visualization, plugins required
- Performance: Nx often performs better on large monorepos (>50 packages)
**TypeScript Monorepo Configuration** — root tsconfig `references` array plus `composite`/`declaration`/`declarationMap` per package. See `references/full-guide.md` (§ Monorepo TypeScript Configuration) for the full config.
## Modern Tooling Expertise
### Biome vs ESLint
**Use Biome when:** speed is critical, want a single tool for lint + format, TypeScript-first project, okay with 64 TS rules vs 100+ in typescript-eslint.
**Stay with ESLint when:** need specific rules/plugins, have complex custom rules, working with Vue/Angular (limited Biome support), need type-aware linting (Biome doesn't have this yet).
### Type Testing Strategies
**Vitest Type Testing (Recommended)** — write `.test-d.ts` files using `expectTypeOf` to assert on prop/return types at compile time. See `references/full-guide.md` (§ Vitest Type Testing) for a full example.
**When to Test Types:** publishing libraries, complex generic functions, type-level utilities, API contracts.
## Debugging Mastery
View on GitHub