Turso: Integração Incremental como Provider Alternativo

Plano reversível para validar Turso/libSQL em uma projeção de leitura antes de considerar escritas ou migração de dados canônicos.

Status: descriptor e registro planned implementados em src/lib/db/providers/turso; nenhum adapter ou tráfego Turso existe. Um token da Platform API está guardado localmente em .dev.vars, arquivo ignorado pelo Git. Nenhuma chamada de provisionamento foi feita, nenhum database foi criado por esta integração e ainda faltam TURSO_DATABASE_URL e TURSO_AUTH_TOKEN. O segredo não deve aparecer em documentação, logs, código cliente ou commits.

Papel do Turso nesta arquitetura

Turso é a primeira implementação alternativa porque oferece SQLite/libSQL com acesso adequado a runtimes serverless. Ele será usado inicialmente como ProjectionStore de leitura, não como substituto instantâneo de PostgreSQL, Supabase Auth, Realtime, RLS ou Storage.

O primeiro experimento deve provar três coisas:

  1. o domínio consegue depender de CatalogReadStore, não do SDK Supabase;
  2. o mesmo contrato funciona com Supabase e Turso;
  3. shadow reads podem comparar resultado, latência e custo sem afetar usuários.

Se o experimento falhar, a flag é desligada e o provider atual continua como fonte da resposta. Não há migração irreversível nesse estágio.

Credenciais não são equivalentes

VariávelEscopoUso
TURSO_API_TOKENorganização/plataformacriar e administrar databases pela Platform API
TURSO_DATABASE_URLdatabaseendpoint libsql://... ou URL compatível
TURSO_AUTH_TOKENdatabaseautenticar consultas nesse database

O token de plataforma já fornecido não substitui o token de database. Para o runtime da aplicação, use uma credencial de database com o menor privilégio e prazo viáveis. Tokens ficam em .dev.vars no desenvolvimento e em secrets da plataforma no deploy.

Nunca use PUBLIC_, VITE_ ou serialização para o HTML nessas variáveis. O navegador acessa dados por endpoints estreitos da aplicação; não recebe uma credencial ampla do database.

Arquitetura da primeira fatia

Página / API


CatalogService
    │ depende de

CatalogReadStore
    ├── SupabaseCatalogAdapter  ← resposta autoritativa no início
    ├── TursoCatalogAdapter     ← shadow read
    └── MemoryCatalogAdapter    ← testes de contrato

O adapter deve ficar em src/lib/db/providers/turso. A instanciação acontece no servidor, em um composition root. Rotas públicas recebem DTOs já autorizados e não expõem SQL, nomes internos de tabelas ou erros do provider.

Uma projeção pública de catálogo é reconstruível, majoritariamente de leitura e tolera atualização assíncrona controlada. Ela evita, no primeiro ciclo:

  • transações de pedido e pagamento;
  • equivalência entre PostgreSQL RLS e autorização da aplicação;
  • escrita concorrente;
  • migração de Supabase Auth e Storage;
  • reconciliação de webhooks e documentos fiscais.

Pedidos, pagamentos, estoque e identidade permanecem fora do experimento até que contratos, consistência e operação estejam comprovados.

Ciclos de entrega

CicloEntregaGate para avançar
T0 — Fronteirainventário das consultas do catálogoqueries e semântica documentadas
T1 — ContratoCatalogReadStore + DTOstestes unitários sem Supabase SDK
T2 — Adapter atualSupabase atrás do contratocomportamento atual preservado
T3 — Provisionamentodatabase e credencial mínimabackup/export e rotação testados
T4 — Schema/projeçãotabelas libSQL e backfillcontagem, checksum e amostras equivalentes
T5 — Shadowconsulta Turso fora do caminho de respostadivergência e latência observáveis
T6 — Canáriopequena parcela de leitura pelo TursoSLOs e fallback aprovados
T7 — Expansãomais leituras reconstruíveisrevisão explícita por capacidade

Cada ciclo termina com documentação do estado real. “Código escrito” não significa “ativo em produção”.

Provisionamento seguro

