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.

Or clone locally

git clone -b challenge/2026-04-25 https://github.com/niltonheck/typedrop.git