SKILL.md
Mastering Modern TypeScript
Build enterprise-grade, type-safe applications with TypeScript 5.9+.
Compatibility: TypeScript 5.9+, Node.js 22 LTS, Vite 7, NestJS 11, React 19
Quick Start
# Initialize TypeScript project with ESM
pnpm create vite@latest my-app --template vanilla-ts
cd my-app && pnpm install
Configure strict TypeScript
cat > tsconfig.json << 'EOF'
{
"compilerOptions": {
"target": "ES2024",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"esModuleInterop": true,
"skipLibCheck": true
}
}
EOF
## When to Use This Skill
Use when:
- Building type-safe React, NestJS, or Node.js applications
- Migrating JavaScript codebases to TypeScript
- Implementing advanced type patterns (generics, mapped types, conditional types)
- Configuring modern TypeScript toolchains (Vite, pnpm, ESLint)
- Designing type-safe API contracts with Zod validation
- Comparing TypeScript approaches with Java or Python
## Project Setup Checklist
Before starting any TypeScript project:
- Use pnpm for package management (faster, disk-efficient)
- Configure ESM-first (type: "module" in package.json)
- Enable strict mode in tsconfig.json
- Set up ESLint with @typescript-eslint
- Add Prettier for consistent formatting
- Configure Vitest for testing
## Type System Quick Reference
### Primitive Types
const name: string = "Alice";
const age: number = 30;
const active: boolean = true;
const id: bigint = 9007199254740991n;
const key: symbol = Symbol("unique");
### Union and Intersection Types
// Union: value can be one of several types
type Status = "pending" | "approved" | "rejected";
// Intersection: value must satisfy all types
type Employee = Person & { employeeId: string };
// Discriminated union for type-safe handling
type Result<T> =
| { success: true; data: T }
| { success: false; error: string };
function handleResult<T>(result: Result<T>): T | null {
if (result.success) {
return result.data; // TypeScript knows data exists here
}
console.error(result.error);
return null;
}
### Type Guards
// typeof guard
function process(value: string | number): string {
if (typeof value === "string") {
return value.toUpperCase();
}
return value.toFixed(2);
}
// Custom type guard
interface User { type: "user"; name: string }
interface Admin { type: "admin"; permissions: string[] }
function isAdmin(person: User | Admin): person is Admin {
return person.type === "admin";
}
### The satisfies Operator (TS 5.0+)
Validate type conformance while preserving inference:
// Problem: Type assertion loses specific type info
const colors1 = {
red: "#ff0000",
green: "#00ff00"
} as Record<string, string>;
colors1.red.toUpperCase(); // OK, but red could be undefined
// Solution: satisfies preserves literal types
const colors2 = {
red: "#ff0000",
green: "#00ff00"
} satisfies Record<string, string>;
colors2.red.toUpperCase(); // OK, and TypeScript knows red exists
## Generics Patterns
### Basic Generic Function
function first<T>(items: T[]): T | undefined {
return items[0];
}
const num = first([1, 2, 3]); // number | undefined
const str = first(["a", "b"]); // string | undefined
### Constrained Generics
interface HasLength {
length: number;
}
function logLength<T extends HasLength>(item: T): T {
console.log(item.length);
return item;
}
logLength("hello"); // OK: string has length
logLength([1, 2, 3]); // OK: array has length
logLength(42); // Error: number has no length
### Generic API Response Wrapper
interface ApiResponse<T> {
data: T;
status: number;
timestamp: Date;
}
async function fetchUser(id: string): Promise<ApiResponse<User>> {
const response = await fetch(/api/users/${id});
const data = await response.json();
return {
data,
status: response.status,
timestamp: new Date()
};
}
## Utility Types Reference
Type
Purpose
Example
`Partial<T>`
All properties optional
`Partial<User>`
`Required<T>`
All properties required
`Required<Config>`
`Pick<T, K>`
Select specific properties
`Pick<User, "id" | "name">`
`Omit<T, K>`
Exclude specific properties
`Omit<User, "password">`
`Record<K, V>`
Object with typed keys/values
`Record<string, number>`
`ReturnType<F>`
Extract function return type
`ReturnType<typeof fn>`
`Parameters<F>`
Extract function parameters
`Parameters<typeof fn>`
`Awaited<T>`
Unwrap Promise type
`Awaited<Promise<User>>`
## Conditional Types
// Basic conditional type
type IsString<T> = T extends string ? true : false;
// Extract array element type
type ArrayElement<T> = T extends (infer E)[] ? E : never;
type Numbers = ArrayElement<number[]>; // number
type Strings = ArrayElement<string[]>; // string
// Practical: Extract Promise result type
type UnwrapPromise<T> = T extends Promise<infer R> ? R : T;
## Mapped Types
// Make all properties readonly
type Immutable<T> = {
readonly [K in keyof T]: T[K];
};
// Make all properties nullable
type Nullable<T> = {
[K in keyof T]: T[K] | null;
};
// Create getter functions for each property
type Getters<T> = {
[K in keyof T as get${Capitalize<string & K>}]: () => T[K];
};
interface Person { name: string; age: number }
type PersonGetters = Getters<Person>;
// { getName: () => string; getAge: () => number }
## Framework Integration
### React with TypeScript
// Typed functional component
interface ButtonProps {
label: string;
onClick: () => void;
variant?: "primary" | "secondary";
}
const Button: React.FC<ButtonProps> = ({ label, onClick, variant = "primary" }) => (
<button className={variant} onClick={onClick}>
{label}
</button>
);
// Typed hooks
const [count, setCount] = useState<number>(0);
const userRef = useRef<HTMLInputElement>(null);
### NestJS with TypeScript
// Type-safe DTO with class-validator
import { IsString, IsEmail, MinLength } from 'class-validator';
class CreateUserDto {
@IsString()
@MinLength(2)
name: string;
@IsEmail()
email: string;
}
// Or with Zod (modern approach)
import { z } from 'zod';
const CreateUserSchema = z.object({
name: z.string().min(2),
email: z.string().email()
});
type CreateUserDto = z.infer<typeof CreateUserSchema>;
See [react-integration.md](https://github.com/spillwavesolutions/mastering-typescript-skill/blob/HEAD/mastering-typescript/references/react-integration.md) and [nestjs-integration.md](https://github.com/spillwavesolutions/mastering-typescript-skill/blob/HEAD/mastering-typescript/references/nestjs-integration.md) for detailed patterns.
## Validation with Zod
import { z } from 'zod';
// Define schema
const UserSchema = z.object({
id: z.string().uuid(),
name: z.string().min(1).max(100),
email: z.string().email(),
role: z.enum(["user", "admin", "moderator"]),
createdAt: z.coerce.date()
});
// Infer TypeScript type from schema
type User = z.infer<typeof UserSchema>;
// Validate at runtime
function parseUser(data: unknown): User {
return UserSchema.parse(data); // Throws ZodError if invalid
}
// Safe parsing (returns result object)
const result = UserSchema.safeParse(data);
if (result.success) {
console.log(result.data); // Typed as User
} else {
console.error(result.error.issues);
}
## Modern Toolchain (2025)
Tool
Version
Purpose
TypeScript
5.9+
Type checking and compilation
Node.js
22 LTS
Runtime environment
Vite
7.x
Build tool and dev server
pnpm
9.x
Package manager
ESLint
9.x
Linting with flat config
Vitest
3.x
Testing framework
Prettier
3.x
Code formatting
### ESLint Flat Config (ESLint 9+)
// eslint.config.js
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.strictTypeChecked,
{
languageOptions: {
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
}
);
## Migration Strategies
### Incremental Migration
- Add `allowJs: true` and `checkJs: false` to tsconfig.json
- Rename files from `.js` to `.ts` one at a time
- Add type annotations gradually
- Enable stricter options incrementally
### JSDoc for Gradual Typing
// Before full migration, use JSDoc
/**
* @param {string} name
* @param {number} age
* @returns {User}
*/
function createUser(name, age) {
return { name, age };
}