Typescript Expert Engine
A skill template for development workflows. Streamlines development with pre-configured patterns and best practices.
TypeScript Expert Engine
An advanced skill for TypeScript type-level programming, performance optimization, and real-world problem solving. Covers utility types, conditional types, template literal types, declaration files, and patterns for building type-safe libraries and applications.
When to Use This Skill
Choose this skill when:
- Designing complex type systems with conditional and mapped types
- Building type-safe API clients, ORMs, or configuration systems
- Debugging TypeScript compiler errors and performance issues
- Writing declaration files for untyped JavaScript libraries
- Migrating JavaScript projects to TypeScript with strict mode
Consider alternatives when:
- Learning TypeScript basics → use a TypeScript fundamentals resource
- Working with a specific framework → use that framework's TypeScript skill
- Need general JavaScript patterns → use a JavaScript skill
- Building a full application → use a fullstack skill
Quick Start
// Type-safe event emitter with autocomplete type EventMap = { 'user:login': { userId: string; timestamp: number }; 'user:logout': { userId: string }; 'order:created': { orderId: string; total: number }; 'order:shipped': { orderId: string; trackingId: string }; }; class TypedEventEmitter<T extends Record<string, any>> { private handlers = new Map<string, Set<Function>>(); on<K extends keyof T>(event: K, handler: (payload: T[K]) => void): void { if (!this.handlers.has(event as string)) { this.handlers.set(event as string, new Set()); } this.handlers.get(event as string)!.add(handler); } emit<K extends keyof T>(event: K, payload: T[K]): void { this.handlers.get(event as string)?.forEach(fn => fn(payload)); } } const emitter = new TypedEventEmitter<EventMap>(); emitter.on('user:login', ({ userId, timestamp }) => { /* typed */ }); emitter.emit('order:created', { orderId: '123', total: 99.99 });
Core Concepts
Type-Level Programming Patterns
| Pattern | Purpose | Example |
|---|---|---|
| Conditional Types | Type branching based on conditions | T extends string ? X : Y |
| Mapped Types | Transform all properties | { [K in keyof T]: Readonly<T[K]> } |
| Template Literals | String type manipulation | `on${Capitalize<string>}` |
| Infer | Extract types from structures | T extends Promise<infer U> ? U : T |
| Discriminated Unions | Type-safe state machines | { type: 'success'; data: T } | { type: 'error'; error: E } |
| Branded Types | Nominal typing in structural system | type UserId = string & { __brand: 'UserId' } |
Advanced Utility Types
// Deep partial — makes all nested properties optional type DeepPartial<T> = { [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K]; }; // Path type — dot-notation access to nested properties type Path<T, Prefix extends string = ''> = { [K in keyof T & string]: T[K] extends object ? Path<T[K], `${Prefix}${K}.`> | `${Prefix}${K}` : `${Prefix}${K}`; }[keyof T & string]; // Type-safe get function function get<T, P extends Path<T>>(obj: T, path: P): PathValue<T, P> { return path.split('.').reduce((acc: any, key) => acc?.[key], obj); } // Strict omit — errors if key doesn't exist type StrictOmit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>; // Builder pattern with type tracking type Builder<T, Built extends Partial<T> = {}> = { [K in keyof Omit<T, keyof Built>]: ( value: T[K] ) => Builder<T, Built & Pick<T, K>>; } & ([keyof Omit<T, keyof Built>] extends [never] ? { build: () => T } : {});
Declaration File Patterns
// Typing a third-party JavaScript library declare module 'legacy-lib' { export interface Config { apiKey: string; baseUrl?: string; timeout?: number; } export class Client { constructor(config: Config); get<T>(path: string, params?: Record<string, string>): Promise<T>; post<T>(path: string, body: unknown): Promise<T>; } export function createClient(config: Config): Client; export default createClient; } // Augmenting existing types (module augmentation) declare module 'express' { interface Request { user?: { id: string; email: string; role: string }; correlationId: string; } }
Configuration
| Parameter | Type | Default | Description |
|---|---|---|---|
strict | boolean | true | Enable all strict type-checking options |
target | string | 'ES2022' | ECMAScript target version |
moduleResolution | string | 'bundler' | Module resolution: bundler, node16, or nodenext |
exactOptionalPropertyTypes | boolean | true | Distinguish undefined from missing |
noUncheckedIndexedAccess | boolean | true | Add undefined to index signatures |
verbatimModuleSyntax | boolean | true | Enforce type-only import syntax |
Best Practices
-
Enable strict mode and noUncheckedIndexedAccess from the start — Enabling strict options later requires fixing thousands of errors. Start strict and every new file is type-safe by default.
noUncheckedIndexedAccesscatches the most common source of runtimeundefinederrors. -
Use discriminated unions for state management — Instead of
{ loading: boolean; error: Error | null; data: T | null }where invalid states are representable, use{ type: 'loading' } | { type: 'error'; error: Error } | { type: 'success'; data: T }. -
Prefer
unknownoveranyfor untyped values —anydisables type checking entirely.unknownforces you to narrow the type before using it, catching errors at compile time. Useanyonly at interop boundaries with extreme caution. -
Use branded types for domain identifiers —
stringdoesn't distinguish auserIdfrom anorderId. Branded types (type UserId = string & { __brand: 'UserId' }) prevent accidentally passing the wrong ID to functions. -
Keep type complexity below 5 levels of nesting — Deeply nested conditional types are unreadable and slow to compile. Extract intermediate types with descriptive names. If a type is hard to understand, it's too complex.
Common Issues
TypeScript compiler extremely slow — Deep conditional types, large unions (>25 members), and complex mapped types slow compilation. Use --generateTrace to profile. Simplify recursive types, avoid distributing over large unions, and use interface extends instead of intersection types.
Type inference loses specificity with generics — TypeScript sometimes infers wider types than expected (e.g., string instead of 'active'). Use as const for literal types, add explicit generic constraints, or use the satisfies operator to preserve narrow types while checking assignability.
Module augmentation doesn't take effect — Augmentation files must be treated as modules (have at least one import/export). An augmentation file without imports is treated as a script and overwrites rather than augments. Add export {} if no other exports exist.
Reviews
No reviews yet. Be the first to review this template!
Similar Templates
Full-Stack Code Reviewer
Comprehensive code review skill that checks for security vulnerabilities, performance issues, accessibility, and best practices across frontend and backend code.
Test Suite Generator
Generates comprehensive test suites with unit tests, integration tests, and edge cases. Supports Jest, Vitest, Pytest, and Go testing.
Pro Architecture Workspace
Battle-tested skill for architectural, decision, making, framework. Includes structured workflows, validation checks, and reusable patterns for development.