> ## Documentation Index
> Fetch the complete documentation index at: https://zapo.to/llms.txt
> Use this file to discover all available pages before exploring further.

# Stores

> Persista estado de autenticação, sessões Signal e dados de protocolo por domínio pela interface de store plugável do zapo e seus pacotes de backend.

Uma **store** é onde o `zapo` persiste tudo o que uma sessão precisa para sobreviver a um reinício: credenciais de pareamento, estado do protocolo Signal, coleções de app-state e, opcionalmente, seu arquivo de mensagens/threads/contatos. Você constrói uma com `createStore` e a passa para o client.

```ts theme={null}
import { createStore } from 'zapo-js'
import { createSqliteStore } from '@zapo-js/store-sqlite'

const store = createStore({
  backends: {
    sqlite: createSqliteStore({ path: '.auth/state.sqlite', driver: 'auto' })
  },
  providers: {
    auth: 'sqlite',
    signal: 'sqlite',
    preKey: 'sqlite',
    session: 'sqlite',
    identity: 'sqlite',
    senderKey: 'sqlite',
    appState: 'sqlite',
    privacyToken: 'sqlite',
    messages: 'sqlite',
    threads: 'sqlite',
    contacts: 'sqlite'
  }
})
```

<h2 id="the-model">
  O modelo
</h2>

O `createStore` separa **backends** (onde os dados ficam) de **providers** (qual backend cada domínio usa). Isso permite misturar backends — por exemplo, manter o estado quente do signal no Redis enquanto arquiva mensagens no Postgres.

```ts theme={null}
createStore({
  backends: {
    redis: createRedisStore({ redis }),
    postgres: createPostgresStore({ pool })
  },
  providers: {
    auth: 'redis',
    signal: 'redis',
    preKey: 'redis',
    session: 'redis',
    identity: 'redis',
    senderKey: 'redis',
    appState: 'redis',
    privacyToken: 'redis',
    messages: 'postgres',
    threads: 'postgres',
    contacts: 'postgres'
  }
})
```

<h2 id="providers-are-required-when-you-set-backends">
  Providers são obrigatórios quando você define `backends`
</h2>

Assim que `backends` contém ao menos uma entrada, **todo domínio de persistência precisa ser atribuído explicitamente** em `providers`. Os domínios obrigatórios são `auth`, `signal`, `preKey`, `session`, `identity`, `senderKey`, `appState`, `privacyToken`, `messages`, `threads` e `contacts`. Tanto os tipos TypeScript quanto uma verificação em tempo de execução garantem isso — `createStore` lança um erro listando as chaves `providers.*` faltantes quando alguma é omitida.

Três valores são válidos para cada domínio:

* Um nome de backend de `backends` (por exemplo `'sqlite'`) — persiste esse domínio ali.
* `'memory'` — mantém esse domínio no provider de memória embutido nesta execução.
* `'none'` — válido apenas para os domínios de arquivo opcionais (`messages`, `threads`, `contacts`); ignora o domínio por completo.

A verificação existe porque cobertura parcial é quase sempre um bug. Se você persiste apenas `auth` e deixa o estado do Signal, o app-state ou a caixa de mensagens recairem para a memória, o dispositivo pareia uma vez e depois perde o estado de protocolo a cada reinício. Escolha `'memory'` de forma deliberada quando for esse o seu objetivo.

```ts theme={null}
createStore({
  backends: { sqlite: createSqliteStore({ path: '.auth/state.sqlite' }) },
  providers: {
    auth: 'sqlite',
    signal: 'sqlite',
    preKey: 'sqlite',
    session: 'sqlite',
    identity: 'sqlite',
    senderKey: 'sqlite',
    appState: 'sqlite',
    privacyToken: 'sqlite',
    messages: 'none',  // pula o arquivo de mensagens
    threads: 'none',
    contacts: 'none'
  }
})
```

Quando `backends` está vazio ou ausente, cada domínio recai para a memória (e os domínios de mailbox para `'none'`) — útil para testes, mas o dispositivo se re-pareia a cada reinício.

