beagle-turso 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.
- checksums.yaml +7 -0
- data/README.md +89 -0
- data/ext/beagle_turso/Cargo.toml +12 -0
- data/ext/beagle_turso/extconf.rb +4 -0
- data/ext/beagle_turso/src/lib.rs +264 -0
- data/lib/beagle/turso/version.rb +5 -0
- data/lib/beagle/turso.rb +18 -0
- metadata +70 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 1330a594f1ac57da72b23e18b54eab7e725165754935b738c1f2154351592882
|
|
4
|
+
data.tar.gz: e19055c30925647fee54400f58a0038883a85e21fccc55f50020610a2a0275e2
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 110a98fa2e42812b091525c4f7b9a26f09f4bc19806c0310eaf48128c596df8061d142914e55fb77cd9bdcc7fe297fc0f6a8e55a627f4c2b8f11a63003c551b3
|
|
7
|
+
data.tar.gz: 50636fc7b516e13a161927f21ee147de26c34f18195c5f6b6380ceb6147071d689d09244d563dea368de66f6a82e32608145de9bf52904cad8d689ab95eb6e78
|
data/README.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# beagle-turso
|
|
2
|
+
|
|
3
|
+
A Ruby driver for [Turso](https://turso.tech)'s database engine, backed by a
|
|
4
|
+
native Rust extension (`beagle_turso_core`, via [Magnus](https://github.com/matsadler/magnus)/[rb_sys](https://github.com/oxidize-rb/rb-sys)).
|
|
5
|
+
|
|
6
|
+
A `Database` can be opened two ways:
|
|
7
|
+
|
|
8
|
+
- **Local-only** — in-memory or an on-disk file, no network involved.
|
|
9
|
+
- **Synced** — a local file kept in sync with a remote Turso database via
|
|
10
|
+
`push`/`pull`.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
Add to your `Gemfile`:
|
|
15
|
+
|
|
16
|
+
```ruby
|
|
17
|
+
gem "beagle-turso"
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
or install directly:
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
gem install beagle-turso
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Installing builds the native extension via `rb_sys`/`rake-compiler`, so a
|
|
27
|
+
Rust toolchain is required unless a precompiled binary gem is available for
|
|
28
|
+
your platform.
|
|
29
|
+
|
|
30
|
+
## Local example
|
|
31
|
+
|
|
32
|
+
```ruby
|
|
33
|
+
require "beagle/turso"
|
|
34
|
+
|
|
35
|
+
db = Beagle::Turso::Database.open_local(":memory:")
|
|
36
|
+
conn = db.connect
|
|
37
|
+
|
|
38
|
+
conn.execute("CREATE TABLE users (id INTEGER, name TEXT)", [])
|
|
39
|
+
conn.execute("INSERT INTO users (id, name) VALUES (?, ?)", [1, "Ada"])
|
|
40
|
+
|
|
41
|
+
rows = conn.query("SELECT id, name FROM users", [])
|
|
42
|
+
# => [[1, "Ada"]]
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`execute` returns the number of affected rows (an `Integer`); `query`
|
|
46
|
+
returns an `Array` of row `Array`s. Bind parameters may be `nil`, `Integer`,
|
|
47
|
+
`Float`, `String` (bound as `TEXT`), or a binary `String` — one with
|
|
48
|
+
`ASCII-8BIT`/`BINARY` encoding — bound as `BLOB`.
|
|
49
|
+
|
|
50
|
+
## Synced example
|
|
51
|
+
|
|
52
|
+
```ruby
|
|
53
|
+
require "beagle/turso"
|
|
54
|
+
|
|
55
|
+
db = Beagle::Turso::Database.open(
|
|
56
|
+
local_path: "/path/to/local.db",
|
|
57
|
+
remote_url: ENV.fetch("TURSO_DATABASE_URL"),
|
|
58
|
+
auth_token: ENV.fetch("TURSO_AUTH_TOKEN")
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
conn = db.connect
|
|
62
|
+
conn.execute("INSERT INTO users (id, name) VALUES (?, ?)", [2, "Grace"])
|
|
63
|
+
|
|
64
|
+
db.push # propagate local writes to the remote
|
|
65
|
+
db.pull # pull remote changes down to the local file (returns true/false)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
`Database.open` also accepts `bootstrap_if_empty:` (defaults to `true`),
|
|
69
|
+
which pulls all remote data down on first open if the local file is new/empty.
|
|
70
|
+
Calling `push`/`pull` on a database opened via `open_local` (or via `open`
|
|
71
|
+
without both `remote_url:` and `auth_token:`) raises `RuntimeError`.
|
|
72
|
+
|
|
73
|
+
### Durability note: writes commit on-sync, not synchronously
|
|
74
|
+
|
|
75
|
+
Writes to a synced database commit to the **local** file immediately, as
|
|
76
|
+
part of `execute` — they do not wait on the network. They are only
|
|
77
|
+
propagated to the remote **on sync**, i.e. whenever `push` is explicitly
|
|
78
|
+
called; `push` is not called automatically after every write. Until a
|
|
79
|
+
`push` succeeds, a write that's durable locally is not yet visible to other
|
|
80
|
+
replicas of the same remote database.
|
|
81
|
+
|
|
82
|
+
## Credential safety
|
|
83
|
+
|
|
84
|
+
`auth_token` is never included in `inspect` output or in error messages —
|
|
85
|
+
confirmed by this gem's `no_secret_logging_spec.rb`.
|
|
86
|
+
|
|
87
|
+
## License
|
|
88
|
+
|
|
89
|
+
[MIT](https://opensource.org/licenses/MIT).
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
// Wraps beagle_turso_core's Database and Connection as persistent Ruby
|
|
2
|
+
// objects, exposing a synchronous open -> connect -> execute -> query API.
|
|
3
|
+
// Lifted from the proven spike at spikes/magnus-wrap/src/lib.rs (Plan 2's
|
|
4
|
+
// binding proof).
|
|
5
|
+
//
|
|
6
|
+
// One deliberate deviation from the spike: each wrapped fn/method takes
|
|
7
|
+
// `ruby: &Ruby` as its leading parameter (magnus's `function!`/`method!`
|
|
8
|
+
// macros inject it automatically — see the magnus 0.8 README's "Error
|
|
9
|
+
// Handling" example) instead of calling the fallible `Ruby::get()` inside
|
|
10
|
+
// the body. That sidesteps the "Ruby unavailable" fallback branch, which
|
|
11
|
+
// has no live `&Ruby` to call the non-deprecated `Ruby::exception_*`
|
|
12
|
+
// accessors on and so would otherwise need the deprecated
|
|
13
|
+
// `magnus::exception::standard_error()` free function. Net effect is
|
|
14
|
+
// identical; the code is simpler and compiles with zero deprecation
|
|
15
|
+
// warnings.
|
|
16
|
+
|
|
17
|
+
use beagle_turso_core::{Connection, Database, OpenOptions, Value};
|
|
18
|
+
use magnus::{
|
|
19
|
+
encoding::EncodingCapable, function, method, prelude::*, Error, Float, Integer, IntoValue,
|
|
20
|
+
RArray, RString, Ruby,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
#[magnus::wrap(class = "Beagle::Turso::Database", free_immediately)]
|
|
24
|
+
struct RbDatabase {
|
|
25
|
+
inner: Database,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
#[magnus::wrap(class = "Beagle::Turso::Connection", free_immediately)]
|
|
29
|
+
struct RbConnection {
|
|
30
|
+
inner: Connection,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
fn rt_err(ruby: &Ruby, msg: String) -> Error {
|
|
34
|
+
Error::new(ruby.exception_runtime_error(), msg)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
fn ruby_array_to_params(ruby: &Ruby, arr: RArray) -> Result<Vec<Value>, Error> {
|
|
38
|
+
// Safe: `from_value` below is a raw type check on the Ruby object (exact
|
|
39
|
+
// class match — no `to_int`/`to_f`/`to_str` coercion method dispatch), so
|
|
40
|
+
// for the sanctioned nil/Integer/Float/String inputs no arbitrary Ruby
|
|
41
|
+
// code (which could GC/mutate the array) runs during this loop; an
|
|
42
|
+
// unsupported type falls straight to the `else` arm without invoking any
|
|
43
|
+
// Ruby method at all. The binary/text split below on an `RString` is
|
|
44
|
+
// likewise native: `EncodingCapable::enc_get` is a direct
|
|
45
|
+
// `rb_enc_get_index` C call (no method dispatch). For the binary branch
|
|
46
|
+
// `as_slice` is copied into an owned `Vec` immediately, before any
|
|
47
|
+
// further Ruby call in this loop iteration, so the borrow never outlives
|
|
48
|
+
// a point where the backing string could be mutated/GC'd out from under
|
|
49
|
+
// it.
|
|
50
|
+
let items = unsafe { arr.as_slice() };
|
|
51
|
+
let ascii_8bit = ruby.ascii8bit_encindex();
|
|
52
|
+
let mut out = Vec::with_capacity(items.len());
|
|
53
|
+
for &item in items {
|
|
54
|
+
let v = if item.is_nil() {
|
|
55
|
+
Value::Null
|
|
56
|
+
} else if let Some(i) = Integer::from_value(item) {
|
|
57
|
+
Value::Integer(i.to_i64()?)
|
|
58
|
+
} else if let Some(f) = Float::from_value(item) {
|
|
59
|
+
Value::Real(f.to_f64())
|
|
60
|
+
} else if let Some(s) = RString::from_value(item) {
|
|
61
|
+
if s.enc_get() == ascii_8bit {
|
|
62
|
+
Value::Blob(unsafe { s.as_slice() }.to_vec())
|
|
63
|
+
} else {
|
|
64
|
+
Value::Text(s.to_string()?)
|
|
65
|
+
}
|
|
66
|
+
} else {
|
|
67
|
+
return Err(Error::new(
|
|
68
|
+
ruby.exception_type_error(),
|
|
69
|
+
"unsupported bind parameter type",
|
|
70
|
+
));
|
|
71
|
+
};
|
|
72
|
+
out.push(v);
|
|
73
|
+
}
|
|
74
|
+
Ok(out)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
fn value_to_ruby(ruby: &Ruby, v: &Value) -> magnus::Value {
|
|
78
|
+
match v {
|
|
79
|
+
Value::Null => ruby.qnil().as_value(),
|
|
80
|
+
Value::Integer(i) => (*i).into_value_with(ruby),
|
|
81
|
+
Value::Real(f) => (*f).into_value_with(ruby),
|
|
82
|
+
Value::Text(s) => s.clone().into_value_with(ruby),
|
|
83
|
+
Value::Blob(b) => ruby.str_from_slice(b).as_value(),
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
impl RbDatabase {
|
|
88
|
+
fn open_local(ruby: &Ruby, path: String) -> Result<RbDatabase, Error> {
|
|
89
|
+
let opts = OpenOptions {
|
|
90
|
+
local_path: path,
|
|
91
|
+
remote_url: None,
|
|
92
|
+
auth_token: None,
|
|
93
|
+
bootstrap_if_empty: true,
|
|
94
|
+
};
|
|
95
|
+
let db = Database::open(opts).map_err(|e| rt_err(ruby, e.to_string()))?;
|
|
96
|
+
Ok(RbDatabase { inner: db })
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Positional primitive backing the keyword-arg `Database.open` defined in
|
|
100
|
+
// Ruby (see lib/beagle/turso.rb). Kept positional so magnus can
|
|
101
|
+
// TryConvert `nil`/`String`/bool natively for each argument, instead of
|
|
102
|
+
// parsing an RHash of keyword args by hand in Rust.
|
|
103
|
+
fn open(
|
|
104
|
+
ruby: &Ruby,
|
|
105
|
+
local_path: String,
|
|
106
|
+
remote_url: Option<String>,
|
|
107
|
+
auth_token: Option<String>,
|
|
108
|
+
bootstrap_if_empty: bool,
|
|
109
|
+
) -> Result<RbDatabase, Error> {
|
|
110
|
+
let opts = OpenOptions {
|
|
111
|
+
local_path,
|
|
112
|
+
remote_url,
|
|
113
|
+
auth_token,
|
|
114
|
+
bootstrap_if_empty,
|
|
115
|
+
};
|
|
116
|
+
let db = Database::open(opts).map_err(|e| rt_err(ruby, e.to_string()))?;
|
|
117
|
+
Ok(RbDatabase { inner: db })
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
fn connect(ruby: &Ruby, rb_self: &Self) -> Result<RbConnection, Error> {
|
|
121
|
+
let c = rb_self
|
|
122
|
+
.inner
|
|
123
|
+
.connect()
|
|
124
|
+
.map_err(|e| rt_err(ruby, e.to_string()))?;
|
|
125
|
+
Ok(RbConnection { inner: c })
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
fn push(ruby: &Ruby, rb_self: &Self) -> Result<(), Error> {
|
|
129
|
+
rb_self
|
|
130
|
+
.inner
|
|
131
|
+
.push()
|
|
132
|
+
.map_err(|e| rt_err(ruby, e.to_string()))
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
fn pull(ruby: &Ruby, rb_self: &Self) -> Result<bool, Error> {
|
|
136
|
+
rb_self
|
|
137
|
+
.inner
|
|
138
|
+
.pull()
|
|
139
|
+
.map_err(|e| rt_err(ruby, e.to_string()))
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Release the underlying engine handle and, for a synced database, end its
|
|
143
|
+
// remote sync session, instead of waiting for GC. Idempotent.
|
|
144
|
+
fn close(_ruby: &Ruby, rb_self: &Self) -> Result<(), Error> {
|
|
145
|
+
rb_self.inner.close();
|
|
146
|
+
Ok(())
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
fn is_closed(_ruby: &Ruby, rb_self: &Self) -> bool {
|
|
150
|
+
rb_self.inner.is_closed()
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
impl RbConnection {
|
|
155
|
+
fn execute(ruby: &Ruby, rb_self: &Self, sql: String, params: RArray) -> Result<u64, Error> {
|
|
156
|
+
let p = ruby_array_to_params(ruby, params)?;
|
|
157
|
+
rb_self
|
|
158
|
+
.inner
|
|
159
|
+
.execute(&sql, &p)
|
|
160
|
+
.map_err(|e| rt_err(ruby, e.to_string()))
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
fn query(ruby: &Ruby, rb_self: &Self, sql: String, params: RArray) -> Result<RArray, Error> {
|
|
164
|
+
let p = ruby_array_to_params(ruby, params)?;
|
|
165
|
+
let rows = rb_self
|
|
166
|
+
.inner
|
|
167
|
+
.query(&sql, &p)
|
|
168
|
+
.map_err(|e| rt_err(ruby, e.to_string()))?;
|
|
169
|
+
let out = ruby.ary_new();
|
|
170
|
+
for row in rows {
|
|
171
|
+
let rb_row = ruby.ary_new();
|
|
172
|
+
for val in &row.values {
|
|
173
|
+
rb_row.push(value_to_ruby(ruby, val))?;
|
|
174
|
+
}
|
|
175
|
+
out.push(rb_row)?;
|
|
176
|
+
}
|
|
177
|
+
Ok(out)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Like `query`, but also returns column names — needed by callers (e.g.
|
|
181
|
+
// an ActiveRecord adapter) that must know column identity, not just
|
|
182
|
+
// positional values. Returns a 2-element `[columns, rows]` array;
|
|
183
|
+
// `rows` is built with the same loop `query` uses.
|
|
184
|
+
fn query_result(
|
|
185
|
+
ruby: &Ruby,
|
|
186
|
+
rb_self: &Self,
|
|
187
|
+
sql: String,
|
|
188
|
+
params: RArray,
|
|
189
|
+
) -> Result<RArray, Error> {
|
|
190
|
+
let p = ruby_array_to_params(ruby, params)?;
|
|
191
|
+
let result = rb_self
|
|
192
|
+
.inner
|
|
193
|
+
.query_result(&sql, &p)
|
|
194
|
+
.map_err(|e| rt_err(ruby, e.to_string()))?;
|
|
195
|
+
let columns = ruby.ary_new();
|
|
196
|
+
for name in &result.columns {
|
|
197
|
+
columns.push(name.clone().into_value_with(ruby))?;
|
|
198
|
+
}
|
|
199
|
+
let rows = ruby.ary_new();
|
|
200
|
+
for row in result.rows {
|
|
201
|
+
let rb_row = ruby.ary_new();
|
|
202
|
+
for val in &row.values {
|
|
203
|
+
rb_row.push(value_to_ruby(ruby, val))?;
|
|
204
|
+
}
|
|
205
|
+
rows.push(rb_row)?;
|
|
206
|
+
}
|
|
207
|
+
let out = ruby.ary_new();
|
|
208
|
+
out.push(columns)?;
|
|
209
|
+
out.push(rows)?;
|
|
210
|
+
Ok(out)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
fn last_insert_rowid(ruby: &Ruby, rb_self: &Self) -> Result<i64, Error> {
|
|
214
|
+
rb_self
|
|
215
|
+
.inner
|
|
216
|
+
.last_insert_rowid()
|
|
217
|
+
.map_err(|e| rt_err(ruby, e.to_string()))
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
fn execute_batch(ruby: &Ruby, rb_self: &Self, sql: String) -> Result<(), Error> {
|
|
221
|
+
rb_self
|
|
222
|
+
.inner
|
|
223
|
+
.execute_batch(&sql)
|
|
224
|
+
.map_err(|e| rt_err(ruby, e.to_string()))
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Release the underlying connection handle instead of waiting for GC.
|
|
228
|
+
// Idempotent.
|
|
229
|
+
fn close(_ruby: &Ruby, rb_self: &Self) -> Result<(), Error> {
|
|
230
|
+
rb_self.inner.close();
|
|
231
|
+
Ok(())
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
fn is_closed(_ruby: &Ruby, rb_self: &Self) -> bool {
|
|
235
|
+
rb_self.inner.is_closed()
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
#[magnus::init]
|
|
240
|
+
fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
241
|
+
let turso = ruby.define_module("Beagle")?.define_module("Turso")?;
|
|
242
|
+
let db = turso.define_class("Database", ruby.class_object())?;
|
|
243
|
+
db.define_singleton_method("open_local", function!(RbDatabase::open_local, 1))?;
|
|
244
|
+
// Positional primitive; the public keyword-arg `Database.open` wrapper
|
|
245
|
+
// lives in Ruby (lib/beagle/turso.rb) and delegates here.
|
|
246
|
+
db.define_singleton_method("_open", function!(RbDatabase::open, 4))?;
|
|
247
|
+
db.define_method("connect", method!(RbDatabase::connect, 0))?;
|
|
248
|
+
db.define_method("push", method!(RbDatabase::push, 0))?;
|
|
249
|
+
db.define_method("pull", method!(RbDatabase::pull, 0))?;
|
|
250
|
+
db.define_method("close", method!(RbDatabase::close, 0))?;
|
|
251
|
+
db.define_method("closed?", method!(RbDatabase::is_closed, 0))?;
|
|
252
|
+
let conn = turso.define_class("Connection", ruby.class_object())?;
|
|
253
|
+
conn.define_method("execute", method!(RbConnection::execute, 2))?;
|
|
254
|
+
conn.define_method("query", method!(RbConnection::query, 2))?;
|
|
255
|
+
conn.define_method("query_result", method!(RbConnection::query_result, 2))?;
|
|
256
|
+
conn.define_method(
|
|
257
|
+
"last_insert_rowid",
|
|
258
|
+
method!(RbConnection::last_insert_rowid, 0),
|
|
259
|
+
)?;
|
|
260
|
+
conn.define_method("execute_batch", method!(RbConnection::execute_batch, 1))?;
|
|
261
|
+
conn.define_method("close", method!(RbConnection::close, 0))?;
|
|
262
|
+
conn.define_method("closed?", method!(RbConnection::is_closed, 0))?;
|
|
263
|
+
Ok(())
|
|
264
|
+
}
|
data/lib/beagle/turso.rb
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
require_relative "turso/version"
|
|
2
|
+
require "beagle_turso/beagle_turso" # native extension
|
|
3
|
+
|
|
4
|
+
module Beagle
|
|
5
|
+
module Turso
|
|
6
|
+
class Database
|
|
7
|
+
# Opens a local-only database, or one synced with a remote Turso
|
|
8
|
+
# database when both `remote_url` and `auth_token` are given (see
|
|
9
|
+
# `#push`/`#pull`). Delegates to the native `_open` primitive, which
|
|
10
|
+
# takes its arguments positionally.
|
|
11
|
+
def self.open(local_path:, remote_url: nil, auth_token: nil, bootstrap_if_empty: true)
|
|
12
|
+
_open(local_path, remote_url, auth_token, bootstrap_if_empty)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private_class_method :_open
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: beagle-turso
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- BeagleSoftwareUK
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-08-08 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: rb_sys
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0.9'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '0.9'
|
|
27
|
+
description: |
|
|
28
|
+
A Ruby driver for Turso's database engine, backed by a native Rust
|
|
29
|
+
extension (beagle_turso_core, via Magnus/rb-sys). Opens a local-only
|
|
30
|
+
database (in-memory or on-disk) or one kept in sync with a remote Turso
|
|
31
|
+
database via explicit push/pull. Writes are durable to the local file
|
|
32
|
+
immediately as they happen; push is what propagates them to the remote
|
|
33
|
+
on sync, not synchronously with every write.
|
|
34
|
+
email:
|
|
35
|
+
executables: []
|
|
36
|
+
extensions:
|
|
37
|
+
- ext/beagle_turso/extconf.rb
|
|
38
|
+
extra_rdoc_files: []
|
|
39
|
+
files:
|
|
40
|
+
- README.md
|
|
41
|
+
- ext/beagle_turso/Cargo.toml
|
|
42
|
+
- ext/beagle_turso/extconf.rb
|
|
43
|
+
- ext/beagle_turso/src/lib.rs
|
|
44
|
+
- lib/beagle/turso.rb
|
|
45
|
+
- lib/beagle/turso/version.rb
|
|
46
|
+
homepage: https://github.com/BeagleSoftwareUK/beagle-turso
|
|
47
|
+
licenses:
|
|
48
|
+
- MIT
|
|
49
|
+
metadata:
|
|
50
|
+
source_code_uri: https://github.com/BeagleSoftwareUK/beagle-turso
|
|
51
|
+
post_install_message:
|
|
52
|
+
rdoc_options: []
|
|
53
|
+
require_paths:
|
|
54
|
+
- lib
|
|
55
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
56
|
+
requirements:
|
|
57
|
+
- - ">="
|
|
58
|
+
- !ruby/object:Gem::Version
|
|
59
|
+
version: '3.3'
|
|
60
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
61
|
+
requirements:
|
|
62
|
+
- - ">="
|
|
63
|
+
- !ruby/object:Gem::Version
|
|
64
|
+
version: '0'
|
|
65
|
+
requirements: []
|
|
66
|
+
rubygems_version: 3.5.22
|
|
67
|
+
signing_key:
|
|
68
|
+
specification_version: 4
|
|
69
|
+
summary: Ruby driver for Turso's new engine, backed by beagle_turso_core (Rust).
|
|
70
|
+
test_files: []
|