TypeDrop

2026-06-25 Challenge

2026-06-25 Medium

Typed API Rate Limiter with Sliding Window & Policy Registry

You're building the rate-limiting middleware for a multi-tenant REST API gateway. Raw policy configurations arrive as `unknown` from a remote config store; your limiter must validate them, look up the correct typed policy per tenant, track request counts in a sliding-window algorithm, and return a strongly-typed admission decision — with zero `any`.

Goals

  • Implement `registerPolicy` to safely narrow `unknown` → `RateLimitPolicy` and return a typed `Result<T,E>` with specific `PolicyValidationError` variants.
  • Implement `admit` to dispatch on the policy's discriminated `kind` and apply the correct fixed-window or sliding-window admission algorithm.
  • Implement `snapshot` to return a `Record<TenantId, …>` reflecting each tenant's in-window request count and reset time at the given `now`.
  • Use branded types throughout — never conflate raw `string`/`number` with `TenantId`, `RequestId`, or `EpochMs`.
challenge.ts
// Core types and main class signature

export type TenantId = string & { readonly __brand: "TenantId" };
export type EpochMs  = number & { readonly __brand: "EpochMs" };

export type RateLimitPolicy =
  | { readonly kind: "fixed";   readonly maxRequests: number; readonly windowMs: number }
  | { readonly kind: "sliding"; readonly maxRequests: number; readonly windowMs: number }
  | { readonly kind: "tiered";  readonly tier: string;
      readonly tiers: Record<string, { maxRequests: number; windowMs: number }> };

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

export type AdmissionDecision =
  | { readonly decision: "allowed"; remainingRequests: number; windowResetAt: EpochMs }
  | { readonly decision: "denied";  retryAfterMs: number;      windowResetAt: EpochMs };

export class RateLimiter {
  registerPolicy(tenantId: TenantId, raw: unknown): Result<RateLimitPolicy, PolicyValidationError>;
  admit(tenantId: TenantId, requestId: RequestId, now: EpochMs): AdmissionDecision;
  snapshot(now: EpochMs): Record<TenantId, { policy: RateLimitPolicy; requestsInWindow: number; windowResetAt: EpochMs }>;
}
Hints (click to reveal)

Hints

  • To narrow `unknown` safely, start with `typeof raw === 'object' && raw !== null` before accessing any fields — then use `'kind' in raw` and check the value.
  • For the sliding window, store timestamps as a mutable copy of the `readonly` array: `[...existing, now]` creates a new array you can assign back without fighting `readonly`.
  • The `Record<TenantId, …>` return type of `snapshot` requires `TenantId` as the index — use `Object.fromEntries` or build up a plain object cast through the branded key type.

Or clone locally

git clone -b challenge/2026-06-25 https://github.com/niltonheck/typedrop.git