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-10-16 Hard

Typed Middleware Pipeline with Typed Context & Error Boundaries

You're building the request-processing core for a Node.js-style API gateway. Each incoming request flows through a chain of middleware functions that progressively enrich a typed context object — the compiler must guarantee that later middleware can only read properties that earlier middleware has already written, and that every error path is exhaustively handled through a typed Result boundary.

Goals

  • Implement the `Pipeline<Ctx>` class so that `.pipe()` returns a new pipeline whose context type is `Ctx & Adds`, enforced entirely at compile time with no unsafe casts.
  • Implement `run()` to execute middleware in order, threading the enriched context through each step and short-circuiting on the first `fail` result.
  • Implement `handleError` as an exhaustive switch over every `AppError` kind so the compiler rejects any unhandled variant.
  • Implement the generic helpers `ok`, `fail`, `mapResult`, and `collectResults` with fully inferred types and zero use of `any` or type assertions.
challenge.ts
// Key types at a glance:

type Result<T, E extends AppError> =
  | { readonly ok: true;  readonly value: T }
  | { readonly ok: false; readonly error: E };

type AppError =
  | { kind: "AuthError";       message: string }
  | { kind: "ValidationError"; field: string; message: string }
  | { kind: "NotFoundError";   resource: string }
  | { kind: "RateLimitError";  retryAfterMs: number }
  | { kind: "InternalError";   cause: unknown };

type MiddlewareFn<Ctx extends BaseContext, Adds extends Record<string, unknown>, Err extends AppError> =
  (ctx: Ctx) => Promise<Result<Ctx & Adds, Err>>;

// Pipeline builder — context type widens with each .pipe() call:
declare class Pipeline<Ctx extends BaseContext> {
  pipe<Adds extends Record<string, unknown>, Err extends AppError>(
    fn: MiddlewareFn<Ctx, Adds, Err>
  ): Pipeline<Ctx & Adds>;

  run(base: BaseContext): Promise<Result<Ctx, AppError>>;
}

declare function createPipeline(): Pipeline<BaseContext>;
Hints (click to reveal)

Hints

  • The immutable pipeline chain is the key challenge: store middleware as a typed array internally, but expose `pipe` as returning `Pipeline<Ctx & Adds>` — think about how to cast internally while keeping the public API honest.
  • For exhaustive error handling, add a `default:` branch that assigns `error` to a variable typed as `never` and returns it — the compiler will catch missing cases.
  • For `collectResults`, a simple `for...of` loop that returns early on the first `fail` is enough — you do not need `Promise.all` here since the inputs are already-resolved Results.

Or clone locally

git clone -b challenge/2026-10-16 https://github.com/niltonheck/typedrop.git