TypeDrop
2026-10-09 Challenge
2026-10-09
Medium
Typed Retry-with-Fallback Async Executor
You're building the resilience layer for a microservice that calls unreliable third-party APIs. Each operation declares its own retry policy, a typed fallback strategy, and a structured error taxonomy — the compiler must enforce that fallback return types are compatible with the primary operation, that error kinds are exhaustively handled, and that the final resolved value is correctly narrowed.
Goals
- Define the `OpError` discriminated union, `Result<T,E>` generic type, `RetryPolicy`, and `FallbackStrategy<T>` discriminated union with correct TypeScript shapes.
- Implement `runWithRetry<T>` that retries only on listed error kinds, waits between attempts, and dispatches the correct fallback strategy when retries are exhausted.
- Implement `classifyError` with exhaustive narrowing over all `OpError` variants using a never-guard.
- Implement `summariseResults` that returns a `Record<OpError['kind'], number>` with all three keys always present, even when zero.
challenge.ts
// Key types and main function signature
type OpError =
| { kind: "network"; statusCode: number; message: string }
| { kind: "timeout"; elapsedMs: number; message: string }
| { kind: "validation"; fields: string[]; message: string };
type Result<T, E> =
| { ok: true; value: T }
| { ok: false; error: E };
type FallbackStrategy<T> =
| { type: "static"; value: T }
| { type: "compute"; fn: () => Promise<T> }
| { type: "rethrow" };
interface Operation<T> {
exec: () => Promise<Result<T, OpError>>;
policy: RetryPolicy;
fallback: FallbackStrategy<T>;
}
async function runWithRetry<T>(
op: Operation<T>
): Promise<Result<T, OpError>> { ... }
Hints (click to reveal)
Hints
- For exhaustive matching in `classifyError`, assign the default case to a `const _: never` variable — the compiler will error if any variant is unhandled.
- The `FallbackStrategy<T>` type parameter must flow into `Operation<T>` so that a `static` fallback's `value` type matches `T` — no casting needed if the union is defined generically.
- `Record<OpError['kind'], number>` forces you to initialise all three keys (`network`, `timeout`, `validation`) before returning from `summariseResults`.
Useful resources
Or clone locally
git clone -b challenge/2026-10-09 https://github.com/niltonheck/typedrop.git