# Scalability - TypeScript Implementation

TypeScript-specific implementation of the principles defined in [SCALABILITY_CORE.md](../core/SCALABILITY_CORE.md).

## Statelessness

- **Stateless Request Handlers:** Never store per-user or per-request state in module-level variables, closures, or singletons. Each request handler must derive all needed state from the request context, database, or cache. Module-level scope in Node.js persists across requests in the same process -- storing state there causes cross-request contamination.
- **Typed Session State:** Store session data in a distributed cache (Redis) or secure cookies, not in process memory. Define a typed session interface and validate session data on read. Use libraries like `connect-redis` (Express) or `@fastify/session` with typed session schemas.
- **Immutable Request Context:** Pass request context as a typed, readonly object through the request lifecycle. Use `Readonly<T>` or `as const` assertions to prevent handlers from mutating shared context.
- **Singleton Services (Not State):** It is correct to initialize singleton service instances (database clients, cache connections, HTTP clients) at the module level for reuse across requests. These are stateless connectors, not per-request state.

## Decoupling (Async First)

- **Typed Message Queues:** When offloading work to a message queue (BullMQ, SQS, RabbitMQ), define typed message payloads with Zod schemas. Validate messages on both the producer and consumer side. Use generic queue wrappers that enforce payload types:
  ```typescript
  interface QueueMessage<T> {
    id: string;
    payload: T;
    timestamp: number;
  }
  ```
- **Worker Threads for CPU-Bound Work:** Move CPU-intensive operations (image processing, PDF generation, heavy computation) to worker threads using `worker_threads` or a typed pool library (`piscina`, `tinypool`). Type the message interface between the main thread and workers.
- **Event-Driven Architecture:** Use typed event emitters for decoupling modules. Define event maps with strict typing so that listeners receive correctly typed payloads:
  ```typescript
  interface AppEvents {
    'order:created': { orderId: string; userId: string };
    'payment:completed': { paymentId: string; amount: number };
  }
  ```
- **Async/Await Over Callbacks:** Use `async/await` consistently. Never mix callbacks and promises in the same flow. Type all async functions with explicit return types -- `Promise<User>` not `Promise<any>`. Use `Promise.all()` for independent parallel operations and `Promise.allSettled()` when partial failure is acceptable.
- **Debounce and Throttle:** Type debounce and throttle utilities so that the wrapped function preserves the original function's parameter and return types. Use generics to maintain type safety through the wrapper.

## Database Scaling

- **Type-Safe Query Builders:** Use a type-safe ORM or query builder (Prisma, Drizzle, Kysely) that generates types from the database schema. This prevents column name typos, type mismatches, and invalid query construction at compile time.
- **Pagination:** Never fetch unbounded result sets. Type pagination parameters and responses:
  ```typescript
  interface PaginatedResult<T> {
    data: T[];
    cursor: string | null;
    hasMore: boolean;
  }
  ```
  Use cursor-based pagination for stable, efficient paging. Offset-based pagination degrades at scale.
- **Connection Pooling:** Configure database connection pool sizes based on expected concurrency. Use typed pool configuration. Monitor pool usage and set appropriate timeouts for idle connections. For serverless environments, use connection poolers (PgBouncer, Supavisor) to avoid exhausting connections.
- **Read Replicas:** Type the distinction between read and write database clients. Create separate typed client instances for primary (read-write) and replica (read-only) connections. Route read-heavy queries (listings, search, dashboards) to replicas.
- **Caching Layer:** Implement a typed caching layer between the application and database. Use generics for cache keys and values:
  ```typescript
  interface Cache {
    get<T>(key: string): Promise<T | null>;
    set<T>(key: string, value: T, ttl: number): Promise<void>;
  }
  ```

## Graceful Degradation

- **Circuit Breaker Pattern:** Implement typed circuit breakers for external service calls. Use a library like `opossum` with typed configuration. Define fallback functions that return the same type as the primary function:
  ```typescript
  const breaker = new CircuitBreaker<[OrderRequest], OrderResponse>(
    submitOrder,
    { timeout: 5000, errorThresholdPercentage: 50 },
  );
  breaker.fallback(() => ({ status: 'queued' }));
  ```
- **Typed Feature Flags:** Define feature flags as a typed configuration object. Use discriminated unions for flag states (enabled, disabled, percentage-rollout). Type-check that all code paths handle both enabled and disabled states.
- **Timeout Contracts:** Set explicit timeouts for every external call. Type timeout configuration. Use `AbortController` with `AbortSignal.timeout()` for fetch-based requests. Provide typed fallback responses when timeouts are hit.
- **Health Check Endpoints:** Implement typed health check responses that report the status of each dependency (database, cache, external services). Return degraded status when non-critical dependencies are down.

## API Versioning

- **Type-Safe API Versioning:** Define versioned API schemas as separate TypeScript types. Use discriminated unions or branded types to prevent accidentally mixing types from different API versions:
  ```typescript
  interface OrderV1 { id: string; total: number; }
  interface OrderV2 { id: string; total: number; currency: string; }
  type Order = OrderV1 | OrderV2;
  ```
- **Schema Migration Types:** When evolving API types, create explicit migration functions typed as `(v1: TypeV1) => TypeV2`. This makes schema evolution traceable and testable.
- **Backward-Compatible Serialization:** When adding fields to response types, make new fields optional (`?`) to maintain backward compatibility. Use Zod's `.passthrough()` or `.strip()` to handle unknown fields gracefully during deserialization.
- **End-to-End Type Safety:** Use tRPC, Hono RPC, or shared type packages to share API types between client and server. This eliminates API contract drift and catches breaking changes at compile time.
