TypeDrop
2026-03-23 Challenge
2026-03-23
Hard
Typed Plugin Middleware Chain Executor
You're building the extensibility core for a developer platform where third-party plugins can register typed middleware that transforms a shared request context. Each plugin declares the exact context shape it reads and the shape it writes, and the chain executor must thread them together in order — surfacing typed errors and a full execution trace through a `Result<T, E>` monad with zero `any`.
Goals
- Define a `Result<T,E>` discriminated union and implement `ok`/`err` constructors, then build a `PluginError` discriminated union covering validation, timeout, and dependency variants.
- Model `Plugin<In, Out>` with a generic constraint `Out extends In` that enforces context-only-grows semantics, and implement `PluginRegistry` with a fluent `register()` builder.
- Implement `executeChain` that runs plugins sequentially, enforces per-plugin timeouts via `Promise.race`, accumulates a `TraceEntry` for every started plugin, and short-circuits on the first error.
- Implement `formatPluginError` with an exhaustive `switch` over `PluginError` kinds, using a `never`-typed default branch so TypeScript proves no variant is unhandled.
challenge.ts
// Key types — understand these before implementing
export type Result<T, E> =
| { ok: true; value: T }
| { ok: false; error: E };
export interface BaseContext {
readonly requestId: string;
meta: Record<string, unknown>;
}
export type MiddlewareFn<In extends BaseContext, Out extends In> = (
ctx: In
) => Promise<Result<Out, PluginError>>;
export interface Plugin<In extends BaseContext, Out extends In> {
id: string;
timeoutMs: number;
run: MiddlewareFn<In, Out>;
}
// Main entry point
export async function executeChain(
initCtx: BaseContext,
registry: PluginRegistry
): Promise<ChainResult<BaseContext>>;
Hints (click to reveal)
Hints
- For the timeout race, create a `Promise<Result<never, PluginError>>` that rejects (or resolves to `err(...)`) after `timeoutMs` milliseconds — then `Promise.race` it against the plugin's own promise.
- Store plugins in `PluginRegistry` as `Plugin<BaseContext, BaseContext>[]`; the `register<In, Out>` generic signature is safe to accept because `Plugin<In, Out>` is assignable to `Plugin<BaseContext, BaseContext>` given the covariant/contravariant positions here — but you may need a targeted cast only inside `register`.
- The `never`-typed default in `formatPluginError` looks like: `default: { const _exhaustive: never = error; throw new Error(...); }` — TypeScript will error if you ever add a new `PluginError` variant without handling it.
Useful resources
Or clone locally
git clone -b challenge/2026-03-23 https://github.com/niltonheck/typedrop.git