beagle-turso

A Ruby driver for Turso's database engine, backed by a native Rust extension (beagle_turso_core, via Magnus/rb_sys).

A Database can be opened two ways:

  • Local-only — in-memory or an on-disk file, no network involved.
  • Synced — a local file kept in sync with a remote Turso database via push/pull.

Install

Add to your Gemfile:

gem "beagle-turso"

or install directly:

gem install beagle-turso

Installing builds the native extension via rb_sys/rake-compiler, so a Rust toolchain is required unless a precompiled binary gem is available for your platform.

Local example

require "beagle/turso"

db = Beagle::Turso::Database.open_local(":memory:")
conn = db.connect

conn.execute("CREATE TABLE users (id INTEGER, name TEXT)", [])
conn.execute("INSERT INTO users (id, name) VALUES (?, ?)", [1, "Ada"])

rows = conn.query("SELECT id, name FROM users", [])
# => [[1, "Ada"]]

execute returns the number of affected rows (an Integer); query returns an Array of row Arrays. Bind parameters may be nil, Integer, Float, String (bound as TEXT), or a binary String — one with ASCII-8BIT/BINARY encoding — bound as BLOB.

Synced example

require "beagle/turso"

db = Beagle::Turso::Database.open(
  local_path: "/path/to/local.db",
  remote_url: ENV.fetch("TURSO_DATABASE_URL"),
  auth_token: ENV.fetch("TURSO_AUTH_TOKEN")
)

conn = db.connect
conn.execute("INSERT INTO users (id, name) VALUES (?, ?)", [2, "Grace"])

db.push # propagate local writes to the remote
db.pull # pull remote changes down to the local file (returns true/false)

Database.open also accepts bootstrap_if_empty: (defaults to true), which pulls all remote data down on first open if the local file is new/empty. Calling push/pull on a database opened via open_local (or via open without both remote_url: and auth_token:) raises RuntimeError.

Durability note: writes commit on-sync, not synchronously

Writes to a synced database commit to the local file immediately, as part of execute — they do not wait on the network. They are only propagated to the remote on sync, i.e. whenever push is explicitly called; push is not called automatically after every write. Until a push succeeds, a write that's durable locally is not yet visible to other replicas of the same remote database.

Which Turso database?

Point a synced database at a Turso database dedicated to this engine — ideally a fresh, empty one. On first open of an empty local replica, bootstrap_if_empty pulls the remote down; against a fresh remote that's a clean start, and the engine then manages its own state in that database (it creates turso_cdc, __turso_internal_*, and turso_sync_last_change_id tables and takes an exclusive lock on the local replica file — only one OS process may hold a given replica open at a time).

Two things to avoid:

  • Don't co-host a synced database with the legacy libSQL/Hrana driver. This engine (Turso's newer Rust core) and the classic libSQL client have different write/sync models; running both against the same remote database at once risks corrupting its sync state. Give this engine its own database.
  • Migrating an existing, populated libSQL database into the synced engine is not validated. A fresh database is the supported, tested path. If you must adopt existing data, treat it as unproven — test push/pull round-trips before relying on it.

A single remote database, dedicated to this engine and written by one process at a time, is the model it's built for.

Credential safety

auth_token is never included in inspect output or in error messages — confirmed by this gem's no_secret_logging_spec.rb.

License

MIT.