@sume-com/sdk waitForJob is not exported: a 26-line replacement

The docs show waitForJob, but the npm build of @sume-com/sdk 0.2.0 does not export it. Here is a fetch version with 429 tolerance and a deadline.

5 min readSume
All posts

If import { waitForJob } from "@sume-com/sdk" fails with "does not provide an export named waitForJob", it is not your setup. The registry lists @sume-com/sdk 0.2.0 as published on 2026-08-02 and its build does not export waitForJob, SumeJobTimeoutError or SumeJobRequestError, although the docs page for the SDK describes all three. Until a newer release lands, poll the job with fetch. The 26-line function below does what the docs describe: deadline, a floor on the poll gap, tolerance for 429 and 5xx on status reads, and a final read of the job record.

What the published 0.2.0 build does export: createSumeClient, the generated operations such as generateImageV1 and getApiJobStatus, uploadFile, verifyWebhook, waitForRun and subscribeFormatRun. The last two wait on Format, Action or Agent runs, which are a different surface from generation jobs at /v1/jobs/:id.

What I compared

I installed 0.2.0 from npm on 2026-10-02 and listed the module's exports, then read the repository's SDK docs at the same date.

SDK surface, npm 0.2.0 (published 2026-08-02) vs docs.sume.com, read 2026-10-02
HelperIn npm 0.2.0Described in docs
createSumeClient, generated operationsYesYes
uploadFile, verifyWebhookYesYes
waitForRun, subscribeFormatRunYesYes
waitForJobNoYes
SumeJobTimeoutError, SumeJobRequestErrorNoYes

The replacement

It returns the job record from GET /v1/jobs/{id}, not from /result, because /result answers 409 job_not_completed for failed and canceled jobs. Read status, result and error off the record. A TimeoutError means you stopped watching; the job keeps running.

const sleep = (ms, signal) => new Promise((res, rej) => {
  const t = setTimeout(res, ms);
  signal?.addEventListener("abort", () => { clearTimeout(t); rej(signal.reason); }, { once: true });
});

export async function waitForJob(jobId, { base, key, timeoutMs = 20 * 60_000, maxTransient = 6, signal } = {}) {
  const stop = AbortSignal.any([AbortSignal.timeout(timeoutMs), ...(signal ? [signal] : [])]);
  const get = (path) => fetch(`${base}/v1/jobs/${jobId}${path}`, { headers: { Authorization: `Bearer ${key}` }, signal: stop });
  for (let bad = 0; ; ) {
    const res = await get("/status");
    if (res.ok) {
      bad = 0;
      const { data } = await res.json();
      if (data.terminal) {
        const job = await (await get("")).json();
        return job.data.job; // read status, result and error off the record
      }
      await sleep(Math.max(2, data.next_poll_after_seconds ?? 2) * 1000, stop);
    } else if ((res.status === 429 || res.status >= 500) && ++bad <= maxTransient) {
      const hint = Number(res.headers.get("retry-after")) || 2 ** bad;
      await sleep(Math.min(hint, 30) * 1000, stop);
    } else {
      throw new Error(`job ${jobId}: status read failed with ${res.status}`);
    }
  }
}

How it behaves

Against a status endpoint that answers one 429 with retry-after: 1 and then completes, it makes three status reads and returns the record; when the deadline is shorter than the job, it throws a TimeoutError.

  • Poll gap is the larger of 2 seconds and next_poll_after_seconds.
  • A 429 or 5xx on a status read waits for retry-after (or 2, 4, 8 seconds, capped at 30) and retries up to six times in a row, then throws.
  • Any other non-2xx, such as 401 or 404, throws at once, because retrying will not change it.
  • The deadline defaults to 20 minutes, the figure the docs suggest for video.

Limits

Confirm the field names against a real job before relying on them. Do not treat a thrown error as a reason to resubmit: store the job id, and if you must retry the submit, send the same Idempotency-Key. When a newer SDK release exports waitForJob, switch to it; its retry behavior is described on the docs page, not in this function.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume