miles990

javascript-typescript

Modern JavaScript and TypeScript development patterns

miles990 20 6 Updated 8mo ago

Resources

1
GitHub

Install

npx skillscat add miles990/claude-software-skills/javascript-typescript

Install via the SkillsCat registry.

About this skill

We need to produce a 2-3 sentence plain-text summary, objective, factual, no marketing language, no superlatives, no calls to action. Must be natural prose, no bullet points, no headings, no markdown formatting. At most 60 words. Must not wrap answer in quotes. So just output the summary text. We need to explain what the skill does, what problem it solves, when to use it. The skill is "Modern JavaScript and TypeScript development patterns". It provides patterns, type definitions, type guards, etc. It solves problem of building robust applications, ensuring type safety, maintainability.

SKILL.md

JavaScript & TypeScript

Overview

Modern JavaScript (ES6+) and TypeScript patterns for building robust applications.


TypeScript Fundamentals

Type Definitions

// Basic types
type UserId = string;
type Timestamp = number;

// Object types
interface User {
  id: UserId;
  email: string;
  name: string;
  createdAt: Timestamp;
  metadata?: Record<string, unknown>;
}

// Union types
type Status = 'pending' | 'active' | 'inactive';

// Discriminated unions
type Result<T, E = Error> =
  | { success: true; data: T }
  | { success: false; error: E };

// Generic types
interface Repository<T extends { id: string }> {
  find(id: string): Promise<T | null>;
  findAll(filter?: Partial<T>): Promise<T[]>;
  create(data: Omit<T, 'id'>): Promise<T>;
  update(id: string, data: Partial<T>): Promise<T>;
  delete(id: string): Promise<void>;
}

// Utility types
type CreateUserInput = Omit<User, 'id' | 'createdAt'>;
type UpdateUserInput = Partial<Pick<User, 'name' | 'metadata'>>;
type UserKeys = keyof User;

// Conditional types
type Nullable<T> = T | null;
type NonNullableFields<T> = {
  [K in keyof T]-?: NonNullable<T[K]>;
};

// Template literal types
type EventName = `on${Capitalize<string>}`;
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
type ApiRoute = `/${string}`;

Type Guards

// Type predicates
function isUser(obj: unknown): obj is User {
  return (
    typeof obj === 'object' &&
    obj !== null &&
    'id' in obj &&
    'email' in obj &&
    typeof (obj as User).id === 'string'
  );
}

// Discriminated union guards
interface Dog {
  kind: 'dog';
  bark(): void;
}

interface Cat {
  kind: 'cat';
  meow(): void;
}

type Animal = Dog | Cat;

function handleAnimal(animal: Animal) {
  switch (animal.kind) {
    case 'dog':
      animal.bark(); // TypeScript knows this is Dog
      break;
    case 'cat':
      animal.meow(); // TypeScript knows this is Cat
      break;
  }
}

// Assertion functions
function assertIsString(value: unknown): asserts value is string {
  if (typeof value !== 'string') {
    throw new Error('Value must be a string');
  }
}

// Exhaustive checks
function assertNever(x: never): never {
  throw new Error(`Unexpected value: ${x}`);
}

Async Patterns

Promises and Async/Await

