Skip to content

All versions since v0.14.1

v0.14.1

What’s Changed

Improvements

  • Adapter conformance suite: StateConformanceFixture now propagates generateId and generateInvalidId through to the cases. Previously the runner only forwarded stateAdapter and poisonTransaction, so adapters configured with a custom validateId could not exercise the caller-supplied id path in conformance tests.

Breaking Changes (testing helpers only)

  • Removed StateAdapterConformanceContext and NotifyAdapterConformanceContext types. Callers writing conformance suites for external adapters (e.g. it.extend<NotifyAdapterConformanceContext>(...) in vitest specs) should switch to StateConformanceFixture / NotifyConformanceFixture. No runtime API changes — this only affects adapter authors using the conformance harness.

Packages

All packages released at 0.14.1: queuert, @queuert/postgres, @queuert/sqlite, @queuert/redis, @queuert/nats, @queuert/otel, @queuert/dashboard.

v0.15.0

What’s Changed

Breaking Changes

  • triggerJob/triggerJobs renamed to rescheduleJob/rescheduleJobs with an optional schedule parameter ({ at: Date } | { afterMs: number }). Omitting it reschedules to now (previous behavior). A past time clamps to now. Only pending jobs are reschedulable. Batch validation is atomic.
  • Error classes renamed: JobNotTriggerableError / JobsNotTriggerableError → JobNotReschedulableError / JobsNotReschedulableError. New batch error variants JobsNotFoundError and JobsNotReschedulableError for rescheduleJobs. The status property removed from the singular error; the batch variant exposes jobIds.
  • Observability event renamed: jobTriggered → jobRescheduled. Now also emitted on worker retry (not just client reschedules), making it the single signal for “scheduledAt changed.” jobAttemptFailed is now a pure failure event — rescheduledAt/rescheduledAfterMs removed. scheduleAfterMs dropped from jobCreated and jobRescheduled events.
  • StateProvider gains required transactionConcurrency field ("concurrent" | "serialized"). Custom provider authors must declare it.
  • Dashboard: POST /jobs/:id/trigger renamed to /reschedule; “Trigger now” button renamed to “Reschedule”.
  • OTEL: Counter queuert.job.triggered → queuert.job.rescheduled; log entry job_triggered → job_rescheduled.

Features

  • Blocker chain cap: A job can declare at most 100 blocker chains. startChain, startChains, and continueWith throw the new BlockerLimitExceededError when exceeded.
  • Dashboard UX improvements: Infinite scroll on list views (chains, jobs, chain detail), real links on cards (right-click/Cmd-click to open in new tab), and smart back navigation that preserves filters.

Bug Fixes

  • createProcessors type resolution: Previously resolved job type names from the local slice only; now resolves from all registered slices.
  • "incomplete" dedup scope: Multi-step chains that continued past the root job are now correctly matched. Previously, once the root completed, the chain was no longer matched even though it was still running.
  • Concurrent unblock race (@queuert/postgres, @queuert/sqlite): Row-level locks on blocked jobs during unblockJobs prevent a race where concurrent blocker completions could leave dependent jobs stranded in blocked status. job_blocker PK widened to support duplicate chain IDs.

Performance

  • Autovacuum tuning (@queuert/postgres): New migration pins autovacuum_vacuum_threshold = 5000 / scale_factor = 0 on job and job_blocker tables, replacing size-proportional 0.02. Gives predictable vacuum cadence regardless of table size.

Packages

All packages released at 0.15.0: queuert, @queuert/postgres, @queuert/sqlite, @queuert/redis, @queuert/nats, @queuert/otel, @queuert/dashboard.

v0.15.1

What’s Changed

Bug Fixes

  • Saturated workers no longer busy-loop. A worker whose slots were all busy kept polling getNextJobAvailableInMs as fast as the event loop allowed: with a due job in the backlog the poll reported 0ms, so the wait returned immediately and the worker re-entered the loop without being able to take the work. Saturated in-process workers now wait for a slot to free (or for pollIntervalMs) instead of polling.

Performance

  • Postgres job acquisition now uses the acquisition index. acquireJob and getNextJobAvailableInMs matched jobs with type_name IN (...) ordered by scheduled_at, which Postgres cannot satisfy from the (type_name, scheduled_at) index for more than one job type — it fell back to scanning and sorting the entire pending backlog on every acquisition and every poll. Both queries are now per-job-type LATERAL lookups, turning a backlog scan into one index lookup per job type. Workers polling a large Postgres backlog should see a substantial drop in database load.
  • On Postgres, when several job types are polled together, acquisition now picks a job type at random and takes its oldest due job rather than always taking the globally oldest job. This keeps a backlogged job type from starving the others.
  • Postgres getNextJobAvailableInMs no longer takes row locks on jobs scheduled in the future.

