Communitygithub.com

agents-inc/skills

Edge-gehostete SQLite-Datenbank mit libSQL-Treiber und eingebetteten Replikaten

Was ist skills?

Dieses Skill beschreibt die Architektur- und Nutzungsmuster für den Zugriff auf Turso/libSQL-Datenbanken aus verschiedenen Laufzeitumgebungen, einschließlich Node.js, Edge-Funktionen und serverlosen Umgebungen. Es werden die vier zentralen `@libsql/client`-API-Methoden (`execute()`, `batch()`, `transaction()`, `sync()`) sowie die korrekte Importpfad-Wahl (`@libsql/client` vs. `@libsql/client/web`) detailliert erläutert. Das Skill hebt hervor, dass `batch()` mit explizitem Transaktionsmodus (`"write"`, `"read"`, `"deferred"`) für atomare Multi-Statement-Operationen gegenüber interaktiven `transaction()`-Aufrufen zu bevorzugen ist, da es in einem einzigen Roundtrip ausgeführt wird und nicht unnötig einen Datenbank-Lock hält. Es behandelt außerdem das Setup eingebetteter Replikate über `file:`-URL und `syncUrl` für Lesevorgänge im Mikrosekunden-Bereich, wobei alle Schreibvorgänge an das primäre Remote-Database weitergeleitet werden (15–50 ms Latenz). Das Skill unterstützt die Plattformen Node.js, Bun, Deno, Cloudflare Workers, Vercel Edge Functions und andere serverlose/Edge-Runtimes und ist für Multi-Tenant-SaaS-Anwendungen mit Datenbank-pro-Mandant sowie für Datenbank-Gruppen mit Multi-Region-Platzierung optimiert.

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

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

Turso / libSQL Patterns

Kurzanleitung: Verwenden Sie @libsql/client für den gesamten Turso-Datenbankzugriff. Nutzen Sie execute() für einzelne Abfragen, batch() für atomare Multi-Statement-Operationen (bevorzugt gegenüber interaktiven Transaktionen) und transaction() nur dann, wenn nachfolgende Abfragen von vorherigen Ergebnissen abhängen. Für Edge-/Serverless-Runtimes ohne Dateisystemzugriff importieren Sie von @libsql/client/web. Konfigurieren Sie für Lesevorgänge ohne Latenz eingebettete Replikate mit einer lokalen Datei-URL + syncUrl. Alle Schreibvorgänge werden an die primäre Datenbank weitergeleitet – planen Sie für 15–50 ms Schreiblatenz. Turso ist unter der Haube SQLite: Single-Writer-Modell, kein ALTER TABLE ... ADD CONSTRAINT, keine gespeicherten Prozeduren.


<critical_requirements>

KRITISCH: Vor der Nutzung dieses Skills

Der gesamte Code muss den Projektkonventionen in CLAUDE.md folgen (kebab-case, benannte Exporte, Import-Reihenfolge, import type, benannte Konstanten)

(Sie MÜSSEN batch() mit einem Transaktionsmodus für atomare Multi-Statement-Operationen verwenden – es ist schneller und sicherer als interaktives transaction(), da es in einem einzigen Roundtrip ausgeführt wird)

(Sie MÜSSEN in Edge-/Serverless-Runtimes ohne Dateisystemzugriff (Cloudflare Workers, Vercel Edge Functions) von @libsql/client/web importieren – der Basissimport @libsql/client zieht native Bindungen, die in diesen Umgebungen fehlschlagen)

(Sie MÜSSEN einen Transaktionsmodus ("write", "read" oder "deferred") als zweites Argument für batch() und transaction() angeben – der Standard ist "deferred", der bei INSERT/UPDATE/DELETE stillschweigend keinen Schreib-Lock erwirbt)

(Sie MÜSSEN client.close() aufrufen, wenn der Client in kurzlebigen Prozessen nicht mehr benötigt wird – offene Clients halten Verbindungen und Dateihandles)

