ADR-004 — Frontend Gateway Abstraction (invokeEdge / invokeAction)
| Campo | Valor |
|---|---|
| Status | ✅ Aceito |
| Data | 2026-04-30 |
| Autores | Equipe OLP |
| Implementação | src/lib/edge-function.ts |
Contexto
O frontend precisa chamar 60+ Edge Functions. O SDK do Supabase oferece supabase.functions.invoke(), mas:
- Não suporta cookies HttpOnly com
credentials: 'include'de forma trivial (auth do projeto é cookie-only — ver ADR-005) - Não permite alternar entre Supabase direto e Cloudflare Worker sem reescrever chamadas
- Não tem retry transitório em 502/503/504
- Não impõe o contrato
{ success, data|message }
Além disso, durante a migração progressiva para o Cloudflare Worker Gateway (que reescreve cookies cross-domain, faz redirects e injeta telemetria), seria inviável editar centenas de chamadas.
Decisão
Toda chamada do frontend a Edge Function passa por uma das funções de src/lib/edge-function.ts:
| Função | Quando |
|---|---|
invokeEdge(fn, body) | Chamada genérica |
invokeAction(fn, action, params) | Padrão action-dispatch (mais comum) — body vira { action, params } |
invokeActionFlat(fn, action, params) | Action no nível raiz ({ action, ...params }) |
invokeUploadBase64(fn, action, file) | Upload de arquivo via base64 dentro de JSON (preserva cookie HttpOnly) |
invokeUploadFormData(fn, formData) | Upload multipart (apenas quando base64 inviável) |
Comportamento embutido
- Toggle Worker via env:
VITE_USE_WORKER=true+VITE_WORKER_URL→ roteia para o gateway. Caso contrário, vai direto aVITE_SUPABASE_URL. credentials: 'include'em toda request — cookie HttpOnly viaja automaticamente.- Retry exponencial em 502/503/504 (2 tentativas, 2s e 4s) e em erros de rede.
- Resposta tipada
EdgeResponse<T> = { success, data?, message?, error?, ...rest }. - Flag
_transient: truequando a falha é por infra (consumidores podem distinguir de erro de negócio). - Aviso em DEV (
import.meta.env.DEV) quandosuccess: false— feedback rápido sem ruído em prod.
Alternativas consideradas
A. Usar supabase.functions.invoke direto
Pros: zero código próprio. Cons: cookie HttpOnly não é first-class, sem toggle Worker, sem retry. Migração para gateway seria N edits. Veredicto: rejeitado.
B. axios com interceptor global
Pros: ecossistema rico. Cons: +30KB no bundle, retry de axios não diferencia transitório vs negócio, e não traz nada que fetch + 200 linhas não façam. Veredicto: rejeitado — bundle e dependência não compensam.
C. Camada tRPC no client
Pros: tipagem ponta a ponta. Cons: exige espelhar todo backend em schema; já refutado em ADR-003. Veredicto: rejeitado.
Consequências
Positivas
- Migração para o Worker foi uma flag, sem editar chamadas
- Retry transitório melhora UX em redes flaky sem código por hook
- 100% das chamadas usam cookie HttpOnly automaticamente
rg "supabase.functions.invoke"retorna 0 — auditoria O(1)
Negativas
- 1 abstração a mais
invokeUploadFormDataeinvokeUploadBase64exigem decisão consciente (FormData não funciona bem cross-origin com cookies)
Trade-offs
| Eixo | Custo | Benefício |
|---|---|---|
| Lock-in | Toda chamada depende de edge-function.ts | 1 ponto para migração / observabilidade |
| Retry universal | Pode mascarar bugs em testes | Flag _transient permite diferenciar |
| Tipagem | EdgeResponse<T> é genérico | Hooks React Query encapsulam o tipo concreto |
Conformidade
Como verificar:
rg "supabase\.functions\.invoke" src --type ts # deve ser 0
rg "fetch\(['\"][^'\"]*functions/v1" src --type ts # deve ser 0Sinais de violação:
fetchpara path/functions/v1/*em qualquer arquivosrc/- Hook que usa
axiospara chamar Edge Function
Débito técnico conhecido
Varredura em 2026-04-30: zero violações no frontend. Todas as 60+ Edge Functions são consumidas via invokeAction / invokeEdge.
Referências
src/lib/edge-function.tsdocs/architecture/CLOUDFLARE_WORKER_GATEWAY.md- ADR-003 — contrato consumido
- ADR-005 — motivação para
credentials: 'include' - ADR-007 — quem orquestra retry de aplicação