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.
Files changed (55) hide show
  1. {dirsql-0.2.0 → dirsql-0.2.2}/PKG-INFO +1 -1
  2. {dirsql-0.2.0 → dirsql-0.2.2}/packages/rust/src/lib.rs +47 -0
  3. {dirsql-0.2.0 → dirsql-0.2.2}/packages/rust/tests/sdk.rs +39 -1
  4. {dirsql-0.2.0 → dirsql-0.2.2}/pyproject.toml +1 -1
  5. {dirsql-0.2.0 → dirsql-0.2.2}/Cargo.lock +0 -0
  6. {dirsql-0.2.0 → dirsql-0.2.2}/Cargo.toml +0 -0
  7. {dirsql-0.2.0 → dirsql-0.2.2}/README.md +0 -0
  8. {dirsql-0.2.0 → dirsql-0.2.2}/packages/python/Cargo.toml +0 -0
  9. {dirsql-0.2.0 → dirsql-0.2.2}/packages/python/README.md +0 -0
  10. {dirsql-0.2.0 → dirsql-0.2.2}/packages/python/python/conftest.py +0 -0
  11. {dirsql-0.2.0 → dirsql-0.2.2}/packages/python/src/lib.rs +0 -0
  12. {dirsql-0.2.0 → dirsql-0.2.2}/packages/python/tests/__init__.py +0 -0
  13. {dirsql-0.2.0 → dirsql-0.2.2}/packages/python/tests/conftest.py +0 -0
  14. {dirsql-0.2.0 → dirsql-0.2.2}/packages/python/tests/integration/__init__.py +0 -0
  15. {dirsql-0.2.0 → dirsql-0.2.2}/packages/python/tests/integration/test_async_dirsql.py +0 -0
  16. {dirsql-0.2.0 → dirsql-0.2.2}/packages/python/tests/integration/test_binding.py +0 -0
  17. {dirsql-0.2.0 → dirsql-0.2.2}/packages/python/tests/integration/test_dirsql.py +0 -0
  18. {dirsql-0.2.0 → dirsql-0.2.2}/packages/python/tests/integration/test_docs_examples.py +0 -0
  19. {dirsql-0.2.0 → dirsql-0.2.2}/packages/python/tests/integration/test_docs_gaps.py +0 -0
  20. {dirsql-0.2.0 → dirsql-0.2.2}/packages/python/tests/integration/test_from_config.py +0 -0
  21. {dirsql-0.2.0 → dirsql-0.2.2}/packages/rust/Cargo.toml +0 -0
  22. {dirsql-0.2.0 → dirsql-0.2.2}/packages/rust/README.md +0 -0
  23. {dirsql-0.2.0 → dirsql-0.2.2}/packages/rust/benches/db_bench.rs +0 -0
  24. {dirsql-0.2.0 → dirsql-0.2.2}/packages/rust/benches/differ_bench.rs +0 -0
  25. {dirsql-0.2.0 → dirsql-0.2.2}/packages/rust/benches/matcher_bench.rs +0 -0
  26. {dirsql-0.2.0 → dirsql-0.2.2}/packages/rust/benches/scanner_bench.rs +0 -0
  27. {dirsql-0.2.0 → dirsql-0.2.2}/packages/rust/src/bin/dirsql.rs +0 -0
  28. {dirsql-0.2.0 → dirsql-0.2.2}/packages/rust/src/cli/mod.rs +0 -0
  29. {dirsql-0.2.0 → dirsql-0.2.2}/packages/rust/src/cli/router.rs +0 -0
  30. {dirsql-0.2.0 → dirsql-0.2.2}/packages/rust/src/cli/serialize.rs +0 -0
  31. {dirsql-0.2.0 → dirsql-0.2.2}/packages/rust/src/cli/server.rs +0 -0
  32. {dirsql-0.2.0 → dirsql-0.2.2}/packages/rust/src/config.rs +0 -0
  33. {dirsql-0.2.0 → dirsql-0.2.2}/packages/rust/src/db.rs +0 -0
  34. {dirsql-0.2.0 → dirsql-0.2.2}/packages/rust/src/differ.rs +0 -0
  35. {dirsql-0.2.0 → dirsql-0.2.2}/packages/rust/src/matcher.rs +0 -0
  36. {dirsql-0.2.0 → dirsql-0.2.2}/packages/rust/src/parser.rs +0 -0
  37. {dirsql-0.2.0 → dirsql-0.2.2}/packages/rust/src/scanner.rs +0 -0
  38. {dirsql-0.2.0 → dirsql-0.2.2}/packages/rust/src/watcher.rs +0 -0
  39. {dirsql-0.2.0 → dirsql-0.2.2}/packages/rust/tests/async_sdk.rs +0 -0
  40. {dirsql-0.2.0 → dirsql-0.2.2}/packages/rust/tests/cli_e2e.rs +0 -0
  41. {dirsql-0.2.0 → dirsql-0.2.2}/packages/rust/tests/cli_integration.rs +0 -0
  42. {dirsql-0.2.0 → dirsql-0.2.2}/packages/rust/tests/docs_examples.rs +0 -0
  43. {dirsql-0.2.0 → dirsql-0.2.2}/packages/rust/tests/docs_gaps.rs +0 -0
  44. {dirsql-0.2.0 → dirsql-0.2.2}/packages/rust/tests/from_config.rs +0 -0
  45. {dirsql-0.2.0 → dirsql-0.2.2}/packages/rust/tests/readonly_query.rs +0 -0
  46. {dirsql-0.2.0 → dirsql-0.2.2}/python/dirsql/__init__.py +0 -0
  47. {dirsql-0.2.0 → dirsql-0.2.2}/python/dirsql/_async.py +0 -0
  48. {dirsql-0.2.0 → dirsql-0.2.2}/python/dirsql/_cli/__init__.py +0 -0
  49. {dirsql-0.2.0 → dirsql-0.2.2}/python/dirsql/_cli/binary_path.py +0 -0
  50. {dirsql-0.2.0 → dirsql-0.2.2}/python/dirsql/_cli/binary_path_test.py +0 -0
  51. {dirsql-0.2.0 → dirsql-0.2.2}/python/dirsql/_cli/is_windows.py +0 -0
  52. {dirsql-0.2.0 → dirsql-0.2.2}/python/dirsql/_cli/is_windows_test.py +0 -0
  53. {dirsql-0.2.0 → dirsql-0.2.2}/python/dirsql/_cli/main.py +0 -0
  54. {dirsql-0.2.0 → dirsql-0.2.2}/python/dirsql/_cli/main_test.py +0 -0
  55. {dirsql-0.2.0 → dirsql-0.2.2}/python/dirsql/test_async.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dirsql
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Requires-Dist: pytest>=8 ; extra == 'dev'
5
5
  Requires-Dist: pytest-describe>=2 ; extra == 'dev'
6
6
  Requires-Dist: pytest-asyncio>=0.23 ; extra == 'dev'
@@ -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
+ }
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "dirsql"
7
- version = "0.2.0"
7
+ version = "0.2.2"
8
8
  description = "Ephemeral SQL index over a local directory"
9
9
  license = "MIT"
10
10
  requires-python = ">=3.10"
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