(Sie DÜRFEN NICHT direkt auf die lokale eingebettete Replikat-Datenbankdatei zugreifen, während der Client läuft – gleichzeitiger Zugriff führt zu Datenkorruption)

</critical_requirements>


Auto-Erkennung: Turso, libSQL, @libsql/client, createClient, turso.io, embedded replica, syncUrl, syncInterval, turso db, turso group, libsql, .turso.io, TURSO_DATABASE_URL, TURSO_AUTH_TOKEN

Wann verwenden:

  • Abfragen einer von Turso gehosteten SQLite-Datenbank aus beliebigen Runtimes (Node.js, Edge, Serverless)
  • Einrichten von eingebetteten Replikaten für latenzfreie lokale Lesevorgänge, die von einem entfernten Primärserver synchronisiert werden
  • Multi-Tenant-SaaS mit einer Datenbank pro Mandant (Turso unterstützt Millionen von Datenbanken)
  • Serverless-/Edge-Funktionen, die eine Datenbank ohne Connection-Pooling-Komplexität benötigen
  • Ausführen von atomaren Multi-Statement-Operationen mit batch() oder interaktivem transaction()
  • Verwalten von Datenbankgruppen und Multi-Region-Platzierung über die Turso-CLI

Wann NICHT verwenden:

  • Schreibintensive Workloads, die starke Multi-Writer-Konsistenz erfordern (Turso ist Single-Writer, Schreibvorgänge werden an den Primärserver weitergeleitet)
  • Komplexe relationale Abfragen, die PostgreSQL-Funktionen benötigen (CTEs mit mutierenden Unterabfragen, gespeicherte Prozeduren, erweiterte Constraints)
  • Komplexe verteilte Transaktionen über mehrere Datenbanken hinweg
  • Große analytische Datensätze (SQLite-Zeilenbegrenzungen und Parallelitätsbeschränkungen gelten)

Detaillierte Ressourcen:


Philosophie

Turso bringt SQLite an den Edge, indem es libSQL (einen Fork von SQLite) als verwalteten Dienst mit Multi-Region-Replikation hostet. Der @libsql/client-Treiber bietet eine einheitliche API, die identisch funktioniert, egal ob Sie eine Verbindung zu einer entfernten Turso-Datenbank, einer lokalen SQLite-Datei, einer In-Memory-Datenbank oder einem eingebetteten Replikat herstellen, das von einem entfernten Primärserver synchronisiert wird.

Kernprinzipien:

  1. Batch vor Transaktion – batch() sendet alle Anweisungen in einem einzigen Roundtrip und führt sie in einer impliziten Transaktion aus. Interaktives transaction() erfordert mehrere Roundtrips und hält einen Datenbank-Lock (5-Sekunden-Timeout). Verwenden Sie batch(), es sei denn, Sie benötigen bedingte Logik zwischen Abfragen.
  2. Schreibvorgänge treffen immer den Primärserver – Selbst bei eingebetteten Replikaten werden Schreibvorgänge an die entfernte primäre Datenbank weitergeleitet. Die Schreiblatenz beträgt 15–50 ms, abhängig von der Entfernung zur Primärregion. Planen Sie entsprechend: optimistische Benutzeroberfläche, Hintergrundsynchronisation, vermeiden Sie schreibintensive Hot Paths.
  3. Eingebettete Replikate für Lesevorgänge – Eine lokale SQLite-Datei, die vom entfernten Primärserver synchronisiert wird. Lesevorgänge sind im Mikrosekundenbereich. Schreibvorgänge werden an den entfernten Server weitergeleitet. Die lokale Datei wird nach einem erfolgreichen Schreibvorgang aktualisiert (Read-your-Writes-Semantik).
  4. Zwei Importpfade – @libsql/client enthält native SQLite-Bindungen für Node.js und unterstützt file:-URLs. @libsql/client/web ist reines JS/WASM für Edge-Runtimes (Cloudflare Workers, Vercel Edge Functions) und kann keine lokalen Dateien öffnen.
  5. SQLite-Semantik – Turso ist SQLite. Kein ADD CONSTRAINT, keine gespeicherten Prozeduren, kein LISTEN/NOTIFY, Single-Writer-WAL-Modus. Kennen Sie die Einschränkungen von SQLite, bevor Sie sich für Turso entscheiden.

