Communitygithub.com

Patrones de Turso/libSQL

Base de datos SQLite alojada en el edge con driver libSQL y réplicas integradas

¿Qué es libSQL?

Esta skill proporciona una guía completa para usar Turso (libSQL) como base de datos SQLite alojada en el edge, con soporte para réplicas integradas que sincronizan desde una base de datos primaria remota. Está diseñada para desarrolladores que trabajan en runtimes Node.js, edge (Cloudflare Workers, Vercel Edge Functions) o serverless. La skill enfatiza el uso de `batch()` sobre transacciones interactivas para operaciones atómicas de múltiples sentencias, ya que reduce viajes de ida y vuelta y evita bloqueos prolongados. También cubre patrones clave como la configuración del cliente con `createClient()`, ejecución de consultas parametrizadas, selección de rutas de importación (`@libsql/client` para Node.js vs. `@libsql/client/web` para edge), y configuración de réplicas integradas con `file:` URL + `syncUrl` para lecturas de latencia cero. Además, incluye un marco de decisión para elegir entre `batch()` y `transaction()`, y una lista de banderas rojas sobre errores comunes, como omitir el modo de transacción o usar la ruta de importación incorrecta en entornos edge.

Compatible con✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/agents-inc/skills/tree/HEAD/src/skills/api-baas-turso

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Patrones de Turso / libSQL

Guía rápida: Usa @libsql/client para todo acceso a bases de datos Turso. Usa execute() para consultas individuales, batch() para operaciones atómicas de múltiples sentencias (preferido sobre transacciones interactivas), y transaction() solo cuando consultas posteriores dependan de resultados anteriores. Para runtimes edge/serverless sin acceso al sistema de archivos, importa desde @libsql/client/web. Para lecturas de latencia cero, configura réplicas integradas con una URL de archivo local + syncUrl. Todas las escrituras se reenvían a la base de datos primaria: diseña para una latencia de escritura de 15-50 ms. Turso es SQLite bajo el capó: modelo de un solo escritor, no ALTER TABLE ... ADD CONSTRAINT, sin procedimientos almacenados.


<critical_requirements>

CRÍTICO: Antes de Usar Esta Skill

Todo el código debe seguir las convenciones del proyecto en CLAUDE.md (kebab-case, exports nombrados, orden de imports, import type, constantes nombradas)

(DEBES usar batch() con un modo de transacción para operaciones atómicas de múltiples sentencias: es más rápido y seguro que transaction() interactivo porque se ejecuta en un solo viaje de ida y vuelta)

(DEBES importar desde @libsql/client/web en runtimes edge/serverless que carecen de acceso al sistema de archivos (Cloudflare Workers, Vercel Edge Functions, etc.) — la importación base de @libsql/client incluye bindings nativos que fallan en estos entornos)

(DEBES especificar un modo de transacción ("write", "read" o "deferred") como segundo argumento en batch() y transaction() — el valor predeterminado es "deferred", que falla silenciosamente al adquirir un bloqueo de escritura para INSERT/UPDATE/DELETE)

(DEBES llamar a client.close() cuando el cliente ya no sea necesario en procesos de corta duración — los clientes abiertos mantienen conexiones y manejadores de archivos)

(NO DEBES acceder directamente al archivo de base de datos de réplica integrada local mientras el cliente está en ejecución — el acceso concurrente causa corrupción de datos)

</critical_requirements>


Detección automática: Turso, libSQL, @libsql/client, createClient, turso.io, réplica integrada, syncUrl, syncInterval, turso db, turso group, libsql, .turso.io, TURSO_DATABASE_URL, TURSO_AUTH_TOKEN

Cuándo usar:

  • Consultar una base de datos SQLite alojada en Turso desde cualquier runtime (Node.js, edge, serverless)
  • Configurar réplicas integradas para lecturas locales de latencia cero sincronizadas desde una base de datos primaria remota
  • SaaS multiinquilino con base de datos por inquilino (Turso soporta millones de bases de datos)
  • Funciones serverless/edge que necesitan una base de datos sin la complejidad del pool de conexiones
  • Ejecutar operaciones atómicas de múltiples sentencias con batch() o transacción interactiva transaction()
  • Gestionar grupos de bases de datos y colocación multirregión a través de la CLI de Turso