// Promise creation
function delay(ms: number): Promise<void> {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

// Async/await with error handling
async function fetchUser(id: string): Promise<Result<User>> {
  try {
    const response = await fetch(`/api/users/${id}`);

    if (!response.ok) {
      return {
        success: false,
        error: new Error(`HTTP ${response.status}`),
      };
    }

    const data = await response.json();
    return { success: true, data };
  } catch (error) {
    return {
      success: false,
      error: error instanceof Error ? error : new Error(String(error)),
    };
  }
}

// Parallel execution
async function fetchAllUsers(ids: string[]): Promise<User[]> {
  const results = await Promise.all(ids.map((id) => fetchUser(id)));

  return results
    .filter((r): r is { success: true; data: User } => r.success)
    .map((r) => r.data);
}

// Promise.allSettled for partial failures
async function fetchUsersSettled(ids: string[]) {
  const results = await Promise.allSettled(
    ids.map((id) => fetch(`/api/users/${id}`).then((r) => r.json()))
  );

  return results.map((result, index) => ({
    id: ids[index],
    status: result.status,
    value: result.status === 'fulfilled' ? result.value : null,
    error: result.status === 'rejected' ? result.reason : null,
  }));
}

// Sequential execution with reduce
async function processSequentially<T, R>(
  items: T[],
  processor: (item: T) => Promise<R>
): Promise<R[]> {
  return items.reduce(async (accPromise, item) => {
    const acc = await accPromise;
    const result = await processor(item);
    return [...acc, result];
  }, Promise.resolve([] as R[]));
}

Async Iterators

// Async generator
async function* paginate<T>(
  fetcher: (page: number) => Promise<{ data: T[]; hasMore: boolean }>
): AsyncGenerator<T[], void, unknown> {
  let page = 1;
  let hasMore = true;

  while (hasMore) {
    const result = await fetcher(page);
    yield result.data;
    hasMore = result.hasMore;
    page++;
  }
}

// Using async iterator
async function getAllItems() {
  const items: Item[] = [];

  for await (const batch of paginate(fetchPage)) {
    items.push(...batch);
  }

  return items;
}

// Async iterator utilities
async function* map<T, R>(
  iterable: AsyncIterable<T>,
  fn: (item: T) => R | Promise<R>
): AsyncGenerator<R> {
  for await (const item of iterable) {
    yield await fn(item);
  }
}

async function* filter<T>(
  iterable: AsyncIterable<T>,
  predicate: (item: T) => boolean | Promise<boolean>
): AsyncGenerator<T> {
  for await (const item of iterable) {
    if (await predicate(item)) {
      yield item;
    }
  }
}

async function* take<T>(
  iterable: AsyncIterable<T>,
  count: number
): AsyncGenerator<T> {
  let taken = 0;
  for await (const item of iterable) {
    if (taken >= count) break;
    yield item;
    taken++;
  }
}

Functional Patterns

Higher-Order Functions

// Currying
const add = (a: number) => (b: number) => a + b;
const add5 = add(5);
console.log(add5(3)); // 8

// Pipe and compose
const pipe =
  <T>(...fns: Array<(arg: T) => T>) =>
  (value: T): T =>
    fns.reduce((acc, fn) => fn(acc), value);

const compose =
  <T>(...fns: Array<(arg: T) => T>) =>
  (value: T): T =>
    fns.reduceRight((acc, fn) => fn(acc), value);

// Usage
const processString = pipe(
  (s: string) => s.trim(),
  (s: string) => s.toLowerCase(),
  (s: string) => s.replace(/\s+/g, '-')
);

// Memoization
function memoize<Args extends unknown[], Result>(
  fn: (...args: Args) => Result
): (...args: Args) => Result {
  const cache = new Map<string, Result>();

  return (...args: Args): Result => {
    const key = JSON.stringify(args);

    if (cache.has(key)) {
      return cache.get(key)!;
    }

    const result = fn(...args);
    cache.set(key, result);
    return result;
  };
}

// Debounce
function debounce<T extends (...args: any[]) => any>(
  fn: T,
  delay: number
): (...args: Parameters<T>) => void {
  let timeoutId: ReturnType<typeof setTimeout>;

  return (...args: Parameters<T>) => {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => fn(...args), delay);
  };
}

// Throttle
function throttle<T extends (...args: any[]) => any>(
  fn: T,
  limit: number
): (...args: Parameters<T>) => void {
  let inThrottle = false;

  return (...args: Parameters<T>) => {
    if (!inThrottle) {
      fn(...args);
      inThrottle = true;
      setTimeout(() => (inThrottle = false), limit);
    }
  };
}

Immutable Data Patterns

