Skip to main content
Cada pacote de backend exporta uma factory create*Store que você passa para uma entrada de backends do createStore. Todos os backends implementam os mesmos contratos de store por domínio, então trocar de backend é uma mudança de config, não de código. O tipo do backend é WaStoreBackend<S, C> — os dois parâmetros assumem por padrão a matriz completa de domínios (todo domínio de persistência mais todo domínio de cache). Todo pacote @zapo-js/store-* incluído entrega um backend completo; um backend parcial escrito à mão se estreita com WaStoreBackend<S, C> e o createStore() recusa domínios não declarados em tempo de compilação. Veja Backends customizados.

SQLite

@zapo-js/store-sqlite — createSqliteStore(config).
Informe exatamente um entre path e connection. Com path, a biblioteca abre (e faz ref-count d)e sua própria connection e a fecha em store.destroy(). Com connection, você é dono do ciclo de vida — store.destroy() mantém a connection aberta para você continuar usando em outros lugares.

Escolhendo um driver

Desde o @zapo-js/store-sqlite 1.3.0 a store não precisa mais de um addon nativo: better-sqlite3 é uma peer dependency opcional, e o módulo embutido node:sqlite do runtime serve de fallback. Isso torna a persistência em SQLite viável em plataformas onde o addon não compila — Termux, musl sem prebuilds, CI travada. O comportamento de instalações existentes não muda: com better-sqlite3 presente, 'auto' continua preferindo-o e nunca toca em node:sqlite.
No Node 20, 21 ou 22.5–22.12 sem a flag, node:sqlite não existe, então 'auto' não consegue cair nele — você recebe o erro de instalação do better-sqlite3, e instalar o addon é a solução real ali.
O formato em disco é idêntico entre os drivers — mesmo schema, mesmas migrations — então você pode trocar o driver de um banco existente sem migrar nada. A vazão de escrita é praticamente a mesma nos três; o próprio SQLite domina. O node:sqlite só é mais lento ao materializar result sets largos ou de várias linhas, já que constrói um objeto novo por linha: cerca de 1,3× numa única linha larga e 2,5× numa página de 50 linhas com blobs, medido contra o better-sqlite3 no Node 22. Esse custo cai nas queries de histórico/mailbox, não nas escritas de sessão por mensagem.

Use sua própria connection

Compartilhe um único handle do SQLite com o resto da sua aplicação abrindo-o você mesmo com openSqliteConnection e passando via connection:
openSqliteConnection aceita a mesma opção driver e segue a mesma ordem de resolução.

PostgreSQL

@zapo-js/store-postgres — createPostgresStore(config).
Também exporta createPgPool e ensurePgMigrations. Requer a peer dependency pg.

MySQL

@zapo-js/store-mysql — createMysqlStore(config).
Também exporta createMysqlPool e ensureMysqlMigrations. Requer a peer dependency mysql2.

Chunking de inserts em lote

batchInsertChunkSize limita quantas linhas os backends PostgreSQL e MySQL agrupam em um único INSERT multi-linha em escritas em lote — sessões do Signal, identidades remotas, distribuições de sender key, geração de prekeys e os caminhos upsertBatch de message/thread/contact usados pela write-behind persistence. O valor é arredondado para baixo até a potência de dois mais próxima internamente (500 → 256, 1000 → 512, …), e cada lote é decomposto em sub-chunks de potência de dois. Isso mantém o conjunto de prepared statements distintos por conexão limitado a log2(chunkSize) + 1 independentemente de como N varia entre chamadas — importante para ficar abaixo do cache do cliente mysql2 e da quota max_prepared_stmt_count do MySQL, e para manter o cache de statements nomeados do pg estável. Mantenha o padrão a menos que benchmarks do seu workload mostrem benefício. Aumente para envios em grupo de alto fanout sustentados; reduza se os limites do seu banco forem apertados.

Redis

@zapo-js/store-redis — createRedisStore(config).

TTL por domínio do store

Domínios de store persistentes nunca expiram por padrão. Defina storeTtlMs.<domínio>Ms para o Redis prunar as chaves daquele domínio automaticamente — implementado como PEXPIRE em cada escrita, sem precisar de cleanup em background. Qualquer domínio deixado de fora permanece persistente (comportamento byte-idêntico ao anterior).
Duas semânticas, por tipo de domínio:
auth não tem botão de TTL de propósito: expirar credenciais de login deslogaria o device. Escolha um TTL de crypto maior que sua pior janela de ociosidade — uma sessão ociosa além dessa janela evicta as chaves de Signal / app-state e força um novo handshake ou re-sync. Valores ttlMs não-positivos são rejeitados na construção.
Requer a peer dependency ioredis.

MongoDB

@zapo-js/store-mongo — createMongoStore(config).
Requer a peer dependency mongodb. Escritas em lote usam { ordered: false } para que upserts independentes rodem em paralelo — uma falha por documento não aborta o resto do lote.

Expiração e limpeza de cache

Os cinco domínios de cache (retry, groupMetadata, chatMetadata, deviceList, messageSecret) usam um TTL definido em cacheTtlMs. A forma como entradas expiradas são removidas varia por backend:
No PostgreSQL e MySQL, sem startCleanup suas tabelas de cache crescem monotonicamente. Leituras continuam ignorando linhas expiradas (dado obsoleto nunca é servido), mas o uso de disco cresce sem limite. Inicie um poller por session id.
cleanup.intervalMs tem default 60_000 (60s). result.destroy() para todos os pollers iniciados por ele, então chamar poller.stop() manualmente só é útil se você quiser interromper a limpeza antes de derrubar o backend. No MongoDB, a latência de ~60s do TTL monitor faz entradas de cache poderem persistir um pouco além do TTL configurado. Aceitável para groupMetadata/deviceList; troque pelo Redis se precisar de eviction mais agressivo.

Misturando backends

O createStore permite que cada domínio escolha um backend por nome, então você pode combiná-los:
Todo domínio de persistência deve ser listado quando backends é definido — veja Stores para a regra e os valores aceitos ('<backend>', 'memory', 'none').
Última modificação em 21 de setembro de 2026