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