Skip to content

All versions since v0.1.1

v0.1.1

Patch Release

  • Add OIDC trusted publishing support for npm

Packages

All packages released at 0.1.1: queuert, @queuert/postgres, @queuert/sqlite, @queuert/redis.

v0.1.2

Patch Release

  • Add comprehensive README documentation and package READMEs

Packages

All packages released at 0.1.2: queuert, @queuert/postgres, @queuert/sqlite, @queuert/redis.

v0.2.0

Highlights

This release brings OpenTelemetry observability, runtime job validation, a NATS notify adapter, and significant API improvements with several breaking changes.

Breaking Changes

  • API split: createQueuert split into createQueuertClient and createQueuertInProcessWorker for clearer separation of concerns
  • Adapter API simplified: Removed provideContext, made txContext optional
  • Log API changed: Tuple args replaced with named data/error properties
  • Renamed JobSequence to JobChain across the entire API
  • Simplified migrateToLatest API: Removed nested transaction support

New Features

  • ObservabilityAdapter (@queuert/otel): Full OpenTelemetry integration with histogram metrics for duration tracking and gauge metrics for worker idle/processing state
  • JobTypeRegistry: Compile-time and runtime validation with schema adapters for Zod, Valibot, and TypeBox
  • NATS notify adapter (@queuert/nats): New notify adapter with optional JetStream KV support
  • Job attempt middlewares: Contextual logging support during job processing
  • Deferred scheduling: Schedule jobs for future execution via schedule option
  • Thundering herd optimization: Hint-based notification system to reduce unnecessary polling

Improvements

  • Single-statement migrations for cleaner provider implementations
  • Restructured documentation into modular design docs
  • Reorganized examples to single-purpose design with clear naming conventions
  • Relaxed peer dependency version constraints for nats and otel
  • Safe SQL literal helpers (pgLiteral, sqliteLiteral)
  • Dual-context type separation in StateAdapter

Bug Fixes

  • Prevent context leakage to independent chains during job processing
  • Fix orphaned timers blocking process exit
  • Correct GetStateAdapterJobId type extraction for dual-context adapters
  • Suppress notify_context_absence warning when notify adapter is not set

Packages

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

v0.3.0

Highlights

This release adds a web dashboard, client query API with pagination, distributed tracing across job chains, transaction hooks for safe side-effect buffering, and a major documentation site — alongside extensive API refinements and breaking changes.

Breaking Changes

  • Type exports cleaned up: Reference types renamed for consistency
  • Error classes redesigned: Improved error hierarchy and client API consistency
  • State adapter methods renamed: Clearer naming conventions across all adapters
  • waitForJobChainCompletion → awaitJobChain: Simplified chain awaiting API
  • Blockers extracted: Moved from Job base type to separate JobWithBlockers wrapper
  • Trace context split: Separated into distinct chain and job trace fields
  • CommitHooks → TransactionHooks: Renamed with added discard support
  • withNotify replaced: Explicit CommitHooks / TransactionHooks for side-effect buffering
  • chain_index added: Deterministic chain ordering and continuation deduplication
  • originId removed: Deduplication key used for continuations instead
  • rootChainId removed: Unified chain deletion model
  • startBlockers callback → blockers array: Simplified blocker declaration
  • State adapter retry wrapper removed: Retry logic no longer built into adapters
  • updatedAt removed: Dropped from StateJob and database schema
  • API naming simplified: Removed redundant Queuert prefix from exports
  • Worker config simplified: Reduced property verbosity
  • Deduplication API renamed: strategy → scope
  • Worker redesigned: Parallel slot-based execution model
  • createInProcessWorker accepts client: Takes client instance instead of individual adapters
  • OTEL metric prefix hardcoded: Removed messaging semantic convention attributes
  • deleteJobChains returns deleted chains
  • acquireJob returns hasMore

New Features

  • @queuert/dashboard: Embeddable SolidJS web dashboard for job queue observation
  • Client query methods: queryJobs and queryJobChains with pagination and type narrowing
  • Distributed tracing: Full trace context persistence across job chains with OTEL blocker spans
  • Transaction hooks: Buffer observability events via transaction lifecycle hooks
  • Cascade deletion: cascade option on deleteJobChains for transitive dependency removal
  • Table prefix (@queuert/postgres): Configurable table name prefix
  • State adapter conformance test suite (queuert/testing): Shared test harness for adapter implementations
  • Migration version tracking (@queuert/postgres, @queuert/sqlite): Track applied migration versions
  • Documentation site: Astro-based docs with TSDoc and explicit API types

Bug Fixes

  • Add NOT NULL constraint to chain_id column (postgres, sqlite)
  • Scope deduplication key by chain type name
  • Assign continuedJob in workerless continueWith path
  • Fix transaction handling rework
  • Remove notification listeners before releasing pool client (postgres)
  • Only subscribe to job notifications when worker has idle slots
  • Prevent workers from reaping their own in-progress jobs with concurrent slots

Packages

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

v0.3.1

Patch Changes

  • Fix dashboard sub-path asset loading and CSP nonce support
  • Update documentation with API reference section
  • Exclude testing exports from published packages
  • Fix bogus version references in changelogs

Packages

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

v0.3.2

What’s Changed

Dashboard: Sub-path mounting (basePath)

  • Added a new basePath option to createDashboard() for mounting the dashboard behind a reverse proxy or framework router at a sub-path (e.g. /internal/queuert). All server-side routing, asset loading, and client-side SPA navigation respect the configured prefix.
  • The frontend now derives its base URL from the <base> tag, making sub-path routing reliable across deployment setups.
  • Requests outside the configured basePath correctly return 404, including paths that share a prefix (e.g. /application/... when basePath is /app).

Dashboard: CSP nonce removed (breaking)

  • The fetch handler no longer accepts a second { nonce } argument. CSP nonce injection should be handled in your own middleware if needed. The @queuert/dashboard package is marked @experimental.

Packages

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

v0.4.0

What’s Changed

Features

  • Feature slices: New mergeJobTypeRegistries() and mergeJobTypeProcessors() utilities for splitting job type definitions and processors into independent feature modules (slices), then merging them at the application level. Duplicate job types are detected at both compile time and runtime.
  • Cross-slice type safety: defineJobTypes now accepts an optional TExternal type parameter for compile-time validation of blocker and continueWith references across slice boundaries.
  • New type exports: Navigation types (ResolvedJob, ResolvedJobChain, ResolvedJobWithBlockers, etc.), reference types (JobTypeReference, NominalJobTypeReference, StructuralJobTypeReference), and utility types (JobTypeRegistryDefinitions, EntryJobTypeDefinitions, BlockerChains, ContinuationJobs).

Improvements

  • Narrowed trace context types: ObservabilityAdapter and StateAdapter interfaces now use string | null for trace contexts instead of unknown, making types more precise and predictable.
  • Postgres trace context columns changed from jsonb to text for consistency with the narrowed types.

Breaking Changes

  • Trace context types narrowed from unknown to string | null — affects custom ObservabilityAdapter and StateAdapter implementations.
  • Postgres schema: trace columns changed from jsonb to text (migration required).
  • Navigation types renamed: JobOf → ResolvedJob, JobChainOf → ResolvedJobChain, ChainJobTypes → ChainJobTypeNames, BlockedJobTypes → BlockedJobTypeNames, HasBlockers → JobTypeHasBlockers.
  • Handler/callback types renamed: AttemptHandlerFn → AttemptHandler, PrepareFn → AttemptPrepare, CompleteFn → AttemptComplete, CompleteCallbackOptions → AttemptCompleteOptions, PrepareConfig → AttemptPrepareOptions.
  • Registry phantom property $definitions replaced with symbol; use JobTypeRegistryDefinitions<T> utility type instead.
  • PartialJobTypeReference replaced by JobTypeReference union (NominalJobTypeReference | StructuralJobTypeReference).
  • JobTypeRegistryConfig now requires a getTypeNames callback.

Packages

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

v0.5.0

What’s Changed

Breaking Changes

  • Renamed core APIs for consistency: defineJobTypes is now defineJobTypeRegistry, processor registries use createJobTypeProcessorRegistry, and createInProcessWorker accepts processorRegistry. mergeJobTypeProcessors is now mergeJobTypeProcessorRegistries.
  • continueWith restricted to local types: continueWith targets now validate against local job type definitions only. Blockers remain validated against the full set (local + external).
  • Removed rarely-used type exports: CompletedJob, JobWithBlockers, PendingJob, BlockedJob

Performance

  • Faster type checking: Navigation types rewritten in tail-recursive form with precomputed maps, reducing type instantiations by up to 86% in blocker-heavy scenarios.

Internal

  • Enabled isolatedDeclarations for improved .d.ts compatibility with alternative TypeScript tooling
  • Added Turborepo for task orchestration and caching
  • Added processing capacity and type complexity benchmarks

Packages

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

v0.5.1

What’s Changed

Features

  • Compile-time validation for job type definitions — New ValidatedJobTypeDefinitions and JobTypeDefinitionErrors types catch definition errors (missing output schemas, invalid blocker references, unknown type names) as TypeScript errors instead of accepting them silently
  • Compile-time validation for createInProcessWorker — Processor registries containing job types unknown to the client are now rejected at compile time instead of only failing at runtime
  • Validation adapter examples updated to use the new compile-time validation types

Packages

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

v0.6.0

What’s Changed

Features

  • Batch startJobChains API — Create multiple job chains in a single operation via client.startJobChains(), with full type-safe return values and optimized database round-trips for PostgreSQL (unnest + CTE-based batch inserts)
  • Savepoint-protected user callbacks — prepare and complete callbacks during job processing now run inside database savepoints on PostgreSQL. If a user callback executes a failing SQL statement, only the savepoint is rolled back — the outer transaction remains usable for error handling (reschedule, retry, etc.)
  • Optional withSavepoint on StateAdapter — Custom state adapter authors can implement withSavepoint to opt into savepoint protection. The PostgreSQL adapter implements this automatically

Breaking Changes

  • StateAdapter interface updated — createJob/addJobBlockers (singular) replaced by createJobs/addJobsBlockers (plural/batched). Custom state adapter implementations must update to the new array-based signatures. The official PostgreSQL and SQLite adapters are already updated
  • Removed type exports — CompleteJobChainResult and JobChainCompleteOptions are no longer exported from queuert (now internal types prefixed with _)

Packages

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

v0.7.0

What’s Changed

Features

  • Savepoint support for TransactionHooks — New withSavepoint(fn) and createSavepoint() methods enable partial rollback of buffered side effects (notifications, observability events) within a transaction without discarding the entire transaction’s work. Hook definitions can now provide a checkpoint callback via the new optional HookDef.checkpoint field.
  • HookDef and TransactionHooksSavepoint types are now exported from @queuert/core.

Bug Fixes

  • Lease renewal scoped to staged mode — Lease renewal after getJobBlockers no longer runs in atomic/deferred modes where the transaction stays open and renewal is unnecessary.
  • Error handler transaction rollback — When the error handler’s runInTransaction fails, prepare and complete transactions are now properly rejected (rolled back) instead of resolved, preventing jobs from being left in inconsistent state.
  • Default TJobId type corrected — PgStateAdapter and SqliteStateAdapter type defaults changed from string to UUID, matching actual runtime behavior.

Breaking Changes

  • StateAdapter.withSavepoint is now a required method (previously optional) with a simplified positional signature: withSavepoint(txCtx, fn). Custom state adapter implementations must add this method.
  • Default TJobId generic on PgStateAdapter and SqliteStateAdapter changed from string to UUID. Code relying on the previous string default may see new type errors.

Packages

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

v0.8.0

What’s Changed

Breaking Changes

  • Renamed worker and client options for clarity: registry → jobTypeRegistry, processorRegistry → jobTypeProcessorRegistry, processDefaults → jobTypeProcessorDefaults. The InProcessWorkerProcessDefaults type is now JobTypeProcessorDefaults. mergeJobTypeRegistries and mergeJobTypeProcessorRegistries now take { slices: [...] } instead of positional args.
  • The Job type now has a 5th type parameter TOutput. Completed jobs expose an output field.
  • Dashboard now takes a Client instance instead of raw adapters.

Features

  • triggerJob API: New client method to trigger a pending job immediately, bypassing its scheduled time. Dashboard also gains a “Trigger” button for pending jobs.
  • jobChainTypeName filter on listJobs: Query jobs scoped to chains of a specific type.
  • Chain deletion in dashboard: Delete job chains with a confirmation dialog.
  • Merged registry rejection: createJobTypeProcessorRegistry now throws a clear TypeError when passed a merged registry.

Bug Fixes

  • continueWith now uses distributive conditional types for correct type checking across union job types.
  • completeJobChain rejects un-narrowed union jobs, preventing ambiguous completions.

Packages

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

v0.8.1

What’s Changed

Bug Fixes

  • Dashboard: Fixed job detail continuation lookup to use a targeted query instead of fetching the entire chain, significantly improving performance for long chains.
  • Dashboard: Fixed a race condition where changing filters while a “load more” request was in-flight could append stale results in the chain and job list views.
  • Dashboard: The leasedBy badge in the job list now only appears for running jobs.
  • Dashboard: Fixed TypeScript type inference for createDashboard — the client option now preserves generic types instead of requiring Client<any, any>.

Packages

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

v0.9.0

What’s Changed

Features

  • Richer error messages: Failed jobs now store the full stack trace and custom Error properties (e.g., code, statusCode) instead of just [object Object] or a bare message string. Long errors are truncated at 10,000 characters.
  • New observability events: jobChainDeleted and jobTriggered events are now emitted through logging, OpenTelemetry, and custom observability adapters.
  • Safer triggerJob: Now guards against non-pending jobs (AND status = 'pending') and uses row-level locking (FOR UPDATE) in Postgres to prevent race conditions.

Breaking Changes

  • Default Postgres schema changed: The Postgres adapter now defaults to schema: "public" with tablePrefix: "queuert_" (previously schema: "queuert" with no prefix). Pass { schema: "queuert", tablePrefix: "" } to createPgStateAdapter to preserve existing behavior.
  • Removed type exports: NavigationMap, BaseNavigationMap, BaseNavigationEntry, JobTypeRegistryNavigation, ChainJobTypeNames, ContinuationJobTypes, EntryJobTypeDefinitions, BlockedJobTypeNames, ChainTypesReaching, JobTypeProcessorRegistryNavigation, processorNavigationSymbol. Use JobTypeNames, JobTypeEntryNames, and JobTypeProperty as replacements.
  • Union-based merged registries: Merged registry definitions are now a union type instead of an intersection.

Packages

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

v0.9.1

What’s Changed

Bug Fixes

  • Fix broken package exports in published npm packages — publishConfig overrides (e.g. exports pointing to dist/) were not being applied during changeset publish, causing v0.9.0 packages to be unusable

Packages

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

v0.9.2

What’s Changed

Bug Fixes

  • Fix broken package exports in published npm packages — tsdown’s devExports writes source paths to exports and dist paths to publishConfig.exports, but npm publish (used by changeset publish) does not apply publishConfig overrides. Switched to pnpm publish which handles this natively.

Packages

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

v0.9.3

What’s Changed

Improvements

  • Simplified JobAttemptMiddleware type signature — The second type parameter (TJobTypeDefinitions) has been removed from JobAttemptMiddleware and JobTypeProcessorDefaults. Middleware definitions are now simpler: use JobAttemptMiddleware<typeof stateAdapter> instead of JobAttemptMiddleware<typeof stateAdapter, JobTypeRegistryDefinitions<typeof registry>>. The JobTypeRegistryDefinitions import is no longer needed for middleware.

  • Fixed ResolvedJobChain output type — Intermediate chain steps with output: undefined no longer leak undefined into the final chain’s completed output type.

Packages

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

v0.9.4

What’s Changed

Runtime Safety

  • Mutating client methods (startJobChain, startJobChains, deleteJobChains, triggerJob, completeJobChain) now enforce that a transaction context from runInTransaction is provided at runtime, throwing TransactionContextRequiredError if omitted. This matches the existing TypeScript type requirements and ensures consistent behavior for JavaScript callers.
  • New exported error class TransactionContextRequiredError for specific error handling.

Bug Fixes

  • Dashboard chain-delete and job-trigger routes now correctly wrap mutating calls in a transaction context.

Packages

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

v0.9.5

What’s Changed

Features

  • Deduplication: excludeJobChainIds option — Skip specific chain IDs during deduplication matching, useful for recurring self-scheduling jobs that would otherwise deduplicate against their own still-incomplete chain
  • Dashboard: Cascade delete for job chains — New “Cascade delete” checkbox in the delete dialog that removes all transitive blocker chains. Also available via ?cascade=true query parameter on DELETE /api/chains/:id
  • Client type covariance — Client<A | B> is now assignable to Client<A>, fixing type errors when passing multi-job-type clients to code that only cares about a subset
  • Compile-time validation for createJobTypeProcessorRegistry — TypeScript now produces a clear error naming missing job types if the client doesn’t cover all required types
  • New public exports — BaseTxContext, HookDef, and TransactionHooksSavepoint are now exported from queuert
  • Cleanup guide and example — New documentation and showcase example for cleaning up completed job chains

Breaking Changes

  • createDashboard() is now async — callers must await it
  • DeduplicationOptions is now generic (DeduplicationOptions<TJobId>)

Packages

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

v0.10.0

What’s Changed

Features

  • Postgres vacuum support: New vacuum() method on the Postgres state adapter for on-demand dead-tuple reclamation on job tables. A new vacuum_tuning migration (applied automatically by migrateToLatest()) configures fillfactor = 75 and aggressive per-table autovacuum settings to reduce table bloat automatically
  • SQLite vacuum support: New vacuum() method on the SQLite state adapter for on-demand page reclamation via PRAGMA incremental_vacuum. migrateToLatest() now validates that auto_vacuum = INCREMENTAL is set on the database — this pragma must be configured before table creation

Bug Fixes

  • NATS notification buffering: The NATS notify adapter now flushes the connection after each publish, ensuring timely delivery of job-scheduled, chain-completed, and ownership-lost notifications

Improvements

  • Relaxed OpenTelemetry peer dependency: @opentelemetry/api peer dependency relaxed from ^1.9.0 to ^1.0.0, supporting a wider range of OpenTelemetry versions

Packages

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

v0.11.0

What’s Changed

Features

  • Public in-process adapters. createInProcessStateAdapter and createInProcessNotifyAdapter moved from queuert/internal to the main queuert entry. The in-process state adapter was rewritten around per-type sorted sets and a journal-based rollback log.
  • New queuert/conformance subpath export. Framework-agnostic conformance runner (runStateAdapterConformance, runNotifyAdapterConformance, ConformanceError, ConformanceReport) with a built-in expect shim, for users authoring custom adapters. Documented under Advanced → Custom Adapters.
  • client.deleteJobChain(id, …) — new singular API, symmetric to deleteJobChains. Returns the deleted chain or undefined if missing; throws BlockerReferenceError if external jobs depend on it; supports cascade.
  • client.triggerJobs(ids, …) — new plural API. Atomic validation: throws JobNotFoundError / JobNotTriggerableError for the first invalid job before any job is triggered; returns jobs in input order; empty input returns [].
  • Redis Cluster support (@queuert/redis). SET+PUBLISH Lua script passes the pub/sub channel via ARGV instead of KEYS, avoiding CROSSSLOT rejection on Redis Cluster. New examples for ioredis and node-redis cluster clients.
  • Optional withSavepoint hook on PgStateProvider and SqliteStateProvider — drivers that track transaction state client-side (e.g. postgres.js) can override the default SAVEPOINT / RELEASE / ROLLBACK TO SQL path with native savepoint support.
  • Job<…> gained a TOutput generic parameter — the completed variant now carries output.
  • New guides and examples: Prioritization guide (with showcase-multiworker-prioritization example) and Processing Reliability / error recovery guide (with showcase-error-recovery example).

Breaking Changes

  • runInTransaction renamed to withTransaction on StateAdapter, PgStateProvider, SqliteStateProvider.
  • HookDef renamed to HookDefinition.
  • createPgNotifyAdapter / createRedisNotifyAdapter: option provider renamed to notifyProvider.
  • createInProcessStateAdapter and createInProcessNotifyAdapter are now async and must be await-ed.
  • In-process adapters moved from queuert/internal to queuert.
  • client.deleteJobChain no longer throws JobChainNotFoundError for missing chains — it returns undefined instead.
  • StateAdapter contract: triggerJob replaced by triggerJobs (bucketed triggered / notFound / notTriggerable result); deleteJobChains returns { deleted, blockerRefs }; the adapter enforces blocker validation in one all-or-nothing statement.
  • executeSql in PgStateProvider / SqliteStateProvider now takes typed SQL hints (paramTypes, columnTypes) instead of a boolean returns flag. Custom providers must update accordingly.
  • pgLiteral removed from @queuert/postgres entry; sqliteLiteral removed from @queuert/sqlite entry.

Packages

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

v0.12.0

What’s Changed

Features

  • Onion-style attempt middleware. New AttemptMiddleware API with wrapHandler / wrapPrepare / wrapComplete hooks that wrap each phase of an attempt and inject typed context. Middleware now lives per-Processors slice (createProcessors({ attemptMiddleware })) instead of running worker-wide.
  • Concurrent SELECT reads. New AsyncRwLock lets pure-SELECT queries run in parallel while writers stay serialized. End-to-end throughput improves on every adapter — for example, SQLite better-sqlite3 ~7,177 → ~8,396 jobs/s and Postgres pg notify ~1,536 → ~1,948 jobs/s.
  • Concurrent transaction-hook flush. createTransactionHooks dispatches flush and discard via Promise.allSettled instead of sequentially. Cross-hook ordering is no longer guaranteed; in-tree hooks are unaffected.
  • Cached prepared statements. Pass an optional id to sql() and built-in providers cache the prepared statement (postgres.js prepare: true, pg query.name, SQLite db.prepare cache). Dynamic SQL still runs unprepared.
  • Adapter close() lifecycle. NotifyAdapter and StateAdapter now expose an idempotent close() for explicit teardown.
  • Validation-adapter conformance suite. New runValidationAdapterConformance runner exported from queuert/conformance for validation-adapter authors. Mirrors runStateAdapterConformance in shape.
  • UnknownJobTypeError. Raised by merged JobTypes slices when a referenced type name isn’t owned by any slice.

Breaking Changes

Slice-based registries

  • JobTypeRegistry / JobTypeProcessorRegistry → JobTypes / Processors.
  • createClient and createInProcessWorker now accept a single slice or a readonly array of slices and merge them inline.
  • Standalone merge helpers (mergeJobTypeRegistries, mergeJobTypeProcessorRegistries) removed.
  • createJobTypeRegistry → createJobTypes; defineJobTypeRegistry → defineJobTypes; DefineJobTypes → JobTypeDefs; JobAttemptMiddleware → AttemptMiddleware.

Worker option restructuring

  • pollIntervalMs is now a top-level option on createInProcessWorker.
  • Per-type backoffConfig / leaseConfig defaults move onto createProcessors.
  • The old worker-loop backoffConfig (controlling crash recovery, not retries) is renamed to recoveryBackoffConfig.
  • jobTypeProcessorDefaults is gone.

Chain naming standardization

  • Drop Job from JobChain everywhere: types (Chain, ChainStatus, CompletedChain, ResolvedChain, ChainData, ChainNotFoundError), client methods (startChain(s), getChain, awaitChain, listChains, listChainJobs, completeChain, deleteChain(s)).
  • StateAdapter getters drop ById: getJob, getChain, listChains, listChainJobs, deleteChains.
  • NotifyAdapter events: notifyChainCompleted, listenChainCompleted.
  • ObservabilityAdapter events: chainCreated, chainCompleted, chainDeleted, chainDuration.
  • Filter parameters: excludeJobChainIds → excludeChainIds; jobChainId → chainId; jobChainTypeName → chainTypeName.
  • Log entry types and messages: "chain_created" / "chain_completed" / "chain_deleted".
  • OTEL metric names renamed: queuert.job_chain.{created,completed,deleted,duration} → queuert.chain.{...}. Update dashboards and alerts that reference the old names. Metric attributes (chainTypeName) are unchanged.
  • Schema migration 20260430000000_rename_chain_indexes runs automatically via migrateToLatest(). Postgres uses ALTER INDEX … RENAME TO; SQLite drops + recreates. Three indexes drop the job_ prefix.

Adapter lifecycle and wake hints

  • NotifyAdapter and StateAdapter now require an idempotent close() that cascades into the underlying provider when one is owned. After close(), notify/listen and most state methods reject.
  • Provider-level close() is optional and only present on resource-owning providers (e.g. createPgPoolNotifyProvider). Pass-through providers over caller-owned pools/clients omit it.
  • PgPoolNotifyProvider type removed — createPgPoolNotifyProvider now returns plain PgNotifyProvider.
  • notifyJobScheduled(typeName) no longer takes a count. Wake-hint budgets move to provideWakeHint(typeName, count) and consumeWakeHint(typeName), keyed by type name and additive across concurrent publishers.
  • Application code using built-in adapters should explicitly close them before tearing down caller-owned pools/clients.

Concurrent reads

  • createAsyncLock / AsyncLock → createAsyncRwLock / AsyncRwLock with acquireRead() / acquireWrite() returning a Disposable-compatible LockHandle. Idiomatic usage: using h = await lock.acquireWrite();.
  • SqliteStateProvider.executeSql and PgStateProvider.executeSql signature changes: params is now required (was optional); new required paramTypes: Record<number, RuntimeType> and readOnly: boolean fields.
  • Custom SQLite providers must consult readOnly to choose between acquireRead() and acquireWrite() — wiring everything to an exclusive lock silently disables concurrent reads.
  • @queuert/typed-sql’s sql() now takes readOnly via the options object instead of as a second positional argument.

Lock option replaces FOR UPDATE getters

  • getJobForUpdate and getLatestChainJobForUpdate removed.
  • Pass lock: "exclusive" to getJob / getChain instead.
  • getChain({ lock: "exclusive" }) now locks only the latest job in the chain (the rootJob if no continuation, otherwise the last continuation). The rootJob’s chainTypeName is immutable, so locking it earned nothing. Return shape unchanged.

Cached prepared statements (custom providers)

  • Custom PgStateProvider / SqliteStateProvider see an optional id?: string on executeSql. They continue to type-check unchanged and execute every statement unprepared. To opt into caching, accept id and use it as a cache key / pg query.name / postgres.js prepare toggle.
  • PgBouncer / Supavisor users (transaction-mode poolers) should write a custom provider that ignores id, since server-side prepared statements break under those proxies.

Smaller surface tweaks

  • RescheduleJobError no longer carries cause: undefined when constructed without an explicit cause. 'cause' in err is now false when not supplied (matching every other queuert error). Value-level (err.cause) is still undefined.
  • BaseTxContext is now exported only from queuert. The duplicate re-export from queuert/internal is removed. @queuert/postgres now imports it from queuert to match @queuert/sqlite.

TypeScript flags every wrong call site at compile time across all of these — there is no runtime fallback or deprecation period.

Bug Fixes

  • @queuert/postgres: addJobsBlockersSql now acquires FOR UPDATE on the latest job of each blocker chain (via a new locked_blocker_chain_latest CTE) before inserting blocker rows. Closes a race where a blocker chain could complete between the status read and the blocker insert and leave the blocked job stuck. No schema change.

Packages

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

v0.13.0

What’s Changed

Features

  • Worker defaults: createInProcessWorker accepts a new defaults: { backoffConfig?, leaseConfig? } option for fleet-wide fallbacks. Resolution order is processor → registry → worker defaults → library default. InProcessWorkerDefaults is exported from the package root.
  • Required cross-slice middleware: new requiredAttemptMiddleware option on createInProcessWorker enforces (at compile time against the merged slice middleware, and at runtime by reference identity) that every slice includes the listed AttemptMiddleware instances as an in-order subsequence. The worker does not execute them itself — this guarantees cross-cutting concerns (auth, tracing, logging) are wired into every slice without giving up the per-slice middleware model.
  • Multi-slice validation: validation adapters now accept either a single external slice or a readonly array, restoring the multi-slice composition path that was removed alongside mergeJobTypeRegistries in 0.12. New public JobTypesDefinitions<T> resolves either form to the merged BaseJobTypeDefinitions record.
  • RuntimeType re-export from @queuert/postgres and @queuert/sqlite so custom state-provider authors can type their serialization/parsing without reaching into @queuert/typed-sql.
  • New state-sqlite-bun example demonstrates @queuert/sqlite running on bun:sqlite.

Breaking Changes

  • workerId → workerName: createInProcessWorker’s identity option is renamed to workerName — an optional human-readable label restricted to /^[A-Za-z0-9._-]+$/. The runtime always appends a random UUID to produce the final worker id (${workerName}-${uuid}, or just ${uuid} when omitted), making duplicate worker ids impossible to express. Observability events and error fields continue to expose the full id under workerId.
  • Validation adapter signatures: adapters accept the “single slice or readonly array” shape createClient already takes. ExternalJobTypeDefinitions is no longer exported — use JobTypeDefinitions for a single slice or JobTypesDefinitions for either form. Adapter authors implementing runValidationAdapterConformance must add a buildWithExternalSlices fixture, and buildWithExternalSlice’s blockers tightens from a readonly array to a tuple — schemas under test must switch from array(...) to tuple([...]) (z.tuple, v.tuple, Type.Tuple, type([...])).
  • Stricter public error fields: contextual fields on JobNotFoundError, ChainNotFoundError, JobTakenByAnotherWorkerError, JobAlreadyCompletedError, JobNotTriggerableError, and WaitChainTimeoutError (jobId, chainTypeName, etc.) are now non-optional string / number, and their constructor options are required — caught errors no longer need null-checks. JobTakenByAnotherWorkerError.leasedBy is now string | null (an omitted constructor option is normalized to null); narrowings via === undefined should switch to === null.
  • @queuert/otel attribute renames: every metric attribute key is renamed to OpenTelemetry semantic-convention style (lowercase, dotted) to align with the span attributes already emitted by the tracing layer. Metric names are unchanged, so queries still resolve — but dashboards, alerts, and recording rules that group by or filter on the old keys silently return empty results until updated. workerId → queuert.worker.id, typeName → queuert.job.type, chainTypeName → queuert.chain.type, continued → queuert.job.continued, operation → queuert.adapter.operation. Two value encodings also changed on queuert.job.completed: queuert.job.continued is now a real boolean (not "true" / "false"), and queuert.worker.id is omitted for workerless completions (no longer the literal string "null").
  • executeSql.id uniqueness: in @queuert/postgres and @queuert/sqlite, executeSql’s id now uniquely identifies the resolved SQL within a provider — the template applier folds variants like schema / tablePrefix into a hashed suffix. The built-in pg.Pool provider drops its SHA1 name-hash (query.name = id); built-in better-sqlite3 and node:sqlite providers drop their per-database SQL→Statement maps and key the cache by id. Custom providers can do the same.

