TypeDrop

2026-02-25 Challenge

2026-02-25 Medium

Typed Middleware Pipeline Builder

You're building the request-handling core of an internal HTTP gateway. Middleware functions transform a typed context object one step at a time — the challenge is composing them into a pipeline where each middleware's output type flows into the next middleware's input type, all enforced at compile time.

Goals

  • Implement `Awaited_<T>` and `PipelineOutput<Middlewares>` as conditional types that use `infer` to extract inner and last-middleware output types.
  • Implement `compose` and `composeAsync` as fully generic functions that chain two middlewares with no manual type annotations at call sites.
  • Implement `runPipeline` to execute middlewares sequentially, returning a typed `PipelineResult` discriminated union that captures both success and per-step failure.
  • Define the `Brand` helper and `AuthedContext` branded type, then implement `withAuth` as a middleware that narrows a plain `RequestContext` into an `AuthedContext` or throws.
challenge.ts
/** A single middleware: receives a context of type In, returns Out. */
type Middleware<In, Out> = (ctx: In) => Out;

/** Unwrap a Promise<T> → T; leave non-promise types unchanged. */
type Awaited_<T> = /* TODO: conditional type using infer */;

/** Infer the output type of the last middleware in a tuple. */
type PipelineOutput<
  Middlewares extends readonly Middleware<unknown, unknown>[]
> = /* TODO: recursive conditional type */;

/** Discriminated union returned by runPipeline. */
type PipelineResult<T> =
  | { ok: true; value: T }
  | { ok: false; error: string; step: number };

/** Chain two middlewares — intermediate type is fully inferred. */
declare function compose<A, B, C>(
  f: Middleware<A, B>,
  g: Middleware<B, C>
): Middleware<A, C>;

/** Run an ordered array of middlewares, catching per-step errors. */
declare function runPipeline(
  initial: unknown,
  middlewares: Array<Middleware<unknown, unknown>>
): PipelineResult<unknown>;
Hints (click to reveal)

Hints

  • For `PipelineOutput`, use a variadic tuple pattern: `Middlewares extends [...infer _Init, infer Last]` to grab only the final element, then extract its output with a second `infer`.
  • A branded type is just an intersection with a phantom property: `T & { readonly __brand: B }` — this makes two structurally identical types nominally distinct.
  • In `runPipeline`, keep a `current: unknown` variable and iterate with a `for` loop so you can track the step index when catching errors.

Or clone locally

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