SQLite Stores Overview
Conveyor provides three SQLite store packages, one for each supported runtime. They all share a common base implementation in @conveyor/store-sqlite-core and expose an identical API through the SqliteStore class.
Architecture
@conveyor/store-sqlite-core (shared logic, BaseSqliteStore)
| | |
v v v
store-sqlite-node store-sqlite-bun store-sqlite-deno
(node:sqlite) (bun:sqlite) (@db/sqlite + fallback)Each runtime-specific package provides a thin wrapper that injects the appropriate database opener function into the shared BaseSqliteStore. The core handles all SQL queries, migrations, transactions, and event delivery.
Choosing a Package
| Package | Runtime | Driver | Minimum Version |
|---|---|---|---|
@conveyor/store-sqlite-node | Node.js | node:sqlite (DatabaseSync) | Node 22.13+ or Deno 2.2+ |
@conveyor/store-sqlite-bun | Bun | bun:sqlite | Bun 1.2+ |
@conveyor/store-sqlite-deno | Deno | @db/sqlite (FFI) | Deno 2.2+ |
All three packages export a class named SqliteStore with the same constructor signature and behavior.
Configuration
interface SqliteStoreOptions extends StoreOptions {
/** Path to the SQLite database file, or ":memory:" for in-memory. */
filename: string;
/** Run migrations automatically on connect() (default: true). */
autoMigrate?: boolean;
/** Called when an event handler throws. Defaults to console.warn. */
onEventHandlerError?: (error: unknown) => void;
}File-Based Database
const store = new SqliteStore({ filename: './data/queue.db' });
await store.connect();In-Memory Database
const store = new SqliteStore({ filename: ':memory:' });
await store.connect();How It Works
WAL Mode and Concurrency
On connect(), the store enables WAL (Write-Ahead Logging) mode and sets a 5-second busy timeout:
PRAGMA journal_mode = WAL;
PRAGMA busy_timeout = 5000;WAL mode allows concurrent readers while a single writer is active, which is the best concurrency model for SQLite.
Transactions
All multi-step operations (deduplication checks, job fetching, flow saving, bulk inserts) use BEGIN IMMEDIATE transactions. This acquires a write lock immediately, preventing other writers from interleaving:
// Internal implementation pattern
this.db.exec('BEGIN IMMEDIATE');
try {
const result = fn();
this.db.exec('COMMIT');
return result;
} catch (err) {
this.db.exec('ROLLBACK');
throw err;
}Prepared Statements
Frequently used queries are prepared once during connect() and reused for the lifetime of the store. This avoids repeated SQL parsing overhead. The prepared statement cache includes:
insertJob-- insert a new job with all columnsgetJob-- fetch a job by queue name and IDremoveJob-- delete a job by queue name and IDcountByState-- count jobs by stateactiveCount-- count active jobsinsertPaused/removePaused/getPaused-- pause/resume management
All other queries use inline this.db.prepare() calls with parameterized values.
Events
SQLite has no built-in pub/sub mechanism like PostgreSQL's LISTEN/NOTIFY. Events are delivered synchronously to subscribers within the same process using an in-memory callback registry.
This means SQLite stores do not support cross-process event delivery. If you need events across multiple processes, use PgStore instead.
Auto-Migrations
The store uses a conveyor_migrations table to track schema versions. On connect() (unless autoMigrate is false), it runs any pending migrations. The tables created are:
conveyor_jobs-- stores all job dataconveyor_paused_names-- tracks paused job names per queueconveyor_group_cursors-- tracks round-robin group cursorsconveyor_migrations-- migration version tracking
Sequence Counter
SQLite stores maintain an in-memory sequence counter (seqCounter) initialized from MAX(seq) + 1 on connect. This counter provides insertion ordering for FIFO/LIFO without relying on autoincrement. Jobs are ordered by priority ASC, seq ASC (FIFO) or priority ASC, seq DESC (LIFO).
Limitations
- Single process only. SQLite locks the database file for writes. While WAL mode allows concurrent reads, only one writer can proceed at a time. Multiple processes writing to the same database file may encounter
SQLITE_BUSYerrors. - No cross-process events. Events are in-process only. Use PgStore for multi-process setups.
- Polling for delayed jobs. The core Worker class polls for delayed job promotion rather than relying on push notifications.
- File locking on NFS. SQLite file locking is unreliable on network file systems. Use a local filesystem for the database file.
Cleanup
disconnect() clears all event subscribers and closes the database connection:
await store.disconnect();All SQLite stores support Symbol.asyncDispose:
await using store = new SqliteStore({ filename: ':memory:' });
await store.connect();
// store.disconnect() called automaticallySee Also
- Store overview and comparison
- SQLite for Node.js
- SQLite for Bun
- SQLite for Deno
- PgStore for multi-process production
