| name | TypeScript |
| version | 0.1.0 |
| description | This skill should be used when the user asks to "write TypeScript", "create a TS module", "implement a TypeScript class", "model this type", "add types", "type this function", "use generics", "use branded types", "use discriminated unions", "fix this TypeScript", "refactor this TS code", "use modern TypeScript", "configure tsconfig", or any task involving writing, reviewing, or improving TypeScript code. Provides best practices for type modeling, modern language features (TypeScript 5.5-5.8), error handling, module patterns, and project configuration. This skill covers TypeScript (.ts/.tsx) files, NOT plain JavaScript with JSDoc. |
TypeScript
Best practices for writing modern, type-safe TypeScript. Covers type modeling, modern compiler features (5.5-5.8), error handling, module patterns, and project configuration.
This skill targets .ts and .tsx files. For plain JavaScript with JSDoc type annotations, use the JavaScript skill instead.
Project Setup
tsconfig.json
Enable strict mode in tsconfig.json for every TypeScript project:
{
"compilerOptions": {
"strict": true,
"target": "ES2024",
"module": "nodenext",
"moduleResolution": "nodenext",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"esModuleInterop": true,
"skipLibCheck": true,
"isolatedModules": true,
"verbatimModuleSyntax": true,
"noUncheckedIndexedAccess": true,
"noUncheckedSideEffectImports": true
},
"include": ["src/**/*.ts"],
"exclude": ["node_modules"]
}
Adjust target, module, and moduleResolution to match the project's runtime:
- Node.js projects:
"module": "nodenext", "moduleResolution": "nodenext"
- Bundler-driven projects:
"module": "preserve", "moduleResolution": "bundler"
- Direct execution (Node.js 22.6+): Add
"erasableSyntaxOnly": true to ensure all TS-specific syntax can be stripped without altering runtime behavior. Avoid enum, namespace, and parameter properties (constructor(public x: number)) when this flag is enabled.
Notable Compiler Options (5.5-5.8)
| Option | Version | Purpose |
|---|
noUncheckedSideEffectImports | 5.6 | Errors on unresolvable import "polyfill" |
erasableSyntaxOnly | 5.8 | Restricts to type-erasable syntax for direct execution |
verbatimModuleSyntax | 5.4 | Enforces explicit import type for type-only imports |
noUncheckedIndexedAccess | 4.1 | Adds undefined to index signature results |
Type Modeling
Prefer Inference Over Annotation
Let TypeScript infer types when the result is unambiguous. Annotate return types on public API surfaces and when inference would be too wide:
const count = items.length;
const doubled = numbers.map(n => n * 2);
function parseConfig(raw: string): AppConfig {
return JSON.parse(raw) as AppConfig;
}
Discriminated Unions
Model variant types using a shared literal discriminant property. Use exhaustiveness checking with never to catch unhandled cases:
type Result<T> =
| { kind: 'success'; value: T }
| { kind: 'error'; error: Error };
function handle<T>(result: Result<T>): T {
switch (result.kind) {
case 'success': return result.value;
case 'error': throw result.error;
default: {
const _exhaustive: never = result;
throw new Error(`Unhandled case: ${_exhaustive}`);
}
}
}
The satisfies Operator
Use satisfies to validate a value conforms to a type while preserving narrower inference. Prefer satisfies over as for type validation:
type Route = { path: string; auth: boolean };
type Routes = Record<string, Route>;
const annotated: Routes = { home: { path: '/', auth: false } };
annotated.home;
const checked = {
home: { path: '/', auth: false },
dashboard: { path: '/dash', auth: true },
} satisfies Routes;
checked.home;
const frozen = {
home: { path: '/', auth: false },
} as const satisfies Routes;
frozen.home.path;
Branded Types
Create nominally distinct types from the same underlying type to prevent accidental interchange:
declare const __brand: unique symbol;
type Brand<T, B extends string> = T & { readonly [__brand]: B };
type UserId = Brand<string, 'UserId'>;
type OrderId = Brand<string, 'OrderId'>;
function createUserId(id: string): UserId {
if (!id) throw new Error('Invalid user ID');
return id as UserId;
}
function lookupUser(id: UserId): User { }
lookupUser(createUserId('abc'));
lookupUser('abc');
NoInfer<T>
Use NoInfer (TS 5.4+) to control which parameter TypeScript infers a generic from:
function createFSM<TState extends string>(
states: TState[],
initial: NoInfer<TState>
) { }
createFSM(['idle', 'running', 'done'], 'idle');
createFSM(['idle', 'running', 'done'], 'invalid');
Const Type Parameters
Use const type parameters (TS 5.0+) to infer literal types instead of widened types:
function createRoutes<const T extends readonly Route[]>(routes: T) {
return routes;
}
For advanced patterns including template literal types, mapped types, conditional types, and complex generics, consult references/type-patterns.md.
Modern Language Features
Inferred Type Predicates (TS 5.5)
TypeScript 5.5 infers type predicates from function bodies. Manual type guards are no longer needed for simple narrowing:
const validUsers = users.filter(user => user !== null);
Inference requires: a single return statement, no parameter mutation, and a boolean expression tied to a parameter refinement. Truthiness checks (!!x) infer predicates for object types but not for numbers (where 0 is falsy).
Explicit Resource Management (Stage 3)
Use using and await using for automatic resource cleanup:
function readFile(path: string) {
using handle = openFile(path);
return handle.read();
}
async function connectDB() {
await using conn = await pool.getConnection();
return conn.query('SELECT 1');
}
Requires "lib": ["esnext.disposable"] in tsconfig. Check target runtime support โ Chrome 134+, not yet cross-browser.
Error Handling
Error Cause Chains
Wrap errors with context using the cause option:
try {
const data = JSON.parse(raw);
} catch (err) {
throw new Error('Failed to parse config', { cause: err });
}
Result Pattern
Prefer discriminated union results over thrown exceptions for expected failure cases:
type Result<T, E = Error> =
| { ok: true; value: T }
| { ok: false; error: E };
async function fetchUser(id: string): Promise<Result<User>> {
const res = await fetch(`/api/users/${id}`);
if (!res.ok) return { ok: false, error: new Error(`HTTP ${res.status}`) };
return { ok: true, value: await res.json() };
}
Reserve throw for truly exceptional / unrecoverable situations. Use Result for operations with expected failure modes (network requests, parsing, validation).
Module Patterns
ESM Conventions
- Use
"type": "module" in package.json
- Prefer named exports over default exports for discoverability and refactoring
- Use the
"exports" field to define the package's public API
- Avoid barrel files (
index.ts re-exporting everything) in application code โ they degrade tree-shaking, slow builds, and risk circular dependencies
- Prefer direct imports:
import { thing } from './utils/thing.js'
- Use
import type (or verbatimModuleSyntax) for type-only imports
Class Patterns
Use private fields (#field) and accessor keywords:
class Elevator {
#currentFloor: number;
#destination: number | null = null;
constructor(startFloor: number) {
this.#currentFloor = startFloor;
}
get currentFloor(): number {
return this.#currentFloor;
}
}
TypeScript 7 / Compiler Rewrite
The TypeScript compiler is being rewritten in Go (Project Corsa). TypeScript 7 is expected in 2026 with ~10x faster builds. Key migration considerations:
strict enabled by default
- Minimum target
es2015 (no more es5)
node10 module resolution removed โ use nodenext or bundler
- Standard LSP protocol replaces custom TSServer protocol
enum, namespace, and parameter properties remain supported but cannot be type-erased (relevant for erasableSyntaxOnly)
Avoid patterns that may complicate migration: deeply nested namespaces, complex enum merging, and reliance on custom TSServer APIs.
Additional Resources
references/type-patterns.md โ Advanced type patterns: template literal types, mapped types, conditional types, complex generics, utility types, and type narrowing techniques.
references/api-reference.md โ Quick reference for ES2024/2025 features, Web APIs, and TypeScript compiler options with usage examples and runtime support status.