O que libSQL faz?
Guia Rápido: Use
@libsql/clientpara todo acesso a banco de dados Turso. Useexecute()para consultas únicas,batch()para operações atômicas de múltiplas instruções (preferível a transações interativas) etransaction()apenas quando consultas subsequentes dependerem de resultados anteriores. Para runtimes edge/serverless sem acesso ao sistema de arquivos, importe de@libsql/client/web. Para leituras com latência zero, configure réplicas incorporadas com uma URL de arquivo local +syncUrl. Todas as escritas são encaminhadas para o primário -- projete para latência de 15-50ms. Turso é SQLite internamente: modelo de escritor único, semALTER TABLE ... ADD CONSTRAINT, sem procedimentos armazenados.
<critical_requirements>
CRÍTICO: Antes de Usar Este Skill
Todo código deve seguir as convenções do projeto em CLAUDE.md (kebab-case, named exports, ordenação de importações,
import type, constantes nomeadas)
(Você DEVE usar batch() com um modo de transação para operações atômicas de múltiplas instruções -- é mais rápido e seguro que transaction() interativa, pois executa em uma única viagem de ida e volta)
(Você DEVE importar de @libsql/client/web em runtimes edge/serverless sem acesso ao sistema de arquivos (Cloudflare Workers, Vercel Edge Functions) -- a importação base @libsql/client puxa ligações nativas que falham nesses ambientes)
(Você DEVE especificar um modo de transação ("write", "read" ou "deferred") como segundo argumento para batch() e transaction() -- o padrão é "deferred", que silenciosamente falha ao adquirir um bloqueio de escrita para INSERT/UPDATE/DELETE)
(Você DEVE chamar client.close() quando o cliente não for mais necessário em processos de curta duração -- clientes abertos mantêm conexões e manipuladores de arquivos)
(Você NÃO DEVE acessar o arquivo de banco de dados da réplica incorporada diretamente enquanto o cliente estiver em execução -- acesso concorrente causa corrupção de dados)
</critical_requirements>
Detecção Automática: Turso, libSQL, @libsql/client, createClient, turso.io, réplica incorporada, syncUrl, syncInterval, turso db, turso group, libsql, .turso.io, TURSO_DATABASE_URL, TURSO_AUTH_TOKEN
Quando usar:
- Consultar um banco de dados SQLite hospedado no Turso de qualquer runtime (Node.js, edge, serverless)
- Configurar réplicas incorporadas para leituras locais com latência zero sincronizadas a partir de um primário remoto
- SaaS multi-inquilino com banco de dados por inquilino (Turso suporta milhões de bancos de dados)
- Funções serverless/edge que precisam de um banco de dados sem complexidade de pool de conexões
- Executar operações atômicas de múltiplas instruções com
batch()outransaction()interativa - Gerenciar grupos de banco de dados e posicionamento multirregião via CLI do Turso
Quando NÃO usar:
- Cargas de trabalho pesadas de escrita que exigem consistência forte de múltiplos escritores (Turso é escritor único, escritas encaminhadas para o primário)
- Consultas relacionais complexas que precisam de recursos do PostgreSQL (CTEs com subconsultas mutantes, procedimentos armazenados, restrições avançadas)
- Transações distribuídas complexas em vários bancos de dados
- Grandes conjuntos de dados analíticos (limitações de tamanho de linha e concorrência do SQLite se aplicam)
Recursos Detalhados:
- examples/core.md -- Configuração do cliente, execute, batch, transações, caminhos de importação
- examples/embedded-replicas.md -- Réplicas locais, sincronização, modo offline, criptografia
- reference.md -- Frameworks de decisão, definições de tipos, comandos CLI, tabelas de consulta
Filosofia
Turso leva SQLite para a borda hospedando libSQL (um fork do SQLite) como um serviço gerenciado com replicação multirregião. O driver @libsql/client fornece uma API unificada que funciona de forma idêntica, seja conectando a um banco de dados Turso remoto, a um arquivo SQLite local, a um banco de dados em memória ou a uma réplica incorporada que sincroniza a partir de um primário remoto.
Princípios fundamentais:
- Batch sobre transação --
batch()envia todas as instruções em uma única viagem de ida e volta e as executa em uma transação implícita.transaction()interativa requer várias viagens de ida e volta e mantém um bloqueio de banco de dados (timeout de 5 segundos). Usebatch()a menos que precise de lógica condicional entre consultas. - Escritas sempre vão para o primário -- Mesmo com réplicas incorporadas, as escritas são encaminhadas para o banco de dados primário remoto. A latência de escrita é de 15 a 50 ms, dependendo da distância até a região primária. Projete para isso: UI otimista, sincronização em segundo plano, evite caminhos críticos de muitas escritas.
- Réplicas incorporadas para leituras -- Um arquivo SQLite local sincronizado a partir do primário remoto. As leituras são em nível de microssegundos. As escritas encaminham para o remoto. O arquivo local é atualizado após uma escrita bem-sucedida (semântica leia-suas-próprias-escritas).
- Dois caminhos de importação --
@libsql/clientinclui ligações SQLite nativas para Node.js e suporta URLsfile:.@libsql/client/webé JS/WASM puro para runtimes edge (Cloudflare Workers, Vercel Edge Functions) e não pode abrir arquivos locais. - Semântica SQLite -- Turso é SQLite. Sem
ADD CONSTRAINT, sem procedimentos armazenados, semLISTEN/NOTIFY, modo WAL de escritor único. Conheça as limitações do SQLite antes de escolher Turso.
Padrões Principais
Padrão 1: Configuração do Cliente
Crie um cliente com createClient(). A url determina o tipo de conexão: libsql:// para Turso remoto, file: para SQLite local (apenas Node.js), :memory: para em memória (testes). Sempre use variáveis de ambiente para authToken -- nunca codifique credenciais.
Veja examples/core.md para padrões completos de configuração, incluindo módulos singleton e exemplos ruins.
Padrão 2: Executando Consultas
execute() executa uma única instrução SQL. Sempre use consultas parametrizadas com args -- nunca interpolação de strings.
// Posicional: args como array
await client.execute({
sql: "SELECT * FROM users WHERE id = ?",
args: [userId],
});
// Nomeado: args como objeto (nomes sem prefixo correspondem a :name, @name, $name no SQL)
await client.execute({
sql: "INSERT INTO users (name, email) VALUES (:name, :email)",
args: { name, email },
});
Retorna ResultSet com rows (Array<Row>), columns, rowsAffected, lastInsertRowid (bigint). Veja examples/core.md para mapeamento de resultados tipados e exemplos ruins.
Padrão 3: Operações Batch
batch() executa várias instruções atomicamente em uma única viagem de ida e volta. Todas bem-sucedidas ou todas revertidas. Sempre especifique o modo de transação como segundo argumento.
const results = await client.batch(
[
{ sql: "INSERT INTO users (name) VALUES (?)", args: ["Alice"] },
{
sql: "INSERT INTO audit_log (action, entity_id) VALUES (?, last_insert_rowid())",
args: ["user_created"],
},
],
"write", // Obrigatório para INSERT/UPDATE/DELETE
);
Use "write" para mutações, "read" para apenas SELECT (permite execução paralela), "deferred" para iniciar somente leitura e escalar se necessário. last_insert_rowid() funciona entre instruções no mesmo batch. Veja examples/core.md para padrões de multi-insert e batch somente leitura, e reference.md para a tabela de comparação de modos de transação.
Padrão 4: Transações Interativas
Use transaction() apenas quando consultas subsequentes dependerem de resultados de consultas anteriores. Ela mantém um bloqueio de banco de dados (timeout ocioso de 5 segundos) e requer várias viagens de ida e volta. Sempre use try/catch/finally com close().
const tx = await client.transaction("write");
try {
const { rows } = await tx.execute({
sql: "SELECT balance FROM accounts WHERE id = ?",
args: [fromId],
});
// ... lógica condicional baseada nos resultados ...
await tx.commit();
} catch (error) {
await tx.rollback();
throw error;
} finally {
tx.close();
}
Se todas as instruções forem conhecidas antecipadamente sem lógica condicional, use batch() em vez disso. Veja examples/core.md para um exemplo completo de compra com verificação de estoque.
Padrão 5: Caminhos de Importação para Diferentes Runtimes
// Node.js, Bun, Deno (tem acesso ao sistema de arquivos, ligações nativas)
import { createClient } from "@libsql/client";
// Runtimes Edge/Serverless SEM sistema de arquivos (Cloudflare Workers, Vercel Edge)
import { createClient } from "@libsql/client/web";
O @libsql/client base inclui ligações SQLite nativas que falham em runtimes edge. @libsql/client/web é JS/WASM puro, mas não pode abrir URLs file: locais. Veja examples/core.md para um exemplo completo de Cloudflare Worker.
Padrão 6: Réplicas Incorporadas
Um arquivo SQLite local que sincroniza a partir de um primário Turso remoto. Leituras são locais (microssegundos), escritas encaminham para o remoto (15-50ms).
const client = createClient({
url: "file:local-replica.db",
syncUrl: process.env.TURSO_DATABASE_URL,
authToken: process.env.TURSO_AUTH_TOKEN,
syncInterval: 60, // Sincronização automática a cada 60 segundos
});
await client.sync(); // Popular a réplica local antes da primeira leitura
Pontos-chave: URL file: para a réplica local, syncUrl para o primário remoto, chame sync() na inicialização, leituras são locais, escritas encaminham para o remoto com semântica leia-suas-próprias-escritas.
Quando usar: VMs, VPS, containers ou qualquer processo de longa duração com acesso ao sistema de arquivos. Não disponível em runtimes serverless/edge sem sistema de arquivos. Veja examples/embedded-replicas.md para padrões completos, incluindo sincronização manual, modo offline e criptografia.
Padrão 7: Grupos de Banco de Dados e Multirregião
Turso organiza bancos de dados em grupos. Cada grupo tem uma região primária e locais de réplica opcionais. Todos os bancos de dados em um grupo herdam seus locais.
Todas as escritas vão para a região primária, independentemente de qual réplica atendeu à leitura. Adicionar mais locais melhora a latência de leitura globalmente, mas não reduz a latência de escrita. A latência de escrita é determinada pela distância até a região primária.
Veja reference.md para a referência completa de comandos CLI do Turso (turso db create, turso group create, turso db shell, etc.).
<decision_framework>
Framework de Decisão
batch() vs transaction()
Todas as instruções SQL são conhecidas antecipadamente (sem lógica condicional entre elas)?
+-- SIM --> Use batch() (única viagem de ida e volta, transação implícita, preferido)
+-- NÃO --> Instruções posteriores dependem de resultados de instruções anteriores?
+-- SIM --> Use transaction() (interativa, várias viagens de ida e volta, mantém bloqueio)
+-- NÃO --> Use batch()
Seleção de Caminho de Importação
Qual ambiente de runtime?
+-- Node.js / Bun / Deno / VM / container
| +-- Precisa de réplicas incorporadas (arquivo local)?
| | +-- SIM --> @libsql/client com URL file: + syncUrl
| | +-- NÃO --> @libsql/client com URL libsql://
+-- Cloudflare Workers / Vercel Edge / navegador / serverless sem sistema de arquivos
+-- @libsql/client/web (apenas conexões remotas, sem URLs file:)
Réplica Incorporada vs Apenas Remoto
O processo é de longa duração com acesso ao sistema de arquivos?
+-- SIM --> Precisa de latência de leitura abaixo de milissegundo?
| +-- SIM --> Réplica incorporada (URL file: + syncUrl)
| +-- NÃO --> Apenas remoto é mais simples (URL libsql://)
+-- NÃO (serverless, edge, curta duração)
+-- Apenas remoto (@libsql/client/web, URL libsql://)
Seleção de Modo de Transação
Quais operações o batch/transação executará?
+-- Apenas consultas SELECT --> "read" (permite execução paralela em réplicas)
+-- Qualquer INSERT / UPDATE / DELETE --> "write" (adquire bloqueio exclusivo)
+-- Incerteza no momento da chamada --> "deferred" (inicia leitura, escala se necessário)
</decision_framework>
<red_flags>
SINALIZADORES VERMELHOS
Problemas de Alta Prioridade:
- Modo de transação ausente em
batch()/transaction()-- Omitir o segundo argumento usa como padrão"deferred", que inicia somente leitura e pode silenciosamente falhar ao adquirir um bloqueio de escrita para INSERT/UPDATE/DELETE. Sempre especifique o modo explicitamente. - Usando
@libsql/clientem runtimes edge -- O pacote base inclui ligações SQLite nativas que falham em Cloudflare Workers, Vercel Edge Functions e ambientes semelhantes. Use@libsql/client/webem vez disso. - Interpolação de strings em SQL --
execute(\SELECT * FROM users WHERE id = '${id}'`)é uma vulnerabilidade de injeção SQL. Sempre use consultas parametrizadas comargs`. - Acessando o arquivo da réplica incorporada diretamente -- Abrir o arquivo
.dblocal com outro cliente SQLite enquanto o cliente libSQL está em execução causa corrupção de dados. Acesse apenas através do cliente.
Problemas de Prioridade Média:
- Usando
transaction()quandobatch()é suficiente -- Transações interativas mantêm um bloqueio de banco de dados com timeout de 5 segundos, requerem várias viagens de ida e volta e bloqueiam outros escritores. Usebatch()para conjuntos de instruções predeterminados. - Não chamar
client.close()-- Em processos de curta duração (scripts CLI, desmontagem de teste), esquecer de fechar o cliente deixa conexões e manipuladores de arquivos abertos. - Ignorando a latência de escrita com réplicas incorporadas -- Leituras são em microssegundos (locais), mas escritas são de 15 a 50 ms (encaminhadas para o primário remoto). Projete adequadamente -- evite loops de escrita apertados.
- Definindo
syncIntervalmuito baixo -- Cada sincronização puxa todos os frames alterados (4KB cada). Intervalos abaixo de 1 segundo em bancos de dados com muitas escritas geram sobrecarga significativa de rede e E/S.
Erros Comuns:
- Nome do pacote errado -- O pacote é
@libsql/client, nãolibsql-client,@turso/clientouturso-client. - Prefixo de parâmetro nomeado no objeto args -- Args usam nomes sem prefixo:
{ name: "Alice" }corresponde a:name,@namee$nameno SQL. Não inclua o prefixo:{ ":name": "Alice" }não corresponderá. - Esperar que
lastInsertRowidseja um número -- Ébigint | undefined. Se precisar de um número, converta explicitamente:Number(result.lastInsertRowid), mas esteja ciente da perda de precisão para rowids muito grandes. - Usando
executeMultiple()para operações atômicas --executeMultiple()executa texto SQL bruto (separado por ponto e vírgula) sem parametrização e sem transação implícita. Usebatch()para operações parametrizadas atômicas.
Armadilhas e Casos Extremos:
- Instruções
batch()compartilham uma transação, mas NÃO são paralelas -- Elas executam sequencialmente.last_insert_rowid()reflete a instrução INSERT anterior dentro do mesmo batch.
[O conteúdo continua com a seção truncada...]