TypeDrop
2026-09-22 Challenge
2026-09-22
Medium
Typed Job Queue with Priority Scheduling & Result Collection
You're building the task-scheduling core for a background job processor. Jobs arrive with different priorities and payload shapes, a concurrency-limited runner executes them, and every result — success or failure — is collected into a strongly-typed summary the compiler fully understands.
Goals
- Define a three-variant discriminated union `Job` and the generic `JobSuccess`, `JobFailure`, and `JobResult` types with correct constraints.
- Build a `HandlerRegistry` mapped type that maps each job `kind` to a handler typed for exactly that variant — no broader union.
- Implement `runQueue` to sort by priority, enforce a concurrency limit, capture every result (success or failure), and return a fully-typed `QueueSummary`.
- Implement `getSuccesses` so callers automatically receive the narrowed `JobSuccess<JobByKind<K>>[]` type without any explicit annotation at the call site.
challenge.ts
// Key types & main function signature
export type Job =
| { kind: "email"; id: string; priority: number; to: string; subject: string; body: string }
| { kind: "report"; id: string; priority: number; reportId: string; format: "pdf" | "csv" | "xlsx" }
| { kind: "thumbnail"; id: string; priority: number; imageUrl: string; widths: number[] };
export type JobByKind<K extends Job["kind"]> = Extract<Job, { kind: K }>;
export type JobResult<J extends Job> =
| { status: "success"; job: J; output: string; durationMs: number }
| { status: "failure"; job: J; error: string; retryable: boolean };
export type HandlerRegistry = { [K in Job["kind"]]: (job: JobByKind<K>) => Promise<JobResult<JobByKind<K>>> };
export async function runQueue(
jobs: Job[],
registry: HandlerRegistry,
concurrency: number,
): Promise<QueueSummary> { /* TODO */ }
export function getSuccesses<K extends Job["kind"]>(
results: JobResult<Job>[],
kind: K,
): JobSuccess<JobByKind<K>>[] { /* TODO */ }
Hints (click to reveal)
Hints
- For `HandlerRegistry`, a mapped type `{ [K in Job["kind"]]: JobHandler<JobByKind<K>> }` gives each key its own narrowly-typed handler — no union bleed-through.
- To enforce concurrency, keep a count of active slots and use a queue of pending promises; resolve the next job as each slot frees up.
- In `getSuccesses`, filter first by `result.job.kind === kind`, then by `result.status === "success"` — two type guards in sequence let TypeScript narrow all the way to `JobSuccess<JobByKind<K>>`.
Useful resources
Or clone locally
git clone -b challenge/2026-09-22 https://github.com/niltonheck/typedrop.git