TypeDrop

2026-04-28 Challenge

2026-04-28 Hard

Typed API Rate-Limiter Middleware Chain

You're building the gateway middleware layer for a multi-tenant REST API platform. Raw inbound requests arrive as `unknown` from the network edge; your engine must validate them, route each request through a composable chain of strongly-typed rate-limiter strategies (token bucket, sliding window, concurrency cap), and emit a discriminated-union decision — with zero `any`.

Goals

  • Implement branded-type constructors for `TenantId`, `RouteKey`, and `TimestampMs` without using `as` or `any`.
  • Implement `validateRequest` to narrow `unknown` input into a fully typed `ApiRequest`, returning a discriminated-union `Result` for every failure mode.
  • Implement `evaluateStrategy` with correct per-strategy state mutation (token refill, timestamp ring-buffer eviction, concurrency counter) and typed `RateLimitError` results.
  • Wire `processRequest`, `runChain`, `releaseSlot`, and `lookupChain` together so the full gateway pipeline — validation → chain evaluation → typed verdict — works end-to-end.
challenge.ts
// Key types and main entry point — challenge.ts (preview)

type TenantId  = string & { readonly __brand: "TenantId" };
type RouteKey  = string & { readonly __brand: "RouteKey" };
type TimestampMs = number & { readonly __brand: "TimestampMs" };

type RateLimiterStrategy =
  | { readonly type: "TOKEN_BUCKET";    readonly config: TokenBucketConfig;    state: TokenBucketState    }
  | { readonly type: "SLIDING_WINDOW";  readonly config: SlidingWindowConfig;  state: SlidingWindowState  }
  | { readonly type: "CONCURRENCY_CAP"; readonly config: ConcurrencyCapConfig; state: ConcurrencyCapState };

type Result<T, E> =
  | { readonly ok: true;  readonly value: T }
  | { readonly ok: false; readonly error: E };

type GatewayError =
  | { readonly kind: "VALIDATION_FAILED"; inner: ValidationError }
  | { readonly kind: "RATE_LIMITED";      inner: RateLimitError  }
  | { readonly kind: "MIDDLEWARE_PANIC";  message: string        };

// Main entry point — validate raw input, then run the middleware chain
function processRequest(
  raw: unknown,
  chain: MiddlewareChain          // RateLimiterStrategy[]
): Result<"ALLOWED", GatewayError> { /* TODO */ }
Hints (click to reveal)

Hints

  • For branded constructors without `as`, try a generic helper `function brand<T extends string | number>(v: T): T & { readonly __brand: unique symbol }` — but note each brand needs its own unique symbol; a per-type `satisfies` on the return annotation is the cleanest escape hatch.
  • In `evaluateStrategy`, exhaustive narrowing on `strategy.type` in a `switch` statement lets TypeScript infer the exact config and state shapes in each branch — no extra type guards needed.
  • For `validateRequest`'s `meta` field, use `Object.entries` + `Array.prototype.reduce` to rebuild a `Record<string, string>` dropping non-string values, keeping the function free of `any`.

Or clone locally

git clone -b challenge/2026-04-28 https://github.com/niltonheck/typedrop.git