Cuándo NO usar:

  • Cargas de trabajo intensivas en escritura que requieren consistencia fuerte de múltiples escritores (Turso es de un solo escritor, las escrituras se reenvían a la base de datos primaria)
  • Consultas relacionales complejas que necesitan características de PostgreSQL (CTEs con subconsultas mutantes, procedimientos almacenados, restricciones avanzadas)
  • Transacciones distribuidas complejas a través de múltiples bases de datos
  • Grandes conjuntos de datos analíticos (se aplican las limitaciones de tamaño de fila y concurrencia de SQLite)

Recursos detallados:

  • examples/core.md — Configuración del cliente, ejecutar, batch, transacciones, rutas de importación
  • examples/embedded-replicas.md — Réplicas locales, sincronización, modo sin conexión, cifrado
  • reference.md — Marcos de decisión, definiciones de tipos, comandos CLI, tablas de referencia

Filosofía

Turso lleva SQLite al edge alojando libSQL (un fork de SQLite) como un servicio gestionado con replicación multirregión. El driver @libsql/client proporciona una API unificada que funciona de manera idéntica ya sea que te conectes a una base de datos Turso remota, un archivo SQLite local, una base de datos en memoria o una réplica integrada que sincroniza desde una base de datos primaria remota.

Principios fundamentales:

  1. Batch sobre transacción — batch() envía todas las sentencias en un solo viaje de ida y vuelta y las ejecuta en una transacción implícita. La transacción interactiva transaction() requiere múltiples viajes de ida y vuelta y mantiene un bloqueo de base de datos (tiempo de espera de 5 segundos). Usa batch() a menos que necesites lógica condicional entre consultas.
  2. Las escrituras siempre van a la base de datos primaria — Incluso con réplicas integradas, las escrituras se reenvían a la base de datos primaria remota. La latencia de escritura es de 15-50 ms dependiendo de la distancia a la región primaria. Diseña para esto: UI optimista, sincronización en segundo plano, evita rutas críticas intensivas en escritura.
  3. Réplicas integradas para lecturas — Un archivo SQLite local sincronizado desde la base de datos primaria remota. Las lecturas son a nivel de microsegundos. Las escrituras se reenvían a la remota. El archivo local se actualiza después de una escritura exitosa (semántica de leer-tus-propias-escrituras).
  4. Dos rutas de importación — @libsql/client incluye bindings nativos de SQLite para Node.js y soporta URLs file:. @libsql/client/web es JS/WASM puro para runtimes edge (Cloudflare Workers, Vercel Edge Functions) y no puede abrir archivos locales.
  5. Semántica de SQLite — Turso es SQLite. No ADD CONSTRAINT, sin procedimientos almacenados, sin LISTEN/NOTIFY, modo WAL de un solo escritor. Conoce las limitaciones de SQLite antes de elegir Turso.

Patrones Principales

Patrón 1: Configuración del Cliente

Crea un cliente con createClient(). La url determina el tipo de conexión: libsql:// para Turso remoto, file: para SQLite local (solo Node.js), :memory: para en memoria (pruebas). Siempre usa variables de entorno para authToken — nunca codifiques credenciales.

Consulta examples/core.md para patrones completos de configuración, incluidos módulos singleton y malos ejemplos.


Patrón 2: Ejecutar Consultas

execute() ejecuta una sola sentencia SQL. Siempre usa consultas parametrizadas con args — nunca interpolación de cadenas.

// Posicional: args como array
await client.execute({
  sql: "SELECT * FROM users WHERE id = ?",
  args: [userId],
});

// Nombrado: args como objeto (los nombres simples coinciden con :name, @name, $name en SQL)
await client.execute({
  sql: "INSERT INTO users (name, email) VALUES (:name, :email)",
  args: { name, email },
});

Devuelve ResultSet con rows (Array<Row>), columns, rowsAffected, lastInsertRowid (bigint). Consulta examples/core.md para mapeo de resultados tipados y malos ejemplos.


Patrón 3: Operaciones Batch

batch() ejecuta múltiples sentencias de forma atómica en un solo viaje de ida y vuelta. Todas tienen éxito o todas se revierten. Siempre especifica el modo de transacción 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", // Requerido para INSERT/UPDATE/DELETE
);

Usa "write" para mutaciones, "read" para solo SELECT (permite ejecución paralela), "deferred" para comenzar como solo lectura y escalar. last_insert_rowid() funciona a través de sentencias en el mismo batch. Consulta examples/core.md para patrones de inserción múltiple y batch de solo lectura, y reference.md para la tabla comparativa de modos de transacción.


