zapo é desenhado para que um único processo conduza muitas contas a partir de uma store compartilhada. Cada conta vive atrás de um sessionId estável; tudo que é seguro compartilhar (o pool de conexão do backend, a factory do WebSocket, o logger) é compartilhado, e tudo que é específico da conta (sessões Signal, identidades, app-state, mailbox) é particionado por sessionId.
O padrão
sessionId é a chave durável de uma conta — o mesmo id entre restarts retoma o mesmo device pareado. Mudá-lo orfana as credenciais anteriores.
O que é por sessão vs compartilhado
Migrar para multi-tenant é (1) instanciar N
WaClients na mesma store, e (2) dimensionar o pool de backend + o orçamento de memória para N sessões concorrentes.
Ciclo de vida da sessão
store.session(sessionId) é memoizado. A primeira chamada materializa o bundle por domínio (locks por sessão, wrappers de cache opcionais, …) e o cacheia dentro da store; chamadas posteriores com o mesmo id retornam o mesmo bundle.
WaClient chama store.session(sessionId) sob demanda; você normalmente não o invoca.
Adicionando tenants em runtime
Não há etapa de pré-registro — basta construir um novoWaClient com um novo sessionId:
Removendo tenants
Não existe APIstore.removeSession(id). O map interno de sessões da store só é limpo por store.destroy(). Para processos multi-tenant longos:
- Logout, mantém a entry.
await client.logout()apaga o estado persistente daquelesessionId(sujeito alogoutStoreClear). O bundleWaStoreSessioncontinua no map interno da store — inerte, mas segurando as stores por domínio até o processo reiniciar. Aceitável quando o churn de tenants é baixo em relação à memória total. - Reiniciar o processo quando você precisa reclamar cada byte (ex.: depois de desprovisionar muitos tenants de uma vez). Destrói a store e reconstrói.
Propriedade entre processos
Em deploys multi-processo, decida como ossessionIds mapeiam pra processos:
- Um processo por
sessionIdvia hash consistente / roteamento sticky no load balancer ou queue (mais simples). - Eleição de líder antes de abrir o client (advisory lock do Postgres, Redis
SET NX, lease etcd) — útil pra failover HA.
cacheLayer aperta isso: seu L1 não tem canal de invalidação entre processos, então as linhas de backend de um sessionId devem ser donas de um único processo durante todo o lifecycle. O L1 de um processo que assume começa frio e pode servir leituras stale até pegar as escritas que o dono anterior fez.
Compartilhando um media processor
WaMediaProcessor é um wrapper stateless sobre seus binários de mídia (sharp, ffmpeg/ffprobe, file-type). A mesma instância pode servir todos os WaClients — não há estado por sessão dentro do processor, então reutilizá-lo evita pagar o custo de lookup / lazy-import dos binários N vezes.
Orçamento de memória
Os caps emWaCreateStoreOptions.memory.limits valem por sessão. Com N sessões concorrentes, a RAM in-process no pior caso escala linearmente:
Ajuste os caps por sessão para baixo conforme N cresce, ou mova o mailbox / domínios de alta cardinalidade para um backend persistente (o provider in-memory existe para testes e contas pequenas). Os TTLs em
memory.cacheTtlMs são independentes de N — eles só limitam por quanto tempo uma entry sobrevive em cada cache.
Estratégias de sharding
@zapo-js/store-sqlite é single-host e o arquivo SQLite é segurado por um processo — escolha um dos backends de rede para qualquer layout com mais de um processo.
Shutdown gracioso
client.disconnect() faz flush da fila de write-behind por sessão e fecha o socket sem desvincular o device, então o próximo boot retoma a partir da store. store.destroy() então libera o backend compartilhado (pool, file handle, …). Chamar disconnect() em todos os clients antes de store.destroy() garante que as escritas pendentes de cada sessão sejam flushed; store.destroy() não faz isso por você.
Veja também
- Stores — o modelo de persistência por
sessionIde a camada opcional de read-through cache. - Produção & deploy — checklist operacional mais amplo (logging, timeouts, segurança).
- Reconnection — a política de reconexão é por sessão; não existe loop de reconexão compartilhado.
