TypeDrop
2026-08-23 Challenge
2026-08-23
Hard
Typed Middleware Pipeline with Branded Types & Conditional Inference
You're building the request-processing core for an internal HTTP gateway. Every incoming request passes through a chain of typed middleware — authentication, rate-limiting, body parsing, and authorization — each of which may enrich the context object or short-circuit with a typed error response. The hardest part is ensuring the TypeScript compiler tracks exactly which context properties have been added by each middleware stage, so downstream handlers never access fields that haven't been populated yet.
Goals
- Define `Brand<T, B>` and use it to create nominal `RequestId`, `UserId`, and `AuthToken` types that are structurally distinct from plain `string`.
- Implement `MiddlewareResult<T, E>` as a discriminated union and `MiddlewareFn<In, Out, E>` as a typed async function, then implement `GatewayError` covering all four variants.
- Implement `LastOutput<T>` using conditional types and `infer` to extract the output context of the last element in a middleware tuple, and implement `composeMiddleware` to thread context through each stage.
- Implement all four concrete middleware functions and `handleRequest`, ensuring each stage only accesses fields present on its specific input context type, and implement exhaustive `matchGatewayError`.
challenge.ts
// Key types & main function signature
type Brand<T, B extends string> = T & { readonly __brand: B };
type RequestId = Brand<string, "RequestId">;
type UserId = Brand<string, "UserId">;
type AuthToken = Brand<string, "AuthToken">;
type MiddlewareResult<T, E> =
| { status: "continue"; ctx: T }
| { status: "halt"; error: E };
type MiddlewareFn<In, Out, E> =
(ctx: In) => Promise<MiddlewareResult<Out, E>>;
type GatewayError =
| { kind: "unauthenticated"; message: string }
| { kind: "rate_limited"; retryAfterMs: number }
| { kind: "parse_error"; field: string; reason: string }
| { kind: "forbidden"; requiredRole: string };
// Infer the output context of the LAST middleware in the tuple:
type LastOutput<T extends readonly MiddlewareFn<unknown, unknown, GatewayError>[]> = /* TODO */;
// Compose an ordered tuple of middleware into one async function:
declare function composeMiddleware<
const Stages extends readonly MiddlewareFn<unknown, unknown, GatewayError>[]
>(stages: Stages): (ctx: RawContext) => Promise<MiddlewareResult<LastOutput<Stages>, GatewayError>>;
Hints (click to reveal)
Hints
- For `LastOutput<T>`, try indexing the tuple with its last index — `T extends readonly [...infer _, infer Last] ? Last extends MiddlewareFn<unknown, infer Out, GatewayError> ? Out : never : never` is a useful pattern.
- Inside `composeMiddleware`, you'll need to cast the running context between stages at runtime (a single internal `as unknown as NextIn` is acceptable inside the implementation body — but not in the type stubs).
- For exhaustive matching in `matchGatewayError`, add a `default` branch that assigns `error` to `never` — the compiler will complain if any variant is unhandled.
Useful resources
Or clone locally
git clone -b challenge/2026-08-23 https://github.com/niltonheck/typedrop.git