Skip to content

kvet/queuert

Repository files navigation

Queuert

npm version license

Durable, typed job chains that commit with your database transactions.

Queuert is a job-chain library — durable, typed background work in your database. Job chains compose like Promise chains (.then, Promise.all), but they survive crashes and commit with your transactions. Postgres or SQLite, no Redis required, no separate server.

Documentation | Getting Started | Comparison

How it looks

Define a typed chain of jobs. Each step's input, output, and continuation are inferred — wrong-shape continuations are compile errors.

const jobTypes = defineJobTypes<{
  "provision-account": {
    entry: true;
    input: { userId: number };
    continueWith: { typeName: "send-welcome-email" };
  };
  "send-welcome-email": {
    input: { userId: number; accountId: string };
    continueWith: { typeName: "sync-to-crm" };
  };
  "sync-to-crm": {
    input: { userId: number; accountId: string };
  };
}>();

Start the chain inside your DB transaction. If the transaction rolls back, the chain is never created. No outbox glue, no dual-write window.

const client = await createClient({ stateAdapter, jobTypes });

await withTransactionHooks(async (transactionHooks) =>
  db.transaction(async (tx) => {
    const user = await tx.users.create({ name: "Alice", email: "alice@example.com" });

    await client.startChain({
      tx,
      transactionHooks,
      typeName: "provision-account",
      input: { userId: user.id },
      //         ↑ wrong shape here is a compile error
    });
  }),
);

Each handler continues with the next step. The compiler enforces that continueWith matches the declared next type's input.

const worker = await createInProcessWorker({
  client,
  processors: createProcessors({
    client,
    jobTypes,
    processors: {
      "provision-account": {
        attemptHandler: async ({ job, complete }) => {
          const accountId = await provisionAccount(job.input.userId);

          return complete(async ({ continueWith }) =>
            continueWith({
              typeName: "send-welcome-email",
              input: { userId: job.input.userId, accountId },
              //      ↑ missing accountId would be a compile error
            }),
          );
        },
      },
      // ...handlers for "send-welcome-email" and "sync-to-crm"
    },
  }),
});

const stop = await worker.start();

Where it fits

Background-work libraries trade off across two axes: what storage tier they own, and what shape of work they model.

Queuert pg-boss BullMQ Temporal Inngest
Category Job-chain library Job queue Job queue Workflow platform Workflow platform
Storage Your DB (PG / SQLite) Postgres Redis Separate cluster Inngest server
Transactional enqueue ✅ structural 🟡 per-call adapter ❌ app discipline ❌ app discipline ❌ app discipline
Operate a server? No No Redis Yes Yes

Queuert sits between job queues and workflow engines. A one-job chain is a queue; a multi-step chain with blockers is closer to a workflow. Neither label fully fits, which is why the canonical term is "job-chain library."

For deeper comparisons, see the docs site — one page per neighbor.

Why Queuert

  • Transactional, both ends. Enqueue commits inside your DB transaction; handler completion and next-step continueWith commit in the same transaction as your domain writes. For DB-bound work, no outbox at enqueue and no idempotency-key ritual at processing — both halves are structural.
  • Typed job chains. Inputs, outputs, continuations, and blockers infer end-to-end via defineJobTypes. Refactoring is compiler-checked.
  • Lives in your database. Postgres or SQLite. No Redis required, no workflow server, no separate persistence tier to operate.
  • Sub-second wakeup. LISTEN/NOTIFY (or Redis pub/sub, or NATS) wakes workers when a row commits — not on a polling timer.
  • Schedule for later. Delay a chain to a specific time or duration. Schedule retries with backoff. Future work, no extra infrastructure.
  • Deduplication. Pass a deduplication key on enqueue. Identical keys collapse to a single chain — at-most-once, by construction.
  • Lean and battle-tested. Zero runtime dependencies in every package — driver libraries are peerDependencies you already own. 4,000+ tests across adapters and a shared conformance suite every state and notify adapter must pass.

Installation

# Core (required)
npm install queuert

# State adapter (pick one)
npm install @queuert/postgres   # PostgreSQL — recommended for production
npm install @queuert/sqlite     # SQLite (experimental)

# Notify adapter (optional, for sub-second wakeup)
npm install @queuert/redis      # Redis pub/sub
npm install @queuert/nats       # NATS pub/sub (experimental)
# Or use PostgreSQL LISTEN/NOTIFY via @queuert/postgres — no extra infra

# Dashboard (optional, experimental)
npm install @queuert/dashboard

# Observability (optional)
npm install @queuert/otel

Learn more

License

MIT.

About

Durable, typed job chains that commit with your database transactions. No Redis required, no separate server.

Topics

Resources

License

Stars

Watchers

Forks

Contributors

Languages