
Turso
- 59 installs
- 14 repo stars
- Updated March 2, 2026
- oakoss/agent-skills
Helps with ai & agent building tasks during AI-assisted development.
About
turso is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- turso
- AI & Agent Building
- AI-coding skill
Turso by the numbers
- 59 all-time installs (skills.sh)
- +1 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #6,453 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/oakoss/agent-skills --skill tursoAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 59 |
|---|---|
| repo stars | ★ 14 |
| Last updated | March 2, 2026 |
| Repository | oakoss/agent-skills ↗ |
What it does
Helps with ai & agent building tasks during AI-assisted development.
Files
Turso
Overview
Turso is an edge-hosted SQLite platform built on libSQL, an open-source fork of SQLite. It provides globally distributed databases with embedded replicas for local-first reads, a platform API for programmatic database management, and native vector search. Use Turso for edge-deployed applications needing low-latency reads, per-tenant database isolation, or offline-capable embedded replicas. Avoid when you need a traditional relational database with complex joins across tenants or require PostgreSQL-specific features.
Quick Reference
| Pattern | API / Command | Key Points |
|---|---|---|
| Remote client | createClient({ url, authToken }) | Connect to Turso cloud database |
| Local file client | createClient({ url: 'file:local.db' }) | Pure local SQLite via libSQL |
| Embedded replica | createClient({ url, syncUrl, authToken }) | Local reads, remote sync |
| Manual sync | client.sync() | Pull latest changes from remote |
| Periodic sync | syncInterval: 60 in client config | Auto-sync interval in seconds |
| Execute query | client.execute({ sql, args }) | Positional ? or named $param args |
| Batch operations | client.batch([...statements], mode) | Atomic multi-statement execution |
| Interactive transaction | client.transaction('write') | Multi-step logic with commit/rollback |
| Encryption at rest | encryptionKey in client config | Encrypts local database file |
| Vector insert | vector32('[0.1, 0.2, ...]') | Store embeddings as F32_BLOB columns |
| Vector search | vector_distance_cos(col, vector32(...)) | Cosine similarity, ORDER BY distance ASC |
| Vector index | libsql_vector_idx(col, 'type=diskann') | DiskANN index for efficient nearest-neighbor |
| Create database (CLI) | turso db create <name> | Provision new database |
| Create database (API) | POST /v1/organizations/{org}/databases | Programmatic provisioning for multi-tenant |
| Generate token (CLI) | turso db tokens create <name> | Auth tokens with optional read-only and expiration |
| Drizzle integration | drizzle(createClient({ url, authToken })) | Use drizzle-orm/libsql driver |
| In-memory database | createClient({ url: ':memory:' }) | Ephemeral database for testing |
Common Mistakes
| Mistake | Correct Pattern |
|---|---|
Using syncUrl without authToken | Embedded replicas require both syncUrl and authToken |
Forgetting client.sync() before reads | Call sync() or set syncInterval to keep replica current |
Using vector_distance_cos without a vector index | Create a libsql_vector_idx index for efficient similarity search |
| Hardcoding auth tokens in source code | Use environment variables for TURSO_DATABASE_URL and TURSO_AUTH_TOKEN |
| Using interactive transactions for simple batches | Use client.batch() for atomic multi-statement operations |
| Not closing transactions on error | Always call transaction.close() in a finally block |
| Creating one database for all tenants | Use multi-database per-tenant isolation for SaaS workloads |
| Passing raw arrays as vector values | Wrap vectors with vector32() SQL function or pass as Float32Array |
Using ORDER BY distance DESC for nearest neighbor | Cosine distance: smaller is closer, use ORDER BY distance ASC |
Delegation
If the drizzle-orm skill is available, delegate ORM schema definition, query building, relations, and migration patterns to it.Otherwise, recommend: pnpm dlx skills add oakoss/agent-skills -s drizzle-orm -a claude-code -y>
If the local-first skill is available, delegate architecture decisions, sync engine comparisons, and conflict resolution strategies to it.Otherwise, recommend: pnpm dlx skills add oakoss/agent-skills -s local-first -a claude-code -y>
If the pglite skill is available, delegate PGlite-specific patterns for comparison with Turso embedded replicas.- Database provisioning: Use
Taskagent for multi-tenant setup automation - Vector search tuning: Use
Exploreagent to research embedding models and dimensions - Code review: Delegate to
code-revieweragent
References
- Client SDK setup, connection modes, and configuration
- Embedded replicas, sync strategies, and offline mode
- Batch operations and interactive transactions
- Vector search, embeddings, and similarity queries
- Multi-database per-tenant architecture and platform API
- CLI commands and database management
- Drizzle ORM integration with libSQL driver
- Schema migrations and database operations
CLI Management
Installation
curl -sSfL https://get.tur.so/install.sh | bashAuthentication
turso auth login
turso auth tokenDatabase Commands
Create a Database
turso db create my-app
turso db create my-app --group us-eastList Databases
turso db listShow Database Details
turso db show my-app
turso db show my-app --urlOpen Interactive Shell
turso db shell my-app
turso db shell my-app "SELECT * FROM users LIMIT 5"Delete a Database
turso db destroy my-app -yGroup Commands
Groups define the locations where databases are replicated.
Create a Group
turso group create us-east
turso group create us-east --location iadAdd Location to Group
turso group locations add us-east lhrList Groups
turso group listToken Commands
Create Database Token
turso db tokens create my-app
turso db tokens create my-app --read-only
turso db tokens create my-app --expiration 7d3hInvalidate Tokens
turso db tokens invalidate my-appLocation Management
List Available Locations
turso db locationsShow Closest Location
turso db locations --closestConnection URLs
Database URLs follow the pattern:
libsql://[database-name]-[org-slug].turso.ioRetrieve the URL programmatically:
turso db show my-app --urlLocal Development
Create a Local Database File
turso dev --db-file local.dbStarts a local libSQL server for development without needing a cloud database.
Environment Variables
export TURSO_DATABASE_URL="$(turso db show my-app --url)"
export TURSO_AUTH_TOKEN="$(turso db tokens create my-app)"Common CLI Workflows
Full Setup
turso auth login
turso group create default --location iad
turso db create my-app --group default
turso db show my-app --url
turso db tokens create my-appSchema from File
turso db shell my-app < schema.sqlCreate Database from Existing
turso db create my-app-staging --from-db my-appDump Database
turso db shell my-app .dump > backup.sqlClient SDK
Installation
npm install @libsql/clientConnection Modes
Remote (Turso Cloud)
import { createClient } from '@libsql/client';
const client = createClient({
url: process.env.TURSO_DATABASE_URL!,
authToken: process.env.TURSO_AUTH_TOKEN,
});Local File
import { createClient } from '@libsql/client';
const client = createClient({
url: 'file:local.db',
});In-Memory (Testing)
import { createClient } from '@libsql/client';
const client = createClient({
url: ':memory:',
});Encrypted Local Database
import { createClient } from '@libsql/client';
const client = createClient({
url: 'file:secure.db',
encryptionKey: process.env.DB_ENCRYPTION_KEY!,
});Executing Queries
Simple Query
const result = await client.execute('SELECT * FROM users');
console.log(result.rows);
console.log(result.columns);Positional Parameters
const result = await client.execute({
sql: 'SELECT * FROM users WHERE age > ? AND city = ?',
args: [18, 'New York'],
});Named Parameters
const result = await client.execute({
sql: 'SELECT * FROM users WHERE created_at > $year AND status = $status',
args: { year: 2020, status: 'active' },
});Insert with Result
const result = await client.execute({
sql: 'INSERT INTO users (name, email) VALUES (?, ?)',
args: ['Alice Smith', 'alice@example.com'],
});
console.log(result.rowsAffected);
console.log(result.lastInsertRowid);Result Set Structure
interface ResultSet {
columns: string[];
columnTypes: string[];
rows: Row[];
rowsAffected: number;
lastInsertRowid: bigint | undefined;
}Row values are accessible by column name or index:
const result = await client.execute(
'SELECT id, name, email FROM users LIMIT 1',
);
const row = result.rows[0];
row.name;
row[1];
row.length;Integer Mode Configuration
const client = createClient({
url: 'file:local.db',
intMode: 'bigint',
});| Mode | Return Type | Use Case |
|---|---|---|
'number' | number | Default, safe for values < 2^53 |
'bigint' | bigint | Large integers, row IDs |
'string' | string | Interop with JSON serialization |
Data Type Mapping
| SQLite Type | TypeScript Type | Notes |
|---|---|---|
| TEXT | string | Strings, dates, JSON |
| INTEGER | number/bigint | Depends on intMode |
| REAL | number | Floating-point |
| BLOB | ArrayBuffer | Binary data, vectors |
| NULL | null | Nullable columns |
Working with Binary Data
await client.execute({
sql: 'INSERT INTO files (data) VALUES (?)',
args: [new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f])],
});
const result = await client.execute('SELECT data FROM files WHERE id = 1');
const blob = new Uint8Array(result.rows[0].data as ArrayBuffer);Cleanup
client.close();Always close the client when the application shuts down to release resources and flush pending operations.
Drizzle Integration
Installation
npm install drizzle-orm @libsql/client
npm install -D drizzle-kitRemote Connection
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);Embedded Replica Connection
import { drizzle } from 'drizzle-orm/libsql';
import { createClient } from '@libsql/client';
const turso = createClient({
url: 'file:replica.db',
syncUrl: process.env.TURSO_DATABASE_URL!,
authToken: process.env.TURSO_AUTH_TOKEN!,
syncInterval: 60,
});
export const db = drizzle(turso);Schema Definition
Drizzle uses SQLite column types with the drizzle-orm/sqlite-core module:
import {
sqliteTable,
text,
integer,
real,
blob,
} 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(),
createdAt: text('created_at')
.notNull()
.$defaultFn(() => new Date().toISOString()),
});
export const posts = sqliteTable('posts', {
id: integer('id').primaryKey({ autoIncrement: true }),
title: text('title').notNull(),
content: text('content'),
authorId: integer('author_id')
.notNull()
.references(() => users.id),
});Querying with Drizzle
import { eq } from 'drizzle-orm';
import { db } from './db';
import { users, posts } from './schema';
const allUsers = await db.select().from(users);
const user = await db
.select()
.from(users)
.where(eq(users.email, 'alice@example.com'));
await db.insert(users).values({
name: 'Alice',
email: 'alice@example.com',
});
await db.update(users).set({ name: 'Alice Smith' }).where(eq(users.id, 1));
await db.delete(users).where(eq(users.id, 1));Relations
import { relations } from 'drizzle-orm';
export const usersRelations = relations(users, ({ many }) => ({
posts: many(posts),
}));
export const postsRelations = relations(posts, ({ one }) => ({
author: one(users, {
fields: [posts.authorId],
references: [users.id],
}),
}));Enable relational queries by passing the schema:
import * as schema from './schema';
export const db = drizzle(turso, { schema });
const userWithPosts = await db.query.users.findFirst({
with: { posts: true },
where: eq(users.id, 1),
});Drizzle Kit Configuration
import { type Config } from 'drizzle-kit';
export default {
schema: './src/db/schema.ts',
out: './drizzle',
dialect: 'turso',
dbCredentials: {
url: process.env.TURSO_DATABASE_URL!,
authToken: process.env.TURSO_AUTH_TOKEN,
},
} satisfies Config;Migration Commands
npx drizzle-kit generate
npx drizzle-kit migrate
npx drizzle-kit push
npx drizzle-kit studioLocal Development with Drizzle
For local development without a cloud database:
import { type Config } from 'drizzle-kit';
export default {
schema: './src/db/schema.ts',
out: './drizzle',
dialect: 'sqlite',
dbCredentials: {
url: 'file:local.db',
},
} satisfies Config;Embedded Replicas
Embedded replicas maintain a local SQLite copy of a remote Turso database. Reads happen locally with zero network latency. Writes go to the remote primary and sync back to the local replica.
Basic Setup
import { createClient } from '@libsql/client';
const client = createClient({
url: 'file:replica.db',
syncUrl: process.env.TURSO_DATABASE_URL!,
authToken: process.env.TURSO_AUTH_TOKEN!,
});Both syncUrl and authToken are required for embedded replicas.
Manual Sync
Pull the latest changes from the remote database on demand:
await client.sync();
const result = await client.execute('SELECT * FROM users');Periodic Sync
Configure automatic background synchronization:
const client = createClient({
url: 'file:replica.db',
syncUrl: process.env.TURSO_DATABASE_URL!,
authToken: process.env.TURSO_AUTH_TOKEN!,
syncInterval: 60,
});The syncInterval value is in seconds. The client syncs in the background at this interval.
Offline Mode
Enable offline mode to use the local replica without attempting remote connections:
const client = createClient({
url: 'file:replica.db',
syncUrl: process.env.TURSO_DATABASE_URL!,
authToken: process.env.TURSO_AUTH_TOKEN!,
offline: true,
});When offline: true, the client reads from and writes to the local database only. Call client.sync() explicitly when connectivity is restored.
Read-Your-Writes Consistency
const client = createClient({
url: 'file:replica.db',
syncUrl: process.env.TURSO_DATABASE_URL!,
authToken: process.env.TURSO_AUTH_TOKEN!,
readYourWrites: true,
});With readYourWrites: true, the client automatically syncs after each write operation so subsequent reads reflect the latest state.
Encrypted Embedded Replica
const client = createClient({
url: 'file:replica.db',
syncUrl: process.env.TURSO_DATABASE_URL!,
authToken: process.env.TURSO_AUTH_TOKEN!,
encryptionKey: process.env.DB_ENCRYPTION_KEY!,
});Server-Side Usage Pattern
Embedded replicas work well in long-running server processes (Node.js, Bun) where the local file persists between requests:
import { createClient } from '@libsql/client';
const db = createClient({
url: 'file:/data/replica.db',
syncUrl: process.env.TURSO_DATABASE_URL!,
authToken: process.env.TURSO_AUTH_TOKEN!,
syncInterval: 30,
});
export async function getUsers() {
const result = await db.execute('SELECT * FROM users');
return result.rows;
}When to Use Embedded Replicas
| Scenario | Recommendation |
|---|---|
| Serverless functions | Remote client (no local file) |
| Long-running servers | Embedded replica |
| Edge workers with storage | Embedded replica |
| Edge workers without storage | Remote client |
| Local development | Local file or embedded replica |
| Mobile/desktop apps | Embedded replica |
| CI/CD and testing | In-memory or local file |
Sync Behavior
- Initial sync: Downloads the full database on first connection
- Subsequent syncs: Transfers only changed pages (incremental)
- Write path: Writes go to the remote primary; the local replica syncs afterward
- Conflict handling: The remote primary is authoritative; local writes that conflict are resolved server-side
Multi-Tenant Architecture
Turso supports a database-per-tenant model where each tenant gets an isolated SQLite database. The platform API enables programmatic provisioning.
Architecture Overview
Platform API
└── Organization
└── Group (location/region)
├── tenant-abc.db
├── tenant-def.db
└── tenant-ghi.dbGroups define where databases are hosted. All databases in a group share the same locations. Each tenant database is fully isolated with independent schema, data, and access tokens.
Platform API
Create a Database
curl -L -X POST 'https://api.turso.tech/v1/organizations/{org}/databases' \
-H 'Authorization: Bearer TOKEN' \
-H 'Content-Type: application/json' \
-d '{ "name": "tenant-abc", "group": "default" }'TypeScript Provisioning
const TURSO_API_TOKEN = process.env.TURSO_API_TOKEN!;
const TURSO_ORG = process.env.TURSO_ORG!;
async function createTenantDatabase(tenantId: string) {
const response = await fetch(
`https://api.turso.tech/v1/organizations/${TURSO_ORG}/databases`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${TURSO_API_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: `tenant-${tenantId}`,
group: 'default',
}),
},
);
if (!response.ok) {
throw new Error(`Failed to create database: ${response.statusText}`);
}
return response.json();
}Create Auth Token for a Database
turso db tokens create tenant-abc
turso db tokens create tenant-abc --read-only
turso db tokens create tenant-abc --expiration 7dToken Generation via API
async function createDatabaseToken(
dbName: string,
options?: { readOnly?: boolean; expiration?: string },
) {
const params = new URLSearchParams();
if (options?.expiration) params.set('expiration', options.expiration);
const response = await fetch(
`https://api.turso.tech/v1/organizations/${TURSO_ORG}/databases/${dbName}/auth/tokens?${params}`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${TURSO_API_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
permissions: {
read_attach: options?.readOnly ? { databases: ['*'] } : undefined,
},
}),
},
);
return response.json();
}Tenant Connection Factory
import { createClient, type Client } from '@libsql/client';
const clients = new Map<string, Client>();
function getTenantClient(tenantId: string): Client {
const existing = clients.get(tenantId);
if (existing) return existing;
const client = createClient({
url: `libsql://tenant-${tenantId}-${process.env.TURSO_ORG}.turso.io`,
authToken: process.env[`TURSO_TOKEN_${tenantId.toUpperCase()}`],
});
clients.set(tenantId, client);
return client;
}Schema Management Across Tenants
Apply schema changes to all tenant databases using the platform API:
async function listDatabases(): Promise<string[]> {
const response = await fetch(
`https://api.turso.tech/v1/organizations/${TURSO_ORG}/databases`,
{
headers: { Authorization: `Bearer ${TURSO_API_TOKEN}` },
},
);
const data = await response.json();
return data.databases.map((db: { name: string }) => db.name);
}
async function migrateAllTenants(migrationSql: string) {
const databases = await listDatabases();
const tenantDbs = databases.filter((name) => name.startsWith('tenant-'));
const results = await Promise.allSettled(
tenantDbs.map(async (dbName) => {
const client = createClient({
url: `libsql://${dbName}-${TURSO_ORG}.turso.io`,
authToken: TURSO_API_TOKEN,
});
try {
await client.execute(migrationSql);
return { db: dbName, status: 'success' };
} finally {
client.close();
}
}),
);
return results;
}Multi-Tenant vs Shared Database
| Approach | Isolation | Provisioning | Schema Changes | Cost |
|---|---|---|---|---|
| Database-per-tenant | Full | Platform API | Per-database | Per-database |
| Shared with row-level | Logical | None | Single schema | Shared |
Database-per-tenant is preferred when tenants need isolated data, independent backup/restore, or different performance profiles. Use shared databases with row-level filtering for simpler applications with many low-usage tenants.
Schema Migrations
Manual Migration Pattern
For direct SQL migrations without an ORM:
CREATE TABLE IF NOT EXISTS schema_migrations (
version INTEGER PRIMARY KEY,
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
);TypeScript Migration Runner
import { createClient } from '@libsql/client';
interface Migration {
version: number;
sql: string;
}
const migrations: Migration[] = [
{
version: 1,
sql: `CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT NOT NULL UNIQUE,
name TEXT NOT NULL
)`,
},
{
version: 2,
sql: `ALTER TABLE users ADD COLUMN created_at TEXT DEFAULT (datetime('now'))`,
},
{
version: 3,
sql: `CREATE INDEX idx_users_email ON users(email)`,
},
];
async function migrate(client: ReturnType<typeof createClient>) {
await client.execute(`
CREATE TABLE IF NOT EXISTS schema_migrations (
version INTEGER PRIMARY KEY,
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
)
`);
const applied = await client.execute(
'SELECT version FROM schema_migrations ORDER BY version',
);
const appliedVersions = new Set(applied.rows.map((r) => r.version as number));
const pending = migrations.filter((m) => !appliedVersions.has(m.version));
for (const migration of pending) {
await client.batch(
[
migration.sql,
{
sql: 'INSERT INTO schema_migrations (version) VALUES (?)',
args: [migration.version],
},
],
'write',
);
}
return pending.length;
}SQLite Schema Constraints
SQLite has limited ALTER TABLE support:
| Operation | Supported | Alternative |
|---|---|---|
| Add column | Yes | ALTER TABLE t ADD COLUMN c TYPE |
| Rename column | Yes | ALTER TABLE t RENAME COLUMN old TO new |
| Drop column | Yes | ALTER TABLE t DROP COLUMN c |
| Change column type | No | Recreate table with new schema |
| Add constraint | No | Recreate table with constraint |
| Rename table | Yes | ALTER TABLE old RENAME TO new |
Table Recreation Pattern
For unsupported alterations, recreate the table:
BEGIN TRANSACTION;
CREATE TABLE users_new (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'active'
);
INSERT INTO users_new (id, email, name)
SELECT id, email, name FROM users;
DROP TABLE users;
ALTER TABLE users_new RENAME TO users;
COMMIT;Database Seeding
async function seed(client: ReturnType<typeof createClient>) {
await client.batch(
[
{
sql: 'INSERT OR IGNORE INTO users (email, name) VALUES (?, ?)',
args: ['admin@example.com', 'Admin'],
},
{
sql: 'INSERT OR IGNORE INTO users (email, name) VALUES (?, ?)',
args: ['demo@example.com', 'Demo User'],
},
],
'write',
);
}Schema from Existing Database
Dump Full Schema
turso db shell my-app .schema > schema.sqlCreate Database from Dump
turso db create my-app-staging
turso db shell my-app-staging < schema.sqlClone Database
turso db create my-app-staging --from-db my-appEnvironment-Specific Configuration
import { createClient } from '@libsql/client';
function createDbClient() {
if (process.env.NODE_ENV === 'test') {
return createClient({ url: ':memory:' });
}
if (process.env.NODE_ENV === 'development') {
return createClient({ url: 'file:dev.db' });
}
return createClient({
url: process.env.TURSO_DATABASE_URL!,
authToken: process.env.TURSO_AUTH_TOKEN!,
});
}
export const client = createDbClient();Transactions
Batch Operations
Batch executes multiple SQL statements atomically in a single round-trip:
await client.batch(
[
'CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, email TEXT)',
"INSERT INTO users VALUES (1, 'first@example.com')",
"INSERT INTO users VALUES (2, 'second@example.com')",
],
'write',
);Parameterized Batch
const results = await client.batch(
[
{
sql: 'INSERT INTO users (email) VALUES (?)',
args: ['alice@example.com'],
},
{
sql: 'INSERT INTO users (email) VALUES (?)',
args: ['bob@example.com'],
},
{
sql: 'SELECT * FROM users WHERE email LIKE ?',
args: ['%@example.com'],
},
],
'write',
);
console.log(results[0].lastInsertRowid);
console.log(results[2].rows);Named Parameters in Batch
await client.batch(
[
{
sql: 'UPDATE users SET email = $new WHERE email = $old',
args: { old: 'alice@example.com', new: 'alice.smith@example.com' },
},
{
sql: 'DELETE FROM users WHERE email = $email',
args: { email: 'bob@example.com' },
},
],
'write',
);Read-Only Batch
const [countResult, usersResult] = await client.batch(
[
'SELECT COUNT(*) as total FROM users',
'SELECT * FROM users ORDER BY id DESC LIMIT 10',
],
'read',
);Transaction Modes
| Mode | Behavior |
|---|---|
'write' | Acquires write lock immediately |
'read' | Read-only, concurrent with other reads |
'deferred' | Starts as read, upgrades to write on first write stmt |
Interactive Transactions
For multi-step operations with application logic between statements:
const transaction = await client.transaction('write');
try {
await transaction.execute({
sql: 'UPDATE accounts SET balance = balance - ? WHERE name = ?',
args: [100, 'Alice'],
});
const balance = await transaction.execute({
sql: 'SELECT balance FROM accounts WHERE name = ?',
args: ['Alice'],
});
if ((balance.rows[0].balance as number) < 0) {
throw new Error('Insufficient funds');
}
await transaction.execute({
sql: 'UPDATE accounts SET balance = balance + ? WHERE name = ?',
args: [100, 'Bob'],
});
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
} finally {
transaction.close();
}Batch vs Interactive Transaction
| Feature | batch() | transaction() |
|---|---|---|
| Round-trips | Single | Multiple |
| Application logic between | No | Yes |
| Performance | Better (one round-trip) | More flexible |
| Error handling | All-or-nothing | Manual commit/rollback |
| Use case | Known set of statements | Conditional logic needed |
Prefer batch() when all statements are known upfront. Use interactive transactions only when application logic must run between database operations.
Vector Search
Vector similarity search is built natively into Turso and libSQL. No extensions are required.
Schema Setup
CREATE TABLE movies (
id INTEGER PRIMARY KEY,
title TEXT,
year INTEGER,
embedding F32_BLOB(4)
);The F32_BLOB(n) type stores n-dimensional float32 vectors. Common dimensions: 384 (MiniLM), 768 (BERT), 1536 (OpenAI ada-002), 3072 (OpenAI text-embedding-3-large).
Vector Index
Create a DiskANN index for efficient nearest-neighbor search:
CREATE INDEX movies_idx ON movies (
libsql_vector_idx(embedding, 'type=diskann', 'metric=cosine')
);Supported metrics: cosine, l2.
Inserting Vectors
SQL
INSERT INTO movies (title, year, embedding)
VALUES ('Napoleon', 2023, vector32('[0.800, 0.579, 0.481, 0.229]'));TypeScript with Float32Array
const embedding = new Float32Array([0.8, 0.579, 0.481, 0.229]);
await client.execute({
sql: 'INSERT INTO movies (title, year, embedding) VALUES (?, ?, vector32(?))',
args: ['Napoleon', 2023, `[${embedding.join(',')}]`],
});Querying Vectors
Cosine Similarity Search
SELECT title,
vector_extract(embedding),
vector_distance_cos(embedding, vector32('[0.064, 0.777, 0.661, 0.687]')) AS distance
FROM movies
ORDER BY distance ASC
LIMIT 10;Cosine distance: 0 means identical, higher means more different. Always ORDER BY distance ASC for nearest neighbors.
TypeScript Query
const queryVector = [0.064, 0.777, 0.661, 0.687];
const results = await client.execute({
sql: `SELECT title, vector_distance_cos(embedding, vector32(?)) AS distance
FROM movies
ORDER BY distance ASC
LIMIT ?`,
args: [`[${queryVector.join(',')}]`, 10],
});Vector Functions
| Function | Purpose |
|---|---|
vector32('[...]') | Create float32 vector from text |
vector64('[...]') | Create float64 vector |
vector_extract(col) | Extract vector as text representation |
vector_distance_cos(col, vec) | Cosine distance between vectors |
Batch Embedding Storage
const BATCH_SIZE = 50;
async function storeEmbeddings(
documents: Array<{ text: string; embedding: number[] }>,
) {
for (let i = 0; i < documents.length; i += BATCH_SIZE) {
const batch = documents.slice(i, i + BATCH_SIZE).map((doc) => ({
sql: 'INSERT INTO documents (content, embedding) VALUES (?, vector32(?))',
args: [doc.text, `[${doc.embedding.join(',')}]`] as (string | number)[],
}));
await client.batch(batch, 'write');
}
}RAG Pattern
Retrieve relevant documents for augmenting LLM context:
async function findRelevantDocs(queryEmbedding: number[], limit = 5) {
const result = await client.execute({
sql: `SELECT content,
vector_distance_cos(embedding, vector32(?)) AS distance
FROM documents
WHERE distance < 0.5
ORDER BY distance ASC
LIMIT ?`,
args: [`[${queryEmbedding.join(',')}]`, limit],
});
return result.rows.map((row) => ({
content: row.content as string,
distance: row.distance as number,
}));
}Full Setup Example
CREATE TABLE documents (
id INTEGER PRIMARY KEY AUTOINCREMENT,
content TEXT NOT NULL,
embedding F32_BLOB(1536)
);
CREATE INDEX documents_idx ON documents (
libsql_vector_idx(embedding, 'type=diskann', 'metric=cosine')
);