Jobs & Queue
@bext-stack/framework/jobs is the ergonomic layer over bext's SDK queue —
Laravel's ShouldQueue jobs + Job::dispatch(), in TypeScript. Declare jobs
(name → payload type + handler), dispatch them from a render or an action, and
a worker processes queued envelopes, routing to the right handler with
automatic retries.
When To Use It
- Move slow work off the request (send email, resize an image, charge a card).
- Anything you want retried on transient failure.
- Typed enqueue + typed handler, so payloads can't drift.
Declaring & dispatching
import { createJobs } from "@bext-stack/framework/jobs";
const jobs = createJobs({
appId: "my-site", // → pushes to the SDK queue over loopback
jobs: {
sendInvoice: { handler: async (p: { orderId: string }) => { /* … */ } },
chargeCard: { maxAttempts: 3, handler: async (p: { amount: number }) => { /* may throw → retried */ } },
},
});
// enqueue from a render / action — typed payload, returns the queue job id:
await jobs.dispatch("sendInvoice", { orderId: "o1" });
await jobs.dispatch("chargeCard", { amount: 4200 }, { delaySecs: 30 });
Processing
A worker (e.g. a bext task worker) pulls envelopes off the queue and processes
each — process routes to the handler and, on failure, re-enqueues with
attempt + 1 while attempts remain:
// for each envelope pulled from the queue:
const result = await jobs.process(envelope);
// → { job: "chargeCard", ok: false, attempt: 1, retried: true, error: "gateway timeout" }
// → …later… { job: "chargeCard", ok: true, attempt: 2 }
process never throws — it returns a JobResult (ok, attempt, retried?,
error?), so the worker loop stays simple. maxAttempts (default 1) bounds
retries.
Pluggable transport (and testing)
The queue is a Dispatcher. The default pushes to the SDK queue; inject one for
tests or a self-contained flow:
import { memoryDispatcher } from "@bext-stack/framework/jobs";
const disp = memoryDispatcher();
const jobs = createJobs({ jobs: { … }, dispatcher: disp });
disp.bind(jobs);
await jobs.dispatch("sendInvoice", { orderId: "o1" });
const results = await disp.drain(); // runs every queued envelope through process()
Try It
The live jobs demo dispatches three jobs
and processes them in one request (in-memory) — one fails then succeeds on retry.
Source in sites/demo/src/app/examples/jobs/page.tsx.
See Also
- Background Jobs / Task Scheduler — the SDK queue + workers underneath.
- Durable Flows — multi-step, durable workflows.
- Notifications — a common job payload.
- Application Toolkit — the rest of the TypeScript app layer.