Kernmuster

Muster 1: Client-Einrichtung

Erstellen Sie einen Client mit createClient(). Die url bestimmt den Verbindungstyp: libsql:// für entfernte Turso-, file: für lokale SQLite (nur Node.js), :memory: für In-Memory (Tests). Verwenden Sie immer Umgebungsvariablen für authToken – kodieren Sie Anmeldeinformationen niemals fest.

Vollständige Einrichtungsmuster inklusive Singleton-Module und schlechter Beispiele finden Sie in examples/core.md.


Muster 2: Abfragen ausführen

execute() führt eine einzelne SQL-Anweisung aus. Verwenden Sie immer parametrisierte Abfragen mit args – niemals String-Interpolation.

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

// Benannt: args als Objekt (nackte Namen entsprechen :name, @name, $name in SQL)
await client.execute({
  sql: "INSERT INTO users (name, email) VALUES (:name, :email)",
  args: { name, email },
});

Gibt ResultSet zurück mit rows (Array), columns, rowsAffected, lastInsertRowid (bigint). Siehe examples/core.md für typisierte Ergebniszuordnung und schlechte Beispiele.


Muster 3: Batch-Operationen

batch() führt mehrere Anweisungen atomar in einem einzigen Roundtrip aus. Alle erfolgreich oder alle zurückgerollt. Geben Sie immer den Transaktionsmodus als zweites Argument an.

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", // Erforderlich für INSERT/UPDATE/DELETE
);

Verwenden Sie "write" für Mutationen, "read" für reine SELECT-Abfragen (ermöglicht parallele Ausführung), "deferred" zum Starten als schreibgeschützt und Eskalieren bei Bedarf. last_insert_rowid() funktioniert über Anweisungen im selben Batch hinweg. Siehe examples/core.md für Multi-Insert- und Read-Only-Batch-Muster und reference.md für die Vergleichstabelle der Transaktionsmodi.


Muster 4: Interaktive Transaktionen

Verwenden Sie transaction() nur, wenn nachfolgende Abfragen von Ergebnissen vorheriger Abfragen abhängen. Es hält einen Datenbank-Lock (5-Sekunden-Idle-Timeout) und erfordert mehrere Roundtrips. Verwenden Sie immer try/catch/finally mit close().

const tx = await client.transaction("write");
try {
  const { rows } = await tx.execute({
    sql: "SELECT balance FROM accounts WHERE id = ?",
    args: [fromId],
  });
  // ... bedingte Logik basierend auf Ergebnissen ...
  await tx.commit();
} catch (error) {
  await tx.rollback();
  throw error;
} finally {
  tx.close();
}

Wenn alle Anweisungen im Voraus bekannt sind und keine bedingte Logik erforderlich ist, verwenden Sie stattdessen batch(). Siehe examples/core.md für ein vollständiges Beispiel zum Kauf mit Bestandsprüfung.


Muster 5: Importpfade für verschiedene Runtimes

// Node.js, Bun, Deno (hat Dateisystemzugriff, native Bindungen)
import { createClient } from "@libsql/client";

// Edge-/Serverless-Runtimes OHNE Dateisystem (Cloudflare Workers, Vercel Edge)
import { createClient } from "@libsql/client/web";

Der Basissimport @libsql/client bündelt native SQLite-Bindungen, die in Edge-Runtimes fehlschlagen. @libsql/client/web ist reines JS/WASM, kann aber keine lokalen file:-URLs öffnen. Siehe examples/core.md für ein vollständiges Cloudflare-Worker-Beispiel.


