TypeScript — Rules and Conventions
1. Philosophy
- Strict by default —
strict: truecatches entire classes of bugs at compile time. - Types as documentation — Prefer inference, annotate only boundaries (API, props, exports).
- No
any— Useunknown+ narrowing.anydisables type checking. - Discriminated unions over optional chains — Model state explicitly.
- Result pattern for errors — Never throw in async code. Return
Result<T, E>.
2. Minimum Versions
| Technology | Minimum Version |
|---|---|
| TypeScript | 5.4+ |
| Node.js | 22+ |
| pnpm | 11+ |
3. tsconfig.json — Configurations
Base config (extends all)
// tsconfig.base.json
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true,
"resolveJsonModule": true,
"isolatedModules": true,
"verbatimModuleSyntax": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
}
}
App config (Vite/Astro/Next)
// tsconfig.app.json
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"noEmit": true,
"jsx": "react-jsx",
"types": ["vite/client", "astro/client"]
},
"include": ["src/**/*", "vite.config.ts", "astro.config.mjs"],
"exclude": ["node_modules", "dist"]
}
Library config (publishing)
// tsconfig.lib.json
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"declaration": true,
"declarationDir": "dist/types",
"outDir": "dist",
"rootDir": "src"
},
"include": ["src/**/*"],
"exclude": ["**/*.test.ts", "**/*.spec.ts", "node_modules", "dist"]
}
Strictness ladder (add incrementally)
// tsconfig.strict.json
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"alwaysStrict": true,
"noImplicitAny": true,
"noImplicitThis": true,
"useUnknownInCatchVariables": true
}
}
Rules
moduleResolution: "bundler"— matches Vite/esbuild/tsc behaviorverbatimModuleSyntax— preservesimport type/export typeisolatedModules— required for esbuild/Vite transpilationnoUncheckedIndexedAccess—obj[key]returnsT | undefinedexactOptionalPropertyTypes— distinguishes?from| undefined
4. Type System Essentials
Inference first, annotate boundaries
// ✅ Good: inference
const users = await fetchUsers()
const active = users.filter(u => u.active)
// ✅ Annotate: function boundaries
async function fetchUsers(): Promise<User[]> { ... }
function processUser(user: User): ProcessedUser { ... }
// ❌ Bad: redundant annotations
const count: number = 5
const name: string = "Alice"
interface vs type
Use interface |
Use type |
|---|---|
| Object shapes, class contracts | Unions, intersections, tuples |
| Declaration merging needed | Mapped/conditional types |
| Public API surfaces | Internal aliases |
// interface: extensible, mergeable
interface User {
id: string;
name: string;
}
interface User {
email: string;
} // merged
// type: algebraic, precise
type User = { id: string; name: string } & { email: string };
type Status = "loading" | "success" | "error";
type Tuple = [string, number];
Generics — constraints and defaults
// Constraint
function pick<T extends { id: string }>(items: T[], id: string): T | undefined {
return items.find((i) => i.id === id);
}
// Default
interface Repository<T = User> {
find(id: string): Promise<T | null>;
save(entity: T): Promise<void>;
}
// Variance (advanced)
type Producer<out T> = () => T; // covariant (return only)
type Consumer<in T> = (value: T) => void; // contravariant (arg only)
Utility types (essential only)
| Utility | Use Case |
|---|---|
Partial<T> |
All props optional |
Required<T> |
All props required |
Pick<T, K> |
Subset of keys |
Omit<T, K> |
Exclude keys |
Record<K, T> |
Object map |
ReturnType<Fn> |
Function return type |
Parameters<Fn> |
Function param tuple |
Awaited<P> |
Unwrap Promise |
NonNullable<T> |
Remove null/undefined |
5. Type Narrowing
Type guards
// User-defined guard
function isUser(value: unknown): value is User {
return typeof value === "object" && value !== null && "id" in value;
}
// Usage
const data: unknown = await fetchData();
if (isUser(data)) {
data.id; // narrowed to User
}
Discriminated unions (preferred)
type Result<T, E> = { ok: true; value: T } | { ok: false; error: E };
function handle<T, E>(result: Result<T, E>): void {
if (result.ok) {
console.log(result.value); // T
} else {
console.error(result.error); // E
}
}
never and unknown
// Exhaustiveness check
function assertNever(value: never): never {
throw new Error(`Unexpected: ${value}`);
}
function process(status: Status): void {
switch (status) {
case "loading":
return;
case "success":
return;
case "error":
return;
default:
assertNever(status); // compile error if new status added
}
}
6. Advanced Types
Mapped types
// Make all properties optional and nullable
type PartialNullable<T> = {
[K in keyof T]?: T[K] | null;
};
// Require specific keys
type RequireKeys<T, K extends keyof T> = T & Required<Pick<T, K>>;
// Readonly recursively
type DeepReadonly<T> = {
readonly [K in keyof T]: DeepReadonly<T[K]>;
};
Conditional types
// Non-nullable
type NonNullable<T> = T extends null | undefined ? never : T;
// Flatten promises
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
// Template literal types
type EventName<T extends string> = `on${Capitalize<T>}`;
type Handler = EventName<"click" | "hover">; // "onClick" | "onHover"
7. Standard Decorators (TC39 Stage 3)
// Class decorator
@logged
class UserService {
@debounce(300)
save(user: User): void { ... }
}
// Implementation
function logged(target: Function, context: ClassDecoratorContext) {
return class extends target {
constructor(...args: any[]) {
console.log("Creating", target.name)
super(...args)
}
}
}
function debounce(ms: number) {
return function (target: Function, context: ClassMethodDecoratorContext) {
let timeout: ReturnType<typeof setTimeout>
return function (this: any, ...args: any[]) {
clearTimeout(timeout)
timeout = setTimeout(() => target.apply(this, args), ms)
}
}
}
Experimental decorators (
experimentalDecorators: true) — legacy, not recommended. Use standard only.
8. Result Pattern for Errors
Full pattern in
design-patternsskill. Compact version here:
type Result<T, E = Error> = { ok: true; value: T } | { ok: false; error: E };
function ok<T>(value: T): Result<T, never> {
return { ok: true, value };
}
function err<E>(error: E): Result<never, E> {
return { ok: false, error };
}
// Async helper
async function tryCatch<T, E = Error>(
fn: () => Promise<T>,
): Promise<Result<T, E>> {
try {
return ok(await fn());
} catch (e) {
return err(e as E);
}
}
// Usage
const result = await tryCatch(() => fetchUser(id));
if (result.ok) {
result.value; // User
} else {
result.error; // Error
}
Rules Result Patterns
- Never
throwin async — returnsPromise<never>breaks callers - Return
Result— forces caller to handle both cases - Error as value — serializable, loggable, testable
9. Declaration Files (.d.ts)
Module augmentation
// types/express.d.ts
import "express";
declare module "express" {
interface Request {
user?: User;
}
}
Global types
// types/global.d.ts
declare global {
namespace NodeJS {
interface ProcessEnv {
DATABASE_URL: string;
JWT_SECRET: string;
}
}
}
export {}; // makes this a module
External package types
// types/some-untyped-pkg.d.ts
declare module "some-untyped-pkg" {
export function doSomething(input: string): number;
export interface Config {
debug: boolean;
}
}
Rules Declarations File
types/folder at project root, included intsconfig.jsondeclare modulefor augmentation — never modifynode_modulesexport {}in globals — prevents global pollution
10. Methodology
Before using ANY TypeScript config/pattern not documented in this skill:
- MCP Context7 (priority):
context7_resolve-library-id+context7_query-docsfor TypeScript. - Official docs: typescriptlang.org — verify current compiler options + features.
- Project config:
tsconfig.json,tsconfig.*.json— verify against actual setup. - HARD RULE: If not in this skill AND cannot be verified against 2 authoritative sources → DO NOT USE IT. Document as assumption or risk in report to orchestrator.
11. Prohibitions
- ❌ Do not use
any— useunknown+ narrowing - ❌ Do not use
!!(double negation) — use explicit checks - ❌ Do not use
as Typeassertions — prefer guards/narrowing - ❌ Do not use
interfacefor unions — usetype - ❌ Do not use
enum— useconstobjects +as const - ❌ Do not use experimental decorators — standard only
- ❌ Do not skip
noUncheckedIndexedAccess— catchesundefinedaccess - ❌ Do not use
namespace— use modules (export) - ❌ Do not put runtime logic in types — types are erased
12. References
Note: For JavaScript conventions, see JavaScript Note: For design patterns (Result, etc.), see Design Patterns Note: For package manager conventions, see Package Manager
Last updated: 2026-08