Skip to content

ADR-001 — Fábrica de Clientes Supabase

CampoValor
Status✅ Aceito
Data2026-04-30
AutoresEquipe OLP
RelacionadoADR-011 — categorias operacionais e nomenclatura
Implementaçãosupabase/functions/_shared/supabase-client.ts

Contexto

O SDK do Supabase aceita 3 modos de autenticação:

  • Anon key (RLS ativo, sem usuário) — uso público
  • Anon key + JWT do usuário (RLS ativo, com auth.jwt()) — uso autenticado
  • Service role key (RLS bypassado) — uso de sistema

Sem padronização, cada Edge Function chamava createClient inline. Isso causava:

  • Service role espalhada em código de fluxo de usuário (risco real de bypass acidental de RLS)
  • Token extraído ora do Authorization header, ora do cookie, ora ausente
  • N conexões abertas por isolate Deno sob carga (5k+ clientes em pico)
  • Auditoria difícil: rg createClient retornava ruído misturando casos legítimos e ilegítimos

Decisão

Toda Edge Function obtém clientes Supabase exclusivamente via _shared/supabase-client.ts, que expõe 5 fábricas nomeadas com semântica explícita:

FábricaChaveOrigem do tokenRLSQuando usar
createSupabaseClient(req)ANONCookie olp_auth✅ ativo99% — toda operação de usuário autenticado
createSupabasePublic()ANON✅ ativoLookup público (slug de escola, banners login)
createSupabaseSystem()SERVICE_ROLE❌ bypassaApenas categorias autorizadas (logging, pre-auth, crons)
createSupabasePortalAuth(req)ANONCookie olp_mural✅ ativoPortal autenticado (aluno/responsável)
createSupabaseStorage()SERVICE_ROLEn/aApenas .storage.from()

Regras invioláveis:

  1. Proibido createClient(...) inline em qualquer função (regra reforçada por revisão; varredura atual: 0 violações).
  2. createSupabaseSystem() é exceção controlada — toda nova chamada exige justificativa em PR e enquadramento em categoria documentada (pre-auth, logging, crons, feature flags pré-migração).
  3. Service role nunca vai para o frontend nem é passada como caller token de Edge Function.
  4. Singletons (_publicClient, _systemClient) por isolate para evitar exaustão de conexão.

Alternativas consideradas

A. Cliente único + flag useServiceRole: boolean

Pros: menos arquivos. Cons: trivial passar true por engano; auditoria perde o nome da função na busca; sem semântica para "portal" vs "sistema". Veredicto: rejeitado — segurança preferida sobre brevidade.

B. Wrapper que decide automaticamente baseado no contexto

Pros: chamador "não pensa". Cons: magia implícita; impossível ler o código e saber se RLS está ativo; viola fail-close. Veredicto: rejeitado — explicitness > convenience em camada de segurança.

C. Manter createClient inline, padronizar via lint

Pros: zero abstração nova. Cons: lint regex frágil; não resolve singletons; não tipa a categoria de uso. Veredicto: rejeitado — abstração de 5 funções tem custo trivial.

Consequências

Positivas

  • rg createSupabaseSystem lista exatamente onde o RLS é bypassado — auditoria O(1).
  • Novos devs leem o nome da função e entendem a postura de segurança.
  • Singleton resolve problema de conexão sob carga sem mudança em chamadores.
  • Migração futura (ex.: trocar provedor) altera 1 arquivo.

Negativas

  • 5 nomes a memorizar.
  • Cliente Portal e Cliente System parecem similares — mitigado por JSDoc detalhado e categorização explícita.

Trade-offs

EixoCustoBenefício
Verbosidade+5 imports possíveisAuditabilidade total
AcoplamentoEdge Functions dependem de _shared/1 ponto de mudança
PerformanceSingleton por isolateEvita N conexões em carga
Aprendizado5 conceitosCada um mapeia 1:1 a um caso de uso

Conformidade

Como verificar:

bash
# Deve retornar 0 (apenas o próprio supabase-client.ts pode chamar createClient)
rg "createClient\(" supabase/functions --type ts \
  -g '!**/_shared/supabase-client.ts'

Sinais de violação:

  • import { createClient } from "@supabase/supabase-js" fora de _shared/supabase-client.ts
  • Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") fora de _shared/

Débito técnico conhecido

Varredura em 2026-04-30: zero violações de inline createClient no backend. As únicas chamadas vivem em _shared/supabase-client.ts e em src/integrations/supabase/client.ts (frontend, anon-only, gerado).

Referências

  • supabase/functions/_shared/supabase-client.ts
  • ADR-011 — service_role vs RLS — detalhamento das categorias de uso de service_role
  • ADR-005 — extração cookie-only do token
  • ADR-006 — único caller legítimo de service_role no fluxo CUD
  • mem://architecture/adr-service-role-vs-rls-standard