TypeDrop

2026-03-07 Challenge

2026-03-07 Medium

Typed Job Queue with Retry Logic & Concurrency Limits

You're building the background job runner for a SaaS platform. Jobs arrive with different payloads and priorities, each handler is typed to its payload, and the runner must enforce a concurrency cap, retry failed jobs with exponential back-off, and report a typed summary when the queue drains.

Goals

  • Define a discriminated-union `AnyJob` and a conditional `PayloadFor<K>` type so that `JobRegistry` maps each job kind to a handler that only accepts the matching payload type.
  • Implement `createJobRunner` so that `run()` never executes more than `maxConcurrency` jobs simultaneously, using a Promise-slot pool or equivalent pattern.
  • Implement exponential back-off retry: on handler failure, wait `baseDelayMs * 2^attemptsMade` ms and retry up to `job.maxRetries` times before recording a final failure.
  • Return a fully-typed `RunSummary` from `run()` whose `results` array contains a `JobOutcome` discriminated union for every job processed.
challenge.ts
// Key types — core of the challenge

export type JobKind = "email" | "report" | "webhook";

// Each concrete job locks `kind` and `payload` together
export type EmailJob   = Job<EmailPayload>   & { kind: "email" };
export type ReportJob  = Job<ReportPayload>  & { kind: "report" };
export type WebhookJob = Job<WebhookPayload> & { kind: "webhook" };
export type AnyJob     = EmailJob | ReportJob | WebhookJob;

// Resolve the right payload type for each kind (conditional type)
type PayloadFor<K extends JobKind> = TODO;

// Registry maps every kind to the correctly-typed handler
export type JobRegistry = {
  [K in JobKind]: JobHandler<PayloadFor<K>>;
};

// Factory — enforce concurrency cap + retry logic
export function createJobRunner(
  registry: JobRegistry,
  options: RunnerOptions   // { maxConcurrency, baseDelayMs }
): JobRunner {             // { enqueue(job), run(): Promise<RunSummary> }
  throw new Error("Not implemented");
}
Hints (click to reveal)

Hints

  • For `PayloadFor<K>`, write a conditional type that maps `"email"` → `EmailPayload`, `"report"` → `ReportPayload`, `"webhook"` → `WebhookPayload` — then use it inside the mapped `JobRegistry` type.
  • To dispatch a job to its handler without a type assertion, narrow on `job.kind` inside a `switch` statement; TypeScript will infer the correct payload type in each branch.
  • A simple concurrency pool: keep a `Set` of in-flight Promises; when the set reaches `maxConcurrency`, `await Promise.race(activeSet)` before starting the next job.

Or clone locally

git clone -b challenge/2026-03-07 https://github.com/niltonheck/typedrop.git