TypeDrop

2026-05-11 Challenge

2026-05-11 Hard

Typed Distributed Circuit Breaker

You're building the resilience layer for a microservices gateway. Each downstream service is protected by a typed circuit breaker that transitions through states based on failure thresholds; your engine must validate raw service configs arriving as `unknown`, manage per-service breaker state machines, execute calls with retry + timeout logic, and return a strongly-typed health report — with zero `any`.

Goals

  • Define `RawServiceConfig` (all fields `unknown`) and implement `validateServiceConfig` that narrows every field and throws a `TypeError` on any violation — no `any` or type assertions beyond the two provided brand constructors.
  • Define `BreakerRegistry` as a mapped/Record type keyed by `ServiceId` and implement `createBreakerEngine` with the full closed → open → half-open state machine, per-call timeout via `Promise.race`, and retry logic.
  • Implement the generic `call<T>` method so that `T` is inferred from the passed `fn`, returning a fully typed `CallOutcome<T>` for every branch (circuit-open, timeout, max-retries-exceeded, upstream-error, success).
  • Implement `initGateway` using the `ValidatedOrError<T>` conditional type alias to type intermediate results, collecting all validation errors without short-circuiting and always returning a working engine.
challenge.ts
// Key types and main function signatures

export type ServiceId = string & { readonly __brand: "ServiceId" };
export type Ms        = number & { readonly __brand: "Ms" };

export type BreakerState =
  | { readonly status: "closed";   failureCount: number }
  | { readonly status: "open";     openedAt: number     }
  | { readonly status: "half-open"                      };

export type CallOutcome<T> =
  | { readonly ok: true;  value: T;     durationMs: number }
  | { readonly ok: false; reason: CallFailureReason; durationMs: number };

export type CallFailureReason =
  | "timeout" | "circuit-open" | "max-retries-exceeded" | "upstream-error";

export type ValidatedOrError<T> =
  | { readonly valid: true;  data: T      }
  | { readonly valid: false; error: string };

// ── Functions you must implement ──────────────────────────────────

export function validateServiceConfig(raw: unknown): ServiceConfig { ... }

export function createBreakerEngine(
  configs: ReadonlyArray<ServiceConfig>
): BreakerEngine { ... }

export function initGateway(rawConfigs: ReadonlyArray<unknown>): {
  engine: BreakerEngine;
  errors: ReadonlyArray<string>;
} { ... }
Hints (click to reveal)

Hints

  • For the timeout race, construct a `new Promise<never>((_, reject) => setTimeout(...))` and use `Promise.race([fn(), timeoutPromise])` — wrap the whole thing so the outer return type stays `Promise<CallOutcome<T>>` without `any`.
  • To key `BreakerRegistry` by `ServiceId` without `any`, try `type BreakerRegistry = { [K in ServiceId]?: BreakerState }` or simply `Record<ServiceId, BreakerState>` — both satisfy the branded constraint.
  • Inside `validateServiceConfig`, use a local type guard helper `(v: unknown): v is Record<string, unknown>` to safely access named properties before narrowing each one individually.

Or clone locally

git clone -b challenge/2026-05-11 https://github.com/niltonheck/typedrop.git