TypeScript
Microsoft's statically typed superset of JavaScript that catches property access errors, wrong argument types, and missing null checks at compile time before they reach production
---
name: typescript
description: >
You are a TypeScript developer. You write type-safe JavaScript with strict mode enabled, discriminated unions for state machines, and Zod validation at external data boundaries.
---TypeScript adds a static type system to JavaScript that catches a specific class of bugs at compile time - undefined property access, wrong argument types, unhandled null - that would otherwise surface as runtime errors in production. Enable strict mode from day one; retrofitting it on an existing codebase requires fixing hundreds of implicit any and nullable errors. TypeScript types are erased at runtime, so external data (API responses, form input, localStorage) must be validated separately with a runtime schema library like Zod.
Workflow
- Initialize with strict mode in tsconfig.json: set "strict": true before writing any codeStrict mode enables strictNullChecks, noImplicitAny, and strictFunctionTypes simultaneously - without it TypeScript allows implicit any and non-null values to propagate silently and catches almost nothing useful
- Model state machines as discriminated unions with a shared discriminant fieldA discriminated union's exhaustive switch statement is checked at compile time - add a new state variant and every switch that does not handle it becomes a type error, catching the missing case before it ships
- Use unknown instead of any for values whose type is not known at call timeany disables type checking for that value and every value derived from it, propagating unsafety silently; unknown forces you to narrow the type with a type guard before using it
- Validate all external data with Zod or Valibot at the system boundary - API responses, form input, URL params, localStorageTypeScript types are compile-time only; an API that changes its response shape at runtime produces a runtime crash, not a type error - Zod.parse() throws at the boundary instead of silently letting wrong-shaped data propagate
- Add tsc --noEmit as a dedicated CI step separate from the build stepBuild tools (Babel, esbuild, ts-loader with transpileOnly) strip types without checking them; a separate tsc --noEmit step ensures type errors block the pipeline even when the build succeeds
- Use the satisfies operator for config objects and record types where you want type checking without losing literal typesconst config: Config = {...} widens string literals to string; satisfies checks the value against Config while preserving the narrow literal types for autocomplete and exhaustiveness
Output Format
A well-typed TypeScript module exports named types alongside its functions, uses discriminated unions for variant data, and validates external inputs with Zod at the entry point. Internal functions are typed by inference; external APIs are typed with explicit return types.
// State machine
type State =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: Data }
| { status: 'error'; error: Error };
// External validation
const DataSchema = z.object({ id: z.string(), name: z.string() });
type Data = z.infer<typeof DataSchema>;
const fetchData = async (id: string): Promise<Data> => {
const raw = await api.get(id);
return DataSchema.parse(raw); // throws if shape is wrong
};Examples
Exhaustive switch on discriminated union
Handle all states of a fetch: idle, loading, success, error
switch (state.status) { case 'idle': ... case 'loading': ... case 'success': return state.data; case 'error': return state.error; default: const _: never = state; } - adding a new status variant without updating the switch is a compile error
Wrong: any on an API response
const res = await fetch('/api/user'); const data: any = await res.json();
data.namee.first silently compiles and crashes at runtime with Cannot read properties of undefined - any opts out of every type check on this value and everything derived from it
Right: Zod parse at the API boundary
const raw = await fetch('/api/user').then(r => r.json());
const user = UserSchema.parse(raw); - throws ZodError immediately if the API response shape is wrong, with a clear message about which field failed, instead of a cryptic runtime crash downstream
Edge Cases & Gotchas
- Type assertion (as SomeType) hiding a real mismatch - silently passes type checking but crashes at runtime when the value actually has a different shape. Fix: prefer type guards (instanceof, typeof, in) or Zod parse over assertions for external data
- Non-null assertion (!) on a value that can actually be null - common on DOM queries and optional chaining results. Fix: check explicitly with if (el) or use optional chaining (?.) instead of asserting
- Circular type references causing deep generic types to hang tsc - common with recursive tree types and complex mapped types. Fix: add an explicit type annotation to break the inference cycle or use interface instead of type alias for recursive types
- Declaration merging in module augmentation accidentally widening a third-party type - adding a property to Express.Request via module augmentation affects all uses of Request in the project. Fix: namespace augmentation carefully and add a comment explaining the intent
When to Break These Rules
- Use type assertions (as Type) for well-understood framework patterns where TypeScript inference cannot follow - React forwardRef, generic HOCs, and some ORM query builder return types are legitimate cases
- Write a .d.ts ambient declaration for a third-party library with no types rather than sprinkling @ts-ignore throughout - one declaration file contains the unsafety instead of spreading it
Reference Files
src/data/skills/index.js- add skill entries heresrc/components/Skills/DocRenderer.js- renders this doc format