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
plannedimplementados emsrc/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 faltamTURSO_DATABASE_URLeTURSO_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:
- o domínio consegue depender de
CatalogReadStore, não do SDK Supabase; - o mesmo contrato funciona com Supabase e Turso;
- 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ável | Escopo | Uso |
|---|---|---|
TURSO_API_TOKEN | organização/plataforma | criar e administrar databases pela Platform API |
TURSO_DATABASE_URL | database | endpoint libsql://... ou URL compatível |
TURSO_AUTH_TOKEN | database | autenticar 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.
Por que começar por catálogo
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
| Ciclo | Entrega | Gate para avançar |
|---|---|---|
| T0 — Fronteira | inventário das consultas do catálogo | queries e semântica documentadas |
| T1 — Contrato | CatalogReadStore + DTOs | testes unitários sem Supabase SDK |
| T2 — Adapter atual | Supabase atrás do contrato | comportamento atual preservado |
| T3 — Provisionamento | database e credencial mínima | backup/export e rotação testados |
| T4 — Schema/projeção | tabelas libSQL e backfill | contagem, checksum e amostras equivalentes |
| T5 — Shadow | consulta Turso fora do caminho de resposta | divergência e latência observáveis |
| T6 — Canário | pequena parcela de leitura pelo Turso | SLOs e fallback aprovados |
| T7 — Expansão | mais leituras reconstruíveis | revisã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:
- confirmar organização e nome do database;
- criar o database em região deliberada;
- gerar token de database separado;
- salvar URL e token em secrets, sem imprimir os valores;
- aplicar schema idempotente;
- executar smoke test sanitizado;
- documentar export, restauração e rotação;
- 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:
| Tema | Decisão exigida |
|---|---|
| UUID | representar e validar de forma estável |
| Boolean | normalizar leitura e escrita |
| Datas | UTC e formato canônico |
| JSON | validar no adapter, sem assumir operadores PostgreSQL |
| Decimal | evitar perda em preços; definir representação exata |
| Enum | constraints e validação equivalentes |
| Ordenação | desempate explícito para paginação determinística |
| Defaults | materializar valores importantes no pipeline |
| FKs/cascatas | testar 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:
- ler um snapshot consistente da origem;
- transformar para o DTO canônico;
- fazer upsert idempotente no Turso;
- comparar contagem, IDs e hashes de conteúdo;
- 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.