// Object updates
const updateUser = (user: User, updates: Partial<User>): User => ({
  ...user,
  ...updates,
});

// Nested updates
interface State {
  users: Record<string, User>;
  settings: {
    theme: string;
    notifications: boolean;
  };
}

const updateNestedState = (
  state: State,
  userId: string,
  userUpdate: Partial<User>
): State => ({
  ...state,
  users: {
    ...state.users,
    [userId]: {
      ...state.users[userId],
      ...userUpdate,
    },
  },
});

// Array operations (immutable)
const addItem = <T>(arr: T[], item: T): T[] => [...arr, item];
const removeItem = <T>(arr: T[], index: number): T[] => [
  ...arr.slice(0, index),
  ...arr.slice(index + 1),
];
const updateItem = <T>(arr: T[], index: number, item: T): T[] => [
  ...arr.slice(0, index),
  item,
  ...arr.slice(index + 1),
];

Modern JavaScript Features

Destructuring and Spread

// Object destructuring with defaults and rename
const { name, email, role = 'user', id: odentifier } = user;

// Nested destructuring
const {
  address: { city, country },
} = user;

// Array destructuring
const [first, second, ...rest] = items;
const [, , third] = items; // Skip elements

// Function parameter destructuring
function createUser({
  name,
  email,
  role = 'user',
}: {
  name: string;
  email: string;
  role?: string;
}) {
  return { id: generateId(), name, email, role };
}

// Spread for merging
const merged = { ...defaults, ...overrides };
const combined = [...arr1, ...arr2];

Optional Chaining and Nullish Coalescing

// Optional chaining
const city = user?.address?.city;
const firstItem = items?.[0];
const result = callback?.();

// Nullish coalescing (only null/undefined)
const value = input ?? defaultValue;

// Combining them
const displayName = user?.profile?.displayName ?? user?.name ?? 'Anonymous';

// Logical assignment operators
let config = { timeout: 0 };
config.timeout ||= 5000; // Assigns if falsy (won't assign, 0 is falsy)
config.timeout ??= 5000; // Assigns if null/undefined (won't assign)
config.retries ??= 3; // Assigns (undefined)

Error Handling

// Custom error classes
class AppError extends Error {
  constructor(
    message: string,
    public code: string,
    public statusCode: number = 500
  ) {
    super(message);
    this.name = 'AppError';
    Error.captureStackTrace(this, this.constructor);
  }
}

class ValidationError extends AppError {
  constructor(
    message: string,
    public fields: Record<string, string[]>
  ) {
    super(message, 'VALIDATION_ERROR', 400);
    this.name = 'ValidationError';
  }
}

// Result type pattern (no exceptions)
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 };
}

// Using Result
function parseJSON<T>(json: string): Result<T, SyntaxError> {
  try {
    return ok(JSON.parse(json));
  } catch (e) {
    return err(e as SyntaxError);
  }
}

// Chaining Results
function map<T, U, E>(result: Result<T, E>, fn: (value: T) => U): Result<U, E> {
  return result.ok ? ok(fn(result.value)) : result;
}

function flatMap<T, U, E>(
  result: Result<T, E>,
  fn: (value: T) => Result<U, E>
): Result<U, E> {
  return result.ok ? fn(result.value) : result;
}

Module Patterns

// Named exports
export const API_URL = 'https://api.example.com';
export function fetchData() {}
export class ApiClient {}

// Default export
export default class Logger {}

// Re-exports
export { UserService } from './user-service';
export { default as Logger } from './logger';
export * from './types';
export * as utils from './utils';

// Dynamic imports
async function loadModule() {
  const { default: Chart } = await import('./chart');
  return new Chart();
}

// Conditional imports
const adapter = await import(
  process.env.NODE_ENV === 'test' ? './mock-adapter' : './real-adapter'
);

Related Skills

  • [[frontend]] - React/Next.js development
  • [[backend]] - Node.js server development
  • [[testing]] - Jest, Vitest testing