dirsql 0.2.0__tar.gz → 0.2.2__tar.gz
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.
- {dirsql-0.2.0 → dirsql-0.2.2}/PKG-INFO +1 -1
- {dirsql-0.2.0 → dirsql-0.2.2}/packages/rust/src/lib.rs +47 -0
- {dirsql-0.2.0 → dirsql-0.2.2}/packages/rust/tests/sdk.rs +39 -1
- {dirsql-0.2.0 → dirsql-0.2.2}/pyproject.toml +1 -1
- {dirsql-0.2.0 → dirsql-0.2.2}/Cargo.lock +0 -0
- {dirsql-0.2.0 → dirsql-0.2.2}/Cargo.toml +0 -0
- {dirsql-0.2.0 → dirsql-0.2.2}/README.md +0 -0
- {dirsql-0.2.0 → dirsql-0.2.2}/packages/python/Cargo.toml +0 -0
- {dirsql-0.2.0 → dirsql-0.2.2}/packages/python/README.md +0 -0
- {dirsql-0.2.0 → dirsql-0.2.2}/packages/python/python/conftest.py +0 -0
- {dirsql-0.2.0 → dirsql-0.2.2}/packages/python/src/lib.rs +0 -0
- {dirsql-0.2.0 → dirsql-0.2.2}/packages/python/tests/__init__.py +0 -0
- {dirsql-0.2.0 → dirsql-0.2.2}/packages/python/tests/conftest.py +0 -0
- {dirsql-0.2.0 → dirsql-0.2.2}/packages/python/tests/integration/__init__.py +0 -0
- {dirsql-0.2.0 → dirsql-0.2.2}/packages/python/tests/integration/test_async_dirsql.py +0 -0
- {dirsql-0.2.0 → dirsql-0.2.2}/packages/python/tests/integration/test_binding.py +0 -0
- {dirsql-0.2.0 → dirsql-0.2.2}/packages/python/tests/integration/test_dirsql.py +0 -0
- {dirsql-0.2.0 → dirsql-0.2.2}/packages/python/tests/integration/test_docs_examples.py +0 -0
- {dirsql-0.2.0 → dirsql-0.2.2}/packages/python/tests/integration/test_docs_gaps.py +0 -0
- {dirsql-0.2.0 → dirsql-0.2.2}/packages/python/tests/integration/test_from_config.py +0 -0
- {dirsql-0.2.0 → dirsql-0.2.2}/packages/rust/Cargo.toml +0 -0
- {dirsql-0.2.0 → dirsql-0.2.2}/packages/rust/README.md +0 -0
- {dirsql-0.2.0 → dirsql-0.2.2}/packages/rust/benches/db_bench.rs +0 -0
- {dirsql-0.2.0 → dirsql-0.2.2}/packages/rust/benches/differ_bench.rs +0 -0
- {dirsql-0.2.0 → dirsql-0.2.2}/packages/rust/benches/matcher_bench.rs +0 -0
- {dirsql-0.2.0 → dirsql-0.2.2}/packages/rust/benches/scanner_bench.rs +0 -0
- {dirsql-0.2.0 → dirsql-0.2.2}/packages/rust/src/bin/dirsql.rs +0 -0
- {dirsql-0.2.0 → dirsql-0.2.2}/packages/rust/src/cli/mod.rs +0 -0
- {dirsql-0.2.0 → dirsql-0.2.2}/packages/rust/src/cli/router.rs +0 -0
- {dirsql-0.2.0 → dirsql-0.2.2}/packages/rust/src/cli/serialize.rs +0 -0
- {dirsql-0.2.0 → dirsql-0.2.2}/packages/rust/src/cli/server.rs +0 -0
- {dirsql-0.2.0 → dirsql-0.2.2}/packages/rust/src/config.rs +0 -0
- {dirsql-0.2.0 → dirsql-0.2.2}/packages/rust/src/db.rs +0 -0
- {dirsql-0.2.0 → dirsql-0.2.2}/packages/rust/src/differ.rs +0 -0
- {dirsql-0.2.0 → dirsql-0.2.2}/packages/rust/src/matcher.rs +0 -0
- {dirsql-0.2.0 → dirsql-0.2.2}/packages/rust/src/parser.rs +0 -0
- {dirsql-0.2.0 → dirsql-0.2.2}/packages/rust/src/scanner.rs +0 -0
- {dirsql-0.2.0 → dirsql-0.2.2}/packages/rust/src/watcher.rs +0 -0
- {dirsql-0.2.0 → dirsql-0.2.2}/packages/rust/tests/async_sdk.rs +0 -0
- {dirsql-0.2.0 → dirsql-0.2.2}/packages/rust/tests/cli_e2e.rs +0 -0
- {dirsql-0.2.0 → dirsql-0.2.2}/packages/rust/tests/cli_integration.rs +0 -0
- {dirsql-0.2.0 → dirsql-0.2.2}/packages/rust/tests/docs_examples.rs +0 -0
- {dirsql-0.2.0 → dirsql-0.2.2}/packages/rust/tests/docs_gaps.rs +0 -0
- {dirsql-0.2.0 → dirsql-0.2.2}/packages/rust/tests/from_config.rs +0 -0
- {dirsql-0.2.0 → dirsql-0.2.2}/packages/rust/tests/readonly_query.rs +0 -0
- {dirsql-0.2.0 → dirsql-0.2.2}/python/dirsql/__init__.py +0 -0
- {dirsql-0.2.0 → dirsql-0.2.2}/python/dirsql/_async.py +0 -0
- {dirsql-0.2.0 → dirsql-0.2.2}/python/dirsql/_cli/__init__.py +0 -0
- {dirsql-0.2.0 → dirsql-0.2.2}/python/dirsql/_cli/binary_path.py +0 -0
- {dirsql-0.2.0 → dirsql-0.2.2}/python/dirsql/_cli/binary_path_test.py +0 -0
- {dirsql-0.2.0 → dirsql-0.2.2}/python/dirsql/_cli/is_windows.py +0 -0
- {dirsql-0.2.0 → dirsql-0.2.2}/python/dirsql/_cli/is_windows_test.py +0 -0
- {dirsql-0.2.0 → dirsql-0.2.2}/python/dirsql/_cli/main.py +0 -0
- {dirsql-0.2.0 → dirsql-0.2.2}/python/dirsql/_cli/main_test.py +0 -0
- {dirsql-0.2.0 → dirsql-0.2.2}/python/dirsql/test_async.py +0 -0
|
@@ -44,6 +44,8 @@ use thiserror::Error;
|
|
|
44
44
|
|
|
45
45
|
pub use crate::db::{DbError, Value};
|
|
46
46
|
pub use crate::differ::RowEvent;
|
|
47
|
+
#[doc(hidden)]
|
|
48
|
+
pub use crate::watcher::FileEvent as RawFileEvent;
|
|
47
49
|
|
|
48
50
|
pub type Row = HashMap<String, Value>;
|
|
49
51
|
pub type WatchStream = UnboundedReceiver<RowEvent>;
|
|
@@ -272,6 +274,51 @@ impl DirSQL {
|
|
|
272
274
|
self.poll_once(timeout)
|
|
273
275
|
}
|
|
274
276
|
|
|
277
|
+
/// Split-phase wait helper used by async bindings that cannot safely
|
|
278
|
+
/// invoke the `extract` callback off the host thread (e.g. the napi-rs
|
|
279
|
+
/// TypeScript binding, where JS callbacks must run on the main JS
|
|
280
|
+
/// thread). Blocks up to `timeout` for raw file events and returns them
|
|
281
|
+
/// unprocessed. Pair with [`apply_file_events`](Self::apply_file_events)
|
|
282
|
+
/// to finish the pipeline on the correct thread.
|
|
283
|
+
#[doc(hidden)]
|
|
284
|
+
pub fn wait_file_events(&self, timeout: Duration) -> Result<Vec<FileEvent>> {
|
|
285
|
+
if self.inner.watch_thread_started.load(Ordering::SeqCst) {
|
|
286
|
+
return Err(DirSqlError::Watch(
|
|
287
|
+
"watch() is active; cannot mix with poll_events()".into(),
|
|
288
|
+
));
|
|
289
|
+
}
|
|
290
|
+
self.inner.poll_used.store(true, Ordering::SeqCst);
|
|
291
|
+
self.start_watching()?;
|
|
292
|
+
let guard = self
|
|
293
|
+
.inner
|
|
294
|
+
.watcher
|
|
295
|
+
.lock()
|
|
296
|
+
.map_err(|e| DirSqlError::Lock(e.to_string()))?;
|
|
297
|
+
let watcher = guard
|
|
298
|
+
.as_ref()
|
|
299
|
+
.ok_or_else(|| DirSqlError::Watch("watcher not started".into()))?;
|
|
300
|
+
let mut events = Vec::new();
|
|
301
|
+
if let Some(first) = watcher.recv_timeout(timeout) {
|
|
302
|
+
events.push(first);
|
|
303
|
+
events.extend(watcher.try_recv_all());
|
|
304
|
+
}
|
|
305
|
+
Ok(events)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/// Apply a batch of raw file events through the extract/DB update
|
|
309
|
+
/// pipeline. Counterpart to [`wait_file_events`](Self::wait_file_events).
|
|
310
|
+
/// Runs the `extract` callback inline, so the caller must invoke this on
|
|
311
|
+
/// a thread where that callback is safe to call (the JS main thread for
|
|
312
|
+
/// the TypeScript binding).
|
|
313
|
+
#[doc(hidden)]
|
|
314
|
+
pub fn apply_file_events(&self, events: Vec<FileEvent>) -> Vec<RowEvent> {
|
|
315
|
+
let mut out = Vec::new();
|
|
316
|
+
for fe in events {
|
|
317
|
+
out.extend(self.process_file_event(fe));
|
|
318
|
+
}
|
|
319
|
+
out
|
|
320
|
+
}
|
|
321
|
+
|
|
275
322
|
/// Channel-based watch API. Spawns a background thread that pushes
|
|
276
323
|
/// [`RowEvent`]s into the returned stream. Intended for long-running Rust
|
|
277
324
|
/// consumers (e.g. a CLI `watch` command). Can only be called once per
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
use dirsql::{DirSQL, Row, Table, Value};
|
|
1
|
+
use dirsql::{DirSQL, RawFileEvent, Row, Table, Value};
|
|
2
2
|
use futures_executor::block_on;
|
|
3
3
|
use futures_util::StreamExt;
|
|
4
4
|
use std::collections::HashMap;
|
|
@@ -398,3 +398,41 @@ fn it_streams_watch_error_events() {
|
|
|
398
398
|
other => panic!("expected error event, got: {other:?}"),
|
|
399
399
|
}
|
|
400
400
|
}
|
|
401
|
+
|
|
402
|
+
// The split-phase wait/apply API is used by async bindings (TypeScript) that
|
|
403
|
+
// cannot safely invoke the `extract` callback off the host thread. Verify the
|
|
404
|
+
// two halves round-trip to the same result as the combined `poll_events`.
|
|
405
|
+
#[test]
|
|
406
|
+
fn it_splits_wait_and_apply_for_async_bindings() {
|
|
407
|
+
let root = TempDir::new().unwrap();
|
|
408
|
+
let db = DirSQL::new(root.path(), vec![items_table()]).unwrap();
|
|
409
|
+
db.start_watching().unwrap();
|
|
410
|
+
|
|
411
|
+
// Empty wait returns no events and apply on empty returns no row events.
|
|
412
|
+
let empty = db.wait_file_events(Duration::from_millis(50)).unwrap();
|
|
413
|
+
assert!(empty.is_empty());
|
|
414
|
+
assert!(db.apply_file_events(Vec::new()).is_empty());
|
|
415
|
+
|
|
416
|
+
// Write a file, then drain raw FileEvents without running extract.
|
|
417
|
+
fs::write(root.path().join("new.txt"), "hello").unwrap();
|
|
418
|
+
let mut raw = Vec::new();
|
|
419
|
+
let deadline = std::time::Instant::now() + Duration::from_secs(2);
|
|
420
|
+
while raw.is_empty() && std::time::Instant::now() < deadline {
|
|
421
|
+
raw.extend(db.wait_file_events(Duration::from_millis(250)).unwrap());
|
|
422
|
+
}
|
|
423
|
+
assert!(!raw.is_empty(), "expected at least one raw file event");
|
|
424
|
+
assert!(
|
|
425
|
+
raw.iter()
|
|
426
|
+
.any(|e| matches!(e, RawFileEvent::Created(_) | RawFileEvent::Modified(_)))
|
|
427
|
+
);
|
|
428
|
+
|
|
429
|
+
// Apply runs extract and mutates the DB. Inserts land in the index.
|
|
430
|
+
let row_events = db.apply_file_events(raw);
|
|
431
|
+
assert!(!row_events.is_empty());
|
|
432
|
+
|
|
433
|
+
let rows = db.query("SELECT name FROM items").unwrap();
|
|
434
|
+
assert!(rows.iter().any(|r| matches!(
|
|
435
|
+
r.get("name"),
|
|
436
|
+
Some(Value::Text(name)) if name == "hello"
|
|
437
|
+
)));
|
|
438
|
+
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|