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