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