TypeScript union for Sume bulk queue items, checked with Deno

Model queue items by status so run_id and error are typed per case: null while queued, null for a child that never started, and an exhaustive switch.

5 min readSume
All posts

A Sume bulk queue item is a union by status: queued, running, completed, failed or canceled. The fields that go with each status differ. run_id is null while an item is queued, and also null for a child that failed before a run started. error is null unless the item failed or was canceled. Type it as a discriminated union and the compiler makes your handler deal with each case, including a failed item with no run to look up.

The file below type-checks with deno check status.ts and runs with deno run status.ts. It has no imports.

The types and a handler

The default branch assigns to never: add a sixth status to the union later and the file stops compiling until the switch handles it.

type Item =
  | { index: number; status: "queued"; run_id: null; error: null }
  | { index: number; status: "running"; run_id: string | null; error: null }
  | { index: number; status: "completed"; run_id: string; error: null }
  | { index: number; status: "failed"; run_id: string | null; error: { code: string; message: string } }
  | { index: number; status: "canceled"; run_id: string; error: { code: string; message: string } };

function label(it: Item): string {
  switch (it.status) {
    case "queued": return "waiting for a slot";
    case "running": return it.run_id ? `running as ${it.run_id}` : "claimed, no run id yet";
    case "completed": return `read the receipt for ${it.run_id}`;
    case "failed": return it.run_id ? `failed run ${it.run_id}: ${it.error.code}` : `never started: ${it.error.code}`;
    case "canceled": return `canceled run ${it.run_id}`;
    default: { const unreachable: never = it; return unreachable; }
  }
}

const items: Item[] = [
  { index: 0, status: "completed", run_id: "run_a", error: null },
  { index: 1, status: "failed", run_id: null, error: { code: "format_run_failed_to_start", message: "x" } },
  { index: 2, status: "running", run_id: null, error: null },
];
for (const it of items) console.log(it.index, label(it));

Where each shape comes from

running allows a null run id because the API counts a child it has claimed but not yet given a run_id as still running. A handler that assumes run_id is set for running will throw on a normal poll.

failed covers two different things, and the type keeps them apart. A child that ran and failed has run_id set and the error format_run_failed. A child that could not start has run_id: null and the error from the failed create-run attempt, for example format_run_failed_to_start. A skipped child run is recorded as failed as well. The rest of the queue carries on in all cases.

canceled always has a run: you cancel a child with POST /v1/format-runs/{run_id}/cancel, and that frees its slot. The API has no cancel call for the queue itself.

Item status, run id and error as the types encode them (read 2026-10-07)
`status``run_id``error`
queuednullnull
runningstring or nullnull
completedstringnull
failedstring or null{ code, message }
canceledstring{ code, message }

What the compiler cannot check

Types describe the response you expect. They do not validate it. If you cast the parsed JSON to Item[], a field the API adds later passes through and a field it removes shows up as undefined at runtime. For code that feeds a billing or catalog table, parse with a schema library or check status at runtime before you trust the cast.

The queue itself has status of queued, running or completed, and completed means every item is terminal, not that every item succeeded. Count the failed and canceled items from counts as well as from the item list, and treat a mismatch between them as a read taken mid-update.

One practical use of the union is a function that returns the next action for each item, such as retry, read receipt or wait. With the exhaustive switch, adding a status to the type forces you to decide what that action is. Keep the retry decision outside the type, since only your own ledger knows whether the SKU has already completed in an earlier queue.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume