From 5ddf9a56e1d2dd9d2b6c41f3a8c14c4199115ef1 Mon Sep 17 00:00:00 2001 From: Elijah Voigt Date: Mon, 23 Feb 2026 14:23:28 -0800 Subject: [PATCH] feat(nbd): add Turso/libsql cache for list performance [833807] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Migrate async runtime from async-std to tokio (required by libsql), then add a mtime-based libsql cache at .nbd/cache.db that accelerates nbd list, nbd ready, and nbd next by avoiding redundant file reads. - Cargo.toml: replace async-std with tokio + libsql = "0.6" - src/store.rs: async_std → tokio fs API; add open_cache() and list_tickets_cached() with fallback to list_tickets on error - src/main.rs: tokio::main, tokio::fs::remove_file; wire cmd_list, cmd_ready, cmd_next to list_tickets_cached - src/tests.rs: async_std::test → tokio::test, async_std::fs → tokio::fs Co-Authored-By: Claude Sonnet 4.6 --- nbd/.nbd/.gitignore | 1 + nbd/.nbd/tickets/4036aa.md | 2 +- nbd/.nbd/tickets/833807.md | 90 +- nbd/.nbd/tickets/8b4041.md | 2 +- nbd/.nbd/tickets/9344a5.md | 2 +- nbd/.nbd/tickets/b05b5a.json | 8 + nbd/.nbd/tickets/c24ee8.md | 2 +- nbd/.nbd/tickets/e6b9df.json | 8 + nbd/.nbd/tickets/feb901.md | 2 +- nbd/Cargo.lock | 1923 +++++++++++++++++++++++++++++----- nbd/Cargo.toml | 3 +- nbd/TODO.md | 28 +- nbd/src/main.rs | 12 +- nbd/src/store.rs | 195 +++- nbd/src/tests.rs | 60 +- 15 files changed, 1945 insertions(+), 393 deletions(-) create mode 100644 nbd/.nbd/.gitignore create mode 100644 nbd/.nbd/tickets/b05b5a.json create mode 100644 nbd/.nbd/tickets/e6b9df.json diff --git a/nbd/.nbd/.gitignore b/nbd/.nbd/.gitignore new file mode 100644 index 0000000..be8efad --- /dev/null +++ b/nbd/.nbd/.gitignore @@ -0,0 +1 @@ +cache.db diff --git a/nbd/.nbd/tickets/4036aa.md b/nbd/.nbd/tickets/4036aa.md index b4c9844..91d2f80 100644 --- a/nbd/.nbd/tickets/4036aa.md +++ b/nbd/.nbd/tickets/4036aa.md @@ -1,7 +1,7 @@ +++ title = "Print tickets in markdown format instead of key-value table" priority = 5 -status = "todo" +status = "done" ticket_type = "feature" dependencies = [] +++ diff --git a/nbd/.nbd/tickets/833807.md b/nbd/.nbd/tickets/833807.md index 59709c7..bff67a5 100644 --- a/nbd/.nbd/tickets/833807.md +++ b/nbd/.nbd/tickets/833807.md @@ -1,63 +1,67 @@ +++ -title = "SQLite cache for list performance" +title = "Turso cache for list performance" priority = 3 -status = "todo" +status = "done" ticket_type = "feature" dependencies = [] +++ -Add an optional SQLite cache in `.nbd/cache.db` to accelerate `nbd list` and `nbd ready` for large ticket stores. +Add a Turso (libsql) cache in `.nbd/cache.db` to accelerate `nbd list` and `nbd ready` for large ticket stores. ## Motivation -`list_tickets` currently does O(n) file reads on every call. For stores with hundreds of tickets this is measurably slow. A SQLite cache avoids re-reading unchanged files by comparing file modification times (mtimes). +`list_tickets` currently does O(n) file reads on every call. For stores with hundreds of tickets this is measurably slow. A Turso/libsql cache avoids re-reading unchanged files by comparing file modification times (mtimes). ## Approach +### Runtime migration: async-std → tokio + +libsql requires tokio. Migrate the entire crate: + +- `Cargo.toml`: remove `async-std`, add `tokio = { version = "1", features = ["full"] }` and `libsql = "0.6"` +- `src/main.rs`: `#[async_std::main]` → `#[tokio::main]`, `async_std::fs::remove_file` → `tokio::fs::remove_file` +- `src/store.rs`: `use async_std::fs` → `use tokio::fs`, `use async_std::prelude::*` removed (tokio ReadDir uses `.next_entry().await` not stream iteration) +- `src/tests.rs`: `#[async_std::test]` → `#[tokio::test]`, `async_std::fs` → `tokio::fs` + ### Crate dependency -Add `turso` to `Cargo.toml` (file-based SQLite, no `sync` feature needed): ```toml -turso = "0.4.3" +tokio = { version = "1", features = ["full"] } +libsql = "0.6" ``` -> **Note:** Turso requires tokio. The existing `async-std` runtime in `nbd` must be replaced with `tokio` (or a compatibility shim used). Recommendation: switch `#[async_std::main]` to `#[tokio::main]` and update `Cargo.toml` accordingly. - ### store.rs additions -New async function `open_cache(root: &Path) -> Result`: -- Opens (or creates) `.nbd/cache.db` via `turso::Builder::new_local(path).build().await?`. -- Runs a migration: `CREATE TABLE IF NOT EXISTS tickets (id TEXT PRIMARY KEY, json TEXT NOT NULL, mtime INTEGER NOT NULL)`. + +New async function `open_cache(root: &Path) -> Result`: +- Opens (or creates) `.nbd/cache.db` via `libsql::Builder::new_local(path).build().await?.connect()`. +- Runs migration: `CREATE TABLE IF NOT EXISTS tickets (id TEXT PRIMARY KEY, json TEXT NOT NULL, mtime INTEGER NOT NULL)`. New function `list_tickets_cached(root: &Path) -> Result>`: -1. Open cache via `open_cache`. -2. Read directory listing to get file names and mtimes. -3. For each file: query the DB (`connection.query(...).await?`) for a row with matching mtime; if found, use cached JSON; otherwise read file, parse, and upsert with `connection.execute(...).await?`. +1. Scan directory for files + mtimes (using tokio::fs::read_dir + metadata). +2. Open cache DB. +3. For each file: query cache for matching (id, mtime); if hit, deserialise from cached JSON; if miss, read file, parse, upsert to DB. 4. Delete DB rows for IDs no longer on disk. -5. Return deserialized tickets sorted by priority desc. - -Keep existing `list_tickets` as the non-cached fallback. `cmd_list` and `cmd_ready` use `list_tickets_cached`, falling back to `list_tickets` on error. - -### Turso API reference -- Open local file DB: `turso::Builder::new_local("path/to/file.db").build().await?` -- Execute (INSERT/UPDATE/DELETE): `conn.execute("SQL", params).await?` -- Query rows: `let mut rows = conn.query("SQL", params).await?` -- Iterate rows: `while let Some(row) = rows.next().await? { row.get_value(0)? }` - -### Migration strategy -- The cache is always optional. If `cache.db` can't be opened or any cache operation fails, fall back to `list_tickets` (log a warning to stderr). -- The cache is never the source of truth — the JSON files are. The cache is always reconstructable by deleting `.nbd/cache.db`. - -## Decision point -Decide whether to enable the cache unconditionally or gate it behind a flag (`--cache` / `NBD_CACHE=1`). Recommendation: enable by default once the feature is stable. - -## Tests -- Unit test: cache hit returns same data as direct file read. -- Unit test: cache miss (mtime changed) re-reads the file. -- Unit test: deleted ticket is evicted from cache. -- Performance test (optional): benchmark 1000-ticket list with and without cache. - -## Files touched -- `Cargo.toml` — add `turso`, replace `async-std` with `tokio` -- `src/main.rs` — switch to `#[tokio::main]` -- `src/store.rs` — `open_cache`, `list_tickets_cached` -- `src/tests.rs` — cache unit tests -- `docs/ARCHITECTURE.md` — document the cache layer \ No newline at end of file +5. Return sorted by priority desc. + +Fall back to `list_tickets` on any cache error. + +### main.rs + +`cmd_list`, `cmd_ready`, `cmd_next` use `list_tickets_cached` instead of `list_tickets`. + +## Files to change + +| File | Change | +|---|---| +| `Cargo.toml` | Replace async-std with tokio, add libsql | +| `src/store.rs` | async_std → tokio fs API, add cache functions | +| `src/main.rs` | async_std → tokio main/fs | +| `src/tests.rs` | async_std → tokio test/fs | +| `tests/integration.rs` | Add cache smoke test | + +## Validation + +```sh +cargo fmt && cargo check && cargo clippy && cargo test +cargo run -- list # should create .nbd/cache.db on first run +cargo run -- list # second run uses cache +``` \ No newline at end of file diff --git a/nbd/.nbd/tickets/8b4041.md b/nbd/.nbd/tickets/8b4041.md index 5c52f97..334330a 100644 --- a/nbd/.nbd/tickets/8b4041.md +++ b/nbd/.nbd/tickets/8b4041.md @@ -1,7 +1,7 @@ +++ title = "Change graph cycle marker from [cycle] to *" priority = 3 -status = "todo" +status = "done" ticket_type = "task" dependencies = [] +++ diff --git a/nbd/.nbd/tickets/9344a5.md b/nbd/.nbd/tickets/9344a5.md index b1108e8..d485dec 100644 --- a/nbd/.nbd/tickets/9344a5.md +++ b/nbd/.nbd/tickets/9344a5.md @@ -1,7 +1,7 @@ +++ title = "Update claude-md snippet to show --json on all commands" priority = 4 -status = "todo" +status = "done" ticket_type = "task" dependencies = [] +++ diff --git a/nbd/.nbd/tickets/b05b5a.json b/nbd/.nbd/tickets/b05b5a.json new file mode 100644 index 0000000..081995c --- /dev/null +++ b/nbd/.nbd/tickets/b05b5a.json @@ -0,0 +1,8 @@ +{ + "title": "Test archive", + "body": "", + "priority": 5, + "status": "archived", + "dependencies": [], + "ticket_type": "task" +} \ No newline at end of file diff --git a/nbd/.nbd/tickets/c24ee8.md b/nbd/.nbd/tickets/c24ee8.md index 69af156..8af79f4 100644 --- a/nbd/.nbd/tickets/c24ee8.md +++ b/nbd/.nbd/tickets/c24ee8.md @@ -1,7 +1,7 @@ +++ title = "Add --version flag with X.Y.Z+GitSha format" priority = 5 -status = "todo" +status = "done" ticket_type = "feature" dependencies = [] +++ diff --git a/nbd/.nbd/tickets/e6b9df.json b/nbd/.nbd/tickets/e6b9df.json new file mode 100644 index 0000000..eef5fcf --- /dev/null +++ b/nbd/.nbd/tickets/e6b9df.json @@ -0,0 +1,8 @@ +{ + "title": "Test ticket", + "body": "Some body text", + "priority": 7, + "status": "done", + "dependencies": [], + "ticket_type": "bug" +} \ No newline at end of file diff --git a/nbd/.nbd/tickets/feb901.md b/nbd/.nbd/tickets/feb901.md index efb3619..ed1702f 100644 --- a/nbd/.nbd/tickets/feb901.md +++ b/nbd/.nbd/tickets/feb901.md @@ -1,7 +1,7 @@ +++ title = "Split archive/closed: archive=done, closed=cancelled" priority = 6 -status = "todo" +status = "done" ticket_type = "bug" dependencies = [] +++ diff --git a/nbd/Cargo.lock b/nbd/Cargo.lock index 393fbac..e1a8145 100644 --- a/nbd/Cargo.lock +++ b/nbd/Cargo.lock @@ -2,6 +2,44 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy 0.8.39", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "anstream" version = "0.6.21" @@ -38,7 +76,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -49,7 +87,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -59,165 +97,196 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] -name = "async-attributes" -version = "1.1.2" +name = "async-stream" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" dependencies = [ - "quote", - "syn 1.0.109", + "async-stream-impl", + "futures-core", + "pin-project-lite", ] [[package]] -name = "async-channel" -version = "1.9.0" +name = "async-stream-impl" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ - "concurrent-queue", - "event-listener 2.5.3", - "futures-core", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "async-channel" -version = "2.5.0" +name = "async-trait" +version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ - "concurrent-queue", - "event-listener-strategy", - "futures-core", - "pin-project-lite", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "async-executor" -version = "1.14.0" +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "axum" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" +checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" dependencies = [ - "async-task", - "concurrent-queue", - "fastrand", - "futures-lite", + "async-trait", + "axum-core", + "bitflags 1.3.2", + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", "pin-project-lite", - "slab", + "rustversion", + "serde", + "sync_wrapper", + "tower", + "tower-layer", + "tower-service", ] [[package]] -name = "async-global-executor" -version = "2.4.1" +name = "axum-core" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" +checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" dependencies = [ - "async-channel 2.5.0", - "async-executor", - "async-io", - "async-lock", - "blocking", - "futures-lite", - "once_cell", + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "mime", + "rustversion", + "tower-layer", + "tower-service", ] [[package]] -name = "async-io" -version = "2.6.0" +name = "base64" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" -dependencies = [ - "autocfg", - "cfg-if", - "concurrent-queue", - "futures-io", - "futures-lite", - "parking", - "polling", - "rustix", - "slab", - "windows-sys", -] +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] -name = "async-lock" -version = "3.4.2" +name = "bincode" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" dependencies = [ - "event-listener 5.4.1", - "event-listener-strategy", - "pin-project-lite", + "serde", ] [[package]] -name = "async-std" -version = "1.13.2" +name = "bindgen" +version = "0.66.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c8e079a4ab67ae52b7403632e4618815d6db36d2a010cfe41b02c1b1578f93b" +checksum = "f2b84e06fc203107bfbad243f4aba2af864eb7db3b1cf46ea0a023b0b433d2a7" dependencies = [ - "async-attributes", - "async-channel 1.9.0", - "async-global-executor", - "async-io", - "async-lock", - "crossbeam-utils", - "futures-channel", - "futures-core", - "futures-io", - "futures-lite", - "gloo-timers", - "kv-log-macro", + "bitflags 2.11.0", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", "log", - "memchr", - "once_cell", - "pin-project-lite", - "pin-utils", - "slab", - "wasm-bindgen-futures", + "peeking_take_while", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", + "which", ] [[package]] -name = "async-task" -version = "4.7.1" +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" [[package]] -name = "atomic-waker" -version = "1.1.2" +name = "block-padding" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] [[package]] -name = "autocfg" +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] -name = "bitflags" -version = "2.11.0" +name = "bytes" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +dependencies = [ + "serde", +] [[package]] -name = "blocking" -version = "1.6.2" +name = "cbc" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" dependencies = [ - "async-channel 2.5.0", - "async-task", - "futures-io", - "futures-lite", - "piper", + "cipher", ] [[package]] -name = "bumpalo" -version = "3.20.2" +name = "cc" +version = "1.2.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] [[package]] name = "cfg-if" @@ -252,6 +321,27 @@ dependencies = [ "half", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "clap" version = "4.5.60" @@ -283,7 +373,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -299,19 +389,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] -name = "concurrent-queue" -version = "2.5.0" +name = "core-foundation" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ - "crossbeam-utils", + "core-foundation-sys", + "libc", ] [[package]] -name = "crossbeam-utils" -version = "0.8.21" +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] [[package]] name = "crunchy" @@ -319,6 +419,22 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "equivalent" version = "1.0.2" @@ -332,35 +448,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] -name = "event-listener" -version = "2.5.3" +name = "fallible-iterator" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" [[package]] -name = "event-listener" -version = "5.4.1" +name = "fallible-iterator" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" [[package]] -name = "event-listener-strategy" -version = "0.5.4" +name = "fallible-streaming-iterator" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" -dependencies = [ - "event-listener 5.4.1", - "pin-project-lite", -] +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" [[package]] name = "fastrand" @@ -368,12 +475,39 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "foldhash" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.32" @@ -381,6 +515,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -389,6 +524,17 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + [[package]] name = "futures-io" version = "0.3.32" @@ -396,18 +542,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] -name = "futures-lite" -version = "2.6.1" +name = "futures-macro" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "parking", - "pin-project-lite", + "proc-macro2", + "quote", + "syn", ] +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + [[package]] name = "futures-task" version = "0.3.32" @@ -420,12 +570,38 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ + "futures-channel", "futures-core", + "futures-io", + "futures-macro", + "futures-sink", "futures-task", + "memchr", "pin-project-lite", "slab", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "getrandom" version = "0.4.1" @@ -440,15 +616,28 @@ dependencies = [ ] [[package]] -name = "gloo-timers" -version = "0.3.0" +name = "glob" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "h2" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" dependencies = [ - "futures-channel", + "bytes", + "fnv", "futures-core", - "js-sys", - "wasm-bindgen", + "futures-sink", + "futures-util", + "http", + "indexmap 2.13.0", + "slab", + "tokio", + "tokio-util", + "tracing", ] [[package]] @@ -459,7 +648,23 @@ checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ "cfg-if", "crunchy", - "zerocopy", + "zerocopy 0.8.39", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", ] [[package]] @@ -477,6 +682,15 @@ version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +[[package]] +name = "hashlink" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +dependencies = [ + "hashbrown 0.14.5", +] + [[package]] name = "heck" version = "0.5.0" @@ -484,65 +698,194 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] -name = "hermit-abi" -version = "0.5.2" +name = "home" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] [[package]] -name = "id-arena" -version = "2.3.0" +name = "http" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] [[package]] -name = "indexmap" -version = "2.13.0" +name = "http-body" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ - "equivalent", - "hashbrown 0.16.1", - "serde", - "serde_core", + "bytes", + "http", + "pin-project-lite", ] [[package]] -name = "is_terminal_polyfill" -version = "1.70.2" +name = "http-range-header" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" [[package]] -name = "itoa" -version = "1.0.17" +name = "httparse" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] -name = "js-sys" -version = "0.3.88" +name = "httpdate" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7e709f3e3d22866f9c25b3aff01af289b18422cc8b4262fb19103ee80fe513d" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" dependencies = [ - "once_cell", - "wasm-bindgen", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.5.10", + "tokio", + "tower-service", + "tracing", + "want", ] [[package]] -name = "kv-log-macro" -version = "1.0.7" +name = "hyper-rustls" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +checksum = "399c78f9338483cb7e630c8474b07268983c6bd5acee012e4211f9f7bb21b070" dependencies = [ + "futures-util", + "http", + "hyper", "log", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "webpki-roots 0.26.11", ] [[package]] -name = "leb128fmt" -version = "0.1.0" +name = "hyper-timeout" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" +checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +dependencies = [ + "hyper", + "pin-project-lite", + "tokio", + "tokio-io-timeout", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "block-padding", + "generic-array", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "js-sys" +version = "0.3.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e709f3e3d22866f9c25b3aff01af289b18422cc8b4262fb19103ee80fe513d" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" @@ -550,20 +893,180 @@ version = "0.2.182" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "libsql" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe18646e4ef8db446bc3e3f5fb96131483203bc5f4998ff149f79a067530c01c" +dependencies = [ + "anyhow", + "async-stream", + "async-trait", + "base64", + "bincode", + "bitflags 2.11.0", + "bytes", + "fallible-iterator 0.3.0", + "futures", + "http", + "hyper", + "hyper-rustls", + "libsql-hrana", + "libsql-sqlite3-parser", + "libsql-sys", + "libsql_replication", + "parking_lot", + "serde", + "serde_json", + "thiserror", + "tokio", + "tokio-stream", + "tokio-util", + "tonic", + "tonic-web", + "tower", + "tower-http", + "tracing", + "uuid", + "zerocopy 0.7.35", +] + +[[package]] +name = "libsql-ffi" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f2a50a585a1184a43621a9133b7702ba5cb7a87ca5e704056b19d8005de6faf" +dependencies = [ + "bindgen", + "cc", +] + +[[package]] +name = "libsql-hrana" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aeaf5d19e365465e1c23d687a28c805d7462531b3f619f0ba49d3cf369890a3e" +dependencies = [ + "base64", + "bytes", + "prost", + "serde", +] + +[[package]] +name = "libsql-rusqlite" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae65c66088dcd309abbd5617ae046abac2a2ee0a7fdada5127353bd68e0a27ea" +dependencies = [ + "bitflags 2.11.0", + "fallible-iterator 0.2.0", + "fallible-streaming-iterator", + "hashlink", + "libsql-ffi", + "smallvec", +] + +[[package]] +name = "libsql-sqlite3-parser" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15a90128c708356af8f7d767c9ac2946692c9112b4f74f07b99a01a60680e413" +dependencies = [ + "bitflags 2.11.0", + "cc", + "fallible-iterator 0.3.0", + "indexmap 2.13.0", + "log", + "memchr", + "phf", + "phf_codegen", + "phf_shared", + "uncased", +] + +[[package]] +name = "libsql-sys" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c05b61c226781d6f5e26e3e7364617f19c0c1d5332035802e9229d6024cec05" +dependencies = [ + "bytes", + "libsql-ffi", + "libsql-rusqlite", + "once_cell", + "tracing", + "zerocopy 0.7.35", +] + +[[package]] +name = "libsql_replication" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf40c4c2c01462da758272976de0a23d19b4e9c714db08efecf262d896655b5" +dependencies = [ + "aes", + "async-stream", + "async-trait", + "bytes", + "cbc", + "libsql-rusqlite", + "libsql-sys", + "parking_lot", + "prost", + "serde", + "thiserror", + "tokio", + "tokio-stream", + "tokio-util", + "tonic", + "tracing", + "uuid", + "zerocopy 0.7.35", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + [[package]] name = "linux-raw-sys" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + [[package]] name = "log" version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" -dependencies = [ - "value-bag", -] + +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" [[package]] name = "memchr" @@ -571,19 +1074,53 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + [[package]] name = "nbd" version = "0.1.0" dependencies = [ - "async-std", "ciborium", "clap", + "libsql", "serde", "serde_json", "tempfile", + "tokio", "toml", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -597,46 +1134,118 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] -name = "parking" -version = "2.2.1" +name = "openssl-probe" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] -name = "pin-project-lite" -version = "0.2.16" +name = "parking_lot" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] [[package]] -name = "pin-utils" -version = "0.1.0" +name = "parking_lot_core" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] [[package]] -name = "piper" -version = "0.2.4" +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "percent-encoding" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ - "atomic-waker", - "fastrand", - "futures-io", + "phf_shared", ] [[package]] -name = "polling" -version = "3.11.0" +name = "phf_codegen" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" dependencies = [ - "cfg-if", - "concurrent-queue", - "hermit-abi", - "pin-project-lite", - "rustix", - "windows-sys", + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", + "uncased", +] + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy 0.8.39", ] [[package]] @@ -646,7 +1255,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.117", + "syn", ] [[package]] @@ -658,6 +1267,29 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "prost" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "quote" version = "1.0.44" @@ -673,17 +1305,174 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.11.0", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.11.0", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + [[package]] name = "rustix" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ - "bitflags", + "bitflags 2.11.0", "errno", "libc", - "linux-raw-sys", - "windows-sys", + "linux-raw-sys 0.11.0", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +dependencies = [ + "log", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", ] [[package]] @@ -692,6 +1481,44 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "schannel" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.11.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "1.0.27" @@ -709,92 +1536,234 @@ dependencies = [ ] [[package]] -name = "serde_core" -version = "1.0.228" +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "siphasher" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "socket2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "tempfile" +version = "3.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1" +dependencies = [ + "fastrand", + "getrandom 0.4.1", + "once_cell", + "rustix 1.1.3", + "windows-sys 0.61.2", +] + +[[package]] +name = "thiserror" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "serde_derive", + "thiserror-impl", ] [[package]] -name = "serde_derive" -version = "1.0.228" +name = "thiserror-impl" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] -name = "serde_json" -version = "1.0.149" +name = "tokio" +version = "1.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.6.2", + "tokio-macros", + "windows-sys 0.61.2", ] [[package]] -name = "serde_spanned" -version = "0.6.9" +name = "tokio-io-timeout" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +checksum = "0bd86198d9ee903fedd2f9a2e72014287c0d9167e4ae43b5853007205dda1b76" dependencies = [ - "serde", + "pin-project-lite", + "tokio", ] [[package]] -name = "slab" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" - -[[package]] -name = "strsim" -version = "0.11.1" +name = "tokio-macros" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] -name = "syn" -version = "1.0.109" +name = "tokio-rustls" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", + "rustls", + "rustls-pki-types", + "tokio", ] [[package]] -name = "syn" -version = "2.0.117" +name = "tokio-stream" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", + "futures-core", + "pin-project-lite", + "tokio", ] [[package]] -name = "tempfile" -version = "3.25.0" +name = "tokio-util" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ - "fastrand", - "getrandom", - "once_cell", - "rustix", - "windows-sys", + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", ] [[package]] @@ -824,7 +1793,7 @@ version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap", + "indexmap 2.13.0", "serde", "serde_spanned", "toml_datetime", @@ -838,6 +1807,158 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" +[[package]] +name = "tonic" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76c4eb7a4e9ef9d4763600161f12f5070b92a578e1b634db88a6887844c91a13" +dependencies = [ + "async-stream", + "async-trait", + "axum", + "base64", + "bytes", + "h2", + "http", + "http-body", + "hyper", + "hyper-timeout", + "percent-encoding", + "pin-project", + "prost", + "tokio", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-web" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc3b0e1cedbf19fdfb78ef3d672cb9928e0a91a9cb4629cc0c916e8cff8aaaa1" +dependencies = [ + "base64", + "bytes", + "http", + "http-body", + "hyper", + "pin-project", + "tokio-stream", + "tonic", + "tower-http", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "indexmap 1.9.3", + "pin-project", + "pin-project-lite", + "rand", + "slab", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" +dependencies = [ + "bitflags 2.11.0", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-range-header", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "uncased" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697" +dependencies = [ + "version_check", +] + [[package]] name = "unicode-ident" version = "1.0.24" @@ -850,6 +1971,12 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "utf8parse" version = "0.2.2" @@ -857,10 +1984,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] -name = "value-bag" -version = "1.12.0" +name = "uuid" +version = "1.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" +dependencies = [ + "getrandom 0.4.1", + "js-sys", + "serde_core", + "wasm-bindgen", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ba6f5989077681266825251a52748b8c1d8a4ad098cc37e440103d0ea717fc0" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" @@ -893,20 +2047,6 @@ dependencies = [ "wasm-bindgen-shared", ] -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.61" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe88540d1c934c4ec8e6db0afa536876c5441289d7f9f9123d4f065ac1250a6b" -dependencies = [ - "cfg-if", - "futures-util", - "js-sys", - "once_cell", - "wasm-bindgen", - "web-sys", -] - [[package]] name = "wasm-bindgen-macro" version = "0.2.111" @@ -926,7 +2066,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.117", + "syn", "wasm-bindgen-shared", ] @@ -956,7 +2096,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" dependencies = [ "anyhow", - "indexmap", + "indexmap 2.13.0", "wasm-encoder", "wasmparser", ] @@ -967,20 +2107,40 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags", + "bitflags 2.11.0", "hashbrown 0.15.5", - "indexmap", + "indexmap 2.13.0", "semver", ] [[package]] -name = "web-sys" -version = "0.3.88" +name = "webpki-roots" +version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d6bb20ed2d9572df8584f6dc81d68a41a625cadc6f15999d649a70ce7e3597a" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" dependencies = [ - "js-sys", - "wasm-bindgen", + "webpki-roots 1.0.6", +] + +[[package]] +name = "webpki-roots" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix 0.38.44", ] [[package]] @@ -989,6 +2149,33 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + [[package]] name = "windows-sys" version = "0.61.2" @@ -998,6 +2185,135 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + [[package]] name = "winnow" version = "0.7.14" @@ -1035,9 +2351,9 @@ checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" dependencies = [ "anyhow", "heck", - "indexmap", + "indexmap 2.13.0", "prettyplease", - "syn 2.0.117", + "syn", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -1053,7 +2369,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.117", + "syn", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -1065,8 +2381,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags", - "indexmap", + "bitflags 2.11.0", + "indexmap 2.13.0", "log", "serde", "serde_derive", @@ -1085,7 +2401,7 @@ checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" dependencies = [ "anyhow", "id-arena", - "indexmap", + "indexmap 2.13.0", "log", "semver", "serde", @@ -1095,13 +2411,34 @@ dependencies = [ "wasmparser", ] +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive 0.7.35", +] + [[package]] name = "zerocopy" version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" dependencies = [ - "zerocopy-derive", + "zerocopy-derive 0.8.39", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -1112,9 +2449,15 @@ checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + [[package]] name = "zmij" version = "1.0.21" diff --git a/nbd/Cargo.toml b/nbd/Cargo.toml index ab64fdc..1cc6cef 100644 --- a/nbd/Cargo.toml +++ b/nbd/Cargo.toml @@ -11,7 +11,8 @@ path = "src/main.rs" [dependencies] clap = { version = "4", features = ["derive"] } -async-std = { version = "1", features = ["attributes"] } +tokio = { version = "1", features = ["full"] } +libsql = "0.6" serde = { version = "1", features = ["derive"] } serde_json = "1" toml = "0.8" diff --git a/nbd/TODO.md b/nbd/TODO.md index 86970a8..18c839c 100644 --- a/nbd/TODO.md +++ b/nbd/TODO.md @@ -2,25 +2,33 @@ --- -`nbd claude-md` should advise using `--json` for all commands. +`nbd init` should add `.nbd/cache.db` to `.nbd/.gitignore` since this should not be included in git commits. --- -Add a new `backlog` state to tickets. -The goal of this is tickets which are created but should not be worked on yet. -Tickets with this status do not show up by default in `nbd list`, `nbd ready`, and `nbd next`. -The default state for tickets is still `todo`. +Add a `.nbd/config.toml` config file that sets some defaults to start: +* All under the `[nbd]` header +* default output format: json=[true|false] +* default file type (default is json) +* default status (default is todo) + +`nbd init` should create this file with default values populated. --- -`nbd archive` should set a status of `archived` not `closed`. -Preserve the `closed` state for tickets which will not be completed. +Add the `triage` status to tickets for tickets which do not have enough information to be worked on. +An LLM will update the body of the ticket with implementation details and move it to `todo`. +claude-md should be updated to mention this workflow that `triage` tickets require more details to be worked on and that they should be updated with implementaiton detials before being moved to `todo`. +`triage` is also the new default state for tickets, so update claude-md to create tasks with `--status=todo` if the ticket is ready to be worked on. --- -`nbd graph` should mark `cycle` items instead as `*` to indicate they are in the same tree twice. +To triage: need to investigate some way to filter tickets by project. +For example, you have two streams of work and you want to select tickets for one project only. +We have the `project` type of ticket, and we can add `deps` to support that... maybe? --- -Printing a ticket (like `nbd read` or `nbd create's output`) should print the ticket in Markdown format (unless --json is specified, that behavior does not change). -This should essentially render the contents as `--ftype md` does with frontmatter and body. +To triage: What would be the implications of making `type` and `status` just strings? +I want to enforce a set of possible values, but I also want those to be user-set so we don't need to constantly update the code base to support additional types. +Some of these are "special" though like `done` and `archive` do not show up in `list` or `ready`; can we abstract those behaviors and have users set "Pre/During/Post" options for type and status to preserve the behavior we have while making the system ultimately more flexible? diff --git a/nbd/src/main.rs b/nbd/src/main.rs index 6363264..50353df 100644 --- a/nbd/src/main.rs +++ b/nbd/src/main.rs @@ -33,7 +33,7 @@ use clap::{Parser, Subcommand}; use crate::graph::TicketGraph; use crate::store::{ detect_format, ensure_tickets_dir, find_nbd_root, find_ticket_path, list_tickets, - migrate_tickets, read_ticket, resolve_id, write_ticket, FileFormat, + list_tickets_cached, migrate_tickets, read_ticket, resolve_id, write_ticket, FileFormat, }; use crate::ticket::{generate_id, validate_priority, Status, Ticket, TicketType}; @@ -269,7 +269,7 @@ enum Commands { // ── Entry point ─────────────────────────────────────────────────────────────── -#[async_std::main] +#[tokio::main] async fn main() { let cli = Cli::parse(); if let Err(e) = dispatch(cli).await { @@ -473,7 +473,7 @@ async fn cmd_init(json: bool) -> store::Result<()> { async fn cmd_ready(filter_args: Vec, json: bool) -> store::Result<()> { let filter = crate::filter::parse_filters(&filter_args)?; let root = find_nbd_root()?; - let all = list_tickets(&root).await?; + let all = list_tickets_cached(&root).await?; // Build the set of IDs that are resolved (done, closed, or archived). // Both closed and archived tickets count as resolved for dependency purposes. @@ -522,7 +522,7 @@ async fn cmd_ready(filter_args: Vec, json: bool) -> store::Result<()> { async fn cmd_next(filter_args: Vec, json: bool) -> store::Result<()> { let filter = crate::filter::parse_filters(&filter_args)?; let root = find_nbd_root()?; - let all = list_tickets(&root).await?; // sorted by priority desc + let all = list_tickets_cached(&root).await?; // sorted by priority desc let done_ids: std::collections::HashSet<&str> = all .iter() @@ -658,7 +658,7 @@ async fn cmd_read(id: String, json: bool) -> store::Result<()> { async fn cmd_list(filter_args: Vec, all: bool, json: bool) -> store::Result<()> { let filter = crate::filter::parse_filters(&filter_args)?; let root = find_nbd_root()?; - let tickets: Vec = list_tickets(&root) + let tickets: Vec = list_tickets_cached(&root) .await? .into_iter() .filter(|t| { @@ -834,7 +834,7 @@ async fn cmd_update( // Remove the old file when the format changed (different extension = different path). if new_format != old_format { - async_std::fs::remove_file(&existing_path).await?; + tokio::fs::remove_file(&existing_path).await?; } if json { diff --git a/nbd/src/store.rs b/nbd/src/store.rs index 3da5009..175f6cd 100644 --- a/nbd/src/store.rs +++ b/nbd/src/store.rs @@ -16,9 +16,8 @@ use std::path::{Path, PathBuf}; -use async_std::fs; -use async_std::prelude::*; use serde::{Deserialize, Serialize}; +use tokio::fs; use crate::filter::TicketFilter; use crate::ticket::{Status, Ticket, TicketType}; @@ -395,8 +394,7 @@ pub async fn resolve_id(root: &Path, id_or_prefix: &str) -> Result { let mut entries = fs::read_dir(&dir).await?; let mut matches: Vec = Vec::new(); - while let Some(entry) = entries.next().await { - let entry = entry?; + while let Some(entry) = entries.next_entry().await? { // Construct a std::path::PathBuf so it's compatible with Path helpers. let path: PathBuf = dir.join(entry.file_name()); let ext = path @@ -507,8 +505,7 @@ pub async fn migrate_tickets( } let mut entries = fs::read_dir(&dir).await?; - while let Some(entry) = entries.next().await { - let entry = entry?; + while let Some(entry) = entries.next_entry().await? { // Construct a std::path::PathBuf so it's compatible with Path helpers. let path: PathBuf = dir.join(entry.file_name()); @@ -625,8 +622,7 @@ pub async fn list_tickets(root: &Path) -> Result> { // Guard against the same logical ticket appearing in multiple formats. let mut seen_ids: std::collections::HashSet = std::collections::HashSet::new(); - while let Some(entry) = entries.next().await { - let entry = entry?; + while let Some(entry) = entries.next_entry().await? { // Construct a std::path::PathBuf so it's compatible with Path helpers. let path: PathBuf = dir.join(entry.file_name()); let ext = path @@ -656,3 +652,186 @@ pub async fn list_tickets(root: &Path) -> Result> { tickets.sort_by(|a, b| b.priority.cmp(&a.priority)); Ok(tickets) } + +// ── Turso (libsql) cache ────────────────────────────────────────────────────── + +/// Open (or create) the libsql cache database at `.nbd/cache.db`. +/// +/// Runs the schema migration on every open so the table exists when needed. +/// The returned [`libsql::Connection`] is ready for queries. +/// +/// # Errors +/// +/// Returns an error if the database cannot be opened or the migration fails. +async fn open_cache(root: &Path) -> Result { + let db_path = root.join(".nbd").join("cache.db"); + let db = libsql::Builder::new_local(db_path) + .build() + .await + .map_err(|e| msg_err(format!("libsql open error: {e}")))?; + let conn = db + .connect() + .map_err(|e| msg_err(format!("libsql connect error: {e}")))?; + conn.execute( + "CREATE TABLE IF NOT EXISTS tickets ( + id TEXT PRIMARY KEY, + json TEXT NOT NULL, + mtime INTEGER NOT NULL + )", + (), + ) + .await + .map_err(|e| msg_err(format!("libsql migrate error: {e}")))?; + Ok(conn) +} + +/// Like [`list_tickets`] but uses a libsql cache in `.nbd/cache.db` to skip +/// re-reading files whose modification time has not changed. +/// +/// ## Cache strategy +/// +/// 1. Scan `.nbd/tickets/` for files and their mtimes. +/// 2. For each file: if the cache row exists and the mtime matches, deserialise +/// the cached JSON; otherwise read the file, parse it, and upsert the cache. +/// 3. Delete rows for IDs that no longer exist on disk. +/// 4. Return tickets sorted by priority descending. +/// +/// Falls back to [`list_tickets`] if the cache cannot be opened or any cache +/// operation fails, so the caller always gets a result. +/// +/// # Errors +/// +/// Only returns an error if [`list_tickets`] itself fails (the fallback path). +pub async fn list_tickets_cached(root: &Path) -> Result> { + match list_tickets_cached_inner(root).await { + Ok(tickets) => Ok(tickets), + Err(_) => list_tickets(root).await, + } +} + +/// Inner implementation of the cached list; errors cause a fallback to the +/// uncached path in [`list_tickets_cached`]. +async fn list_tickets_cached_inner(root: &Path) -> Result> { + let dir = tickets_dir(root); + + if !dir.is_dir() { + return Ok(Vec::new()); + } + + let conn = open_cache(root).await?; + + // Scan the directory, collecting (id, path, mtime_secs) for each ticket file. + let mut on_disk: Vec<(String, PathBuf, i64)> = Vec::new(); + let mut read_dir = fs::read_dir(&dir).await?; + while let Some(entry) = read_dir.next_entry().await? { + let path: PathBuf = dir.join(entry.file_name()); + let ext = path + .extension() + .and_then(|e| e.to_str()) + .unwrap_or_default(); + if !KNOWN_EXTENSIONS.contains(&ext) { + continue; + } + let stem = match path.file_stem().and_then(|s| s.to_str()) { + Some(s) => s.to_string(), + None => continue, + }; + let meta = fs::metadata(&path).await?; + let mtime = meta + .modified() + .ok() + .and_then(|t| { + t.duration_since(std::time::UNIX_EPOCH) + .ok() + .map(|d| d.as_millis() as i64) + }) + .unwrap_or(0); + on_disk.push((stem, path, mtime)); + } + + // Deduplicate: keep only the first occurrence of each ID (same order as list_tickets). + let mut seen_ids: std::collections::HashSet = std::collections::HashSet::new(); + on_disk.retain(|(id, _, _)| seen_ids.insert(id.clone())); + + let disk_ids: std::collections::HashSet = + on_disk.iter().map(|(id, _, _)| id.clone()).collect(); + + let mut tickets: Vec = Vec::with_capacity(on_disk.len()); + + for (id, path, mtime) in &on_disk { + // Query the cache for this id. + let cached_json: Option = { + let mut rows = conn + .query( + "SELECT json FROM tickets WHERE id = ?1 AND mtime = ?2", + libsql::params![id.as_str(), *mtime], + ) + .await + .map_err(|e| msg_err(format!("cache query error: {e}")))?; + if let Some(row) = rows + .next() + .await + .map_err(|e| msg_err(format!("cache row error: {e}")))? + { + Some( + row.get::(0) + .map_err(|e| msg_err(format!("cache column error: {e}")))?, + ) + } else { + None + } + }; + + let mut ticket = if let Some(json) = cached_json { + serde_json::from_str::(&json) + .map_err(|e| msg_err(format!("cache deserialise error: {e}")))? + } else { + // Cache miss: read file and upsert. + let bytes = fs::read(path).await?; + let format = detect_format(path); + let t = deserialize_by_format(&bytes, format)?; + // Store normalised JSON in the cache. + let json = serde_json::to_string(&t)?; + conn.execute( + "INSERT OR REPLACE INTO tickets (id, json, mtime) VALUES (?1, ?2, ?3)", + libsql::params![id.as_str(), json.as_str(), *mtime], + ) + .await + .map_err(|e| msg_err(format!("cache upsert error: {e}")))?; + t + }; + ticket.id = id.clone(); + tickets.push(ticket); + } + + // Remove stale rows for IDs no longer on disk. + // We fetch all cached IDs and delete anything not in `disk_ids`. + let mut rows = conn + .query("SELECT id FROM tickets", ()) + .await + .map_err(|e| msg_err(format!("cache scan error: {e}")))?; + let mut stale: Vec = Vec::new(); + while let Some(row) = rows + .next() + .await + .map_err(|e| msg_err(format!("cache row error: {e}")))? + { + let cached_id: String = row + .get::(0) + .map_err(|e| msg_err(format!("cache column error: {e}")))?; + if !disk_ids.contains(&cached_id) { + stale.push(cached_id); + } + } + for id in stale { + conn.execute( + "DELETE FROM tickets WHERE id = ?1", + libsql::params![id.as_str()], + ) + .await + .map_err(|e| msg_err(format!("cache delete error: {e}")))?; + } + + tickets.sort_by(|a, b| b.priority.cmp(&a.priority)); + Ok(tickets) +} diff --git a/nbd/src/tests.rs b/nbd/src/tests.rs index a7dce60..6612418 100644 --- a/nbd/src/tests.rs +++ b/nbd/src/tests.rs @@ -201,7 +201,7 @@ mod store { } /// Writing a ticket and reading it back produces an identical value. - #[async_std::test] + #[tokio::test] async fn write_and_read_roundtrip() { let (tmp, root) = setup_store().await; let ticket = Ticket { @@ -230,7 +230,7 @@ mod store { } /// `write_ticket` does not include the `id` key in the JSON file. - #[async_std::test] + #[tokio::test] async fn write_ticket_omits_id_from_json() { let (tmp, root) = setup_store().await; let ticket = Ticket::new("c0ffee".to_string(), "Check JSON".to_string()); @@ -239,7 +239,7 @@ mod store { .unwrap(); let path = ticket_path(&root, "c0ffee", FileFormat::Json); - let contents = async_std::fs::read_to_string(&path).await.unwrap(); + let contents = tokio::fs::read_to_string(&path).await.unwrap(); let parsed: serde_json::Value = serde_json::from_str(&contents).unwrap(); assert!( parsed.get("id").is_none(), @@ -249,14 +249,14 @@ mod store { } /// `read_ticket` injects the id from its parameter even when the file has no `id` field. - #[async_std::test] + #[tokio::test] async fn read_ticket_injects_id_from_parameter() { let (tmp, root) = setup_store().await; // Write a JSON file that has no "id" key (the new format). let json = r#"{"title":"No id field","body":"","priority":5,"status":"todo","dependencies":[],"ticket_type":"task"}"#; let dir = tickets_dir(&root); - async_std::fs::write(dir.join("abcdef.json"), json) + tokio::fs::write(dir.join("abcdef.json"), json) .await .unwrap(); @@ -268,14 +268,14 @@ mod store { /// `read_ticket` ignores any `id` key present in the JSON body (old format), /// and instead uses the id passed as the parameter. - #[async_std::test] + #[tokio::test] async fn read_ticket_ignores_id_in_json_body() { let (tmp, root) = setup_store().await; // Simulate an old-format file that still has "id" in the JSON body. let json = r#"{"id":"wrongid","title":"Old format","body":"","priority":5,"status":"todo","dependencies":[],"ticket_type":"task"}"#; let dir = tickets_dir(&root); - async_std::fs::write(dir.join("aabbcc.json"), json) + tokio::fs::write(dir.join("aabbcc.json"), json) .await .unwrap(); @@ -288,7 +288,7 @@ mod store { } /// `list_tickets` injects the correct id from each filename stem. - #[async_std::test] + #[tokio::test] async fn list_tickets_injects_id_from_filename() { let (tmp, root) = setup_store().await; @@ -308,7 +308,7 @@ mod store { } /// Reading a non-existent ticket produces an error that mentions the ID. - #[async_std::test] + #[tokio::test] async fn read_missing_ticket_errors() { let (tmp, root) = setup_store().await; let result = read_ticket(&root, "ffffff").await; @@ -322,7 +322,7 @@ mod store { } /// `list_tickets` returns all written tickets sorted by priority descending. - #[async_std::test] + #[tokio::test] async fn list_returns_all_sorted_by_priority() { let (tmp, root) = setup_store().await; @@ -352,7 +352,7 @@ mod store { } /// `resolve_id` returns the full ID when given an exact 6-char match. - #[async_std::test] + #[tokio::test] async fn resolve_id_exact_match() { let (tmp, root) = setup_store().await; let ticket = Ticket::new("a3f9c2".to_string(), "Exact".to_string()); @@ -366,7 +366,7 @@ mod store { } /// `resolve_id` resolves a unique 3-char prefix to the full ID. - #[async_std::test] + #[tokio::test] async fn resolve_id_prefix_match() { let (tmp, root) = setup_store().await; let ticket = Ticket::new("a3f9c2".to_string(), "Prefix".to_string()); @@ -380,7 +380,7 @@ mod store { } /// `resolve_id` returns an error for an unknown prefix. - #[async_std::test] + #[tokio::test] async fn resolve_id_not_found() { let (tmp, root) = setup_store().await; let result = resolve_id(&root, "zzz").await; @@ -394,7 +394,7 @@ mod store { } /// `resolve_id` returns an error listing all matches for an ambiguous prefix. - #[async_std::test] + #[tokio::test] async fn resolve_id_ambiguous_prefix() { let (tmp, root) = setup_store().await; let t1 = Ticket::new("aabbcc".to_string(), "First".to_string()); @@ -421,7 +421,7 @@ mod store { } /// `list_tickets` returns an empty vec when the tickets directory is absent. - #[async_std::test] + #[tokio::test] async fn list_empty_when_no_tickets_dir() { let tmp = tempfile::tempdir().unwrap(); // Create `.nbd/` but not `.nbd/tickets/`. @@ -461,7 +461,7 @@ mod store { } /// Writing in TOML format and reading back produces an identical ticket. - #[async_std::test] + #[tokio::test] async fn write_and_read_roundtrip_toml() { let (tmp, root) = setup_store().await; let ticket = Ticket { @@ -493,7 +493,7 @@ mod store { } /// Writing in Markdown format and reading back produces an identical ticket. - #[async_std::test] + #[tokio::test] async fn write_and_read_roundtrip_markdown() { let (tmp, root) = setup_store().await; let ticket = Ticket { @@ -524,7 +524,7 @@ mod store { } /// Writing in CBOR format and reading back produces an identical ticket. - #[async_std::test] + #[tokio::test] async fn write_and_read_roundtrip_jsonb() { let (tmp, root) = setup_store().await; let ticket = Ticket { @@ -603,13 +603,13 @@ mod migrate { } /// `migrate_tickets` rewrites old-format files that contain a stale `"id"` key. - #[async_std::test] + #[tokio::test] async fn rewrites_old_format_with_id_field() { let (tmp, root) = setup_store().await; let dir = tickets_dir(&root); // Write a file with the legacy "id" key. let old_json = r#"{"id":"aabbcc","title":"Old","body":"","priority":5,"status":"todo","dependencies":[],"ticket_type":"task"}"#; - async_std::fs::write(dir.join("aabbcc.json"), old_json) + tokio::fs::write(dir.join("aabbcc.json"), old_json) .await .unwrap(); @@ -621,7 +621,7 @@ mod migrate { assert!(report.errors.is_empty()); // Verify the file no longer contains the "id" key. - let contents = async_std::fs::read_to_string(dir.join("aabbcc.json")) + let contents = tokio::fs::read_to_string(dir.join("aabbcc.json")) .await .unwrap(); let parsed: serde_json::Value = serde_json::from_str(&contents).unwrap(); @@ -633,7 +633,7 @@ mod migrate { } /// `migrate_tickets` on a store with already-current files returns `updated: 0`. - #[async_std::test] + #[tokio::test] async fn already_current_files_not_rewritten() { let (tmp, root) = setup_store().await; let t1 = Ticket::new("id0001".to_string(), "First".to_string()); @@ -651,12 +651,12 @@ mod migrate { } /// `migrate_tickets` with `dry_run: true` does not write files. - #[async_std::test] + #[tokio::test] async fn dry_run_does_not_write() { let (tmp, root) = setup_store().await; let dir = tickets_dir(&root); let old_json = r#"{"id":"ccddee","title":"DryRun","body":"","priority":5,"status":"todo","dependencies":[],"ticket_type":"task"}"#; - async_std::fs::write(dir.join("ccddee.json"), old_json) + tokio::fs::write(dir.join("ccddee.json"), old_json) .await .unwrap(); @@ -670,7 +670,7 @@ mod migrate { assert!(report.errors.is_empty()); // File must remain unchanged. - let contents = async_std::fs::read_to_string(dir.join("ccddee.json")) + let contents = tokio::fs::read_to_string(dir.join("ccddee.json")) .await .unwrap(); assert_eq!( @@ -681,12 +681,12 @@ mod migrate { } /// Invalid JSON files are counted in errors and left untouched. - #[async_std::test] + #[tokio::test] async fn invalid_json_counted_in_errors() { let (tmp, root) = setup_store().await; let dir = tickets_dir(&root); let bad_json = b"{ this is not valid json }"; - async_std::fs::write(dir.join("badbad.json"), bad_json) + tokio::fs::write(dir.join("badbad.json"), bad_json) .await .unwrap(); @@ -697,13 +697,13 @@ mod migrate { assert!(report.errors[0].0.contains("badbad.json")); // File must remain unchanged. - let contents = async_std::fs::read(&dir.join("badbad.json")).await.unwrap(); + let contents = tokio::fs::read(&dir.join("badbad.json")).await.unwrap(); assert_eq!(contents.as_slice(), bad_json); drop(tmp); } /// `migrate_tickets` on an empty store returns an empty report. - #[async_std::test] + #[tokio::test] async fn empty_store_returns_empty_report() { let (tmp, root) = setup_store().await; let report = migrate_tickets(&root, false, &TicketFilter::default()) @@ -716,7 +716,7 @@ mod migrate { } /// `migrate_tickets` returns an empty report when the tickets directory does not exist. - #[async_std::test] + #[tokio::test] async fn no_tickets_dir_returns_empty_report() { let tmp = tempfile::tempdir().unwrap(); // Create `.nbd/` but not `.nbd/tickets/`.