Resolver problemas
As dúvidas mais comuns, suas causas e como resolvê-las. Os erros são JSON no formato {"code", "message"}; os SDKs os expõem como error.code / Error::code(). Servidores mais recentes também enviam sqlstate, details, hint e constraint nos erros do banco.
A consulta retorna [], mas as linhas existem
A segurança por linha filtrou os dados. A RLS não faz uma leitura falhar: as linhas que as políticas não permitem simplesmente não existem para quem faz a chamada. Confira:
- Quem está consultando. Sem sessão, a requisição roda como
anon; com sessão, comoauthenticated, comauth.uid()= id do usuário. Uma linha pertencente a outro usuário fica invisível, como esperado. - A política.
USING (owner = auth.uid())só corresponde a linhas cujoownerfoi definido. Linhas inseridas pelo editor SQL ou por uma migração têm oownerque você atribuiu (ouNULL, poisauth.uid()fica vazio nesses contextos). - As políticas do papel. Uma política
TO authenticatednão se aplica aanon. Com RLS ativada e nenhuma política para um papel, ele não vê nenhuma linha.
O mesmo vale para update e delete: alterar zero linhas é um sucesso, não um erro. Para confirmar a alteração, peça a linha de volta (.update({...}).eq('id', id).select('id')) e confira o resultado.
Uma gravação que viola o WITH CHECK de uma política é um erro: 401/403 db_error com new row violates row-level security policy (42501).
401 ou 403 com um token válido
O papel não tem GRANT na tabela, então o Postgres recusa o acesso antes mesmo de consultar a RLS: db_error, permission denied for table notes (42501). Visitantes (anon) recebem 401 (entrar pode resolver); usuários logados recebem 403.
GRANT SELECT, INSERT, UPDATE, DELETE ON public.notes TO authenticated;
GRANT SELECT ON public.notes TO anon; -- only if visitors may read itFunções chamadas por rpc precisam de GRANT EXECUTE. O armazenamento precisa de políticas em storage.objects (Armazenamento).
Um 401 invalid_token é diferente: o token está malformado, expirado ou assinado com outra chave (de outro projeto ou anterior à redefinição de nelcota dev). Entre novamente; os SDKs renovam os tokens sozinhos enquanto o refresh token for válido.
email_not_confirmed ao entrar
O servidor tem NELCOTA_EMAIL_CONFIRMATION_URL configurado, então uma conta nova precisa abrir o link enviado por email antes de entrar com senha. Enquanto aguarda, o cadastro retorna o usuário sem uma sessão. Reenvie o link com resendConfirmation(email) (resend_confirmation no Rust). Localmente, nelcota dev deixa a confirmação desligada.
redirect_not_allowed ao iniciar um login OAuth
redirectTo deve corresponder a uma das NELCOTA_OAUTH_REDIRECT_URLS, separadas por vírgula: mesmo protocolo, host e porta, e um caminho igual ou abaixo do configurado. http://localhost:5173/auth/callback e http://127.0.0.1:5173/auth/callback são origens diferentes. Reinicie o servidor após alterar a variável.
Os tipos não correspondem ao banco
nelcota types gera um retrato do schema. Após uma migração, execute novamente (nelcota types -o src/database.ts ou --lang rust); caso contrário, o compilador não conhece colunas novas e continua aceitando as removidas, com falhas só em execução: invalid_query ou db_error. Executar isso no CI e falhar quando houver diferenças detecta arquivos desatualizados.
O servidor recarrega seu catálogo a cada DDL. Em um Postgres gerenciado sem event trigger, recarregue manualmente: NOTIFY nelcota, 'reload schema';.
service_role é recusado no navegador
@nelcota/client lança NelcotaUsageError: A service_role token bypasses RLS and must never reach a browser ao encontrar esse token em uma página. Ele ignora todas as políticas; qualquer pessoa que abrisse as ferramentas de desenvolvimento teria controle do banco. Use-o apenas em um servidor (por exemplo, com nelcota token service-role --days 30); no navegador, faça o login dos usuários e deixe a RLS decidir.
Só voltam 1000 linhas
Cada leitura retorna no máximo NELCOTA_MAX_ROWS linhas (1000 por padrão), mesmo sem limit ou quando o limit é maior. Use paginação para ler o restante:
- TypeScript:
range(from, to)oulimit+offset;rangeecountno resultado (comcount: 'exact') indicam se existem mais linhas, epages(size)percorre todas elas. - Rust:
range(start, end)oulimit+offset, comcount_exact(). - HTTP:
Content-Range: 0-999/*(ou/totalcomPrefer: count=exact).
Ordene por uma coluna única ao paginar. Aumentar o limite faz cada leitura grande consumir mais memória; as respostas também têm um limite de 8 MiB (413 response_too_large).
429 rate_limited
Login com senha, cadastro, links de email e login com provedor usam orçamentos por endpoint/IP baseados em NELCOTA_AUTH_RATE_LIMIT_PER_MINUTE (30 por padrão). Login com senha também tem orçamento por email. Renovação e resgate PKCE compartilham outro orçamento por IP, dez vezes maior (300 por padrão). As cotas são por processo, não distribuídas entre instâncias. Clientes atrás de NAT podem compartilhar um orçamento por IP. A resposta contém Retry-After; SDKs expõem error.retryAfter (segundos) / Error::retry_after(). Ative NELCOTA_TRUST_PROXY=true apenas atrás de um proxy confiável: usa-se o valor mais à direita de X-Forwarded-For. Restrinja acesso direto à API e configure os saltos CDN/proxy; caso contrário, a cota pode identificar o proxy em vez do usuário.
Um 503 unavailable indica que o banco não pôde ser acessado ou está sobrecarregado; ele também pode incluir Retry-After. Os SDKs repetem leituras em 429 e 503 automaticamente; gravações nunca são repetidas de forma automática.