Vinte contatos deveriam ter sido arquivados em um CRM, mas a interface exibiu um selo de sucesso verde e a operação não fez nada silenciosamente. Um helper em TypeScript que torna o campo error obrigatório em mutações do Supabase-JS agora força os desenvolvedores a confrontar essa falha em vez de escondê-la debaixo do tapete.
Por que o formato de retorno do Supabase-JS é uma armadilha
O cliente JavaScript do Supabase (@supabase/supabase-js) não lança exceções quando uma escrita no banco de dados é rejeitada. Em vez disso, ele resolve a promise com um objeto { data, error }. Se uma política de segurança em nível de linha (RLS), uma violação de chave única ou qualquer outra restrição bloquear a consulta, o data retornará como null e o error conterá a mensagem do banco de dados. O cliente assume que quem faz a chamada irá inspecionar o error; ele nunca aborta a chamada.
Na prática, muitas bases de código tratam a chamada como uma operação de "disparar e esquecer":
await supabase
.from('contacts')
.update({ statut: 'ancien', archived_at: now })
.eq('id', id)
return { ok: true }
Quando o update é bloqueado por uma regra de RLS, a promise ainda é resolvida. Como o desenvolvedor nunca desestrutura { error }, a falha torna-se invisível. A função retorna { ok: true }, a interface mostra sucesso e os dados permanecem inalterados. Nenhum log aparece, nenhum alerta do Sentry é disparado e o bug pode permanecer por dias.
Um linter não é suficiente
Ferramentas de análise estática podem avisar quando uma propriedade error é ignorada, mas elas não podem impor um contrato de tempo de execução. Um desenvolvedor ainda pode escrever const _ = await … e silenciar o aviso, ou pode adicionar um comentário para suprimir a regra. O problema subjacente é que o sistema de tipos permite que uma chamada tenha sucesso sem nunca mencionar o error.
O helper mutate(): tornando o tratamento de erros obrigatório
O autor construiu um pequeno wrapper chamado mutate() que altera o formato do tipo de retorno. Em vez de { data, error }, o helper retorna uma tupla [data, error] onde error é um campo obrigatório. O TypeScript, então, recusa-se a compilar qualquer chamada que descarte o segundo elemento.
async function mutate<T>(promise: Promise<{ data: T | null; error: any }>) {
const { data, error } = await promise
// Send error to Sentry immediately
if (error) Sentry.captureException(error)
return [data, error] as const
}
O uso torna-se explícito:
const [result, err] = await mutate(
supabase
.from('contacts')
.update({ statut: 'ancien', archived_at: now })
.eq('id', id)
)
if (err) {
// Handle or rethrow
return { ok: false, message: err.message }
}
return { ok: true, data: result }
Se um desenvolvedor esquecer de capturar o err, o TypeScript emitirá um erro: “Tuple type [T, any] of length 2 has no element at index 1.” O código não compilará até que o erro seja tratado. O helper também injeta instrumentação do Sentry, garantindo que cada rejeição do banco de dados seja registrada, mesmo que quem faça a chamada a ignore posteriormente.
O que está em jogo
- Integridade dos dados – Falhas silenciosas permitem que estados inválidos se infiltrem na produção. Um contato arquivado que nunca saiu da lista ativa pode fazer com que relatórios subsequentes estejam incorretos.
- Confiança do usuário – Uma interface que afirma sucesso enquanto nada mudou corrói a confiança. Os clientes veem “arquivado”, mas ainda encontram o contato em buscas.
- Tempo do desenvolvedor – Caçar bugs fantasmas consome horas. O tratamento explícito de erros traz o problema à tona no ponto da falha, encurtando o ciclo de depuração.
- Custo operacional – Adicionar algumas linhas extras de código e um pequeno wrapper é insignificante comparado ao custo de um incidente de perda silenciosa de dados.
O trade-off
O helper adiciona verbosidade: cada chamada de mutação agora retorna uma tupla, e quem chama deve escrever um bloco if (err). Algumas equipes podem ver isso como ruído de boilerplate, especialmente para ações simples de CRUD onde esperam sucesso. O contra-argumento é que o código extra é uma salvaguarda, não um recurso opcional. Em ambientes onde a correção dos dados é primordial — sistemas de CRM, finanças, saúde — forçar a verificação se paga.
O que vem a seguir
O autor promete uma terceira parte que examina políticas de RLS que retornam zero linhas sem explicação. Esse padrão, assim como o caso do erro silencioso, esconde falhas por trás de uma consulta aparentemente bem-sucedida. Juntas, as partes da série visam expor o lado de "rumores" da API do Supabase e dar aos desenvolvedores ferramentas concretas para exigir fatos.
Resumo: O design do Supabase-JS permite que uma escrita falha pareça um sucesso, a menos que você se lembre de ler o campo error. Ao envolver as chamadas em um helper reforçado pelo TypeScript que torna o error obrigatório e o registra no Sentry, você transforma falhas silenciosas em eventos visíveis e acionáveis. O modesto aumento no tamanho do código garante confiabilidade dos dados e confiança do usuário — duas coisas que nenhum sucesso silencioso pode jamais entregar.
