sql
import { sql } from "@tynd/core/client";Bundled SQLite. No system libsqlite needed. Connections are keyed by an opaque numeric id on the Rust side.
open(path): Promise<Database>
Opens or creates a database file. ":memory:" for in-memory.
const db = await sql.open("./data.db");
const mem = await sql.open(":memory:");Prefer joining against os.dataDir():
import { os, path } from "@tynd/core/client";
const dataDir = await os.dataDir();
const dbPath = await path.join(dataDir, "com.example.myapp", "data.db");
const db = await sql.open(dbPath);Database methods
exec(sql, params?): Promise<ExecResult>
Run a statement that doesn’t return rows (INSERT, UPDATE, DELETE, CREATE, …).
await db.exec(`CREATE TABLE IF NOT EXISTS users(
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL
)`);
const { changes, lastInsertId } = await db.exec(
"INSERT INTO users(name) VALUES (?1)",
["Alice"],
);query<T>(sql, params?): Promise<T[]>
Run a SELECT and return every row.
const rows = await db.query<{ id: number; name: string }>(
"SELECT * FROM users WHERE name LIKE ?1",
["A%"],
);queryOne<T>(sql, params?): Promise<T | null>
First row only, or null if no match.
const user = await db.queryOne<{ id: number; name: string }>(
"SELECT * FROM users WHERE id = ?1",
[1],
);close(): Promise<void>
Release the connection.
await db.close();list(): Promise<number[]>
IDs of all currently-open connections (useful for debugging).
const ids = await sql.list();Param binding
Positional:
await db.exec("INSERT INTO x(a, b) VALUES (?1, ?2)", ["hi", 42]);Type coercion:
string→TEXTnumber(integer) →INTEGERnumber(float) →REALboolean→INTEGER(0/1)null/undefined→NULLArray/ plain object →TEXT(JSON-serialized) — read back withjson_extract(col, '$.field')
BLOBs
BLOB columns come back as base64 strings over the JSON IPC channel. For large blobs, store a path in the table and use fs.readBinary on the payload.
Migrations
No built-in migrator. Standard pattern:
await db.exec(`CREATE TABLE IF NOT EXISTS meta(k TEXT PRIMARY KEY, v TEXT)`);
const row = await db.queryOne<{ v: string }>(
"SELECT v FROM meta WHERE k='schema_version'",
);
const version = row ? parseInt(row.v) : 0;
const migrations: Array<() => Promise<void>> = [
async () => { await db.exec(`CREATE TABLE notes(id INTEGER PRIMARY KEY, body TEXT)`); },
async () => { await db.exec(`ALTER TABLE notes ADD COLUMN created_at INTEGER`); },
];
for (let i = version; i < migrations.length; i++) {
await migrations[i]();
await db.exec(
"INSERT OR REPLACE INTO meta VALUES('schema_version', ?1)",
[String(i + 1)],
);
}Transactions
Standard SQL — wrap in BEGIN / COMMIT / ROLLBACK:
await db.exec("BEGIN");
try {
await db.exec("INSERT INTO a VALUES(?1)", [1]);
await db.exec("INSERT INTO b VALUES(?1)", [2]);
await db.exec("COMMIT");
} catch (err) {
await db.exec("ROLLBACK");
throw err;
}Notes
- No prepared-statement handle returned to the frontend — each call prepares + binds + executes on the Rust side. Still fast for typical desktop-app workloads.
- WAL journaling is the SQLite default for file DBs; enable with
await db.exec("PRAGMA journal_mode=WAL")if you want crash-resilient writes.
Related
- store — k/v, simpler for flat preferences.
- fs — for large BLOBs stored out-of-row.
- Persistence guide.
Last updated on