libSQL and Turso skill for Codex

Local-speed reads with a remote SQLite primary. Codex chooses the access mode from where the code runs: embedded replicas on servers with a disk, HTTP clients in serverless and edge runtimes. It states the freshness contract (read-your-writes locally, other instances after the sync interval), batches writes to save round trips, keeps tokens scoped and out of bundles, and fans migrations out safely when each tenant has its own database.

  • Databases
  • $libsql-turso
  • 212 lines
  • 2 files
  • ~2,300 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- libsql-turso

Installs to ~/.agents/skills/libsql-turso/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $libsql-turso, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Builds and operates apps on Turso and libSQL, the SQLite fork - remote databases over libsql:// URLs, embedded replicas that read from a local file and sync from the primary, database-per-tenant layouts, the turso CLI, auth tokens, batches and interactive transactions in @libsql/client, Drizzle and Prisma adapters, and libSQL vector search. Use when wiring @libsql/client or the Rust/Go/Python libSQL SDKs, adding embedded replicas for low-latency reads, choosing between edge HTTP access and a local replica, or debugging stale reads, sync errors and token problems.

Use it when

  • The app uses @libsql/client or another libSQL SDK
  • Reads need to be local while writes go to a shared primary
  • Picking between embedded replicas and remote HTTP access per runtime
  • Stale reads, sync failures or token errors appear with Turso

Use something else when

How to check the result

Write on one instance and confirm it reads back immediately there and on a second instance within the sync interval; with the primary unreachable, reads still succeed from the replica.

The mistake it prevents

Opening the embedded replica file from a second process or a shared volume while it syncs, which the Turso docs warn can corrupt it.

~/.agents/skills/libsql-turso/SKILL.md

--- name: libsql-turso description: Builds and operates apps on Turso and libSQL, the SQLite fork - remote databases over libsql:// URLs, embedded replicas that read from a local file… license: MIT ---

Done well means: every read path knows whether it hits a local replica or the remote primary, writes always reach the primary, staleness after other clients’ writes is a documented, bounded number of seconds, and tokens are scoped and never shipped to a browser.

Orient first

  1. Read AGENTS.md, package.json (or Cargo.toml, go.mod) and the DB client module. Note which SDK is in use: @libsql/client (TypeScript), libsql crate (Rust), go-libsql / libsql-client-go (Go), libsql (Python).
  2. Note where the code runs: long-lived server or VM with a disk, serverless function, or edge runtime with no filesystem. That decides the access mode.
  3. Find how migrations run (drizzle-kit, Prisma, plain SQL files) and against which URL.

Turso changes quickly. Before planning around a platform feature, check the current docs (needs network): Turso discontinued hosted edge replicas and multi-db schemas for new users in January 2025, and now promotes a separate Turso Database engine with its own “Turso Sync” (push/pull) for new local-first projects. Embedded replicas in the libSQL SDKs remain supported.

Pick the access mode

RuntimeModeClient setup
Server, VM, container with a writable diskEmbedded replicaurl: "file:..." + syncUrl + authToken
Serverless functions (Lambda, Vercel)Remote over HTTPurl: "libsql://..." + authToken
Edge runtimes (Workers, Vercel Edge)Remote, web clientimport { createClient } from "@libsql/client/web"
Local development and testsLocal file or turso devurl: "file:local.db" or http://127.0.0.1:8080
Mobile/desktop app with offline writesEvaluate Turso Sync or the offline optionRead current docs first

CLI essentials

turso auth login                                  # interactive, needs network
turso db create app-prod --group default
turso db show app-prod --url                      # libsql://app-prod-<org>.turso.io
turso db tokens create app-prod --expiration 30d  # full-access token
turso db tokens create app-prod -r                # read-only token
turso db shell app-prod                           # SQL shell against the primary
turso dev --db-file local.db                      # local libSQL server on :8080

Store tokens in the secret manager or .env (gitignored). Tokens grant database access; a token in client-side JavaScript is a public credential.

Embedded replicas

import { createClient } from "@libsql/client";

export const db = createClient({
  url: "file:/var/data/replica.db",          // local SQLite file: reads are served here
  syncUrl: process.env.TURSO_DATABASE_URL,   // libsql://... primary
  authToken: process.env.TURSO_AUTH_TOKEN,
  syncInterval: 60,                          // seconds between background syncs
});

await db.sync();                             // first sync at startup before serving reads

How it behaves:

  • Reads are local SQLite reads (microseconds), so N+1 patterns hurt far less than over HTTP.
  • Writes are forwarded to the primary. After a successful write, the same replica sees its own write immediately (read-your-writes). Other replicas see it after their next sync.
  • Staleness for other writers is up to syncInterval plus sync time. Call db.sync() explicitly after a webhook or before a read that must be fresh.
  • Sync works in WAL frames of 4 KB; a one-byte change still ships a full frame.
  • Deleting the local file forces a full resync from the primary on next start.

Rules:

  • One client owns one replica file. Do not open the file with another SQLite connection or process while it syncs; the docs warn this can corrupt it.
  • On platforms with ephemeral disks (most containers), the replica rebuilds on every deploy; budget for that first sync on large databases or mount a volume.
  • Embedded replicas need a filesystem; they do not work in edge runtimes.

Rust equivalent:

let db = libsql::Builder::new_remote_replica("replica.db", url, token)
    .sync_interval(std::time::Duration::from_secs(60))
    .build()
    .await?;
