Escopo do storage por dono¶
Duas pessoas podem usar o seu app no mesmo navegador. Um device de família, um computador de recepção, um tablet compartilhado no balcão — ou só você, com duas contas.
Quando isso acontece, o native.storage precisa saber de quem é cada chave.
Esta página mostra como dizer, o que acontece com o dado que já está gravado, e
por que a resposta não é automática.
O problema, em três linhas¶
# a Alice entra e salva um rascunho
await native.storage.put("rascunho", "minhas anotações")
# ela sai, o Bob entra no mesmo navegador
await native.storage.put("rascunho", "as anotações do Bob")
# a Alice volta
await native.storage.get("rascunho") # "as anotações do Bob"
Sem escopo, a chave é só o nome. O put do Bob passou por cima do valor da
Alice, o list_keys() de cada um devolve as chaves dos dois, e o remove de um
alcança o dado do outro.
E não é só a chave que você escolhe
O cache persistido do query guarda respostas de API
pelo mesmo caminho. Sem escopo, o boot do Bob enchia o QueryCache com
respostas que a Alice tinha persistido — dado de servidor, aparecendo na
tela de quem não pediu. Escopar o storage fecha isso sem uma linha de código
no query.
A correção: diga quem é o dono¶
from tempestweb import native
async def ao_entrar(user_id: str) -> None:
"""Aponta o storage para o keyspace deste usuário."""
await native.storage.configure(owner=user_id)
await native.storage.put("rascunho", "minhas anotações")
Depois dessa chamada, todo put, get, remove e list_keys fica dentro do
keyspace daquele dono. O Bob não lê, não sobrescreve e não lista nada da Alice.
O list_keys() devolve o nome como você escreveu, sem o prefixo:
await native.storage.configure(owner="alice")
await native.storage.put("rascunho", "a")
await native.storage.put("enviados", "b")
await native.storage.list_keys() # ["enviados", "rascunho"]
Configure no boot, antes da primeira chamada
O dono é estado do módulo JS da aba. Ele sobrevive a uma reconexão de socket
no Modo B, mas não a um reload de página — a app tem que reconfigurar no
boot. Um put feito antes do configure grava no keyspace default, sem
aviso, porque o framework não tem como saber que você "já devia" ter
configurado.
Por que você precisa passar o owner¶
Seria mais confortável se o framework descobrisse sozinho. Ele não pode:
- o Modo A roda inteiro no browser. Não há sessão, não há login do lado do servidor, não há nada de onde tirar uma identidade;
- o Modo B tem
session_id, mas isso identifica um transporte, não uma pessoa: ele muda a cada reconexão. Keyar o storage por ele orfanaria o dado no primeiro socket que cair.
Quem sabe quem está logado é a sua app, depois do login. Por isso o owner é um
parâmetro, e não mágica.
O dado que já está gravado¶
O dono default é a string vazia, e ele grava a chave crua — byte a byte o que uma versão sem escopo gravava. Isso é deliberado, e tem uma consequência boa e uma que exige atenção.
A boa: nada quebra
Nada é reescrito, nada é migrado, nenhuma versão de banco se move. App que
nunca chama configure(owner=...) não vê diferença nenhuma, e o dado que já
está no disco continua exatamente onde está.
A que exige atenção: ligar o escopo abre um keyspace vazio
O dado antigo não vem junto. Ele continua legível pelo dono default, mas
o owner="alice" começa do zero — porque só a sua app sabe de quem aquele
dado era.
Se você quer levar o legado para um dono, faça explicitamente:
from tempestweb import native
async def adotar_legado(user_id: str) -> None:
"""Move o dado do keyspace default para o do usuário."""
await native.storage.configure()
legado = {
nome: await native.storage.get(nome)
for nome in await native.storage.list_keys()
}
await native.storage.configure(owner=user_id)
for nome, conteudo in legado.items():
await native.storage.put(nome, conteudo)
Não adote num device onde duas pessoas já usaram
Ali o keyspace default tem o dado das duas misturado, sem registro de
quem é o quê — o store guarda chave → valor, e nada mais. Adotar entregaria
o dado de uma para a outra.
Pior: onde as duas escreveram a mesma chave, existe um valor, o de quem escreveu por último. O da primeira sumiu no dia em que foi sobrescrito, e nada aqui recupera isso — nem antes, nem depois desta mudança.
Nesse caso, comece vazio.
configure acerta os dois knobs¶
configure é a mesma função que escolhe o codec de
compressão, e ela sempre define os dois:
await native.storage.configure(codec="deflate", owner="alice")
await native.storage.configure(codec="deflate") # ⚠️ o owner volta a ""
Uma regra só para a função inteira, igual ao que o codec já fazia. O custo é o pé-na-jaca acima — reconfigurar um derruba o outro —, então passe os dois juntos.
Outra aba pode estar atualizando o app¶
Se outra aba estiver no meio de uma troca de versão do banco, uma chamada de
storage pode falhar com o código blocked:
from tempestweb.native import NativeError
async def salvar(conteudo: str) -> None:
"""Salva, avisando se outra aba está atualizando o app."""
try:
await native.storage.put("rascunho", conteudo)
except NativeError as erro:
if erro.code == "blocked":
print("Outra aba está atualizando o app. Tente de novo.")
else:
raise
Isso é diferente de um erro de escrita: o banco está lá e saudável, e a operação volta a funcionar quando a outra aba fechar. Antes desta versão a chamada simplesmente nunca respondia.
E o código stale, que é o oposto
blocked diz "espere". stale diz "recarregue": outra aba já subiu a
versão do banco e esta página está rodando o build antigo. Tentar de novo não
resolve — só carregar o código novo.
Hoje ele é inalcançável (a versão do banco é 1 e nunca subiu). Ele existe
para o dia do bump, e é o que impede aquele dia de fazer as abas antigas
caírem para o localStorage em silêncio, partindo o dado do app entre dois
backends.
Recapitulando¶
configure(owner=...)dá a cada pessoa o seu keyspace: sem leitura cruzada, sem sobrescrita, sem listar o dado alheio.- O dono vem da sua app porque nem o Modo A nem o Modo B têm identidade de pessoa para oferecer.
- O default (
"") grava chave crua, então nada muda para quem não usa. - Ligar o escopo começa vazio de propósito; adotar o legado é uma decisão da app, e não se faz em device compartilhado.
configure()define codec e dono — passe os dois juntos.blockedsignifica "outra aba está atualizando", não "a escrita falhou";stalesignifica "recarregue, este build está velho".