TypeDrop

2026-08-29 Challenge

2026-08-29 Hard

Typed Middleware Pipeline with Inferred Context Accumulation

You're building the request-handling core for an internal HTTP gateway. Middleware layers run sequentially, each one reading from — and optionally enriching — a typed context object. The compiler must guarantee that a middleware can only access fields that prior layers have already attached, and that the final handler receives the exact accumulated context type — no more, no less.

Goals

  • Implement `createPipeline` as a type-safe fluent builder that threads an ever-growing context type through sequential middleware layers.
  • Implement `run` to execute layers in order, short-circuiting immediately on the first `{ ok: false }` result.
  • Implement `composeMiddleware` to fuse two compatible middleware into one, propagating failures from the first before invoking the second.
  • Implement the `ExtractCtx` and `ContextDiff` utility types using `infer` and mapped-type key remapping respectively.
challenge.ts
// Key types — understand these before implementing:

type BaseCtx = Record<string, unknown>;

type MiddlewareResult<TOut extends BaseCtx> =
  | { ok: true;  ctx: TOut }
  | { ok: false; error: MiddlewareError };

type Middleware<TIn extends BaseCtx, TOut extends TIn> = {
  name: string;
  handler: (ctx: TIn) => Promise<MiddlewareResult<TOut>>;
};

// The builder interface — TCtx grows with each `.use()` call:
interface Pipeline<TCtx extends BaseCtx> {
  use<TOut extends TCtx>(mw: Middleware<TCtx, TOut>): Pipeline<TOut>;
  run(initialCtx: TCtx): Promise<MiddlewareResult<TCtx>>;
}

// Entry point — start with your seed context shape:
declare function createPipeline<T extends BaseCtx>(): Pipeline<T>;
Hints (click to reveal)

Hints

  • The `Pipeline` interface's `use` method must return `Pipeline<TOut>` — store handlers in a plain array internally and cast the return type of `use` at the boundary (the one place a cast is acceptable in a builder pattern).
  • For `ContextDiff`, use a mapped type with an `as` clause: `[K in keyof TAfter as K extends keyof TBefore ? never : K]` to filter out pre-existing keys.
  • In `run`, iterate the stored handlers with a `for...of` loop and an `await` inside — if any result has `ok: false`, return it immediately; only return success after the loop completes.

Or clone locally

git clone -b challenge/2026-08-29 https://github.com/niltonheck/typedrop.git