Skip to content

ADR-004 — Frontend Gateway Abstraction (invokeEdge / invokeAction)

CampoValor
Status✅ Aceito
Data2026-04-30
AutoresEquipe OLP
Implementaçãosrc/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çãoQuando
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

  1. Toggle Worker via env: VITE_USE_WORKER=true + VITE_WORKER_URL → roteia para o gateway. Caso contrário, vai direto a VITE_SUPABASE_URL.
  2. credentials: 'include' em toda request — cookie HttpOnly viaja automaticamente.
  3. Retry exponencial em 502/503/504 (2 tentativas, 2s e 4s) e em erros de rede.
  4. Resposta tipada EdgeResponse<T> = { success, data?, message?, error?, ...rest }.
  5. Flag _transient: true quando a falha é por infra (consumidores podem distinguir de erro de negócio).
  6. Aviso em DEV (import.meta.env.DEV) quando success: 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
  • invokeUploadFormData e invokeUploadBase64 exigem decisão consciente (FormData não funciona bem cross-origin com cookies)

Trade-offs

EixoCustoBenefício
Lock-inToda chamada depende de edge-function.ts1 ponto para migração / observabilidade
Retry universalPode mascarar bugs em testesFlag _transient permite diferenciar
TipagemEdgeResponse<T> é genéricoHooks React Query encapsulam o tipo concreto

Conformidade

Como verificar:

bash
rg "supabase\.functions\.invoke" src --type ts            # deve ser 0
rg "fetch\(['\"][^'\"]*functions/v1" src --type ts        # deve ser 0

Sinais de violação:

  • fetch para path /functions/v1/* em qualquer arquivo src/
  • Hook que usa axios para 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