Muster 6: Eingebettete Replikate

Eine lokale SQLite-Datei, die von einem entfernten Turso-Primärserver synchronisiert wird. Lesevorgänge sind lokal (Mikrosekunden), Schreibvorgänge werden an den entfernten Server weitergeleitet (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, // Auto-Synchronisation alle 60 Sekunden
});
await client.sync(); // Lokales Replikat vor dem ersten Lesevorgang befüllen

Wichtige Punkte: file:-URL für das lokale Replikat, syncUrl für den entfernten Primärserver, sync() beim Start aufrufen, Lesevorgänge sind lokal, Schreibvorgänge werden an den entfernten Server weitergeleitet mit Read-your-Writes-Semantik.

Wann verwenden: VMs, VPS, Container oder jeder langlebige Prozess mit Dateisystemzugriff. Nicht verfügbar in serverlosen/Edge-Runtimes ohne Dateisystem. Siehe examples/embedded-replicas.md für vollständige Muster inklusive manueller Synchronisation, Offline-Modus und Verschlüsselung.


Muster 7: Datenbankgruppen und Multi-Region

Turso organisiert Datenbanken in Gruppen. Jede Gruppe hat eine Primärregion und optionale Replikatstandorte. Alle Datenbanken in einer Gruppe erben deren Standorte.

Alle Schreibvorgänge gehen an die Primärregion, unabhängig davon, welches Replikat den Lesevorgang bearbeitet. Das Hinzufügen weiterer Standorte verbessert die Leselatenz weltweit, reduziert jedoch nicht die Schreiblatenz. Die Schreiblatenz wird durch die Entfernung zur Primärregion bestimmt.

Siehe reference.md für die vollständige Referenz der Turso-CLI-Befehle (turso db create, turso group create, turso db shell usw.).


<decision_framework>

Entscheidungsrahmen

batch() vs. transaction()

Sind alle SQL-Anweisungen im Voraus bekannt (keine bedingte Logik dazwischen)?
+-- JA --> Verwenden Sie batch() (einzelner Roundtrip, implizite Transaktion, bevorzugt)
+-- NEIN --> Hängen spätere Anweisungen von Ergebnissen früherer Anweisungen ab?
    +-- JA --> Verwenden Sie transaction() (interaktiv, mehrere Roundtrips, hält Lock)
    +-- NEIN --> Verwenden Sie batch()

Auswahl des Importpfads

Welche Laufzeitumgebung?
+-- Node.js / Bun / Deno / VM / Container
|   +-- Benötigen Sie eingebettete Replikate (lokale Datei)?
|   |   +-- JA --> @libsql/client mit file:-URL + syncUrl
|   |   +-- NEIN --> @libsql/client mit libsql://-URL
+-- Cloudflare Workers / Vercel Edge / Browser / Serverless ohne Dateisystem
    +-- @libsql/client/web (nur Remote-Verbindungen, keine file:-URLs)

Eingebettetes Replikat vs. Nur-Remote

