activerecord-libsql
ActiveRecord adapter for Turso (libSQL) database.
Connects Rails/ActiveRecord models to Turso via a native Rust extension (magnus + libsql), using the libSQL remote protocol directly — no HTTP client wrapper required.
Requirements
- Ruby >= 3.1
- Rust >= 1.70 (install via rustup)
- ActiveRecord >= 7.0
Installation
# Gemfile
gem "activerecord-libsql"bundle install
bundle exec rake compileConfiguration
database.yml
default: &default
adapter: turso
database: <%= ENV["TURSO_DATABASE_URL"] %> # libsql://xxx.turso.io
token: <%= ENV["TURSO_AUTH_TOKEN"] %>
development:
<<: *default
production:
<<: *defaultNote: Use the
database:key, noturl:. ActiveRecord tries to resolve the adapter from the URL scheme whenurl:is used, which causes a lookup failure.
Direct connection
require "activerecord-libsql"
ActiveRecord::Base.establish_connection(
adapter: "turso",
database: "libsql://your-db.turso.io",
token: "your-auth-token"
)Usage
class User < ActiveRecord::Base
end
# Create
User.create!(name: "Alice", email: "alice@example.com")
# Read
User.where(name: "Alice").first
User.find(1)
User.order(:name).limit(10)
# Update
User.find(1).update!(email: "new@example.com")
# Delete
User.find(1).destroyEmbedded Replicas
Embedded Replicas keep a local SQLite copy of your Turso database on disk, synced from the remote. Reads are served locally (sub-millisecond), writes go to the remote.
Configuration
# database.yml
production:
adapter: turso
database: <%= ENV["TURSO_DATABASE_URL"] %> # libsql://xxx.turso.io
token: <%= ENV["TURSO_AUTH_TOKEN"] %>
replica_path: /var/data/myapp.db # local replica file path
sync_interval: 60 # background sync every 60 seconds (0 = manual only)Or via establish_connection:
ActiveRecord::Base.establish_connection(
adapter: "turso",
database: "libsql://your-db.turso.io",
token: "your-auth-token",
replica_path: "/var/data/myapp.db",
sync_interval: 60
)Manual sync
# Trigger a sync from the remote at any time
ActiveRecord::Base.connection.syncNotes
-
replica_pathmust point to a clean (empty) file or a previously synced replica. Using an existing SQLite file from another source will cause an error. -
sync_intervalis in seconds. Set to0or omit to use manual sync only. -
Multi-process caution: Do not share the same
replica_pathacross multiple Puma workers. Each worker should use a unique path (e.g./var/data/myapp-worker-#{worker_id}.db). - The background sync task runs as long as the
Databaseobject is alive. The adapter holds theDatabasefor the lifetime of the connection.
Schema Management
turso:schema:apply and turso:schema:diff use sqldef (sqlite3def) to manage your Turso schema declaratively — no migration files, no version tracking. You define the desired schema in a .sql file and the task computes and applies only the diff.
Prerequisites
# macOS
brew install sqldef/sqldef/sqlite3def
# Other platforms: https://github.com/sqldef/sqldef/releasesreplica_path must be configured in database.yml (the tasks use the local replica to compute the diff without touching the remote directly).
turso:schema:apply
Applies the diff between your desired schema and the current remote schema.
rake turso:schema:apply[db/schema.sql]Example output:
==> [1/4] Pulling latest schema from remote...
Done.
==> [2/4] Computing schema diff...
2 statement(s) to apply:
ALTER TABLE users ADD COLUMN bio TEXT;
CREATE INDEX idx_users_email ON users (email);
==> [3/4] Applying schema to Turso Cloud...
Done.
==> [4/4] Pulling to confirm...
Done.
==> Schema applied successfully!
If the schema is already up to date:
==> [1/4] Pulling latest schema from remote...
Done.
==> [2/4] Computing schema diff...
Already up to date.
turso:schema:diff
Shows what would be applied without making any changes (dry-run).
rake turso:schema:diff[db/schema.sql]schema.sql format
Plain SQL CREATE TABLE statements. sqldef handles ALTER TABLE / CREATE INDEX / DROP automatically based on the diff.
CREATE TABLE users (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
email TEXT NOT NULL
);
CREATE TABLE posts (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL,
title TEXT NOT NULL,
body TEXT,
created_at TEXT NOT NULL
);Architecture
Rails Model (ActiveRecord)
↓ Arel → SQL string
LibsqlAdapter (lib/active_record/connection_adapters/libsql_adapter.rb)
↓ perform_query / exec_update
TursoLibsql::Database + Connection (Rust native extension)
↓ libsql::Database / Connection (async Tokio runtime → block_on)
Remote mode: Turso Cloud (libSQL remote protocol over HTTPS)
Replica mode: Local SQLite file ←sync→ Turso Cloud
Thread Safety
libsql::Connection implements Send + Sync, making it thread-safe. ActiveRecord's ConnectionPool issues a separate Adapter instance per thread, so @raw_connection is never shared across threads.
Performance
Benchmarked against a Turso cloud database (remote, over HTTPS) from a MacBook on a home network. All numbers include full round-trip network latency.
| Operation | ops/sec | avg latency |
|---|---|---|
| INSERT single row | 9.9 | 101.5 ms |
| SELECT all (100 rows) | 29.1 | 34.3 ms |
| SELECT WHERE | 35.9 | 27.9 ms |
| SELECT find by id | 16.2 | 61.9 ms |
| UPDATE single row | 6.4 | 156.0 ms |
| DELETE single row | 6.9 | 145.2 ms |
| Transaction (10 inserts) | 1.9 | 539.0 ms |
Environment: Ruby 3.4.8 · ActiveRecord 8.1.2 · Turso cloud (remote) · macOS arm64
Runbundle exec ruby bench/benchmark.rbto reproduce.
Latency is dominated by network round-trips to the Turso cloud endpoint. For lower latency, use Embedded Replicas — reads are served from a local SQLite file with sub-millisecond latency.
Feature Support
| Feature | Status |
|---|---|
| SELECT | ✅ |
| INSERT | ✅ |
| UPDATE | ✅ |
| DELETE | ✅ |
| Transactions | ✅ |
| Migrations (basic) | ✅ |
| Schema management (sqldef) | ✅ |
| Prepared statements | ✅ |
| BLOB | ✅ |
| NOT NULL / UNIQUE constraint errors → AR exceptions | ✅ |
| Embedded Replica | ✅ |
Testing
# Unit tests only (no credentials needed)
bundle exec rake spec
# Integration tests (requires TURSO_DATABASE_URL and TURSO_AUTH_TOKEN)
bundle exec rake spec:integration
# All tests
bundle exec rake spec:allSet SKIP_INTEGRATION_TESTS=1 to skip integration tests in CI environments without Turso credentials.
License
MIT