Packages

All packages released at 0.15.1: queuert, @queuert/postgres, @queuert/sqlite, @queuert/redis, @queuert/nats, @queuert/otel, @queuert/dashboard.

v0.16.0 Latest

What’s Changed

Queuert 0.16 rebuilds the job model around the chain’s head row and reworks attempt handling around a single finish outcome API, with a one-step schema migration from v0.15.1. It also brings faster listing queries, a redesigned dashboard, and a leaner, more consistent public API. This release contains many breaking changes — review the notes below before upgrading.

Breaking Changes

  • New job model — a chain’s head row is now the chain itself. ChainStatus is just running | completed; completed jobs carry continuedToId; running jobs expose attemptAt / attemptBy / attemptUntil. “Lease” is renamed to “attempt” throughout (leaseConfig → attemptConfig with timeoutMs / heartbeatMs, LeaseConfig → AttemptConfig, plus matching log events and metrics).
  • Attempt finalization — handlers end an attempt with complete(async ({ finish }) => finish({ output | continueWith | reschedule })). rescheduleJob() and RescheduleJobError are removed; rescheduling is now a non-error outcome. completeChain’s complete option is renamed to handler.
  • Renames — startChain / startChains → createChain / createChains.
  • Listing API reshaped — flat options with a single status, required typeName, status-dependent orderBy checked at compile time, root → independent; the chainId / jobId / chainTypeName filters are removed; CreatedAtCursor → TimestampWithIdCursor.
  • Deduplication — scope is now required and "incomplete" is renamed to "running" (missing or unknown scopes throw TypeError). windowMs and excludeChainIds are removed.
  • Caller-supplied id collisions now throw instead of being silently ignored.
  • cascade removed from deleteChain / deleteChains.
  • TransactionContextRequiredError removed — a plain Error is thrown instead.
  • wrapHandler now wraps the whole attempt, so a catch around next() observes handler failures.
  • Public type exports pruned — internal helper types are no longer exported; CompletedJob and AttemptConfig are newly exported.

Database Migrations (@queuert/postgres, @queuert/sqlite)

  • All migrations are collapsed into 001_initial_schema. You must be on v0.15.1 before upgrading.
  • PostgreSQL: the upgrade copies tables while v0.15.1 workers keep running; a migration_lock table makes concurrent migrateToLatest() calls safe.
  • SQLite: stop all workers and clients before running the migration.
  • vacuum() is removed, along with SQLite’s checkForeignKeys / checkAutoVacuum options.

Features

  • step transaction primitive inside attempt handlers, with a matching wrapStep middleware hook.
  • Batched lookups — getChains / getJobs, with ChainTypeMismatchError.
  • Type names & counts — listChainTypeNames / listJobTypeNames and countByChainTypeNames / countByJobTypeNames.
  • Worker shutdown signal — in-flight handlers receive "worker_stopping" as the abort reason when the worker stops.

Dashboard

  • Full UI redesign: Overview page, Find by ID (⌘K), status tabs, and themes.
  • New endpoints for type names, counts, and by-ID lookups. Malformed IDs and unknown /api paths return 404; uncaught errors return 500 with an { error } body.

Observability (@queuert/otel)

  • Attempt spans record an abort event; prepare / step / complete spans record exceptions and ERROR status.
  • Breaking: blocker-completion span renamed resolve chain.* → complete chain.*; queuert.attempt.result attribute removed (read the outcome from span status); metric names follow the lease → attempt rename.
  • @opentelemetry/api peer dependency now also accepts ^1.0.0.

Performance & Fixes

  • Faster listing queries via per-type anchoring.
  • PostgreSQL and SQLite skip the redundant RELEASE SAVEPOINT round-trip.
  • Type checking is roughly 20–40% cheaper.
  • AttemptMiddleware typed against a concrete adapter now works.
  • chain.input now correctly reflects the entry job’s input.
  • Fixed a race where a job created with blockers could start pending instead of blocked (#4).

Packages

All packages released at 0.16.0: queuert, @queuert/postgres, @queuert/sqlite, @queuert/redis, @queuert/nats, @queuert/otel, @queuert/dashboard.