activerecord-libsql 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Cargo.toml ADDED
@@ -0,0 +1,3 @@
1
+ [workspace]
2
+ members = ["ext/turso_libsql"]
3
+ resolver = "2"
data/README.md ADDED
@@ -0,0 +1,265 @@
1
+ # activerecord-libsql
2
+
3
+ ActiveRecord adapter for [Turso](https://turso.tech) (libSQL) database.
4
+
5
+ Connects Rails/ActiveRecord models to Turso via a native Rust extension ([magnus](https://github.com/matsadler/magnus) + [libsql](https://github.com/tursodatabase/libsql)), using the libSQL remote protocol directly — no HTTP client wrapper required.
6
+
7
+ ## Requirements
8
+
9
+ - Ruby >= 3.1
10
+ - Rust >= 1.70 (install via [rustup](https://rustup.rs))
11
+ - ActiveRecord >= 7.0
12
+
13
+ ## Installation
14
+
15
+ ```ruby
16
+ # Gemfile
17
+ gem "activerecord-libsql"
18
+ ```
19
+
20
+ ```bash
21
+ bundle install
22
+ bundle exec rake compile
23
+ ```
24
+
25
+ ## Configuration
26
+
27
+ ### database.yml
28
+
29
+ ```yaml
30
+ default: &default
31
+ adapter: turso
32
+ database: <%= ENV["TURSO_DATABASE_URL"] %> # libsql://xxx.turso.io
33
+ token: <%= ENV["TURSO_AUTH_TOKEN"] %>
34
+
35
+ development:
36
+ <<: *default
37
+
38
+ production:
39
+ <<: *default
40
+ ```
41
+
42
+ > **Note**: Use the `database:` key, not `url:`. ActiveRecord tries to resolve the adapter from the URL scheme when `url:` is used, which causes a lookup failure.
43
+
44
+ ### Direct connection
45
+
46
+ ```ruby
47
+ require "activerecord-libsql"
48
+
49
+ ActiveRecord::Base.establish_connection(
50
+ adapter: "turso",
51
+ database: "libsql://your-db.turso.io",
52
+ token: "your-auth-token"
53
+ )
54
+ ```
55
+
56
+ ## Usage
57
+
58
+ ```ruby
59
+ class User < ActiveRecord::Base
60
+ end
61
+
62
+ # Create
63
+ User.create!(name: "Alice", email: "alice@example.com")
64
+
65
+ # Read
66
+ User.where(name: "Alice").first
67
+ User.find(1)
68
+ User.order(:name).limit(10)
69
+
70
+ # Update
71
+ User.find(1).update!(email: "new@example.com")
72
+
73
+ # Delete
74
+ User.find(1).destroy
75
+ ```
76
+
77
+ ## Embedded Replicas
78
+
79
+ 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.
80
+
81
+ ### Configuration
82
+
83
+ ```yaml
84
+ # database.yml
85
+ production:
86
+ adapter: turso
87
+ database: <%= ENV["TURSO_DATABASE_URL"] %> # libsql://xxx.turso.io
88
+ token: <%= ENV["TURSO_AUTH_TOKEN"] %>
89
+ replica_path: /var/data/myapp.db # local replica file path
90
+ sync_interval: 60 # background sync every 60 seconds (0 = manual only)
91
+ ```
92
+
93
+ Or via `establish_connection`:
94
+
95
+ ```ruby
96
+ ActiveRecord::Base.establish_connection(
97
+ adapter: "turso",
98
+ database: "libsql://your-db.turso.io",
99
+ token: "your-auth-token",
100
+ replica_path: "/var/data/myapp.db",
101
+ sync_interval: 60
102
+ )
103
+ ```
104
+
105
+ ### Manual sync
106
+
107
+ ```ruby
108
+ # Trigger a sync from the remote at any time
109
+ ActiveRecord::Base.connection.sync
110
+ ```
111
+
112
+ ### Notes
113
+
114
+ - `replica_path` must point to a clean (empty) file or a previously synced replica. Using an existing SQLite file from another source will cause an error.
115
+ - `sync_interval` is in seconds. Set to `0` or omit to use manual sync only.
116
+ - **Multi-process caution**: Do not share the same `replica_path` across multiple Puma workers. Each worker should use a unique path (e.g. `/var/data/myapp-worker-#{worker_id}.db`).
117
+ - The background sync task runs as long as the `Database` object is alive. The adapter holds the `Database` for the lifetime of the connection.
118
+
119
+ ## Schema Management
120
+
121
+ `turso:schema:apply` and `turso:schema:diff` use [sqldef](https://github.com/sqldef/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.
122
+
123
+ ### Prerequisites
124
+
125
+ ```bash
126
+ # macOS
127
+ brew install sqldef/sqldef/sqlite3def
128
+
129
+ # Other platforms: https://github.com/sqldef/sqldef/releases
130
+ ```
131
+
132
+ `replica_path` must be configured in `database.yml` (the tasks use the local replica to compute the diff without touching the remote directly).
133
+
134
+ ### turso:schema:apply
135
+
136
+ Applies the diff between your desired schema and the current remote schema.
137
+
138
+ ```bash
139
+ rake turso:schema:apply[db/schema.sql]
140
+ ```
141
+
142
+ Example output:
143
+
144
+ ```
145
+ ==> [1/4] Pulling latest schema from remote...
146
+ Done.
147
+ ==> [2/4] Computing schema diff...
148
+ 2 statement(s) to apply:
149
+ ALTER TABLE users ADD COLUMN bio TEXT;
150
+ CREATE INDEX idx_users_email ON users (email);
151
+ ==> [3/4] Applying schema to Turso Cloud...
152
+ Done.
153
+ ==> [4/4] Pulling to confirm...
154
+ Done.
155
+ ==> Schema applied successfully!
156
+ ```
157
+
158
+ If the schema is already up to date:
159
+
160
+ ```
161
+ ==> [1/4] Pulling latest schema from remote...
162
+ Done.
163
+ ==> [2/4] Computing schema diff...
164
+ Already up to date.
165
+ ```
166
+
167
+ ### turso:schema:diff
168
+
169
+ Shows what would be applied without making any changes (dry-run).
170
+
171
+ ```bash
172
+ rake turso:schema:diff[db/schema.sql]
173
+ ```
174
+
175
+ ### schema.sql format
176
+
177
+ Plain SQL `CREATE TABLE` statements. sqldef handles `ALTER TABLE` / `CREATE INDEX` / `DROP` automatically based on the diff.
178
+
179
+ ```sql
180
+ CREATE TABLE users (
181
+ id TEXT PRIMARY KEY,
182
+ name TEXT NOT NULL,
183
+ email TEXT NOT NULL
184
+ );
185
+
186
+ CREATE TABLE posts (
187
+ id TEXT PRIMARY KEY,
188
+ user_id TEXT NOT NULL,
189
+ title TEXT NOT NULL,
190
+ body TEXT,
191
+ created_at TEXT NOT NULL
192
+ );
193
+ ```
194
+
195
+ ## Architecture
196
+
197
+ ```
198
+ Rails Model (ActiveRecord)
199
+ ↓ Arel → SQL string
200
+ LibsqlAdapter (lib/active_record/connection_adapters/libsql_adapter.rb)
201
+ ↓ perform_query / exec_update
202
+ TursoLibsql::Database + Connection (Rust native extension)
203
+ ↓ libsql::Database / Connection (async Tokio runtime → block_on)
204
+
205
+ Remote mode: Turso Cloud (libSQL remote protocol over HTTPS)
206
+ Replica mode: Local SQLite file ←sync→ Turso Cloud
207
+ ```
208
+
209
+ ## Thread Safety
210
+
211
+ `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.
212
+
213
+ ## Performance
214
+
215
+ Benchmarked against a **Turso cloud database** (remote, over HTTPS) from a MacBook on a home network. All numbers include full round-trip network latency.
216
+
217
+ | Operation | ops/sec | avg latency |
218
+ |-----------|--------:|------------:|
219
+ | INSERT single row | 9.9 | 101.5 ms |
220
+ | SELECT all (100 rows) | 29.1 | 34.3 ms |
221
+ | SELECT WHERE | 35.9 | 27.9 ms |
222
+ | SELECT find by id | 16.2 | 61.9 ms |
223
+ | UPDATE single row | 6.4 | 156.0 ms |
224
+ | DELETE single row | 6.9 | 145.2 ms |
225
+ | Transaction (10 inserts) | 1.9 | 539.0 ms |
226
+
227
+ > **Environment**: Ruby 3.4.8 · ActiveRecord 8.1.2 · Turso cloud (remote) · macOS arm64
228
+ > Run `bundle exec ruby bench/benchmark.rb` to reproduce.
229
+
230
+ Latency is dominated by network round-trips to the Turso cloud endpoint. For lower latency, use [Embedded Replicas](#embedded-replicas) — reads are served from a local SQLite file with sub-millisecond latency.
231
+
232
+ ## Feature Support
233
+
234
+ | Feature | Status |
235
+ |---------|--------|
236
+ | SELECT | ✅ |
237
+ | INSERT | ✅ |
238
+ | UPDATE | ✅ |
239
+ | DELETE | ✅ |
240
+ | Transactions | ✅ |
241
+ | Migrations (basic) | ✅ |
242
+ | Schema management (sqldef) | ✅ |
243
+ | Prepared statements | ✅ |
244
+ | BLOB | ✅ |
245
+ | NOT NULL / UNIQUE constraint errors → AR exceptions | ✅ |
246
+ | Embedded Replica | ✅ |
247
+
248
+ ## Testing
249
+
250
+ ```bash
251
+ # Unit tests only (no credentials needed)
252
+ bundle exec rake spec
253
+
254
+ # Integration tests (requires TURSO_DATABASE_URL and TURSO_AUTH_TOKEN)
255
+ bundle exec rake spec:integration
256
+
257
+ # All tests
258
+ bundle exec rake spec:all
259
+ ```
260
+
261
+ Set `SKIP_INTEGRATION_TESTS=1` to skip integration tests in CI environments without Turso credentials.
262
+
263
+ ## License
264
+
265
+ MIT
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/activerecord/libsql/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'activerecord-libsql'
7
+ spec.version = ActiveRecord::Libsql::VERSION
8
+ spec.authors = ['aileron']
9
+ spec.email = []
10
+
11
+ spec.summary = 'ActiveRecord adapter for Turso (libSQL) database'
12
+ spec.description = <<~DESC
13
+ An ActiveRecord adapter for Turso, the edge SQLite database powered by libSQL.
14
+ Uses a native Rust extension (via magnus) to connect directly to Turso via the
15
+ libSQL remote protocol, without requiring any external HTTP client.
16
+ DESC
17
+ spec.homepage = 'https://github.com/aileron-inc/activerecord-libsql'
18
+ spec.license = 'MIT'
19
+ spec.required_ruby_version = '>= 3.1.0'
20
+
21
+ spec.metadata['homepage_uri'] = spec.homepage
22
+ spec.metadata['source_code_uri'] = spec.homepage
23
+
24
+ spec.files = Dir[
25
+ 'lib/**/*.rb',
26
+ 'ext/**/*.{rs,toml,rb}',
27
+ '*.md',
28
+ '*.gemspec',
29
+ 'Cargo.{toml,lock}'
30
+ ]
31
+
32
+ spec.require_paths = ['lib']
33
+ spec.extensions = ['ext/turso_libsql/extconf.rb']
34
+
35
+ spec.add_dependency 'activerecord', '>= 7.0'
36
+ spec.add_dependency 'rb_sys', '~> 0.9'
37
+
38
+ spec.add_development_dependency 'rake', '~> 13.0'
39
+ spec.add_development_dependency 'rake-compiler', '~> 1.2'
40
+ spec.add_development_dependency 'rspec', '~> 3.0'
41
+ end
@@ -0,0 +1,20 @@
1
+ [package]
2
+ name = "turso_libsql"
3
+ version = "0.1.0"
4
+ edition = "2021"
5
+
6
+ [lib]
7
+ name = "turso_libsql"
8
+ crate-type = ["cdylib"]
9
+
10
+ [dependencies]
11
+ magnus = { version = "0.8", features = [] }
12
+ libsql = { version = "0.9.23", features = ["encryption", "sync"] }
13
+ tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] }
14
+ once_cell = "1"
15
+
16
+ [profile.release]
17
+ opt-level = 3
18
+ lto = true
19
+ codegen-units = 1
20
+ panic = "abort"
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'mkmf'
4
+ require 'rb_sys/mkmf'
5
+
6
+ create_rust_makefile('turso_libsql/turso_libsql')
@@ -0,0 +1,299 @@
1
+ use magnus::{function, method, prelude::*, Error, Ruby};
2
+ use once_cell::sync::OnceCell;
3
+ use std::sync::Arc;
4
+ use std::time::Duration;
5
+ use tokio::runtime::Runtime;
6
+
7
+ // グローバル Tokio ランタイム(libsql は async API のため必要)
8
+ static RUNTIME: OnceCell<Runtime> = OnceCell::new();
9
+
10
+ fn runtime() -> &'static Runtime {
11
+ RUNTIME.get_or_init(|| {
12
+ tokio::runtime::Builder::new_multi_thread()
13
+ .enable_all()
14
+ .build()
15
+ .expect("Failed to create Tokio runtime")
16
+ })
17
+ }
18
+
19
+ /// async ブロック内から Ruby の RuntimeError を生成するヘルパー
20
+ fn mk_err(msg: impl std::fmt::Display) -> Error {
21
+ let ruby = Ruby::get().expect("called outside Ruby thread");
22
+ Error::new(ruby.exception_runtime_error(), msg.to_string())
23
+ }
24
+
25
+ // -----------------------------------------------------------------------
26
+ // TursoDatabase — Database を保持するラッパー(sync のために必要)
27
+ // -----------------------------------------------------------------------
28
+
29
+ #[magnus::wrap(class = "TursoLibsql::Database", free_immediately, size)]
30
+ struct TursoDatabase {
31
+ inner: Arc<libsql::Database>,
32
+ }
33
+
34
+ impl TursoDatabase {
35
+ /// リモート接続用 Database を作成(既存の remote モード)
36
+ fn new_remote(url: String, token: String) -> Result<Self, Error> {
37
+ let db = runtime().block_on(async {
38
+ libsql::Builder::new_remote(url, token)
39
+ .build()
40
+ .await
41
+ .map_err(mk_err)
42
+ })?;
43
+ Ok(Self { inner: Arc::new(db) })
44
+ }
45
+
46
+ /// Embedded Replica 用 Database を作成
47
+ /// path: ローカル DB ファイルパス
48
+ /// url: Turso リモート URL (libsql://...)
49
+ /// token: 認証トークン
50
+ /// sync_interval_secs: バックグラウンド自動同期間隔(秒)。0 なら手動のみ
51
+ fn new_remote_replica(
52
+ path: String,
53
+ url: String,
54
+ token: String,
55
+ sync_interval_secs: u64,
56
+ ) -> Result<Self, Error> {
57
+ let db = runtime().block_on(async {
58
+ let mut builder = libsql::Builder::new_remote_replica(path, url, token);
59
+ if sync_interval_secs > 0 {
60
+ builder = builder.sync_interval(Duration::from_secs(sync_interval_secs));
61
+ }
62
+ builder.build().await.map_err(mk_err)
63
+ })?;
64
+ Ok(Self { inner: Arc::new(db) })
65
+ }
66
+
67
+ /// Offline write 用 Database を作成
68
+ /// write はローカルに書いてすぐ返す。sync() でまとめてリモートへ反映する。
69
+ /// path: ローカル DB ファイルパス
70
+ /// url: Turso リモート URL (libsql://...)
71
+ /// token: 認証トークン
72
+ /// sync_interval_secs: バックグラウンド自動同期間隔(秒)。0 なら手動のみ
73
+ fn new_synced(
74
+ path: String,
75
+ url: String,
76
+ token: String,
77
+ sync_interval_secs: u64,
78
+ ) -> Result<Self, Error> {
79
+ let db = runtime().block_on(async {
80
+ let mut builder = libsql::Builder::new_synced_database(path, url, token);
81
+ if sync_interval_secs > 0 {
82
+ builder = builder.sync_interval(Duration::from_secs(sync_interval_secs));
83
+ }
84
+ builder.build().await.map_err(mk_err)
85
+ })?;
86
+ Ok(Self { inner: Arc::new(db) })
87
+ }
88
+
89
+ /// リモートから最新フレームを手動で同期する(pull)
90
+ /// offline モードでは write もまとめてリモートへ push される
91
+ fn sync(&self) -> Result<(), Error> {
92
+ let db = Arc::clone(&self.inner);
93
+ runtime().block_on(async move {
94
+ db.sync().await.map_err(mk_err)
95
+ })?;
96
+ Ok(())
97
+ }
98
+
99
+ /// この Database から Connection を取得して TursoConnection を返す
100
+ fn connect(&self) -> Result<TursoConnection, Error> {
101
+ let conn = self.inner.connect().map_err(mk_err)?;
102
+ Ok(TursoConnection {
103
+ inner: Arc::new(conn),
104
+ })
105
+ }
106
+ }
107
+
108
+ // -----------------------------------------------------------------------
109
+ // TursoConnection — Ruby に公開する接続オブジェクト
110
+ // -----------------------------------------------------------------------
111
+
112
+ #[magnus::wrap(class = "TursoLibsql::Connection", free_immediately, size)]
113
+ struct TursoConnection {
114
+ inner: Arc<libsql::Connection>,
115
+ }
116
+
117
+ impl TursoConnection {
118
+ /// 新しいリモート接続を作成する(Ruby: TursoLibsql::Connection.new(url, token))
119
+ /// 後方互換のために残す。内部では TursoDatabase を経由する
120
+ fn new(url: String, token: String) -> Result<Self, Error> {
121
+ let db = runtime().block_on(async {
122
+ libsql::Builder::new_remote(url, token)
123
+ .build()
124
+ .await
125
+ .map_err(mk_err)
126
+ })?;
127
+ let conn = db.connect().map_err(mk_err)?;
128
+ Ok(Self {
129
+ inner: Arc::new(conn),
130
+ })
131
+ }
132
+
133
+ /// SQL を実行し、影響を受けた行数を返す(INSERT/UPDATE/DELETE 用)
134
+ fn execute(&self, sql: String) -> Result<u64, Error> {
135
+ let conn = Arc::clone(&self.inner);
136
+ runtime().block_on(async move {
137
+ conn.execute(&sql, ()).await.map_err(mk_err)
138
+ })
139
+ }
140
+
141
+ /// SQL を実行し、結果を Array of Hash で返す(SELECT 用)
142
+ fn query(&self, sql: String) -> Result<magnus::RArray, Error> {
143
+ let conn = Arc::clone(&self.inner);
144
+
145
+ let rows_data: Vec<Vec<(String, libsql::Value)>> =
146
+ runtime().block_on(async move {
147
+ let mut rows = conn.query(&sql, ()).await.map_err(mk_err)?;
148
+ let mut result: Vec<Vec<(String, libsql::Value)>> = Vec::new();
149
+
150
+ while let Some(row) = rows.next().await.map_err(mk_err)? {
151
+ let col_count = rows.column_count();
152
+ let mut record: Vec<(String, libsql::Value)> =
153
+ Vec::with_capacity(col_count as usize);
154
+
155
+ for i in 0..col_count {
156
+ let name = rows.column_name(i).unwrap_or("?").to_string();
157
+ let val = row.get_value(i).map_err(mk_err)?;
158
+ record.push((name, val));
159
+ }
160
+ result.push(record);
161
+ }
162
+ Ok::<_, Error>(result)
163
+ })?;
164
+
165
+ let ruby = Ruby::get().expect("called outside Ruby thread");
166
+ let outer = ruby.ary_new_capa(rows_data.len());
167
+ for record in rows_data {
168
+ let hash = ruby.hash_new();
169
+ for (col, val) in record {
170
+ let ruby_key = ruby.str_new(&col);
171
+ let ruby_val = libsql_value_to_ruby(&ruby, val)?;
172
+ hash.aset(ruby_key, ruby_val)?;
173
+ }
174
+ outer.push(hash)?;
175
+ }
176
+ Ok(outer)
177
+ }
178
+
179
+ /// プリペアドステートメントで SQL を実行(パラメータ付き)
180
+ fn execute_with_params(&self, sql: String, params: Vec<String>) -> Result<u64, Error> {
181
+ let conn = Arc::clone(&self.inner);
182
+ runtime().block_on(async move {
183
+ let params: Vec<libsql::Value> =
184
+ params.into_iter().map(libsql::Value::Text).collect();
185
+ conn.execute(&sql, libsql::params_from_iter(params))
186
+ .await
187
+ .map_err(mk_err)
188
+ })
189
+ }
190
+
191
+ /// トランザクションを開始する
192
+ fn begin_transaction(&self) -> Result<(), Error> {
193
+ let conn = Arc::clone(&self.inner);
194
+ runtime().block_on(async move {
195
+ conn.execute("BEGIN", ()).await.map_err(mk_err)
196
+ })?;
197
+ Ok(())
198
+ }
199
+
200
+ /// トランザクションをコミットする
201
+ fn commit_transaction(&self) -> Result<(), Error> {
202
+ let conn = Arc::clone(&self.inner);
203
+ runtime().block_on(async move {
204
+ conn.execute("COMMIT", ()).await.map_err(mk_err)
205
+ })?;
206
+ Ok(())
207
+ }
208
+
209
+ /// トランザクションをロールバックする
210
+ fn rollback_transaction(&self) -> Result<(), Error> {
211
+ let conn = Arc::clone(&self.inner);
212
+ runtime().block_on(async move {
213
+ conn.execute("ROLLBACK", ()).await.map_err(mk_err)
214
+ })?;
215
+ Ok(())
216
+ }
217
+
218
+ /// 最後に挿入した行の rowid を返す
219
+ fn last_insert_rowid(&self) -> Result<i64, Error> {
220
+ let conn = Arc::clone(&self.inner);
221
+ runtime().block_on(async move {
222
+ let mut rows = conn
223
+ .query("SELECT last_insert_rowid()", ())
224
+ .await
225
+ .map_err(mk_err)?;
226
+
227
+ if let Some(row) = rows.next().await.map_err(mk_err)? {
228
+ row.get::<i64>(0).map_err(mk_err)
229
+ } else {
230
+ Ok(0)
231
+ }
232
+ })
233
+ }
234
+ }
235
+
236
+ // -----------------------------------------------------------------------
237
+ // libsql::Value → Ruby Value 変換
238
+ // -----------------------------------------------------------------------
239
+
240
+ fn libsql_value_to_ruby(ruby: &Ruby, val: libsql::Value) -> Result<magnus::Value, Error> {
241
+ match val {
242
+ libsql::Value::Null => Ok(ruby.qnil().as_value()),
243
+ libsql::Value::Integer(i) => Ok(ruby.integer_from_i64(i).as_value()),
244
+ libsql::Value::Real(f) => Ok(ruby.float_from_f64(f).as_value()),
245
+ libsql::Value::Text(s) => Ok(ruby.str_new(&s).as_value()),
246
+ libsql::Value::Blob(b) => Ok(ruby.str_from_slice(&b).as_value()),
247
+ }
248
+ }
249
+
250
+ // -----------------------------------------------------------------------
251
+ // Magnus init — Ruby 拡張のエントリポイント
252
+ // -----------------------------------------------------------------------
253
+
254
+ #[magnus::init]
255
+ fn init(ruby: &Ruby) -> Result<(), Error> {
256
+ let module = ruby.define_module("TursoLibsql")?;
257
+
258
+ // TursoLibsql::Database
259
+ let db_class = module.define_class("Database", ruby.class_object())?;
260
+ db_class.define_singleton_method("new_remote", function!(TursoDatabase::new_remote, 2))?;
261
+ db_class.define_singleton_method(
262
+ "new_remote_replica",
263
+ function!(TursoDatabase::new_remote_replica, 4),
264
+ )?;
265
+ db_class.define_singleton_method(
266
+ "new_synced",
267
+ function!(TursoDatabase::new_synced, 4),
268
+ )?;
269
+ db_class.define_method("sync", method!(TursoDatabase::sync, 0))?;
270
+ db_class.define_method("connect", method!(TursoDatabase::connect, 0))?;
271
+
272
+ // TursoLibsql::Connection
273
+ let conn_class = module.define_class("Connection", ruby.class_object())?;
274
+ conn_class.define_singleton_method("new", function!(TursoConnection::new, 2))?;
275
+ conn_class.define_method("execute", method!(TursoConnection::execute, 1))?;
276
+ conn_class.define_method("query", method!(TursoConnection::query, 1))?;
277
+ conn_class.define_method(
278
+ "execute_with_params",
279
+ method!(TursoConnection::execute_with_params, 2),
280
+ )?;
281
+ conn_class.define_method(
282
+ "begin_transaction",
283
+ method!(TursoConnection::begin_transaction, 0),
284
+ )?;
285
+ conn_class.define_method(
286
+ "commit_transaction",
287
+ method!(TursoConnection::commit_transaction, 0),
288
+ )?;
289
+ conn_class.define_method(
290
+ "rollback_transaction",
291
+ method!(TursoConnection::rollback_transaction, 0),
292
+ )?;
293
+ conn_class.define_method(
294
+ "last_insert_rowid",
295
+ method!(TursoConnection::last_insert_rowid, 0),
296
+ )?;
297
+
298
+ Ok(())
299
+ }