create*Store factory you pass into a backends entry of createStore. All backends implement the same per-domain store contracts, so switching backends is a config change, not a code change.
SQLite
@zapo-js/store-sqlite — createSqliteStore(config).
Provide exactly one of
path or connection. With path, the library opens (and ref-counts) its own connection and closes it on store.destroy(). With connection, you own the lifecycle — store.destroy() leaves it open so you can keep using it elsewhere.
Bring your own connection
Share a single SQLite handle with the rest of your application by opening it yourself withopenSqliteConnection and passing it through connection:
better-sqlite3 peer dependency.
PostgreSQL
@zapo-js/store-postgres — createPostgresStore(config).
Also exports
createPgPool and ensurePgMigrations. Requires the pg peer dependency.
MySQL
@zapo-js/store-mysql — createMysqlStore(config).
Also exports
createMysqlPool and ensureMysqlMigrations. Requires the mysql2 peer dependency.
Batch insert chunking
batchInsertChunkSize caps how many rows the PostgreSQL and MySQL backends fold into a single multi-row INSERT for batch writes — Signal sessions, remote identities, sender-key distributions, prekey generation, and the message/thread/contact upsertBatch paths used by write-behind persistence.
The value is rounded down to the nearest power of two internally (500 → 256, 1000 → 512, …), and each batch is decomposed into power-of-two sub-chunks. That keeps the set of distinct prepared statements per connection bounded at log2(chunkSize) + 1 regardless of how N varies between calls — important for staying under the mysql2 client-side cache and MySQL’s max_prepared_stmt_count quota, and for keeping pg’s named statement cache stable.
Leave the default unless benchmarks for your workload show it helps. Raise it for steady high-fanout group sends; lower it if your database limits are tight.
Redis
@zapo-js/store-redis — createRedisStore(config).
Per-domain store TTL
Persistent store domains never expire by default. SetstoreTtlMs.<domain>Ms to make Redis prune that domain’s keys automatically — implemented as PEXPIRE on each write, no background cleanup needed. Any domain left unset stays persistent (byte-identical to the previous behavior).
Requires the
ioredis peer dependency.
MongoDB
@zapo-js/store-mongo — createMongoStore(config).
Requires the
mongodb peer dependency.
Bulk writes use { ordered: false } so independent upserts run in parallel — a per-document failure does not abort the rest of the batch.
Cache expiry and cleanup
The four cache domains (retry, groupMetadata, deviceList, messageSecret) carry a TTL set through cacheTtlMs. How expired entries are evicted differs per backend:
cleanup.intervalMs defaults to 60_000 (60s). result.destroy() stops every poller started through it, so calling poller.stop() yourself is only useful if you want to halt cleanup before tearing down the backend.
For MongoDB, the TTL monitor’s ~60s latency means cache entries can linger past the configured TTL. Acceptable for groupMetadata/deviceList; switch to Redis if you need tighter eviction.
Mixing backends
createStore lets each domain choose a backend by name, so you can combine them:
backends is set — see Stores for the rule and the accepted values ('<backend>', 'memory', 'none').