TypeDrop
2026-06-08 Challenge
2026-06-08
Hard
Typed Plugin Middleware Chain Executor
You're building the request-processing core for an API gateway platform where operators compose pipelines of plugins (auth, rate-limiting, transformation, logging) loaded at runtime from `unknown` JSON configuration. Each plugin is resolved through a typed registry, executed as an ordered async middleware chain with per-plugin timeout enforcement, and the entire run produces a strongly-typed execution trace — with zero `any`.
Goals
- Define the `PluginConfig` discriminated union, `PluginHandler<C>` generic, and `PluginRegistry` mapped type so that each registry key accepts only its matching config variant.
- Implement `validatePluginConfig` to fully narrow `unknown` → `PluginConfig` for all four plugin types without using `as` or `any`.
- Implement `withTimeout<T>` and the `TimeoutError` class so that slow plugin handlers are reliably cancelled and their `pluginId` is preserved in the error.
- Implement `executePipeline` to run plugins sequentially with per-plugin timeout enforcement, correct `PluginOutcome` recording, `abortOnError` support, and a `PipelineTrace` whose `overallStatus` is a type derived from `PluginOutcome["status"]`.
challenge.ts
// Key types & main function signature — challenge.ts (preview)
declare const __brand: unique symbol;
type Brand<T, B extends string> = T & { readonly [__brand]: B };
export type PluginId = Brand<string, "PluginId">;
export type RequestId = Brand<number, "RequestId">;
// Discriminated union — 4 variants
export type PluginConfig =
| { type: "auth"; pluginId: PluginId; apiKeyHeader: string; required: boolean }
| { type: "rateLimit"; pluginId: PluginId; maxRpm: number; burstSize: number }
| { type: "transform"; pluginId: PluginId; stripHeaders: string[]; addHeaders: Record<string, string> }
| { type: "logger"; pluginId: PluginId; level: "debug"|"info"|"warn"|"error"; destination: string };
// Handler generic — C narrows to the exact variant
export type PluginHandler<C extends PluginConfig> =
(config: C, ctx: GatewayContext) => Promise<void>;
// Registry — mapped type over union keys (your job to fill in)
export type PluginRegistry = { [K in PluginConfig["type"]]: PluginHandler<Extract<PluginConfig, { type: K }>> };
export async function executePipeline(
requestId: RequestId,
rawConfigs: unknown[],
registry: PluginRegistry,
ctx: GatewayContext,
options?: PipelineOptions
): Promise<PipelineTrace> { /* TODO */ throw new Error("not implemented"); }
Hints (click to reveal)
Hints
- For `PluginRegistry`, try `{ [K in PluginConfig["type"]]: PluginHandler<Extract<PluginConfig, { type: K }>> }` — `Extract` pulls the exact union member whose `type` matches `K`.
- In `validatePluginConfig`, use `typeof raw === "object" && raw !== null && "type" in raw` guards step by step — TypeScript narrows the type at each check without needing `as`.
- For `withTimeout<T>`, `Promise.race` between the real promise and a `new Promise((_, reject) => setTimeout(() => reject(new TimeoutError(...)), ms))` is the idiomatic pattern.
Useful resources
Or clone locally
git clone -b challenge/2026-06-08 https://github.com/niltonheck/typedrop.git