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.

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.
| `status` | `run_id` | `error` |
|---|---|---|
queued | null | null |
running | string or null | null |
completed | string | null |
failed | string or null | { code, message } |
canceled | string | { 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
- unittest the spreadsheet-row to Sume bulk item builder, no network
A pure function that turns a spreadsheet row into a Sume bulk item, with four unittest cases for trimming, price format, a spend cap in range and blank SKUs.
- unsupported_capability names sume/auto: fix a Sume ad clip request
A 400 unsupported_capability on sume/auto hides the resolved model but lists accepted values in supported. How to fix duration, resolution or audio.
- Format run cap headroom: usage.cap limit, counted and remaining
usage.cap on a run receipt splits the spend-cap check into limit, counted and remaining USD micros. Read it to see how close a run is to failing.
- Validate Wan 3.0 reference limits in Python before submitting
wan-3.0 takes 10 images, 5 videos (15 s total, 16 fps minimum) and 5 audios (15 s total). A short Python check catches an over-limit manifest early.
Written by Sume