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.

Or clone locally

git clone -b challenge/2026-06-08 https://github.com/niltonheck/typedrop.git