libSQL
Quickstart
Add durable libSQL persistence to an existing Flue project with the libSQL blueprint. Run the following command in your terminal or coding agent of choice:
flue add database libsql
Overview
The libSQL blueprint installs @flue/libsql and @libsql/client, creates a source-root db.ts, and updates existing environment documentation when the project has it. The generated adapter maps client result sets to plain objects and serializes operations so a local SQLite file does not receive overlapping writes from one process:
import { libsql } from '@flue/libsql';
import { createClient, type ResultSet } from '@libsql/client';
const client = createClient({ url: process.env.LIBSQL_URL! });
export default libsql({
query: (text, params = []) => {
// await client.execute({ sql: text, args: params }))),
// ...
}
transaction: (fn) => {
// const tx = await client.transaction('write');
// ...
}
close: () => client.close(),
});
Flue discovers the adapter at build time and wires it into the generated Node server. On startup, it creates or verifies the required flue_* tables. Agent sessions, accepted submissions, and workflow-run records then persist in a local SQLite file or self-hosted libSQL server according to LIBSQL_URL; application business data remains application-owned. Embedded replicas require additional syncUrl client configuration. The blueprint applies only to Node targets because Cloudflare deployments use Durable Object SQLite instead.
Configure
| Variable | Purpose |
|---|---|
LIBSQL_URL | Required — A local file (file:./data/flue.db) or a libSQL server (http://host:8080). |
createClient reads this at runtime — it is not baked into the build. For local
development, flue dev --env <file> and flue run --env <file> load any
.env-format file. In production, supply it from your platform’s secret store.
The blueprint installs @flue/libsql and the official @libsql/client, and
writes a source-root db.ts that wraps the client. Flue discovers db.ts at
build time and wires it into the generated Node server. For hosted Turso, use
flue add database turso instead — it is the same
adapter with a Turso client configuration.
@flue/libsql is a Node.js adapter. The Cloudflare target uses Durable
Object SQLite automatically and rejects a db.ts file at build time, so this
guide applies to Node deployments. See Database for the
full picture of how state is stored on each target.
Bring your own driver
@flue/libsql does not pick or bundle a database driver. It runs against a
small runner you wrap around your configured
@libsql/client, so you own the
client and its connection options. A runner is three functions: query (a SQL
string with ? placeholders plus positional params, resolving to result rows),
transaction (runs its callback inside one write transaction), and close.
@libsql/client returns a ResultSet, so map its rows/columns into plain
objects:
import { libsql } from '@flue/libsql';
import { createClient, type ResultSet } from '@libsql/client';
const client = createClient({ url: process.env.LIBSQL_URL! });
const toRows = (rs: ResultSet) =>
rs.rows.map((row) => Object.fromEntries(rs.columns.map((column) => [column, row[column]])));
let tail: Promise<unknown> = Promise.resolve();
const serialize = <T>(operation: () => Promise<T>): Promise<T> => {
const result = tail.then(operation, operation);
tail = result.then(
() => undefined,
() => undefined,
);
return result;
};
export default libsql({
query: (text, params = []) =>
serialize(async () => toRows(await client.execute({ sql: text, args: params }))),
transaction: (fn) =>
serialize(async () => {
const tx = await client.transaction('write');
try {
const result = await fn({
query: async (text, params = []) => toRows(await tx.execute({ sql: text, args: params })),
});
await tx.commit();
return result;
} catch (error) {
await tx.rollback();
throw error;
} finally {
tx.close();
}
}),
close: () => client.close(),
});
Connection targets
createClient decides where state lives — the adapter is identical across all
of them:
| Target | createClient(...) |
|---|---|
| Local SQLite file | { url: 'file:./data/flue.db' } |
Self-hosted libSQL server (sqld) | { url: 'http://127.0.0.1:8080' } |
| Embedded replica (local file synced to a remote) | { url: 'file:local.db', syncUrl, authToken } |
| Hosted Turso | see the Turso guide |
Embedded-file concurrency
When LIBSQL_URL is a local file: database, asynchronous writes can overlap
and surface SQLITE_BUSY. The runner above serializes all operations from one
process so its transactions do not contend with top-level queries. Flue does
not promise multi-process or multi-tenant writes to one embedded file. A
self-hosted libSQL server and hosted Turso serialize writes server-side.
Migrations
The adapter’s migrate() hook runs automatically when the generated Node
server starts. It creates Flue’s flue_* tables idempotently and stamps a
schema version, so a fresh database is provisioned on first boot and an existing
one is reused on restart. There is no separate migration command to run, and a
database written by a newer Flue refuses to start rather than corrupting state.
What gets stored
A Flue database stores runtime state, not your whole application.
| Stored by Flue | Not stored by Flue |
|---|---|
| Agent session messages and compaction state | Sandbox files and installed dependencies |
Accepted direct prompts and dispatch(...) submissions | External API side effects |
| Workflow-run records and persisted events | Application-owned business data unless your own tools store it |
Run indexing for /runs lookups and listRuns() | Provider credentials or secrets |
See Durable Agents for how recovery uses submission state, and the Data Persistence API for the exact adapter contract.
When to choose libSQL
| Use case | Adapter |
|---|---|
| Local development | sqlite() from @flue/runtime/node, or libSQL against a file: database |
| Single-host Node deployment | File-backed sqlite() or libSQL file: |
| Self-hosted SQLite over the network, or an embedded replica | @flue/libsql |
| Hosted, replicated SQLite | @flue/libsql against Turso |
| Multi-replica Node deployment on Postgres | @flue/postgres |
libSQL is the right choice when you want SQLite’s model but reachable over the network or kept close to the app as an embedded replica. For a fully managed, replicated deployment, point the same adapter at Turso.