<h2 id="persisted-domains">
  Domínios persistidos
</h2>

Estes mantêm o estado necessário para manter uma sessão viva. Apoie-os com um backend durável em produção.

| Domínio        | Mantém                                                                    |
| -------------- | ------------------------------------------------------------------------- |
| `auth`         | Credenciais de pareamento e identidade do dispositivo. **Persista isto.** |
| `signal`       | Sessões do Signal (guarda-chuva sobre as sub-stores abaixo).              |
| `preKey`       | Pre-keys do Signal.                                                       |
| `session`      | Sessões do Signal.                                                        |
| `identity`     | Chaves de identidade do Signal.                                           |
| `senderKey`    | Sender keys de grupo.                                                     |
| `appState`     | Coleções de app-state (silenciar, fixar, ler, arquivar, …).               |
| `privacyToken` | Tokens de contato confiável / privacidade.                                |

<h2 id="optional-archive-domains">
  Domínios de arquivo opcionais
</h2>

Estes aceitam `'none'` para desabilitar a persistência por completo:

| Domínio    | Mantém                                            |
| ---------- | ------------------------------------------------- |
| `messages` | Arquivo de mensagens (`B \| 'memory' \| 'none'`). |
| `threads`  | Metadados de thread.                              |
| `contacts` | Diretório de contatos.                            |

<h2 id="cache-domains">
  Domínios de cache
</h2>

Configurados em `cacheProviders` e usam por padrão memória limitada com TTLs:

| Domínio         | Mantém                                     |
| --------------- | ------------------------------------------ |
| `retry`         | Fila de retentativa de mensagens de saída. |
| `groupMetadata` | Cache de metadados de grupo.               |
| `deviceList`    | Cache da lista de dispositivos.            |
| `messageSecret` | Cache de message-secret para addons.       |

```ts theme={null}
createStore({
  backends: { sqlite },
  providers: { /* ... */ },
  cacheProviders: { groupMetadata: 'sqlite', deviceList: 'sqlite' },
  memory: {
    cacheTtlMs: { groupMetadataMs: 600_000, deviceListMs: 600_000 }
  }
})
```

