Appearance
SQLite
Embedded database access with a typed JS bridge.
| Config key | sqlite |
| JS namespace | Sqlite |
| Core | No |
| Phases | Bindable · Lifecycle |
| Hard deps | — |
| Platforms | macOS · Linux · Windows |
SQLite gives your app a local, embedded database backed by crystal-lang/crystal-sqlite3. Open as many databases as you need — each call to lune.Sqlite.open returns an opaque handle you pass to subsequent operations. Use :memory: for an in-process database that lives only for the session, or an absolute path for a persistent file.
Enabling
yaml
plugins:
enabled:
- sqliteOr omit plugins: entirely — SQLite is active by default.
Opening and closing a database
js
import { lune } from "../lunejs/runtime/runtime.js";
// In-memory: cleared when closed
const db = await lune.Sqlite.open({ path: ":memory:" });
// Persistent file
const db = await lune.Sqlite.open({
path: "/Users/alice/Library/Application Support/myapp/data.db",
});
// Always close when done
await lune.Sqlite.close({ db });Executing statements
lune.Sqlite.exec runs any statement that does not return rows — CREATE, INSERT, UPDATE, DELETE, DROP, etc. It returns { changes, lastInsertId }.
js
await lune.Sqlite.exec({
db,
sql: "CREATE TABLE notes (id INTEGER PRIMARY KEY, body TEXT)",
params: [],
});
const { changes, lastInsertId } = await lune.Sqlite.exec({
db,
sql: "INSERT INTO notes (body) VALUES (?)",
params: ["Hello, Lune!"],
});
// changes → 1, lastInsertId → 1Pass an empty array [] for statements with no parameters.
Querying rows
lune.Sqlite.query returns an array of plain objects, one per row, keyed by column name.
js
const rows = await lune.Sqlite.query({
db,
sql: "SELECT id, body FROM notes ORDER BY id",
params: [],
});
// [{ id: 1, body: "Hello, Lune!" }]
// Parameterised query
const filtered = await lune.Sqlite.query({
db,
sql: "SELECT * FROM notes WHERE body LIKE ?",
params: ["%Lune%"],
});Parameter binding
All four SQLite placeholder styles are supported (?, ?N, @name, :name, $name) — whichever your SQL uses, pass values positionally in the array for ? placeholders.
js
// Positional
await lune.Sqlite.exec({
db,
sql: "INSERT INTO t VALUES (?, ?)",
params: [42, "hello"],
});
// Named (pass an array of values in bind order)
await lune.Sqlite.exec({
db,
sql: "INSERT INTO t VALUES (:n, :s)",
params: [42, "hello"],
});Type mapping
| SQLite storage class | JavaScript type |
|---|---|
| INTEGER | number (integer) |
| REAL | number (float) |
| TEXT | string |
| BLOB | string (base64) |
| NULL | null |
Error handling
Both exec and query reject with a LuneError on failure:
js
import { LuneError, lune } from "../lunejs/runtime/runtime.js";
try {
await lune.Sqlite.exec({ db, sql: "NOT VALID SQL", params: [] });
} catch (err) {
if (err instanceof LuneError) {
console.error(err.code, err.message); // "sqlite_error" + driver message
}
}Accessing a database that was never opened (or already closed) throws LuneError with code sqlite_not_open.
JavaScript API
| Method | Signature | Description |
|---|---|---|
open | ({ path }) → Promise<string> | Open or create a database |
close | ({ db }) → Promise<void> | Close and release the database |
exec | ({ db, sql, params }) → Promise<{ changes: number; lastInsertId: number }> | Run a non-SELECT statement |
query | ({ db, sql, params }) → Promise<Record<string, unknown>[]> | Run a SELECT and return all rows |
Notes
- One pool per handle.
crystal-sqlite3manages a connection pool perDB::Databaseinstance. SQLite's WAL mode is recommended for write-heavy apps with concurrent reads. - Lifecycle cleanup. All open databases are closed when the app quits — the
Lifecycleshutdown hook callsdb.closeon every tracked handle. - BLOB columns arrive in JavaScript as base64-encoded strings.
Platform notes
- macOS — Verified.
- Linux — Untested.
- Windows — Verified. Uses bundled
sqlite3.dll; build requires the import library (see WINDOWS_SETUP.md).
Disabling
yaml
plugins:
disabled:
- sqlite