db.sync().await?;
let conn = db.connect()?;

Queries, batches and transactions

const rs = await db.execute({
  sql: "SELECT id, email FROM users WHERE org_id = ?",
  args: [orgId],
});
rs.rows.forEach((r) => console.log(r.id, r.email));

// Atomic batch: one round trip, all-or-nothing
await db.batch([
  { sql: "INSERT INTO orders (id, user_id) VALUES (?, ?)", args: [id, userId] },
  { sql: "UPDATE users SET order_count = order_count + 1 WHERE id = ?", args: [userId] },
], "write");

// Interactive transaction: holds a connection; keep it short
const tx = await db.transaction("write");
try {
  const { rows } = await tx.execute({ sql: "SELECT balance FROM accounts WHERE id = ?", args: [a] });
  if (Number(rows[0].balance) < amount) throw new Error("insufficient funds");
  await tx.execute({ sql: "UPDATE accounts SET balance = balance - ? WHERE id = ?", args: [amount, a] });
  await tx.commit();
} finally {
  tx.close();
}
NeedUse
Several writes, no reads in betweenbatch(..., "write")
Read then decide then writetransaction("write"), as short as possible
Read-only consistent snapshotbatch(..., "read") or transaction("read")
Schema migration fileexecuteMultiple(sql) (not atomic) or a migration tool

Over remote HTTP, each execute is a network round trip. Batch, or join in SQL, rather than looping queries. SQLite allows one writer at a time per database: long write transactions block other writers.

intMode defaults to "number"; set intMode: "bigint" when ids or counters can exceed 2^53.

ORMs

// Drizzle
import { drizzle } from "drizzle-orm/libsql";
export const orm = drizzle(db);
// drizzle.config.ts: dialect: "turso", dbCredentials: { url, authToken }

Prisma uses the @prisma/adapter-libsql driver adapter. Prisma Migrate does not apply migrations to Turso directly: generate SQL with prisma migrate diff and apply it with turso db shell <db> < migration.sql, or move migrations to a tool that supports libSQL.

Run migrations against the primary URL, never against a replica file.

Database per tenant

Turso makes many small databases cheap, which suits strict tenant isolation:

  • Create databases through the Platform API from a provisioning job, in a group whose location matches the tenant’s region.
  • Keep a registry table (tenant id to database name) in a control database.
  • Migrations must fan out to every tenant database. Track the applied version per database and make the runner resumable; one failed tenant must not block the rest.
  • Use group-scoped tokens so the app does not hold one token per tenant.

For choosing between this and shared-schema tenancy, read $multi-tenancy.

libSQL has native vectors without an extension:

CREATE TABLE docs (id INTEGER PRIMARY KEY, body TEXT, embedding F32_BLOB(768));
CREATE INDEX docs_vec_idx ON docs (libsql_vector_idx(embedding));
INSERT INTO docs (body, embedding) VALUES (?, vector32(?));   -- arg: '[0.1, 0.2, ...]'
SELECT d.id, d.body
FROM vector_top_k('docs_vec_idx', vector32(?), 10) AS v
JOIN docs d ON d.rowid = v.id;

Dimensions in F32_BLOB(n) must match the embedding model exactly.

Verification

  • Startup logs show the first sync() completing before traffic is accepted
  • Write on replica A, read on A immediately (visible), read on B after syncInterval
  • Kill the network to the primary: reads continue from the replica, writes fail with a clear error rather than hanging (set request timeouts)
  • Tokens: read-only token used where only reads happen; none in client bundles
  • Migrations applied once to the primary; PRAGMA table_info(<table>) on a replica after sync shows the new schema

Gotchas

  • file: URLs are relative to the process working directory; use absolute paths in services.
  • Two app instances must each have their own replica file; a shared network volume with one file is the corruption scenario the docs warn about.
  • @libsql/client in Next.js middleware or edge routes must import @libsql/client/web; the default entry needs Node APIs.
  • SQLite type affinity still applies: a column declared INTEGER accepts text unless the table is STRICT. Use CREATE TABLE ... STRICT for new tables.
  • Foreign keys are enforced only with PRAGMA foreign_keys = ON on the connection; check what your client and the remote server do before relying on cascades.

Output template

**Runtime:** Fly.io machines (persistent volume) -> embedded replicas
**Client:** @libsql/client, replica /data/replica.db, syncInterval 30 s, sync on boot
**Writes:** forwarded to libsql://app-prod-acme.turso.io; read-your-writes per instance
**Freshness contract:** other instances <= 30 s; checkout flow calls db.sync() first
**Tokens:** app full-access (30 d rotation), analytics read-only
**Checked:** cross-instance visibility test, primary-outage read test, no token in bundle

What the zip contains

SKILL.md plus the interface file below. Nothing runs on install.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "libSQL and Turso"
  short_description: "Local-speed reads with a remote SQLite primary"
  default_prompt: "$libsql-turso Add a Turso embedded replica to our Fly.io app so reads stop going over the network"

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc
Just launched

Codex Skills Pro is live

79 premium skills for Codex, each with scripts, references and an evidence report that proves the result.

Bought one by one$882.21
All 79 skills$49
94% off

That's $0.62 a skill, paid once.

Get all 79 for $49Or pick single skills from $4.99 →

Secure checkout by Creem