TypeScript worker runtime
The @zeridion/flare/worker subpath is a complete background-worker host. You
define jobs with a small factory API, hand them to a FlareWorker, and the
runtime registers with Flare, long-polls for work, dispatches each job to your
handler with bounded concurrency, reports liveness and progress, honours
server-side cancellation, acknowledges each outcome, and drains in-flight work
on shutdown.
The worker is opt-in by import — the thin client at @zeridion/flare stays
dependency-free and edge-safe; importing @zeridion/flare/worker pulls in the
runtime only where you need it.
npm install @zeridion/flare
Runtime: Node 20.3+ (the worker uses AbortSignal.any / AbortSignal.timeout). The thin client remains edge/browser-safe.
Quick start
import { FlareWorker, defineJob, defineRecurringJob } from "@zeridion/flare/worker";
const sendWelcome = defineJob<{ email: string; name: string }>({
jobType: "SendWelcomeEmail",
queue: "default",
maxAttempts: 5,
timeoutSeconds: 60,
async handle(payload, ctx) {
ctx.reportProgress(0.5);
await mailer.send(payload.email, { signal: ctx.signal });
ctx.reportProgress(1);
},
});
const nightly = defineRecurringJob({
jobType: "NightlyCleanup",
cron: "0 3 * * *",
timezone: "UTC",
async handle(ctx) {
await db.sessions.deleteExpired({ signal: ctx.signal });
},
});
const worker = new FlareWorker({
apiKey: process.env.FLARE_API_KEY,
concurrency: 5,
jobs: [sendWelcome, nightly],
});
await worker.run(); // starts polling + wires SIGTERM/SIGINT; resolves once drained
What the runtime does for you
- Registers the worker, its queues, job types, and any recurring schedules on startup. Registration is best-effort — a failure is logged and polling continues.
- Long-polls for jobs, reporting how many free slots it has so the server never hands it more work than it can run.
- Dispatches each job to the matching handler, bounded by your
concurrencylimit. - Heartbeats each in-flight job on a cadence derived from the job's timeout, carrying the latest reported progress. A heartbeat that returns a cancel signal aborts the job.
- Acknowledges every outcome. A successful return acks the job succeeded; any thrown error (including a timeout or a server cancel) acks it failed. The server decides whether to retry — the worker only reports.
- Drains on shutdown: it stops polling, lets in-flight jobs finish within a grace window, and resolves.
Lifecycle methods
| Method | Behaviour |
|---|---|
start() | Begin polling in the background. Returns immediately. No-ops when no jobs are registered. Does not install signal handlers. |
run() | start() plus SIGTERM/SIGINT wiring for a graceful stop. Resolves once the worker has fully drained. Use this for a standalone worker process. |
stop() | Stop polling and drain in-flight jobs within the grace window. Idempotent. |
waitUntilStopped() | Resolves once the worker has fully stopped. |
workerId (read-only) is the identifier the worker registers under, of the
form wrk_{host}_{pid}_{random}. The host segment is sanitized to the
characters the server accepts, and the random segment comes from a
cryptographic source so two workers — even in containers that share a
hostname — never collide.
Cancellation is cooperative
A job is cancelled by aborting ctx.signal — on timeout, on host shutdown, or
when the server requests it. Cancellation only takes effect when your handler
observes the signal. Pass ctx.signal to every async call you make
(fetch(url, { signal: ctx.signal }), database queries, and so on) and check
ctx.signal.aborted in long loops. A handler that ignores the signal cannot be
forced to stop; it runs until it returns on its own.
Because a busy event loop can starve the heartbeat that keeps a job alive,
offload CPU-heavy work or await-yield periodically so heartbeats keep their
cadence.
At-least-once delivery
Acknowledgement is best-effort, so a job can be delivered more than once — for
example, if the process crashes after the work completes but before the
acknowledgement lands. Write idempotent handlers. Guard side effects with a
dedup check keyed on ctx.jobId:
async handle(payload, ctx) {
if (await alreadyProcessed(ctx.jobId)) return;
await doWork(payload);
await markProcessed(ctx.jobId);
}
Reference
| Page | What it covers |
|---|---|
| Defining jobs | defineJob, payload typing and validation, the handler contract. |
| Recurring jobs | defineRecurringJob, cron expressions, and timezones. |
| Job context | The JobContext handed to every handler: metadata, signal, progress, logging. |
| Configuration | FlareWorker options — concurrency, polling, drain, logging. |
See also
- FlareClient — the thin client used to enqueue jobs.
- SDKs overview — feature parity across all SDKs.