Turso and libSQL best practices for SQLite-compatible cloud database development with edge distribution, embedded replicas, and vector search.
Provides best practices for Turso and libSQL development including edge distribution, embedded replicas, and vector search.
npx claudepluginhub futuregerald/futuregerald-claude-pluginThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Turso is a fully managed SQLite-compatible database platform built on libSQL, a fork of SQLite. It provides edge distribution, embedded replicas, native vector search, branching, and point-in-time recovery. Core principle: SQLite simplicity with cloud-scale distribution.
| Task | Command/Pattern |
|---|---|
| Install CLI (macOS) | brew install tursodatabase/tap/turso |
| Install CLI (Linux) | curl -sSfL https://get.tur.so/install.sh | bash |
| Login | turso auth login |
| Create database | turso db create my-db |
| Connect to shell | turso db shell my-db |
| Get credentials | turso db show my-db --url and turso db tokens create my-db |
| Local dev server | turso dev |
| Local with file | turso dev --db-file local.db |
| Create branch | turso db create branch-db --from-db my-db |
| Point-in-time restore | turso db create restored --from-db my-db --timestamp 2024-01-01T00:00:00Z |
| Database dump | turso db shell my-db .dump > dump.sql |
# macOS
brew install tursodatabase/tap/turso
# Linux / Windows (WSL)
curl -sSfL https://get.tur.so/install.sh | bash
# Sign up (opens browser)
turso auth signup
# Login (opens browser)
turso auth login
# Headless mode (WSL/CI)
turso auth login --headless
# Create database (auto-detects closest region)
turso db create my-db
# Show database info
turso db show my-db
# Get connection URL
turso db show my-db --url
# Create auth token
turso db tokens create my-db
# Connect to shell
turso db shell my-db
npm install @libsql/client
# or
pnpm add @libsql/client
import { createClient } from '@libsql/client'
const client = createClient({
url: process.env.TURSO_DATABASE_URL!,
authToken: process.env.TURSO_AUTH_TOKEN,
})
// Simple query
const result = await client.execute('SELECT * FROM users')
// Positional placeholders
const result = await client.execute({
sql: 'SELECT * FROM users WHERE id = ?',
args: [1],
})
// Named placeholders (:, @, or $)
const result = await client.execute({
sql: 'INSERT INTO users (name, email) VALUES (:name, :email)',
args: { name: 'Alice', email: 'alice@example.com' },
})
interface ResultSet {
rows: Array<Row> // Row data (empty for writes)
columns: Array<string> // Column names
rowsAffected: number // Affected rows (writes)
lastInsertRowid?: bigint // Last inserted row ID
}
Batch executes multiple statements in an implicit transaction:
const results = await client.batch(
[
{ sql: 'INSERT INTO users (name) VALUES (?)', args: ['Alice'] },
{ sql: 'INSERT INTO users (name) VALUES (?)', args: ['Bob'] },
],
'write' // Transaction mode: "write" | "read" | "deferred"
)
For complex logic with conditional commits/rollbacks:
const transaction = await client.transaction('write')
try {
const balance = await transaction.execute({
sql: 'SELECT balance FROM accounts WHERE id = ?',
args: [userId],
})
if (balance.rows[0].balance >= amount) {
await transaction.execute({
sql: 'UPDATE accounts SET balance = balance - ? WHERE id = ?',
args: [amount, userId],
})
await transaction.commit()
} else {
await transaction.rollback()
}
} catch (e) {
await transaction.rollback()
throw e
}
| Mode | SQLite Command | Description |
|---|---|---|
write | BEGIN IMMEDIATE | Read/write, serialized on primary |
read | BEGIN TRANSACTION READONLY | Read-only, can run on replicas in parallel |
deferred | BEGIN DEFERRED | Starts as read, upgrades to write on first write |
const client = createClient({
url: 'file:local.db',
})
No auth token needed. Works with standard SQLite features.
# Start local libSQL server
turso dev
# With persistent file
turso dev --db-file local.db
const client = createClient({
url: 'http://127.0.0.1:8080',
})
Supports all libSQL features including extensions.
# Export production data
turso db shell prod-db .dump > dump.sql
# Create local file from dump
cat dump.sql | sqlite3 local.db
const client = createClient({
url: process.env.TURSO_DATABASE_URL!,
authToken: process.env.TURSO_AUTH_TOKEN, // undefined locally
})
# Production
TURSO_DATABASE_URL=libsql://my-db-org.turso.io
TURSO_AUTH_TOKEN=eyJ...
# Development
TURSO_DATABASE_URL=file:local.db
# No auth token needed
Local database that syncs with remote Turso database. Reads are instant (local), writes go to remote.
const client = createClient({
url: 'file:replica.db', // Local file
syncUrl: 'libsql://my-db.turso.io', // Remote primary
authToken: '...',
syncInterval: 60, // Auto-sync every 60 seconds
})
await client.sync()
const client = createClient({
url: 'file:replica.db',
syncUrl: 'libsql://my-db.turso.io',
authToken: '...',
offline: true, // Writes go to local, sync later
})
Native vector search without extensions.
CREATE TABLE movies (
id INTEGER PRIMARY KEY,
title TEXT,
embedding F32_BLOB(384) -- 384-dimensional float32 vector
);
| Type | Storage | Description |
|---|---|---|
FLOAT64 / F64_BLOB | 8D + 1 bytes | 64-bit double precision |
FLOAT32 / F32_BLOB | 4D bytes | 32-bit single precision (recommended) |
FLOAT16 / F16_BLOB | 2D + 1 bytes | 16-bit half precision |
FLOAT8 / F8_BLOB | D + 14 bytes | 8-bit compressed |
FLOAT1BIT / F1BIT_BLOB | D/8 + 3 bytes | 1-bit binary |
INSERT INTO movies (title, embedding)
VALUES ('Inception', vector32('[0.1, 0.2, 0.3, ...]'));
SELECT title,
vector_distance_cos(embedding, vector32('[0.1, 0.2, ...]')) AS distance
FROM movies
ORDER BY distance ASC
LIMIT 10;
-- Create index
CREATE INDEX movies_idx ON movies(libsql_vector_idx(embedding));
-- Query with index (much faster for large tables)
SELECT title
FROM vector_top_k('movies_idx', vector32('[0.1, 0.2, ...]'), 10)
JOIN movies ON movies.rowid = id;
CREATE INDEX movies_idx ON movies(
libsql_vector_idx(embedding, 'metric=cosine', 'compress_neighbors=float8')
);
| Setting | Values | Description |
|---|---|---|
metric | cosine, l2 | Distance function |
max_neighbors | integer | Graph connectivity |
compress_neighbors | vector type | Compression for storage |
search_l | integer | Search precision vs speed |
npm install drizzle-orm @libsql/client
npm install -D drizzle-kit
// drizzle.config.ts
import type { Config } from 'drizzle-kit'
export default {
schema: './db/schema.ts',
out: './migrations',
dialect: 'turso',
dbCredentials: {
url: process.env.TURSO_DATABASE_URL!,
authToken: process.env.TURSO_AUTH_TOKEN,
},
} satisfies Config
// db/schema.ts
import { text, integer, sqliteTable } from 'drizzle-orm/sqlite-core'
export const users = sqliteTable('users', {
id: integer('id').primaryKey({ autoIncrement: true }),
name: text('name').notNull(),
email: text('email').notNull().unique(),
})
import { drizzle } from 'drizzle-orm/libsql'
import { createClient } from '@libsql/client'
const turso = createClient({
url: process.env.TURSO_DATABASE_URL!,
authToken: process.env.TURSO_AUTH_TOKEN,
})
export const db = drizzle(turso)
# Generate migrations
npm run drizzle-kit generate
# Apply migrations
npm run drizzle-kit migrate
turso db create feature-branch --from-db production-db
turso db create restored-db --from-db production-db --timestamp 2024-01-15T10:00:00Z
name: Create Database Branch
on: create
jobs:
create-branch:
runs-on: ubuntu-latest
steps:
- name: Create Database
run: |
curl -X POST \
-H "Authorization: Bearer ${{ secrets.TURSO_API_TOKEN }}" \
-H "Content-Type: application/json" \
-d '{"name": "${{ github.ref_name }}", "group": "default", "seed": {"type": "database", "name": "production"}}' \
"https://api.turso.tech/v1/organizations/${{ secrets.ORG }}/databases"
# 256-bit key for AEGIS-256/AES-256
openssl rand -base64 32
# 128-bit key for AEGIS-128/AES-128
openssl rand -base64 16
turso db create secure-db \
--remote-encryption-key "YOUR_KEY" \
--remote-encryption-cipher aegis256
turso db shell secure-db --remote-encryption-key "YOUR_KEY"
| Cipher | Key Size | Recommendation |
|---|---|---|
aegis128l | 128-bit | Recommended for speed |
aegis256 | 256-bit | Recommended for security |
aes128gcm | 128-bit | NIST compliance |
aes256gcm | 256-bit | NIST compliance |
chacha20poly1305 | 256-bit | AES alternative |
| Extension | Description |
|---|---|
| JSON | JSON functions |
| FTS5 | Full-text search |
| R*Tree | Spatial indexing |
| SQLean Crypto | Hashing, encoding |
| SQLean Fuzzy | Fuzzy string matching |
| SQLean Math | Advanced math |
| SQLean Stats | Statistical functions |
| SQLean Text | String manipulation |
| SQLean UUID | UUID generation |
turso db create my-db --enable-extensions
| Mistake | Fix |
|---|---|
Using @libsql/client/web with file URLs | Use @libsql/client for local files |
| Long-running write transactions | Keep writes short, they block other writes |
| Opening local file during sync | Wait for sync to complete |
| Forgetting to sync embedded replicas | Call sync() or use syncInterval |
| Hardcoding credentials | Use environment variables |
| Not using transactions for related writes | Use batch() or transaction() |
| Creating vector index on wrong column type | Column must be vector type (F32_BLOB, etc.) |
batch() for multiple related operationsread transactions for read-only queries (parallel on replicas)syncInterval for embedded replicascompress_neighbors for large vector indexesExpert guidance for Next.js Cache Components and Partial Prerendering (PPR). **PROACTIVE ACTIVATION**: Use this skill automatically when working in Next.js projects that have `cacheComponents: true` in their next.config.ts/next.config.js. When this config is detected, proactively apply Cache Components patterns and best practices to all React Server Component implementations. **DETECTION**: At the start of a session in a Next.js project, check for `cacheComponents: true` in next.config. If enabled, this skill's patterns should guide all component authoring, data fetching, and caching decisions. **USE CASES**: Implementing 'use cache' directive, configuring cache lifetimes with cacheLife(), tagging cached data with cacheTag(), invalidating caches with updateTag()/revalidateTag(), optimizing static vs dynamic content boundaries, debugging cache issues, and reviewing Cache Component implementations.
Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.