TypeDrop
2026-04-25 Challenge
2026-04-25
Medium
Typed Job Queue Processor
You're building the background job processing engine for a task automation platform. Raw job payloads arrive as `unknown` from a message broker; your processor must validate them, dispatch each job to a strongly-typed handler, enforce per-job-type retry policies, and produce a discriminated-union result per job — with zero `any`.
Goals
- Define the `Job` discriminated union, `JobResult<J>` generic, `JobHandler<J>` alias, and the distributive `HandlerMap` mapped type.
- Implement `parseJob` to safely narrow `unknown` → `Job` with field-level validation and zero type assertions.
- Implement `processJob` with attempt tracking, wall-clock timing, fixed-backoff retry logic, and correct `JobSuccess`/`JobFailure` return types.
- Implement `dispatch` to orchestrate parsing, handler/policy lookup by `job.kind`, and result propagation — keeping all types sound.
challenge.ts
// Key types and main function signatures
export type Job = SendEmailJob | ResizeImageJob | GenerateReportJob;
export type JobResult<J extends Job> = JobSuccess<J> | JobFailure<J>;
// Mapped type: each kind maps to the handler for THAT specific subtype
export type HandlerMap = {
[K in Job["kind"]]: JobHandler<Extract<Job, { kind: K }>>;
};
// Validate unknown → Job (throws TypeError on bad input)
export function parseJob(raw: unknown): Job { /* TODO */ }
// Retry-aware executor
export async function processJob<J extends Job>(
job: J,
handler: JobHandler<J>,
policy: RetryPolicy,
): Promise<JobResult<J>> { /* TODO */ }
// Top-level entry point: parse → dispatch → result
export async function dispatch(
raw: unknown,
handlers: HandlerMap,
): Promise<JobResult<Job>> { /* TODO */ }
Hints (click to reveal)
Hints
- For `HandlerMap`, use `Extract<Job, { kind: K }>` inside the mapped type to get the exact subtype for each key — this makes each handler precisely typed.
- In `parseJob`, use `in` operator narrowing and `typeof` checks to validate each field without ever reaching for `as`.
- Track `const start = Date.now()` before the retry loop and compute `durationMs` only once at the end — both success and failure paths need it.
Useful resources
Or clone locally
git clone -b challenge/2026-04-25 https://github.com/niltonheck/typedrop.git