mastering-typescript

Enterprise-grade TypeScript development with type-safe patterns, modern tooling, and framework integration. Covers TypeScript 5.9+ fundamentals including generics, mapped types, conditional types, and the satisfies operator for type validation Includes framework-specific guidance for React, NestJS, and LangChain.js with practical code examples and type-safe patterns Provides enterprise patterns for error handling, runtime validation with Zod, and API contract design Addresses modern toolchain setup (Vite 7, pnpm, ESLint 9 flat config, Vitest) and incremental migration strategies from JavaScript

INSTALLATION
npx skills add https://github.com/spillwavesolutions/mastering-typescript-skill --skill mastering-typescript
Run in your project or agent environment. Adjust flags if your CLI version differs.

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 &#x26; { 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 &#x26; 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 };

}

BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card