Patrón 4: Transacciones Interactivas

Usa transaction() solo cuando consultas posteriores dependan de los resultados de consultas anteriores. Mantiene un bloqueo de base de datos (tiempo de espera de 5 segundos inactivo) y requiere múltiples viajes de ida y vuelta. Siempre usa try/catch/finally con 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 basada en resultados ...
  await tx.commit();
} catch (error) {
  await tx.rollback();
  throw error;
} finally {
  tx.close();
}

Si todas las sentencias se conocen de antemano sin lógica condicional, usa batch() en su lugar. Consulta examples/core.md para un ejemplo completo de compra con verificación de stock.


Patrón 5: Rutas de Importación para Diferentes Runtimes

// Node.js, Bun, Deno (tiene acceso al sistema de archivos, bindings nativos)
import { createClient } from "@libsql/client";

// Runtimes edge/serverless SIN sistema de archivos (Cloudflare Workers, Vercel Edge)
import { createClient } from "@libsql/client/web";

El paquete base @libsql/client incluye bindings nativos de SQLite que fallan en runtimes edge. @libsql/client/web es JS/WASM puro pero no puede abrir URLs file: locales. Consulta examples/core.md para un ejemplo completo de Cloudflare Worker.


Patrón 6: Réplicas Integradas

Un archivo SQLite local que sincroniza desde una base de datos primaria Turso remota. Las lecturas son locales (microsegundos), las escrituras se reenvían a la remota (15-50 ms).

const client = createClient({
  url: "file:local-replica.db",
  syncUrl: process.env.TURSO_DATABASE_URL,
  authToken: process.env.TURSO_AUTH_TOKEN,
  syncInterval: 60, // Sincronización automática cada 60 segundos
});
await client.sync(); // Poblar réplica local antes de la primera lectura

Puntos clave: URL file: para réplica local, syncUrl para base de datos primaria remota, llama a sync() al inicio, las lecturas son locales, las escrituras se reenvían a la remota con semántica de leer-tus-propias-escrituras.

Cuándo usar: VMs, VPS, contenedores, o cualquier proceso de larga duración con acceso al sistema de archivos. No disponible en runtimes serverless/edge sin sistema de archivos. Consulta examples/embedded-replicas.md para patrones completos que incluyen sincronización manual, modo sin conexión y cifrado.


Patrón 7: Grupos de Bases de Datos y Multirregión

Turso organiza las bases de datos en grupos. Cada grupo tiene una región primaria y ubicaciones de réplica opcionales. Todas las bases de datos en un grupo heredan sus ubicaciones.

Todas las escrituras van a la región primaria, independientemente de qué réplica maneje la lectura. Agregar más ubicaciones mejora la latencia de lectura a nivel global pero no reduce la latencia de escritura. La latencia de escritura está determinada por la distancia a la región primaria.

Consulta reference.md para la referencia completa de comandos CLI de Turso (turso db create, turso group create, turso db shell, etc.).


<decision_framework>

Marco de Decisión

batch() vs transaction()

¿Se conocen todas las sentencias SQL de antemano (sin lógica condicional entre ellas)?
+-- SÍ --> Usa batch() (un solo viaje de ida y vuelta, transacción implícita, preferido)
+-- NO  --> ¿Las sentencias posteriores dependen de los resultados de sentencias anteriores?
    +-- SÍ --> Usa transaction() (interactiva, múltiples viajes de ida y vuelta, mantiene bloqueo)
    +-- NO  --> Usa batch()

Selección de Ruta de Importación

¿Qué entorno de runtime?
+-- Node.js / Bun / Deno / VM / contenedor
|   +-- ¿Necesitas réplicas integradas (archivo local)?
|   |   +-- SÍ --> @libsql/client con URL file: + syncUrl
|   |   +-- NO  --> @libsql/client con URL libsql://
+-- Cloudflare Workers / Vercel Edge / navegador / serverless sin sistema de archivos
    +-- @libsql/client/web (solo conexiones remotas, sin URLs file:)

Réplica Integrada vs Solo Remota

