activerecord-libsql
ActiveRecord adapter for Turso (libSQL) database.
Connects Rails / ActiveRecord models to Turso Cloud via the Hrana v2 HTTP protocol,
implemented in pure Ruby using Net::HTTP — no native extension, no Rust toolchain required.
Requirements
- Ruby >= 3.1
- ActiveRecord >= 7.0
Installation
# Gemfile
gem "activerecord-libsql"
bundle install
No compile step needed. The gem is pure Ruby.
Configuration
database.yml
default: &default
adapter: turso
database: <%= ENV["TURSO_DATABASE_URL"] %> # libsql://xxx.turso.io
token: <%= ENV["TURSO_AUTH_TOKEN"] %>
development:
<<: *default
production:
<<: *default
Note: 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).destroy
Embedded 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.sync
Notes
replica_pathmust point to a writable path. The file is created automatically on first connect.sync_intervalis in seconds. Set to0or omit to use manual sync only.- Multi-process (Puma / Solid Queue): Each worker process gets its own SQLite connection.
sqlite3gem 2.x handles fork safety automatically — connections are closed afterfork()and reopened in the child process. Do not share the samereplica_pathacross multiple Puma workers; use a unique path per worker (e.g./var/data/myapp-worker-#{worker_id}.db).
Solid Queue
This adapter is compatible with Solid Queue.
Known behaviour
FOR UPDATE SKIP LOCKED: Solid Queue uses this clause by default. libSQL and SQLite do not support it, so the adapter strips it automatically before sending SQL to the backend. SQLite serializes all writes, so row-level locking is not needed.- Fork safety: Solid Queue forks worker processes. The adapter handles this correctly —
sqlite3gem 2.x closes connections afterfork(), and ActiveRecord'sdiscard!/reconnectflow re-establishes them in the child process.
Example config
# config/queue.yml
default: &default
dispatchers:
- polling_interval: 1
batch_size: 500
workers:
- queues: "*"
threads: 3
polling_interval: 0.1
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/releases
replica_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]
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
);
Architecture
Rails Model (ActiveRecord)
↓ Arel → SQL string
LibsqlAdapter (lib/active_record/connection_adapters/libsql_adapter.rb)
↓ perform_query (strips FOR UPDATE, expands bind params)
↓
├─ Remote mode ──→ TursoLibsql::Connection (Hrana v2 HTTP via Net::HTTP)
│ ↓
│ Turso Cloud (HTTPS)
│
└─ Embedded Replica / Offline mode
↓
TursoLibsql::LocalConnection (sqlite3 gem)
↓
Local SQLite file ←─ sync ─→ Turso Cloud
Why pure Ruby?
The original implementation used a Rust native extension (tokio + rustls). On macOS,
all Rust TLS libraries are unsafe after fork() — they cause SEGV or deadlocks in
multi-process servers (Puma, Unicorn, Solid Queue). Ruby's Net::HTTP has no such
restriction and is fully fork-safe.
Thread Safety
ActiveRecord's ConnectionPool issues a separate Adapter instance per thread, so
@raw_connection is never shared across threads. Net::HTTP opens a new TCP connection
per request, which is safe for concurrent use.
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 Run
bundle 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 DDL) | ✅ |
| Schema management (sqldef) | ✅ |
| Bind parameters | ✅ |
| NOT NULL / UNIQUE / FK constraint → AR exceptions | ✅ |
| Embedded Replica (local reads) | ✅ |
| Offline write mode | ✅ |
| Solid Queue compatibility | ✅ |
| Fork safety (Puma / Solid Queue) | ✅ |
| Prepared statements (server-side) | ❌ libSQL HTTP does not support them |
| EXPLAIN | ❌ |
| Savepoints | ❌ |
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:all
Set SKIP_INTEGRATION_TESTS=1 to skip integration tests in CI environments without Turso
credentials.
License
MIT