Ist der Prozess langlebig mit Dateisystemzugriff?
+-- JA --> Benötigen Sie Lesevorgänge im Sub-Millisekunden-Bereich?
|   +-- JA --> Eingebettetes Replikat (file:-URL + syncUrl)
|   +-- NEIN --> Nur-Remote ist einfacher (libsql://-URL)
+-- NEIN (Serverless, Edge, kurzlebig)
    +-- Nur-Remote (@libsql/client/web, libsql://-URL)

Auswahl des Transaktionsmodus

Welche Operationen führt der Batch/die Transaktion aus?
+-- Nur SELECT-Abfragen --> "read" (ermöglicht parallele Ausführung auf Replikaten)
+-- Irgendwelche INSERT / UPDATE / DELETE --> "write" (erwirbt exklusiven Lock)
+-- Unsicher zum Zeitpunkt des Aufrufs --> "deferred" (startet als schreibgeschützt, eskaliert bei Bedarf)

</decision_framework>


<red_flags>

ROTE FLAGGEN

Probleme mit hoher Priorität:

  • Fehlender Transaktionsmodus in batch()/transaction() – Das Weglassen des zweiten Arguments standardisiert auf "deferred", das als schreibgeschützt startet und möglicherweise stillschweigend keinen Schreib-Lock für INSERT/UPDATE/DELETE erwirbt. Geben Sie den Modus immer explizit an.
  • Verwendung von @libsql/client in Edge-Runtimes – Das Basispaket bündelt native SQLite-Bindungen, die in Cloudflare Workers, Vercel Edge Functions und ähnlichen Umgebungen fehlschlagen. Verwenden Sie stattdessen @libsql/client/web.
  • String-Interpolation in SQL – execute(\SELECT * FROM users WHERE id = '${id}'`)ist eine SQL-Injection-Sicherheitslücke. Verwenden Sie immer parametrisierte Abfragen mitargs`.
  • Direkter Zugriff auf die eingebettete Replikatdatei – Das Öffnen der lokalen .db-Datei mit einem anderen SQLite-Client, während der libSQL-Client läuft, führt zu Datenkorruption. Greifen Sie nur über den Client darauf zu.

Probleme mit mittlerer Priorität:

  • Verwendung von transaction(), wenn batch() ausreicht – Interaktive Transaktionen halten einen Datenbank-Lock mit einem 5-Sekunden-Timeout, erfordern mehrere Roundtrips und blockieren andere Schreiber. Verwenden Sie batch() für vorbestimmte Anweisungssätze.
  • Nicht-Aufruf von client.close() – In kurzlebigen Prozessen (CLI-Skripte, Test-Tear-Down) führt das Vergessen, den Client zu schließen, zu offenen Verbindungen und Dateihandles.
  • Ignorieren der Schreiblatenz bei eingebetteten Replikaten – Lesevorgänge sind Mikrosekunden (lokal), aber Schreibvorgänge dauern 15–50 ms (an den entfernten Primärserver weitergeleitet). Planen Sie entsprechend – vermeiden Sie enge Schreibschleifen.
  • Zu niedriges syncInterval – Jede Synchronisation zieht alle geänderten Frames (je 4KB). Sub-Sekunden-Intervalle bei schreibintensiven Datenbanken verursachen erheblichen Netzwerk- und I/O-Overhead.

Häufige Fehler:

  • Falscher Paketname – Das Paket heißt @libsql/client, nicht libsql-client, @turso/client oder turso-client.
  • Benannter Parameter-Präfix im Args-Objekt – Args verwenden nackte Namen: { name: "Alice" } entspricht :name, @name und $name in SQL. Fügen Sie den Präfix nicht hinzu: { ":name": "Alice" } wird nicht übereinstimmen.
  • Erwartung, dass lastInsertRowid eine Zahl ist – Es ist bigint | undefined. Wenn Sie eine Zahl benötigen, konvertieren Sie explizit: Number(result.lastInsertRowid), aber seien Sie sich des Präzisionsverlusts bei sehr großen Rowids bewusst.
  • Verwendung von executeMultiple() für atomare Operationen – executeMultiple() führt rohen SQL-Text (durch Semikolon getrennt) ohne Parametrisierung und ohne implizite Transaktion aus. Verwenden Sie batch() für atomare parametrisierte Operationen.

Besonderheiten & Grenzfälle:

  • batch()-Anweisungen teilen eine Transaktion, sind aber NICHT parallel – Sie werden sequenziell ausgeführt. last_insert_rowid() funktioniert nur, weil die Anweisungen in derselben Transaktion ausgeführt werden.

</red_flags>

Individual skills in this repo

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

Verwandte Skills