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