Coming soon. This page describes an API that is not in a release yet. It is here for review.
Rindle can be used as a standalone embedded database, for example as part of a client-side application.
The API is the same everywhere: migrate to change the schema, materialize a SELECT and
subscribe to it, and write with plain SQL.
npm i @rindle/wasmimport { openDatabase } from "@rindle/wasm";
// Stored in OPFS. It survives reloads.
const db = await openDatabase("app.db");
await db.migrate("0001_init", `
CREATE TABLE issue (id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT, closed INTEGER);
`);
type Issue = { id: number; title: string };
const open = db.materialize<Issue>(
`SELECT id, title FROM issue WHERE closed = ? ORDER BY id DESC`, [0],
);
// Fires now, then after every commit that affects it.
open.subscribe((rows) => console.table(rows));
await db.sql.execute({ sql: `INSERT INTO issue (title, closed) VALUES (?, ?)`, args: ["first", 0] });In React, useLiveQuery(db, sql, params) from @rindle/react does the subscribing for you.
npm i @rindle/replicaimport { openDatabase } from "@rindle/replica";
// A SQLite file on disk.
const db = await openDatabase("app.db");
await db.migrate("0001_init", `
CREATE TABLE issue (id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT, closed INTEGER);
`);
const open = db.materialize<{ id: number; title: string }>(
`SELECT id, title FROM issue WHERE closed = ? ORDER BY id DESC`, [0],
);
open.subscribe((rows) => console.table(rows));
await db.sql.execute({ sql: `INSERT INTO issue (title, closed) VALUES (?, ?)`, args: ["first", 0] });npm i @rindle/react-native @rindle/reactimport { openDatabase } from "@rindle/react-native";
import { useLiveQuery } from "@rindle/react";
// A SQLite file in the app's documents directory.
const db = await openDatabase("app.db");
await db.migrate("0001_init", `
CREATE TABLE issue (id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT, closed INTEGER);
`);
function Issues() {
const { rows } = useLiveQuery<{ id: number; title: string }>(
db, `SELECT id, title FROM issue WHERE closed = ? ORDER BY id DESC`, [0],
);
return rows.map((i) => <Text key={i.id}>{i.title}</Text>);
}
await db.sql.execute({ sql: `INSERT INTO issue (title, closed) VALUES (?, ?)`, args: ["first", 0] });Add https://github.com/rocicorp/rindle as a Swift package.
import Rindle
let db = try Database.open(appSupport.appendingPathComponent("app.db"))
try db.migrate("0001_init", """
CREATE TABLE issue (id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT, closed INTEGER);
""")
struct Issue: Decodable { let id: Int; let title: String }
let open = try db.materialize(Issue.self,
"SELECT id, title FROM issue WHERE closed = ? ORDER BY id DESC", [0])
let sub = open.subscribe { rows in print(rows) }
try db.sql.execute("INSERT INTO issue (title, closed) VALUES (?, ?)", ["first", 0])In SwiftUI, a LiveQuery is Observable: read open.rows in a view and it re-renders on commit.
// build.gradle.kts
implementation("sh.rindle:rindle-android:0.1.0")import sh.rindle.Database
val db = Database.open(File(context.filesDir, "app.db"))
db.migrate("0001_init", """
CREATE TABLE issue (id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT, closed INTEGER);
""")
data class Issue(val id: Long, val title: String)
val open = db.materialize<Issue>(
"SELECT id, title FROM issue WHERE closed = ? ORDER BY id DESC", listOf(0))
open.subscribe { rows -> println(rows) }
db.sql.execute("INSERT INTO issue (title, closed) VALUES (?, ?)", listOf("first", 0))In Compose, open.rows is a StateFlow: val rows by open.rows.collectAsState().
cargo add rindleuse rindle::Database;
let db = Database::open("app.db")?;
db.migrate("0001_init", "
CREATE TABLE issue (id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT, closed INTEGER);
")?;
#[derive(serde::Deserialize, Debug)]
struct Issue { id: i64, title: String }
let open = db.materialize::<Issue>(
"SELECT id, title FROM issue WHERE closed = ? ORDER BY id DESC", (0,))?;
open.subscribe(|rows| println!("{rows:?}"));
db.sql().execute("INSERT INTO issue (title, closed) VALUES (?, ?)", ("first", 0))?;Any SQLite
Rindle is also a SQLite extension. Load it into any SQLite host and subscribe with SQL:
.load ./rindle
SELECT id, snapshot FROM rindle_subscribe(
'SELECT id, title FROM issue WHERE closed = 0 ORDER BY id DESC');
INSERT INTO issue (title, closed) VALUES ('first', 0);
-- The changes since the last read, one row per commit.
SELECT seq, delta FROM rindle_changes(1);Adding a Server
The same SQL runs against the Rindle server. When you need many clients on one database, start with Get Started.