T

Typescript Expert Engine

A skill template for development workflows. Streamlines development with pre-configured patterns and best practices.

SkillClipticsdevelopmentv1.0.0MIT
0 views0 copies

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

PatternPurposeExample
Conditional TypesType branching based on conditionsT extends string ? X : Y
Mapped TypesTransform all properties{ [K in keyof T]: Readonly<T[K]> }
Template LiteralsString type manipulation`on${Capitalize<string>}`
InferExtract types from structuresT extends Promise<infer U> ? U : T
Discriminated UnionsType-safe state machines{ type: 'success'; data: T } | { type: 'error'; error: E }
Branded TypesNominal typing in structural systemtype 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

ParameterTypeDefaultDescription
strictbooleantrueEnable all strict type-checking options
targetstring'ES2022'ECMAScript target version
moduleResolutionstring'bundler'Module resolution: bundler, node16, or nodenext
exactOptionalPropertyTypesbooleantrueDistinguish undefined from missing
noUncheckedIndexedAccessbooleantrueAdd undefined to index signatures
verbatimModuleSyntaxbooleantrueEnforce type-only import syntax

Best Practices

  1. 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. noUncheckedIndexedAccess catches the most common source of runtime undefined errors.

  2. 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 }.

  3. Prefer unknown over any for untyped valuesany disables type checking entirely. unknown forces you to narrow the type before using it, catching errors at compile time. Use any only at interop boundaries with extreme caution.

  4. Use branded types for domain identifiersstring doesn't distinguish a userId from an orderId. Branded types (type UserId = string & { __brand: 'UserId' }) prevent accidentally passing the wrong ID to functions.

  5. 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.

Community

Reviews

Write a review

No reviews yet. Be the first to review this template!

Similar Templates