Cada backend remove entradas expiradas de um jeito: `memory` faz sweep in-process, Redis e MongoDB usam TTL nativo, SQLite filtra na leitura, e **PostgreSQL/MySQL exigem um poller opt-in** (`result.startCleanup(sessionId)`) ou as tabelas de cache crescem sem parar. Veja [Expiração e limpeza de cache](/pt-br/reference/stores#cache-expiry-and-cleanup) para a matriz por backend.

<h2 id="read-through-cache-layer">
  Camada de cache read-through
</h2>

Quando um domínio quente do signal aponta para um backend persistente, cada ida e volta de envio/recebimento paga a latência do backend para buscar a sessão, identidade ou sender key do mesmo peer. A opção `cacheLayer` envolve a store do backend com um L1 LRU limitado (o provider de memória nativo) para que leituras repetidas do mesmo peer pulem o backend, enquanto escritas continuam write-through e o backend permanece como fonte de verdade.

Quatro domínios quentes podem ser cacheados:

| Domínio        | Estratégia                                                                                                        |
| -------------- | ----------------------------------------------------------------------------------------------------------------- |
| `session`      | Sessões Signal Double-Ratchet. Read-through + write-through.                                                      |
| `identity`     | Chaves de identidade remotas. Read-through + write-through.                                                       |
| `senderKey`    | Sender keys por (grupo, remetente). Read-through + write-through.                                                 |
| `privacyToken` | Tokens de contato confiável. Read-through + **invalidate-on-write** (o backend mescla campos parciais no upsert). |

Todos os flags têm padrão `false`. Um flag é no-op a menos que aquele domínio resolva para um backend real em `providers` — cachear `'memory'` ou `'none'` na frente de si mesmo não traz ganho e é ignorado.

```ts theme={null}
createStore({
  backends: { postgres, redis },
  providers: {
    auth: 'postgres',
    signal: 'postgres',
    preKey: 'postgres',
    session: 'postgres',
    identity: 'postgres',
    senderKey: 'postgres',
    appState: 'postgres',
    privacyToken: 'postgres',
    messages: 'postgres',
    threads: 'postgres',
    contacts: 'postgres'
  },
  cacheLayer: {
    session: true,
    identity: true,
    senderKey: true,
    privacyToken: true,
    limits: {
      session: 10_000,
      identity: 10_000,
      senderKey: 5_000,
      privacyToken: 5_000
    }
  }
})
```

`limits` define o limite de entradas por domínio; quando excedido, o L1 faz despejo LRU. Sem definir, cada domínio usa o limite padrão do provider de memória correspondente.

<h3 id="when-to-enable-it">
  Quando habilitar
</h3>

Habilite quando seu backend é uma chamada de rede (Redis, Postgres, MySQL, MongoDB) e você envia ou recebe a uma taxa em que os mesmos peers se repetem — típico de bots, fan-out em grupos e gateways multi-tenant. Com um backend SQLite local os ganhos são menores; meça antes de ativar.

<h3 id="single-writer-assumption">
  Premissa de escritor único
</h3>

O L1 é por processo e não tem canal de invalidação entre processos. Habilite `cacheLayer` apenas quando um único processo é dono das linhas de backend de um dado `sessionId` — o modelo de conexão padrão da biblioteca. Sessões diferentes compartilhando um backend é OK; a mesma sessão aberta em dois processos não é.

<Warning>
  Não habilite `cacheLayer` quando múltiplos processos compartilham um backend para o **mesmo** `sessionId`. As escritas de outro processo deixariam este cache desatualizado e corromperiam o ratchet do Signal.
</Warning>

<h3 id="why-not-every-domain">
  Por que não todos os domínios?
</h3>

`signal`, `appState` e `preKey` são deliberadamente excluídos:

* **`signal`** — a leitura de registro por envio já é memoizada dentro do lock do signal; um segundo cache não adiciona nada.
* **`appState`** — o cliente de sync já cacheia o estado das coleções pela vida do contexto de sync, o único escopo em que as leituras se repetem e permanecem coerentes.
* **`preKey`** — pre-keys de uso único são lidas exatamente uma vez e consumidas. Servir uma chave já consumida a partir de um cache desatualizado a reutilizaria e quebraria a forward secrecy.

<h2 id="backends">
  Backends
</h2>

<CardGroup cols={2}>
  <Card title="SQLite" icon="database" href="/pt-br/reference/stores#sqlite" arrow>
    `@zapo-js/store-sqlite` — local, processo único.
  </Card>

  <Card title="PostgreSQL" icon="elephant" href="/pt-br/reference/stores#postgresql" arrow>
    `@zapo-js/store-postgres` — distribuído, relacional.
  </Card>

  <Card title="MySQL" icon="dolphin" href="/pt-br/reference/stores#mysql" arrow>
    `@zapo-js/store-mysql` — distribuído, relacional.
  </Card>

  <Card title="Redis" icon="bolt" href="/pt-br/reference/stores#redis" arrow>
    `@zapo-js/store-redis` — cache + persistência.
  </Card>

  <Card title="MongoDB" icon="leaf" href="/pt-br/reference/stores#mongodb" arrow>
    `@zapo-js/store-mongo` — document store.
  </Card>

  <Card title="Memory" icon="memory">
    Embutido. Ótimo para testes; não sobrevive a um reinício.
  </Card>
</CardGroup>

Veja a [referência de stores](/pt-br/reference/stores) para as opções de config de cada backend.

<h2 id="memory-only-tests">
  Apenas memória (testes)
</h2>

Para experimentos rápidos ou testes, omita `backends` por completo — cada domínio recorre à memória:

```ts theme={null}
const store = createStore({})
const client = new WaClient({ store, sessionId: 'test' }, logger)
```

<Warning>
  Uma store apenas em memória perde todas as credenciais ao reiniciar, então você pareia novamente a cada boot. Use um backend durável para qualquer coisa de longa duração.
</Warning>