O provisionamento pode ser manual no início. Se for automatizado, use a Platform API apenas em tooling administrativo server-side:

  1. confirmar organização e nome do database;
  2. criar o database em região deliberada;
  3. gerar token de database separado;
  4. salvar URL e token em secrets, sem imprimir os valores;
  5. aplicar schema idempotente;
  6. executar smoke test sanitizado;
  7. documentar export, restauração e rotação;
  8. revogar tokens temporários.

O endpoint de health da aplicação deve retornar apenas estado, latência e versão do schema. Não deve retornar URL, token, SQL completo ou dados de teste.

Compatibilidade de schema

Turso/libSQL deriva de SQLite; Supabase usa PostgreSQL. A projeção precisa tratar explicitamente:

TemaDecisão exigida
UUIDrepresentar e validar de forma estável
Booleannormalizar leitura e escrita
DatasUTC e formato canônico
JSONvalidar no adapter, sem assumir operadores PostgreSQL
Decimalevitar perda em preços; definir representação exata
Enumconstraints e validação equivalentes
Ordenaçãodesempate explícito para paginação determinística
Defaultsmaterializar valores importantes no pipeline
FKs/cascatastestar comportamento, não presumir equivalência

O schema Turso é uma materialização da capacidade de catálogo, não uma cópia automática de todas as migrations Supabase.

Alimentação da projeção

Comece com um backfill repetível:

  1. ler um snapshot consistente da origem;
  2. transformar para o DTO canônico;
  3. fazer upsert idempotente no Turso;
  4. comparar contagem, IDs e hashes de conteúdo;
  5. registrar cursor e versão do pipeline.

Para atualização contínua, prefira outbox/eventos versionados. Dual-write síncrono dentro de handlers existentes só deve ser considerado com semântica de falha, replay e reconciliação já definida.

Shadow read

No modo shadow, Supabase ainda produz a resposta. Turso é consultado em paralelo ou de forma amostrada, com timeout curto e sem aumentar a latência percebida.

Compare resultados normalizados:

  • encontrou/não encontrou;
  • IDs e versão da projeção;
  • ordenação e paginação;
  • campos relevantes ao usuário;
  • latência por classe;
  • timeout, erro e staleness.

Não registre payloads completos ou PII. Uma divergência deve ter categoria acionável: atraso de projeção, transformação, ordenação, dado ausente ou falha do provider.

Fallback e consistência

O routing é explícito por operação:

DB_CATALOG_READ_PROVIDER=supabase | turso
DB_CATALOG_SHADOW_PROVIDER=off | turso

Esses nomes são uma proposta de contrato de configuração, não evidência de que as flags já existem. Fallback automático é aceitável para leitura reconstruível quando a resposta velha tem política definida. Para escrita transacional, fallback silencioso pode criar dois escritores e é proibido.

Suíte de conformidade

O adapter Turso só entra em canário se passar pelos mesmos casos do adapter Supabase:

  • produto existente e ausente;
  • apenas itens ativos/publicáveis;
  • filtros combinados;
  • paginação estável com desempate;
  • Unicode, nulos e campos opcionais;
  • preços sem perda de precisão;
  • timeout e erro traduzidos para erros do domínio;
  • schema incompatível detectado no startup/health;
  • resultado sem campos privados.

Critérios de promoção

  • divergência abaixo do limite acordado e explicada;
  • p95 dentro do SLO por região/runtime;
  • backfill e atualização incremental recuperáveis;
  • export e restauração exercitados;
  • rotação de token documentada;
  • custo/quota observáveis;
  • rollback por flag testado;
  • zero dependência do SDK Turso fora do adapter/composition root.

Planos e limites comerciais mudam. Antes de produção, confirme quotas atuais, política de suspensão, backups, regiões e custos na documentação oficial. O free tier serve para validar a arquitetura, não para definir o SLO do produto.

Fora do escopo inicial

  • substituir Supabase Auth;
  • transportar imagens ou outros binários no banco;
  • migrar pedidos, pagamentos ou documentos fiscais;
  • usar o token da Platform API no runtime normal;
  • manter escrita ativa em Supabase e Turso sem outbox/reconciliação;
  • introduzir uma API CRUD genérica em src/pages/api/db.

Leitura relacionada