Concepts
Queue, task, worker, result — the Node.js mental model over the Rust core, and why there's no broker.
Queue, task, worker, result — the Node.js mental model over the Rust core, and why there's no broker.
A task queue hands off slow work — sending an email, resizing an upload, calling a third-party API — to a background worker (a process that pulls jobs off the queue and runs them), so the request that triggered it can return right away. The Node SDK is a typed shell; the scheduler, dispatcher, worker pool, and storage are all the same Rust engine the Python SDK uses. Four concepts cover the surface.
Most Node.js task queues — BullMQ, Bee-Queue, Agenda — need a standalone broker (a separate server that holds and dispatches jobs): you provision a Redis instance, secure it, and keep it running alongside your app. flexiq has no broker. The queue, job results, and cron schedules all live in one embedded SQLite file — nothing extra to install or operate.
import { Queue } from "@byteveda/flexiq";
const queue = new Queue({ dbPath: "flexiq.db" }); // that's the whole setupBullMQ's new Queue(name, { connection: redis }) plus a separately-run
Redis server becomes a single new Queue({ dbPath }) — no connection to
provision. Point every process at the same file (or DSN) and they share one
logical queue. See installation if you
want Postgres or Redis as a shared backend instead of a file.
A Queue is the handle to one store (SQLite file, Postgres schema, or Redis
prefix). You register tasks on it, enqueue jobs, run workers, and inspect state
through it. Multiple processes pointed at the same storage share one logical
queue.
const queue = new Queue({ dbPath: "flexiq.db" });A task is a named function registered with queue.task(name, fn, config?).
Enqueuing references the task by name, so the producer never needs the function
body — only the worker does. Per-task config (retries, timeout, concurrency,
rate limit, circuit breaker) is attached at registration.
Enqueuing a task creates a job — a row in storage with arguments, priority,
status, and result. Jobs move through a state machine: pending → running → complete, or failed → retrying, or dead (retries exhausted), or
cancelled. The engine claims jobs atomically, so the same job never runs
twice concurrently.
queue.runWorker() starts a worker that polls storage, claims due jobs,
dispatches them to the registered task function, and writes results back. Sync
tasks run on an OS-thread pool; async tasks run on a native async pool. Call
worker.stop() to drain and shut down.
await queue.result(id) resolves with the task's return value once the job
finishes (or rejects if it dead-letters). Results are serialized with the
queue's serializer and stored, so any
process sharing the storage can await them.
The architecture is engine-level and shared across SDKs — see Architecture for the job lifecycle, scheduler, storage schema, and mesh internals.