TypeDrop

A new TypeScript challenge every day. Sharpen your types.

TypeDrop delivers a fresh TypeScript challenge every day, generated by AI. Pick a challenge, open it in StackBlitz (preferred) or CodeSandbox (or clone it locally), and make the tests pass. No accounts, no setup — just you and the type system.

Learn more on GitHub →

2026-02-19 Hard

Typed Middleware Pipeline with Retry & Cancellation

You're building an internal HTTP gateway layer that processes outgoing requests through a chain of typed middleware (auth injection, logging, rate-limit headers). Each middleware can transform the request context, short-circuit with a typed error, and the pipeline runner supports per-request cancellation and automatic retry with exponential back-off.

Goals

  • Define `Middleware`, `Executor`, `RetryPolicy`, and `PipelineOptions` as fully-typed generic aliases/interfaces with no `any` or `never` placeholders.
  • Implement `runPipeline` with a recursive `dispatch` function that threads context through middlewares, supports short-circuiting, and retries failed attempts using the supplied `RetryPolicy` with exponential back-off.
  • Implement the three middleware factories (`withBearerAuth`, `withRequestLogger`, `withRateLimitHeader`) and the `exponentialBackoff` helper so all five test cases pass.
  • Implement `sleep` so it respects an optional `AbortSignal`, rejecting immediately with the signal's reason when already aborted or when aborted mid-sleep.
challenge.ts

// Key types at a glance

type Milliseconds = number & { readonly __brand: "Milliseconds" };

interface PipelineContext<TBody = unknown> {
  readonly requestId: string;
  url: string;
  method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
  headers: Record<string, string>;
  body: TBody | undefined;
  meta: Record<string, unknown>;
}

type PipelineResult<TResponse, TError> =
  | { readonly kind: "success"; readonly response: TResponse;
      readonly durationMs: Milliseconds; readonly attempts: number }
  | { readonly kind: "failure"; readonly error: TError;
      readonly durationMs: Milliseconds; readonly attempts: number };

// TODO (a) — fill in the body:
type Middleware<TBody, TResponse, TError> = never;

// TODO (f) — implement this:
async function runPipeline<TBody, TResponse, TError>(
  options: PipelineOptions<TBody, TResponse, TError>
): Promise<PipelineResult<TResponse, TError>> { ... }
Hints (click to reveal)

Hints

  • For `Middleware<TBody, TResponse, TError>`, the `next` parameter's type is the same async function shape as the middleware itself minus the `next` arg — write it as an inline function type `(ctx: PipelineContext<TBody>) => Promise<PipelineResult<TResponse, TError>>`.
  • In `runPipeline`, track `startTime = Date.now()` before the attempt loop and compute `durationMs` only once when you're ready to return — the `durationMs` fields from the executor are intentionally ignored at the pipeline level.
  • For `sleep` + `AbortSignal`, register a one-shot `'abort'` event listener and also check `signal.aborted` synchronously at the top of the function before creating the timeout.

Or clone locally

git clone -b challenge/2026-02-19 https://github.com/niltonheck/typedrop.git