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