TypeDrop
2026-07-14 Challenge
2026-07-14
Hard
Typed Plugin Middleware Pipeline
You're building the request-processing core for an API gateway. Plugins are registered at startup and form a typed middleware chain — each plugin declares the context fields it *reads* and *writes*, and the pipeline must enforce at the type level that every field a plugin reads has already been produced by an earlier plugin.
Goals
- Implement `Accumulate<Plugins>` — a recursive conditional type that intersects BaseContext with each plugin's Writes in order.
- Implement `ValidPipeline<Plugins>` — a recursive conditional type that rejects (returns `never`) any tuple where a plugin's Reads are not satisfied by the accumulated context so far.
- Implement `makePlugin` so that `Reads` and `Writes` are inferred from the `execute` callback, producing a correctly-typed `Plugin<Reads, Writes>`.
- Implement `createPipeline` with sequential execution, short-circuit on the first `Err`, and a compile-time guard that makes an out-of-order plugin tuple a type error.
challenge.ts
// Key types and main function signature
type BaseContext = {
readonly requestId: string;
readonly method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
readonly path: string;
readonly headers: Readonly<Record<string, string>>;
readonly body: unknown;
};
type Plugin<Reads extends object, Writes extends object> = {
readonly name: string;
readonly execute: (ctx: Reads) => Promise<Result<Writes, PluginError>>;
};
// Accumulates all plugin Writes into one intersection, starting from BaseContext
type Accumulate<Plugins extends readonly Plugin<object, object>[],
Acc extends object = BaseContext> = /* TODO */
// Validates that each plugin's Reads ⊆ accumulated context so far
type ValidPipeline<Plugins extends readonly Plugin<object, object>[],
Acc extends object = BaseContext> = /* TODO */
// Only compiles when plugin ordering is valid
function createPipeline<const Plugins extends readonly Plugin<object, object>[]>(
plugins: ValidPipeline<Plugins> extends never ? never : Plugins
): { run: (ctx: BaseContext) => Promise<PipelineResult<Plugins>> }
Hints (click to reveal)
Hints
- For `Accumulate` and `ValidPipeline`, pattern-match the tuple with `Plugins extends [infer Head, ...infer Tail]` and recurse on `Tail` — don't forget the base case when the tuple is empty.
- The compile-time ordering guard works by making the `plugins` parameter type `never` when `ValidPipeline<Plugins>` is `never` — callers can't pass `never` as a value, so the error surfaces naturally.
- Inside `createPipeline`'s `run`, cast the accumulated object carefully: you'll need to widen the type at each step since TypeScript can't track the incremental intersection at runtime — consider using `Object.assign` into a typed accumulator.
Useful resources
Or clone locally
git clone -b challenge/2026-07-14 https://github.com/niltonheck/typedrop.git