Error Handling
Queuert provides only job completion — there is no built-in “failure” state. This is intentional: you control how errors are represented in your job outputs.
Discriminated Union
Section titled “Discriminated Union”Return error information in your output type. The caller inspects the output to determine success or failure.
const jobTypeRegistry = defineJobTypeRegistry<{ "process-payment": { entry: true; input: { orderId: string }; output: { success: true; transactionId: string } | { success: false; error: string }; };}>();Compensation
Section titled “Compensation”For workflows that need rollback, continue to a compensation job that undoes previous steps.
const jobTypeRegistry = defineJobTypeRegistry<{ "charge-card": { entry: true; input: { orderId: string }; continueWith: { typeName: "ship-order" | "refund-charge" }; }; "ship-order": { input: { orderId: string; chargeId: string }; output: { shipped: true }; continueWith: { typeName: "refund-charge" }; // Can continue to refund on failure }; "refund-charge": { input: { chargeId: string }; output: { refunded: true }; };}>();Rescheduling
Section titled “Rescheduling”When a job throws an error, it’s automatically rescheduled with exponential backoff. For
transient failures where you want explicit control over retry timing, use rescheduleJob:
import { rescheduleJob } from "queuert";
const worker = await createInProcessWorker({ client, jobTypeProcessorRegistry: createJobTypeProcessorRegistry({ client, jobTypeRegistry, processors: { "call-external-api": { attemptHandler: async ({ job, prepare, complete }) => { const response = await fetch(job.input.url);
if (response.status === 429) { // Rate limited — retry after the specified delay const retryAfter = parseInt(response.headers.get("Retry-After") || "60", 10); rescheduleJob({ afterMs: retryAfter * 1000 }); }
if (!response.ok) { // Other errors use default exponential backoff throw new Error(`API error: ${response.status}`); }
const data = await response.json(); return complete(() => ({ data })); }, }, }, }),});
const stop = await worker.start();rescheduleJob throws a RescheduleJobError which the worker catches specially. Unlike
regular errors that trigger exponential backoff based on attempt count, rescheduleJob uses
your specified schedule exactly:
rescheduleJob({ afterMs: 30_000 }); // 30 seconds from nowrescheduleJob({ at: new Date("2026-06-15T09:00:00Z") }); // specific timerescheduleJob({ afterMs: 60_000 }, originalError); // with cause for logginglastAttemptError
Section titled “lastAttemptError”On retry, job.lastAttemptError contains the serialized error from the previous attempt. Use it for logging or to adjust retry behavior:
attemptHandler: async ({ job, complete }) => { if (job.lastAttemptError != null) { console.log(`Previous attempt failed: ${job.lastAttemptError}`); } // ...},| Thrown value | Stored as |
|---|---|
Error object | Stack trace (includes message). Own enumerable properties appended as JSON. |
| Plain object | JSON-stringified |
| String | Stored as-is |
Values are truncated to 10,000 characters.
See examples/showcase-error-handling for a complete working example demonstrating discriminated unions, compensation patterns, and explicit rescheduling. See also Job Processing Reliability for engine-level safety guarantees (savepoints, automatic rollback), Timeouts, and Job Processing Modes.