Agent Skills: TypeScript Development

TypeScript development best practices including type system, generics, utility types, configuration, and patterns for React, Node.js, and full-stack applications. Use when writing TypeScript code, converting JavaScript to TypeScript, debugging type errors, or implementing type-safe patterns.

UncategorizedID: vapvarun/claude-backup/typescript

Skill Files

Browse the full folder contents for typescript.

Download Skill

Loading file tree…

skills/typescript/SKILL.md

Skill Metadata

Name
typescript
Description
TypeScript development best practices including type system, generics, utility types, configuration, and patterns for React, Node.js, and full-stack applications. Use when writing TypeScript code, converting JavaScript to TypeScript, debugging type errors, or implementing type-safe patterns.

TypeScript Development

Modern TypeScript patterns and type-safe development.

Type Fundamentals

Basic Types

// Primitives
const name: string = 'John';
const age: number = 30;
const isActive: boolean = true;
const nothing: null = null;
const notDefined: undefined = undefined;

// Arrays
const numbers: number[] = [1, 2, 3];
const strings: Array<string> = ['a', 'b', 'c'];
const mixed: (string | number)[] = [1, 'two', 3];

// Tuples
const tuple: [string, number] = ['hello', 42];
const namedTuple: [name: string, age: number] = ['John', 30];

// Objects
const user: { name: string; age: number } = { name: 'John', age: 30 };

// Any vs Unknown
const dangerous: any = getData();     // Avoid - no type checking
const safe: unknown = getData();       // Prefer - requires type narrowing
if (typeof safe === 'string') {
    console.log(safe.toUpperCase());   // Now TypeScript knows it's string
}

Interfaces vs Types

// Interface - extendable, for objects
interface User {
    id: number;
    name: string;
    email: string;
}

interface AdminUser extends User {
    role: 'admin';
    permissions: string[];
}

// Type - more flexible
type ID = string | number;
type Callback = (data: string) => void;
type Status = 'pending' | 'active' | 'inactive';

// Intersection types
type UserWithTimestamps = User & {
    createdAt: Date;
    updatedAt: Date;
};

// Use interface for objects, type for unions/primitives

Optional & Readonly

interface Config {
    required: string;
    optional?: string;              // May be undefined
    readonly immutable: string;     // Can't be reassigned
}

// Readonly utility
type ReadonlyUser = Readonly<User>;

// Partial - all optional
type PartialUser = Partial<User>;

// Required - all required
type RequiredUser = Required<User>;

Generics

Basic Generics

// Generic function
function identity<T>(value: T): T {
    return value;
}

const num = identity(42);        // T inferred as number
const str = identity('hello');   // T inferred as string

// Generic interface
interface Response<T> {
    data: T;
    status: number;
    message: string;
}

const userResponse: Response<User> = {
    data: { id: 1, name: 'John', email: 'john@example.com' },
    status: 200,
    message: 'Success',
};

// Generic class
class Queue<T> {
    private items: T[] = [];

    enqueue(item: T): void {
        this.items.push(item);
    }

    dequeue(): T | undefined {
        return this.items.shift();
    }
}

const numberQueue = new Queue<number>();
numberQueue.enqueue(1);

Constraints

// Constrain to specific shape
interface HasId {
    id: number;
}

function findById<T extends HasId>(items: T[], id: number): T | undefined {
    return items.find(item => item.id === id);
}

// Multiple constraints
function merge<T extends object, U extends object>(a: T, b: U): T & U {
    return { ...a, ...b };
}

// keyof constraint
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
    return obj[key];
}

const user = { name: 'John', age: 30 };
const name = getProperty(user, 'name');  // string
const age = getProperty(user, 'age');    // number

Utility Types

// Pick - select specific properties
type UserPreview = Pick<User, 'id' | 'name'>;

// Omit - exclude properties
type UserWithoutEmail = Omit<User, 'email'>;

// Record - map keys to values
type UserRoles = Record<string, 'admin' | 'user' | 'guest'>;

// Extract / Exclude
type Status = 'pending' | 'active' | 'deleted';
type ActiveStatus = Extract<Status, 'pending' | 'active'>;  // 'pending' | 'active'
type WithoutDeleted = Exclude<Status, 'deleted'>;           // 'pending' | 'active'

// ReturnType / Parameters
function createUser(name: string, email: string): User {
    return { id: 1, name, email };
}

type CreateUserReturn = ReturnType<typeof createUser>;      // User
type CreateUserParams = Parameters<typeof createUser>;       // [string, string]

// NonNullable
type MaybeString = string | null | undefined;
type DefinitelyString = NonNullable<MaybeString>;           // string

Type Guards

// typeof guard
function process(value: string | number) {
    if (typeof value === 'string') {
        return value.toUpperCase();
    }
    return value * 2;
}

// instanceof guard
class Dog {
    bark() { console.log('Woof!'); }
}

class Cat {
    meow() { console.log('Meow!'); }
}

function speak(animal: Dog | Cat) {
    if (animal instanceof Dog) {
        animal.bark();
    } else {
        animal.meow();
    }
}

// in guard
interface Bird { fly(): void; }
interface Fish { swim(): void; }

function move(animal: Bird | Fish) {
    if ('fly' in animal) {
        animal.fly();
    } else {
        animal.swim();
    }
}

// Custom type guard
interface ApiError {
    code: string;
    message: string;
}

function isApiError(error: unknown): error is ApiError {
    return (
        typeof error === 'object' &&
        error !== null &&
        'code' in error &&
        'message' in error
    );
}

// Usage
try {
    await fetchData();
} catch (error) {
    if (isApiError(error)) {
        console.log(error.code);  // TypeScript knows it's ApiError
    }
}

Advanced Patterns

Discriminated Unions

interface LoadingState {
    status: 'loading';
}

interface SuccessState<T> {
    status: 'success';
    data: T;
}

interface ErrorState {
    status: 'error';
    error: string;
}

type AsyncState<T> = LoadingState | SuccessState<T> | ErrorState;

function handleState<T>(state: AsyncState<T>) {
    switch (state.status) {
        case 'loading':
            return 'Loading...';
        case 'success':
            return state.data;  // TypeScript knows data exists
        case 'error':
            return state.error; // TypeScript knows error exists
    }
}

Template Literal Types

type EventName = 'click' | 'focus' | 'blur';
type EventHandler = `on${Capitalize<EventName>}`;  // 'onClick' | 'onFocus' | 'onBlur'

type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
type Endpoint = `/api/${string}`;

function request(method: HTTPMethod, url: Endpoint) {
    // ...
}

request('GET', '/api/users');    // OK
request('GET', '/users');        // Error: doesn't start with /api/

Mapped Types

// Make all properties optional
type Optional<T> = {
    [K in keyof T]?: T[K];
};

// Make all properties nullable
type Nullable<T> = {
    [K in keyof T]: T[K] | null;
};

// Prefix keys
type Prefixed<T, P extends string> = {
    [K in keyof T as `${P}${string & K}`]: T[K];
};

type PrefixedUser = Prefixed<User, 'user_'>;
// { user_id: number; user_name: string; user_email: string }

Conditional Types

// Basic conditional
type IsString<T> = T extends string ? true : false;

type A = IsString<string>;  // true
type B = IsString<number>;  // false

// Extract array element type
type ElementOf<T> = T extends (infer E)[] ? E : never;

type StringElement = ElementOf<string[]>;  // string

// Function return type
type AsyncReturnType<T> = T extends (...args: any[]) => Promise<infer R> ? R : never;

async function fetchUser(): Promise<User> {
    return { id: 1, name: 'John', email: 'john@example.com' };
}

type FetchedUser = AsyncReturnType<typeof fetchUser>;  // User

React TypeScript

Component Types

import { FC, ReactNode, ComponentProps } from 'react';

// Props interface
interface ButtonProps {
    variant: 'primary' | 'secondary';
    size?: 'sm' | 'md' | 'lg';
    children: ReactNode;
    onClick?: () => void;
}

// Function component
function Button({ variant, size = 'md', children, onClick }: ButtonProps) {
    return (
        <button className={`btn-${variant} btn-${size}`} onClick={onClick}>
            {children}
        </button>
    );
}

// With FC (includes children)
const Card: FC<{ title: string; children: ReactNode }> = ({ title, children }) => (
    <div className="card">
        <h2>{title}</h2>
        {children}
    </div>
);

// Extending HTML element props
interface InputProps extends ComponentProps<'input'> {
    label: string;
    error?: string;
}

function Input({ label, error, ...props }: InputProps) {
    return (
        <div>
            <label>{label}</label>
            <input {...props} />
            {error && <span className="error">{error}</span>}
        </div>
    );
}

Hooks

import { useState, useEffect, useRef, useCallback, useMemo } from 'react';

// useState with type
const [user, setUser] = useState<User | null>(null);
const [items, setItems] = useState<string[]>([]);

// useRef
const inputRef = useRef<HTMLInputElement>(null);
const countRef = useRef<number>(0);

// useCallback with types
const handleClick = useCallback((id: number) => {
    console.log(id);
}, []);

// useMemo
const expensiveValue = useMemo(() => {
    return items.filter(item => item.length > 5);
}, [items]);

// Custom hook
function useLocalStorage<T>(key: string, initialValue: T) {
    const [value, setValue] = useState<T>(() => {
        const stored = localStorage.getItem(key);
        return stored ? JSON.parse(stored) : initialValue;
    });

    useEffect(() => {
        localStorage.setItem(key, JSON.stringify(value));
    }, [key, value]);

    return [value, setValue] as const;
}

Configuration

tsconfig.json

{
    "compilerOptions": {
        "target": "ES2022",
        "module": "ESNext",
        "moduleResolution": "bundler",
        "lib": ["ES2022", "DOM", "DOM.Iterable"],

        "strict": true,
        "noUncheckedIndexedAccess": true,
        "noImplicitReturns": true,
        "noFallthroughCasesInSwitch": true,

        "declaration": true,
        "declarationMap": true,
        "sourceMap": true,

        "esModuleInterop": true,
        "skipLibCheck": true,
        "forceConsistentCasingInFileNames": true,

        "baseUrl": ".",
        "paths": {
            "@/*": ["src/*"],
            "@components/*": ["src/components/*"]
        },

        "outDir": "dist",
        "rootDir": "src"
    },
    "include": ["src/**/*"],
    "exclude": ["node_modules", "dist"]
}

Strict Mode Benefits

// strictNullChecks - catches null/undefined errors
const user: User | null = getUser();
user.name;              // Error: user might be null
user?.name;             // OK: optional chaining

// noImplicitAny - requires explicit types
function process(data) { }  // Error: implicit 'any'
function process(data: unknown) { }  // OK

// strictPropertyInitialization - ensures class properties are initialized
class User {
    name: string;       // Error: not initialized
    name: string = '';  // OK
}