beagle-turso 0.1.3 → 0.2.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 +4 -4
- data/README.md +50 -1
- data/ext/beagle_turso/Cargo.toml +3 -2
- data/ext/beagle_turso/src/errors.rs +30 -0
- data/ext/beagle_turso/src/lib.rs +70 -40
- data/ext/beagle_turso/src/nogvl.rs +63 -0
- data/lib/beagle/turso/errors.rb +19 -0
- data/lib/beagle/turso/version.rb +1 -1
- data/lib/beagle/turso.rb +1 -0
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e153a3302d14dc6f72dcd76b510a0dadb1e16f4f50235258fd81a6ef67be4e91
|
|
4
|
+
data.tar.gz: b52eeb81d1fb68ce40efbe8691202f8c70e9cdceb6491fdce31ad58e4a6714d6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bef6dc8666bdf7b163eb8b2c710b61b4db7cb33ee2b914b8c60904b2c13a333fb0a656f780ad6e909b186e273b1dc113558b5a0161b55c5cf8473760bcddc3b0
|
|
7
|
+
data.tar.gz: fd867e70cc1657e4c5922cf635b80eb4bade93805f41fcd343353975a582812bb18b7b405ecc3ec538fada798d665d76b2409e01ec09d83b4e588869631cd7ce
|
data/README.md
CHANGED
|
@@ -68,7 +68,8 @@ db.pull # pull remote changes down to the local file (returns true/false)
|
|
|
68
68
|
`Database.open` also accepts `bootstrap_if_empty:` (defaults to `true`),
|
|
69
69
|
which pulls all remote data down on first open if the local file is new/empty.
|
|
70
70
|
Calling `push`/`pull` on a database opened via `open_local` (or via `open`
|
|
71
|
-
without both `remote_url:` and `auth_token:`) raises
|
|
71
|
+
without both `remote_url:` and `auth_token:`) raises
|
|
72
|
+
`Beagle::Turso::NotSyncedError`.
|
|
72
73
|
|
|
73
74
|
### Durability note: writes commit on-sync, not synchronously
|
|
74
75
|
|
|
@@ -103,6 +104,54 @@ Two things to avoid:
|
|
|
103
104
|
A single remote database, dedicated to this engine and written by one process
|
|
104
105
|
at a time, is the model it's built for.
|
|
105
106
|
|
|
107
|
+
## Errors
|
|
108
|
+
|
|
109
|
+
Every error the driver raises is a `Beagle::Turso::Error`, a `RuntimeError`
|
|
110
|
+
subclass: `OpenError`, `ConnectError`, `QueryError`, `SyncError`,
|
|
111
|
+
`NotSyncedError` (push/pull on a local-only database), `BusyError` (another
|
|
112
|
+
session or process holds the database), `ClosedError` and `ForkedError`. An
|
|
113
|
+
unsupported bind parameter type raises Ruby's `TypeError`. Auth, network and
|
|
114
|
+
protocol failures during sync all arrive as `SyncError` (or `OpenError` at
|
|
115
|
+
open): the engine does not distinguish them. Messages never contain the auth
|
|
116
|
+
token.
|
|
117
|
+
|
|
118
|
+
Busy conditions now raise `BusyError` with a message beginning
|
|
119
|
+
`database is busy:` (0.1.x said `query failed:` / `sync failed:`); it is
|
|
120
|
+
still a `RuntimeError`. A replica file already held by another process raises
|
|
121
|
+
`OpenError` ("... File is locked by another process").
|
|
122
|
+
|
|
123
|
+
## Threads, fibers and fork
|
|
124
|
+
|
|
125
|
+
- One `Database` may be shared by threads. Give each thread its own
|
|
126
|
+
`Connection`; a shared `Connection` works but serializes its callers.
|
|
127
|
+
- `open`, `connect`, `push` and `pull` release the GVL, so other threads keep
|
|
128
|
+
running while they wait on the network. Queries keep it; they are local and
|
|
129
|
+
fast.
|
|
130
|
+
- While one thread pushes, `closed?` returns immediately on other threads, and
|
|
131
|
+
queries on other connections proceed.
|
|
132
|
+
- On a synced database, `connect` called while another thread is pushing waits
|
|
133
|
+
inside the engine until the push finishes (about the push's duration); the
|
|
134
|
+
GVL is released while it waits, so other threads and fibers keep running.
|
|
135
|
+
Open connections up front (e.g. at boot / pool warm-up) rather than per
|
|
136
|
+
request.
|
|
137
|
+
- `close` during an in-flight `push` is safe: the push finishes or raises, and
|
|
138
|
+
later calls raise `ClosedError`.
|
|
139
|
+
- A blocked native call cannot be interrupted: `Timeout` and `Thread#kill`
|
|
140
|
+
take effect when it returns.
|
|
141
|
+
- **Falcon / Async:** on Ruby 4.0+ with `async` 2.25+ (enable its worker pool
|
|
142
|
+
with `ASYNC_SCHEDULER_WORKER_POOL=true` or
|
|
143
|
+
`Async::Scheduler.new(worker_pool: true)`), a fiber scheduler runs these
|
|
144
|
+
calls on a worker thread, so the reactor keeps serving other requests during
|
|
145
|
+
a sync. On Ruby 3.3/3.4 the GVL is still released, but the scheduler's own
|
|
146
|
+
thread blocks. Run sync from a dedicated thread there. Run one Falcon
|
|
147
|
+
process per replica (`--count 1`): the replica file is locked to one
|
|
148
|
+
process.
|
|
149
|
+
- **Fork:** handles are bound to the process that opened them. In a forked
|
|
150
|
+
child, `open`, `connect`, `push`, `pull` and queries on an inherited handle
|
|
151
|
+
raise `ForkedError`; open a new database there. `close` is a no-op and
|
|
152
|
+
`closed?` is safe (it reports the handle as closed if it can't be inspected),
|
|
153
|
+
so inherited handles need no cleanup in the child.
|
|
154
|
+
|
|
106
155
|
## Credential safety
|
|
107
156
|
|
|
108
157
|
`auth_token` is never included in `inspect` output or in error messages —
|
data/ext/beagle_turso/Cargo.toml
CHANGED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Maps beagle_turso_core errors onto the Ruby hierarchy defined in
|
|
2
|
+
// lib/beagle/turso/errors.rb. Classes are looked up by name at raise time
|
|
3
|
+
// (the error path only), so the Ruby file stays the single definition.
|
|
4
|
+
|
|
5
|
+
use beagle_turso_core::Error as CoreError;
|
|
6
|
+
use magnus::{exception::ExceptionClass, prelude::*, Error, RModule, Ruby};
|
|
7
|
+
|
|
8
|
+
pub fn error_class(ruby: &Ruby, name: &str) -> ExceptionClass {
|
|
9
|
+
ruby.class_object()
|
|
10
|
+
.const_get::<_, RModule>("Beagle")
|
|
11
|
+
.and_then(|m| m.const_get::<_, RModule>("Turso"))
|
|
12
|
+
.and_then(|m| m.const_get::<_, ExceptionClass>(name))
|
|
13
|
+
.unwrap_or_else(|_| ruby.exception_runtime_error())
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
pub fn raise_core(ruby: &Ruby, e: CoreError) -> Error {
|
|
17
|
+
let name = match &e {
|
|
18
|
+
CoreError::Open(_) => "OpenError",
|
|
19
|
+
CoreError::Connect(_) => "ConnectError",
|
|
20
|
+
CoreError::Query(_) => "QueryError",
|
|
21
|
+
CoreError::Sync(_) => "SyncError",
|
|
22
|
+
CoreError::NotSynced(_) => "NotSyncedError",
|
|
23
|
+
CoreError::Busy(_) => "BusyError",
|
|
24
|
+
CoreError::Closed => "ClosedError",
|
|
25
|
+
CoreError::Forked { .. } => "ForkedError",
|
|
26
|
+
// Value conversion problems stay Ruby's TypeError, as in 0.1.x.
|
|
27
|
+
CoreError::Type(_) => return Error::new(ruby.exception_type_error(), e.to_string()),
|
|
28
|
+
};
|
|
29
|
+
Error::new(error_class(ruby, name), e.to_string())
|
|
30
|
+
}
|
data/ext/beagle_turso/src/lib.rs
CHANGED
|
@@ -14,7 +14,17 @@
|
|
|
14
14
|
// identical; the code is simpler and compiles with zero deprecation
|
|
15
15
|
// warnings.
|
|
16
16
|
|
|
17
|
+
// GVL: open_local, open, connect, push and pull (the calls that can wait on the
|
|
18
|
+
// network or disk for a long time) run via nogvl::without_gvl with the GVL
|
|
19
|
+
// released. Queries/execute stay under the GVL: they are short, local, and
|
|
20
|
+
// release/reacquire would cost more than it saves.
|
|
21
|
+
|
|
22
|
+
mod errors;
|
|
23
|
+
mod nogvl;
|
|
24
|
+
|
|
17
25
|
use beagle_turso_core::{Connection, Database, OpenOptions, Value};
|
|
26
|
+
use errors::raise_core;
|
|
27
|
+
use nogvl::without_gvl;
|
|
18
28
|
use magnus::{
|
|
19
29
|
encoding::EncodingCapable, function, method, prelude::*, Error, Float, Integer, IntoValue,
|
|
20
30
|
RArray, RString, Ruby,
|
|
@@ -30,10 +40,6 @@ struct RbConnection {
|
|
|
30
40
|
inner: Connection,
|
|
31
41
|
}
|
|
32
42
|
|
|
33
|
-
fn rt_err(ruby: &Ruby, msg: String) -> Error {
|
|
34
|
-
Error::new(ruby.exception_runtime_error(), msg)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
43
|
fn ruby_array_to_params(ruby: &Ruby, arr: RArray) -> Result<Vec<Value>, Error> {
|
|
38
44
|
// Safe: `from_value` below is a raw type check on the Ruby object (exact
|
|
39
45
|
// class match — no `to_int`/`to_f`/`to_str` coercion method dispatch), so
|
|
@@ -92,7 +98,7 @@ impl RbDatabase {
|
|
|
92
98
|
auth_token: None,
|
|
93
99
|
bootstrap_if_empty: true,
|
|
94
100
|
};
|
|
95
|
-
let db = Database::open(opts)
|
|
101
|
+
let db = without_gvl(|| Database::open(opts))?.map_err(|e| raise_core(ruby, e))?;
|
|
96
102
|
Ok(RbDatabase { inner: db })
|
|
97
103
|
}
|
|
98
104
|
|
|
@@ -113,30 +119,24 @@ impl RbDatabase {
|
|
|
113
119
|
auth_token,
|
|
114
120
|
bootstrap_if_empty,
|
|
115
121
|
};
|
|
116
|
-
let db = Database::open(opts)
|
|
122
|
+
let db = without_gvl(|| Database::open(opts))?.map_err(|e| raise_core(ruby, e))?;
|
|
117
123
|
Ok(RbDatabase { inner: db })
|
|
118
124
|
}
|
|
119
125
|
|
|
120
126
|
fn connect(ruby: &Ruby, rb_self: &Self) -> Result<RbConnection, Error> {
|
|
121
|
-
let
|
|
122
|
-
|
|
123
|
-
.connect()
|
|
124
|
-
.map_err(|e| rt_err(ruby, e.to_string()))?;
|
|
127
|
+
let inner = &rb_self.inner;
|
|
128
|
+
let c = without_gvl(|| inner.connect())?.map_err(|e| raise_core(ruby, e))?;
|
|
125
129
|
Ok(RbConnection { inner: c })
|
|
126
130
|
}
|
|
127
131
|
|
|
128
132
|
fn push(ruby: &Ruby, rb_self: &Self) -> Result<(), Error> {
|
|
129
|
-
rb_self
|
|
130
|
-
|
|
131
|
-
.push()
|
|
132
|
-
.map_err(|e| rt_err(ruby, e.to_string()))
|
|
133
|
+
let inner = &rb_self.inner;
|
|
134
|
+
without_gvl(|| inner.push())?.map_err(|e| raise_core(ruby, e))
|
|
133
135
|
}
|
|
134
136
|
|
|
135
137
|
fn pull(ruby: &Ruby, rb_self: &Self) -> Result<bool, Error> {
|
|
136
|
-
rb_self
|
|
137
|
-
|
|
138
|
-
.pull()
|
|
139
|
-
.map_err(|e| rt_err(ruby, e.to_string()))
|
|
138
|
+
let inner = &rb_self.inner;
|
|
139
|
+
without_gvl(|| inner.pull())?.map_err(|e| raise_core(ruby, e))
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
// Release the underlying engine handle and, for a synced database, end its
|
|
@@ -151,13 +151,25 @@ impl RbDatabase {
|
|
|
151
151
|
}
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
+
fn rows_to_ruby(ruby: &Ruby, rows: Vec<beagle_turso_core::Row>) -> Result<RArray, Error> {
|
|
155
|
+
let out = ruby.ary_new();
|
|
156
|
+
for row in rows {
|
|
157
|
+
let rb_row = ruby.ary_new();
|
|
158
|
+
for val in &row.values {
|
|
159
|
+
rb_row.push(value_to_ruby(ruby, val))?;
|
|
160
|
+
}
|
|
161
|
+
out.push(rb_row)?;
|
|
162
|
+
}
|
|
163
|
+
Ok(out)
|
|
164
|
+
}
|
|
165
|
+
|
|
154
166
|
impl RbConnection {
|
|
155
167
|
fn execute(ruby: &Ruby, rb_self: &Self, sql: String, params: RArray) -> Result<u64, Error> {
|
|
156
168
|
let p = ruby_array_to_params(ruby, params)?;
|
|
157
169
|
rb_self
|
|
158
170
|
.inner
|
|
159
171
|
.execute(&sql, &p)
|
|
160
|
-
.map_err(|e|
|
|
172
|
+
.map_err(|e| raise_core(ruby, e))
|
|
161
173
|
}
|
|
162
174
|
|
|
163
175
|
fn query(ruby: &Ruby, rb_self: &Self, sql: String, params: RArray) -> Result<RArray, Error> {
|
|
@@ -165,16 +177,8 @@ impl RbConnection {
|
|
|
165
177
|
let rows = rb_self
|
|
166
178
|
.inner
|
|
167
179
|
.query(&sql, &p)
|
|
168
|
-
.map_err(|e|
|
|
169
|
-
|
|
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)
|
|
180
|
+
.map_err(|e| raise_core(ruby, e))?;
|
|
181
|
+
rows_to_ruby(ruby, rows)
|
|
178
182
|
}
|
|
179
183
|
|
|
180
184
|
// Like `query`, but also returns column names — needed by callers (e.g.
|
|
@@ -191,37 +195,59 @@ impl RbConnection {
|
|
|
191
195
|
let result = rb_self
|
|
192
196
|
.inner
|
|
193
197
|
.query_result(&sql, &p)
|
|
194
|
-
.map_err(|e|
|
|
198
|
+
.map_err(|e| raise_core(ruby, e))?;
|
|
195
199
|
let columns = ruby.ary_new();
|
|
196
200
|
for name in &result.columns {
|
|
197
201
|
columns.push(name.clone().into_value_with(ruby))?;
|
|
198
202
|
}
|
|
199
|
-
let rows = ruby.
|
|
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
|
-
}
|
|
203
|
+
let rows = rows_to_ruby(ruby, result.rows)?;
|
|
207
204
|
let out = ruby.ary_new();
|
|
208
205
|
out.push(columns)?;
|
|
209
206
|
out.push(rows)?;
|
|
210
207
|
Ok(out)
|
|
211
208
|
}
|
|
212
209
|
|
|
210
|
+
// Like `query_result`, plus each column's declared type (String or nil),
|
|
211
|
+
// which a Sequel adapter needs to pick value conversions. Returns
|
|
212
|
+
// `[columns, decl_types, rows]`.
|
|
213
|
+
fn query_result_with_types(
|
|
214
|
+
ruby: &Ruby,
|
|
215
|
+
rb_self: &Self,
|
|
216
|
+
sql: String,
|
|
217
|
+
params: RArray,
|
|
218
|
+
) -> Result<RArray, Error> {
|
|
219
|
+
let p = ruby_array_to_params(ruby, params)?;
|
|
220
|
+
let result = rb_self
|
|
221
|
+
.inner
|
|
222
|
+
.query_result(&sql, &p)
|
|
223
|
+
.map_err(|e| raise_core(ruby, e))?;
|
|
224
|
+
let columns = ruby.ary_new();
|
|
225
|
+
for name in result.columns {
|
|
226
|
+
columns.push(name)?;
|
|
227
|
+
}
|
|
228
|
+
let types = ruby.ary_new();
|
|
229
|
+
for t in result.decl_types {
|
|
230
|
+
types.push(t)?; // Option<String> -> String or nil
|
|
231
|
+
}
|
|
232
|
+
let out = ruby.ary_new();
|
|
233
|
+
out.push(columns)?;
|
|
234
|
+
out.push(types)?;
|
|
235
|
+
out.push(rows_to_ruby(ruby, result.rows)?)?;
|
|
236
|
+
Ok(out)
|
|
237
|
+
}
|
|
238
|
+
|
|
213
239
|
fn last_insert_rowid(ruby: &Ruby, rb_self: &Self) -> Result<i64, Error> {
|
|
214
240
|
rb_self
|
|
215
241
|
.inner
|
|
216
242
|
.last_insert_rowid()
|
|
217
|
-
.map_err(|e|
|
|
243
|
+
.map_err(|e| raise_core(ruby, e))
|
|
218
244
|
}
|
|
219
245
|
|
|
220
246
|
fn execute_batch(ruby: &Ruby, rb_self: &Self, sql: String) -> Result<(), Error> {
|
|
221
247
|
rb_self
|
|
222
248
|
.inner
|
|
223
249
|
.execute_batch(&sql)
|
|
224
|
-
.map_err(|e|
|
|
250
|
+
.map_err(|e| raise_core(ruby, e))
|
|
225
251
|
}
|
|
226
252
|
|
|
227
253
|
// Release the underlying connection handle instead of waiting for GC.
|
|
@@ -253,6 +279,10 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
|
253
279
|
conn.define_method("execute", method!(RbConnection::execute, 2))?;
|
|
254
280
|
conn.define_method("query", method!(RbConnection::query, 2))?;
|
|
255
281
|
conn.define_method("query_result", method!(RbConnection::query_result, 2))?;
|
|
282
|
+
conn.define_method(
|
|
283
|
+
"query_result_with_types",
|
|
284
|
+
method!(RbConnection::query_result_with_types, 2),
|
|
285
|
+
)?;
|
|
256
286
|
conn.define_method(
|
|
257
287
|
"last_insert_rowid",
|
|
258
288
|
method!(RbConnection::last_insert_rowid, 0),
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// Runs blocking Rust work with Ruby's GVL released, so other Ruby threads keep
|
|
2
|
+
// running and a fiber scheduler that implements `blocking_operation_wait` can
|
|
3
|
+
// move the call to a worker thread (keeping its reactor live).
|
|
4
|
+
//
|
|
5
|
+
// The closure must not touch any Ruby object: convert arguments before calling
|
|
6
|
+
// and build Ruby results after it returns. No unblocking function is passed,
|
|
7
|
+
// so Thread#raise/kill (and Timeout) are delivered only once the call returns:
|
|
8
|
+
// rb_nogvl then raises the pending interrupt by longjmp. That is caught with
|
|
9
|
+
// rb_protect (magnus::rb_sys::protect), so it never unwinds through Rust
|
|
10
|
+
// frames; any value the native call produced is dropped normally (for `open`,
|
|
11
|
+
// the Database is closed) and the exception comes back as `Err`.
|
|
12
|
+
|
|
13
|
+
use std::ffi::c_void;
|
|
14
|
+
use std::panic::{self, AssertUnwindSafe};
|
|
15
|
+
use std::ptr;
|
|
16
|
+
|
|
17
|
+
use magnus::Error;
|
|
18
|
+
|
|
19
|
+
// From ruby/thread.h. Rubies that predate it test only the INTR_FAIL (0x1) and
|
|
20
|
+
// UBF_ASYNC_SAFE (0x2) bits and ignore this one (Task 1, F2).
|
|
21
|
+
const RB_NOGVL_OFFLOAD_SAFE: i32 = 0x4;
|
|
22
|
+
|
|
23
|
+
struct Call<F, R> {
|
|
24
|
+
f: Option<F>,
|
|
25
|
+
out: Option<std::thread::Result<R>>,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
unsafe extern "C" fn trampoline<F, R>(data: *mut c_void) -> *mut c_void
|
|
29
|
+
where
|
|
30
|
+
F: FnOnce() -> R,
|
|
31
|
+
{
|
|
32
|
+
let call = &mut *(data as *mut Call<F, R>);
|
|
33
|
+
let f = call.f.take().expect("nogvl closure run twice");
|
|
34
|
+
// Never unwind across the C frame.
|
|
35
|
+
call.out = Some(panic::catch_unwind(AssertUnwindSafe(f)));
|
|
36
|
+
ptr::null_mut()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
pub fn without_gvl<F, R>(f: F) -> Result<R, Error>
|
|
40
|
+
where
|
|
41
|
+
F: FnOnce() -> R + Send,
|
|
42
|
+
R: Send,
|
|
43
|
+
{
|
|
44
|
+
let mut call = Call { f: Some(f), out: None };
|
|
45
|
+
let protected = magnus::rb_sys::protect(|| {
|
|
46
|
+
unsafe {
|
|
47
|
+
rb_sys::rb_nogvl(
|
|
48
|
+
Some(trampoline::<F, R>),
|
|
49
|
+
&mut call as *mut Call<F, R> as *mut c_void,
|
|
50
|
+
None,
|
|
51
|
+
ptr::null_mut(),
|
|
52
|
+
RB_NOGVL_OFFLOAD_SAFE,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
0 // Qfalse; the value is unused
|
|
56
|
+
});
|
|
57
|
+
// On Err, `call` (and anything the closure produced) drops here normally.
|
|
58
|
+
protected?;
|
|
59
|
+
match call.out.expect("rb_nogvl returned without running the closure") {
|
|
60
|
+
Ok(r) => Ok(r),
|
|
61
|
+
Err(payload) => panic::resume_unwind(payload),
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Beagle
|
|
2
|
+
module Turso
|
|
3
|
+
# Base class for every error the native driver raises. A RuntimeError so
|
|
4
|
+
# code written against 0.1.x, which raised bare RuntimeErrors, still rescues.
|
|
5
|
+
class Error < RuntimeError; end
|
|
6
|
+
|
|
7
|
+
class OpenError < Error; end
|
|
8
|
+
class ConnectError < Error; end
|
|
9
|
+
class QueryError < Error; end
|
|
10
|
+
class SyncError < Error; end
|
|
11
|
+
# push/pull on a database opened without remote_url:/auth_token:.
|
|
12
|
+
class NotSyncedError < Error; end
|
|
13
|
+
# The engine reported the database busy (another session or process holds it).
|
|
14
|
+
class BusyError < Error; end
|
|
15
|
+
class ClosedError < Error; end
|
|
16
|
+
# A handle opened in a parent process was used after fork; open a new one.
|
|
17
|
+
class ForkedError < Error; end
|
|
18
|
+
end
|
|
19
|
+
end
|
data/lib/beagle/turso/version.rb
CHANGED
data/lib/beagle/turso.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: beagle-turso
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- BeagleSoftwareUK
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-10-17 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rb_sys
|
|
@@ -40,8 +40,11 @@ files:
|
|
|
40
40
|
- README.md
|
|
41
41
|
- ext/beagle_turso/Cargo.toml
|
|
42
42
|
- ext/beagle_turso/extconf.rb
|
|
43
|
+
- ext/beagle_turso/src/errors.rs
|
|
43
44
|
- ext/beagle_turso/src/lib.rs
|
|
45
|
+
- ext/beagle_turso/src/nogvl.rs
|
|
44
46
|
- lib/beagle/turso.rb
|
|
47
|
+
- lib/beagle/turso/errors.rb
|
|
45
48
|
- lib/beagle/turso/version.rb
|
|
46
49
|
homepage: https://github.com/BeagleSoftwareUK/beagle-turso
|
|
47
50
|
licenses:
|