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`.
Useful resources
Or clone locally
git clone -b challenge/2026-04-28 https://github.com/niltonheck/typedrop.git