TypeScript Patterns
Type Safety
- •No
any- Ask user first. Offer: generic, interface, or discriminated union - •
unknownrequires type guard -function isX(v: unknown): v is X - •Generics over loose types -
function first<T>(items: T[]): T | undefined - •Branded types for IDs -
type UserId = string & { readonly __brand: 'UserId' }
Architecture
- •No hardcoding - Use config, environment, or injection
- •Single responsibility - One purpose per function/class
- •Dependency injection - Inject interfaces, not implementations
- •Event-driven decoupling - TypedEventEmitter for cross-cutting concerns
Async Patterns
| Need | Pattern |
|---|---|
| Multiple independent fetches | Promise.all([...]) |
| Partial failure handling | Promise.allSettled([...]) |
| First response wins / timeout | Promise.race([...]) |
| Streaming / pagination | async function* generator() |
| Cancelable operations | AbortController + signal |
| Concurrency limiting | Semaphore or mapWithLimit |
| Shared state protection | Mutex |
| Retry with backoff | retry(fn, { attempts, delay, backoff }) |
See reference files for implementations.
References
- •references/async.ts - withTimeout, retry, paginate, collect, deferred, debounceAsync
- •references/concurrency.ts - Mutex, Semaphore, mapWithLimit
- •references/result.ts - Result type with ok, err, unwrap, tryCatch
- •references/typed-emitter.ts - TypedEventEmitter
Checklist
- •
any? → Generic or interface - •
unknown? → Type guard - •Hardcoded value? → Config or inject
- •Multiple responsibilities? → Split
- •Hard import? → Inject interface
- •Decoupling needed? → EventEmitter
- •Streaming/pagination? → AsyncGenerator
- •Cancelable? → AbortController
- •Multiple fetches? → Promise.all or allSettled
- •Need fallback/timeout? → Promise.race
- •Shared state? → Mutex
- •Too many parallel ops? → Semaphore