¿El proceso es de larga duración con acceso al sistema de archivos?
+-- SÍ --> ¿Necesitas latencia de lectura de sub-milisegundo?
|   +-- SÍ --> Réplica integrada (URL file: + syncUrl)
|   +-- NO  --> Solo remota es más simple (URL libsql://)
+-- NO (serverless, edge, corta duración)
    +-- Solo remota (@libsql/client/web, URL libsql://)

Selección del Modo de Transacción

¿Qué operaciones realizará el batch/transacción?
+-- Solo consultas SELECT --> "read" (permite ejecución paralela en réplicas)
+-- Cualquier INSERT / UPDATE / DELETE --> "write" (adquiere bloqueo exclusivo)
+-- Inseguro en el momento de la llamada --> "deferred" (comienza como lectura, escala si es necesario)

</decision_framework>


<red_flags>

BANDERAS ROJAS

Problemas de Alta Prioridad:

  • Falta del modo de transacción en batch()/transaction() — Omitir el segundo argumento por defecto usa "deferred", que comienza como solo lectura y puede fallar silenciosamente al adquirir un bloqueo de escritura para INSERT/UPDATE/DELETE. Siempre especifica el modo explícitamente.
  • Usar @libsql/client en runtimes edge — El paquete base incluye bindings nativos de SQLite que fallan en Cloudflare Workers, Vercel Edge Functions y entornos similares. Usa @libsql/client/web en su lugar.
  • Interpolación de cadenas en SQL — execute(\SELECT * FROM users WHERE id = '${id}'`)es una vulnerabilidad de inyección SQL. Siempre usa consultas parametrizadas conargs`.
  • Acceder directamente al archivo de réplica integrada — Abrir el archivo .db local con otro cliente SQLite mientras el cliente libSQL está en ejecución causa corrupción de datos. Solo accede a través del cliente.

Problemas de Prioridad Media:

  • Usar transaction() cuando batch() es suficiente — Las transacciones interactivas mantienen un bloqueo de base de datos con un tiempo de espera de 5 segundos, requieren múltiples viajes de ida y vuelta y bloquean a otros escritores. Usa batch() para conjuntos de sentencias predeterminadas.
  • No llamar a client.close() — En procesos de corta duración (scripts CLI, limpieza de pruebas), olvidar cerrar el cliente deja conexiones y manejadores de archivos abiertos.
  • Ignorar la latencia de escritura con réplicas integradas — Las lecturas son microsegundos (local), pero las escrituras son de 15-50 ms (reenviadas a la base de datos primaria remota). Diseña en consecuencia: evita bucles de escritura ajustados.
  • Establecer syncInterval demasiado bajo — Cada sincronización obtiene todos los marcos cambiados (4KB cada uno). Intervalos de sub-segundo en bases de datos con muchas escrituras generan una sobrecarga significativa de red y E/S.

Errores Comunes:

  • Nombre de paquete incorrecto — El paquete es @libsql/client, no libsql-client, @turso/client o turso-client.
  • Prefijo de parámetro nombrado en el objeto args — Los args usan nombres simples: { name: "Alice" } coincide con :name, @name y $name en SQL. No incluyas el prefijo: { ":name": "Alice" } no coincidirá.
  • Esperar que lastInsertRowid sea un número — Es bigint | undefined. Si necesitas un número, conviértelo explícitamente: Number(result.lastInsertRowid), pero ten en cuenta la pérdida de precisión para rowids muy grandes.
  • Usar executeMultiple() para operaciones atómicas — executeMultiple() ejecuta texto SQL sin formato (separado por punto y coma) sin parametrización y sin transacción implícita. Usa batch() para operaciones parametrizadas atómicas.

Casos Especiales y Excepciones:

  • Las sentencias de batch() comparten una transacción pero NO son paralelas — Se ejecutan secuencialmente. last_insert_rowid() devuelve el rowid de la última sentencia INSERT, no de todas.
  • Los resultados de batch() pueden devolverse en orden diferente — Mientras que las sentencias se ejecutan en orden, para el modo "read" el motor puede ejecutarlas en paralelo. Siempre asume que los resultados corresponden al orden de las sentencias de entrada (Turso garantiza esto para los modos "write" y "deferred", y para "read" con el optimizador actual).
  • Las réplicas integradas con syncInterval tienen consistencia eventual — Una escritura desde el cliente A puede no ser visible para el cliente B hasta la próxima sincronización. Usa sync() manual después de escrituras críticas o usa la URL remota libsql:// cuando necesites lecturas fuertemente consistentes para una operación específica.

</red_flags>

Individual skills in this repo

This repo contains 11 individual skills — each has its own dedicated page.

Skills relacionados