Bug Fixes

  • Relax the generic bound on AttemptMiddleware’s TStateAdapter from StateAdapter<BaseTxContext, any> to StateAdapter<any, any>. The previous bound was unusable in practice: because the transaction-context type parameter appears in contravariant positions, no concrete adapter with a non-empty txCtx could satisfy StateAdapter<BaseTxContext = {}, any>. Adapter-aware middleware and generic test helpers now type-check without any workarounds.

Packages

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

v0.14.0

What’s Changed

Features

  • Caller-supplied job IDs (breaking): startChain, startChains, and the worker continueWith callback now accept an optional id that becomes the new chain root (or continuation) job ID. State adapters accept a new validateId predicate that runs on both adapter-generated and caller-supplied IDs; failures throw the new InvalidJobIdError. When deduplication fires, the existing row’s ID wins over a caller-supplied id (the returned chain carries deduplicated: true).
  • Aligned adapter ID-generation options (breaking): both PostgreSQL and SQLite adapters now expose a generateId function. PostgreSQL’s idDefault (SQL expression) is removed in favor of JS-side generation; SQLite’s idGenerator is renamed to generateId. A new PostgreSQL migration drops DEFAULT gen_random_uuid() from the id column. Existing UUID generation behavior is unchanged for default configurations.

Bug Fixes

  • scheduled_at is now an honest eligibility floor: state adapters clamp scheduled_at to MAX(requested, now()) on createJobs, rescheduleJob, and unblockJobs. Previously, jobs with a past scheduled_at (or blocked-since-creation jobs whose original scheduled_at went stale while they waited) would jump to the front of the acquisition queue ahead of jobs that genuinely became ready earlier. unblockJobs now preserves a future scheduled_at set at job creation (it previously reset to now() unconditionally); triggerJobs continues to re-anchor to now() as an explicit reset. Applies to PostgreSQL, SQLite, and in-process adapters; no schema migration or backfill required.
  • lastAttemptError cleared on success: when a job completes successfully after a previously failed attempt, completeJob now resets last_attempt_error to NULL alongside the existing status/output/lease updates. Completed rows no longer carry over an error string from a prior failed attempt.
  • requiredAttemptMiddleware type fix: a spurious type error from createInProcessWorker’s requiredAttemptMiddleware check is fixed when middleware is typed against a user-supplied StateAdapter alias (e.g. AttemptMiddleware<MyStateAdapter>). Valid processor slices were being flagged as missing required middleware. Runtime behavior was unaffected; this is a type-only fix.
  • BaseTxContext tightened: changed from {} to Record<string, unknown>. The previous {} constraint accepted any non-nullish value (string, number, function), letting custom StateAdapter authors pick a non-object TTxContext without a type error. All built-in adapters are already object-shaped, so no runtime behavior changes.

Migration Notes

  • PostgreSQL: replace idDefault: "gen_random_uuid()" with generateId: () => crypto.randomUUID() (this is the default and can be omitted).
  • PostgreSQL: replace idDefault: "'job.' || gen_random_uuid()::text" with generateId: () => `job.${crypto.randomUUID()}` .
  • SQLite: rename idGenerator option to generateId.
  • Custom state adapter implementations must accept an optional id per entry in createJobs and apply their own validateId predicate.

Packages

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

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.