@lomatina/queue-lite
v0.1.0
Published
TypeScript-first, durable job queue for Node/Bun. No Redis — uses the database you already have (SQLite, Postgres, or in-memory).
Maintainers
Readme
queue-lite
A TypeScript-first, durable job queue for Node/Bun — without Redis. It uses the
database you already have: SQLite by default (zero native deps via bun:sqlite),
Postgres as a drop-in for multi-server, and an in-memory backend for tests.
import { z } from "zod";
import { createQueue, defineJob, SqliteAdapter } from "queue-lite";
const sendEmail = defineJob({
name: "sendEmail",
schema: z.object({ to: z.string().email(), subject: z.string() }),
handler: async ({ payload }) => {
await mailer.send(payload.to, payload.subject);
},
});
const queue = createQueue({
storage: new SqliteAdapter("queue.db"),
jobs: [sendEmail],
worker: { concurrency: 5 },
});
await queue.start();
await queue.enqueue("sendEmail", { to: "[email protected]", subject: "Welcome!" });
// compile error - wrong payload shape:
// await queue.enqueue("sendEmail", { to: "[email protected]" });
// compile error - unknown job name:
// await queue.enqueue("sendEmial", { to: "...", subject: "..." });Why
Every mature Node job queue needs Redis: friction in dev, cost in deploy, a
coordination point at scale. queue-lite removes that — jobs live in your existing
database, survive restarts, retry on failure, and run scheduled work without a cron
daemon. When you truly outgrow it, switch to Postgres by changing one line.
Features
- Type-safe enqueue — payloads are inferred from each job's Zod schema; wrong name or shape is a compile error, and everything is validated again at runtime.
- Durable — jobs are written to storage before returning; nothing is lost on crash or deploy.
- Retries with backoff — fixed or exponential, with jitter, configurable per job.
- Delays and priorities — run a job later (
delayMs/runAt) or ahead of others. - Cron schedules — recurring jobs that fire exactly once across instances.
- Lifecycle events —
enqueued,started,completed,failed,retried,stalledfor logging/metrics/alerting. - Stalled-job recovery — leases expire and orphaned jobs are reclaimed.
- CLI — inspect and manage the queue from your terminal.
- Pluggable storage — SQLite, Postgres, Memory; one interface, easy to extend.
Install
bun add queue-lite zod
# Postgres backend (optional):
bun add postgresStorage backends
import { SqliteAdapter, PostgresAdapter, MemoryAdapter } from "queue-lite";
new SqliteAdapter("queue.db"); // file-backed (default)
new SqliteAdapter(":memory:"); // ephemeral
new PostgresAdapter(process.env.PG_URL!); // multi-server
new MemoryAdapter(); // testsSwitching backends changes only this one line — your job code is unchanged.
Scheduling, delays, priorities
// Every weekday at 9am
await queue.schedule("dailyReport", "0 9 * * 1-5", { team: "growth" });
// Run 3 days from now
await queue.enqueue("followUp", { userId }, { delayMs: 3 * 24 * 60 * 60 * 1000 });
// Jump the line
await queue.enqueue("chargeCard", { invoiceId }, { priority: 10 });
// Dedupe
await queue.enqueue("syncUser", { userId }, { idempotencyKey: `sync:${userId}` });Inspecting state
await queue.stats(); // { pending, active, failed, ... }
await queue.list({ status: "failed", limit: 20 });
await queue.getJob(id);
await queue.history(id); // every attempt + error
await queue.retry(id); // requeue a failed job
await queue.cancel(id); // cancel a pending/delayed jobCLI
queue-lite stats --db queue.db
queue-lite list --status failed
queue-lite show <job-id>
queue-lite retry <job-id>
queue-lite cancel <job-id>
queue-lite stats --pg "$PG_URL" --jsonUse --db <path> / QUEUE_LITE_DB for SQLite or --pg <conn> / QUEUE_LITE_PG
for Postgres, and --json for machine-readable output.
Testing your jobs
Use the in-memory adapter and a FakeClock to drive the worker deterministically:
import { createQueue, MemoryAdapter, FakeClock } from "queue-lite";
const clock = new FakeClock(0);
const queue = createQueue({ storage: new MemoryAdapter(), jobs: [myJob], clock });
await queue.enqueue("myJob", { /* ... */ });
await queue.runWorkerOnce(); // process all ready jobs, no real timers
expect((await queue.stats()).completed).toBe(1);Roadmap
The StorageAdapter interface and the CLI's read layer are deliberate seams, so
these land without breaking the public API:
- More databases — MySQL/MariaDB, LibSQL/Turso, MongoDB (implement the adapter and pass the conformance suite).
- Richer CLI —
work(run a worker),purge,schedules,--watchlive tail. - Web dashboard — an optional
queue-lite/webserved viaBun.serve, reusing the same adapter calls and streamingqueue.eventsover SSE.
Not for
Extreme-throughput pipelines (tens of thousands of jobs/sec) — reach for BullMQ and
Redis. queue-lite is for everything else, with a clear upgrade path.
License
MIT
