diff --git a/Cargo.lock b/Cargo.lock index a7ae7f7..c4eaa48 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,17 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[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" @@ -19,7 +30,7 @@ dependencies = [ "once_cell", "serde", "version_check", - "zerocopy", + "zerocopy 0.8.40", ] [[package]] @@ -152,6 +163,39 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -179,7 +223,7 @@ dependencies = [ "num-traits", "pastey", "rayon", - "thiserror", + "thiserror 2.0.18", "v_frame", "y4m", ] @@ -207,23 +251,51 @@ dependencies = [ "arrayvec", ] +[[package]] +name = "axum" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" +dependencies = [ + "async-trait", + "axum-core 0.3.4", + "bitflags 1.3.2", + "bytes", + "futures-util", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.32", + "itoa", + "matchit 0.7.3", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper 0.1.2", + "tower 0.4.13", + "tower-layer", + "tower-service", +] + [[package]] name = "axum" version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" dependencies = [ - "axum-core", + "axum-core 0.5.6", "bytes", "form_urlencoded", "futures-util", - "http", - "http-body", + "http 1.4.0", + "http-body 1.0.1", "http-body-util", - "hyper", + "hyper 1.8.1", "hyper-util", "itoa", - "matchit", + "matchit 0.8.4", "memchr", "mime", "percent-encoding", @@ -232,14 +304,31 @@ dependencies = [ "serde_json", "serde_path_to_error", "serde_urlencoded", - "sync_wrapper", + "sync_wrapper 1.0.2", "tokio", - "tower", + "tower 0.5.3", "tower-layer", "tower-service", "tracing", ] +[[package]] +name = "axum-core" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http 0.2.12", + "http-body 0.4.6", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + [[package]] name = "axum-core" version = "0.5.6" @@ -248,12 +337,12 @@ checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" dependencies = [ "bytes", "futures-core", - "http", - "http-body", + "http 1.4.0", + "http-body 1.0.1", "http-body-util", "mime", "pin-project-lite", - "sync_wrapper", + "sync_wrapper 1.0.2", "tower-layer", "tower-service", "tracing", @@ -265,18 +354,62 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bindgen" +version = "0.66.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b84e06fc203107bfbad243f4aba2af864eb7db3b1cf46ea0a023b0b433d2a7" +dependencies = [ + "bitflags 2.11.0", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", + "which", +] + [[package]] name = "bit_field" version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" +[[package]] +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" @@ -301,6 +434,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + [[package]] name = "built" version = "0.8.0" @@ -336,6 +478,9 @@ name = "bytes" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +dependencies = [ + "serde", +] [[package]] name = "cassowary" @@ -352,6 +497,15 @@ dependencies = [ "rustversion", ] +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + [[package]] name = "cc" version = "1.2.56" @@ -364,6 +518,15 @@ dependencies = [ "shlex", ] +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom 7.1.3", +] + [[package]] name = "cfg-if" version = "1.0.4" @@ -384,6 +547,27 @@ dependencies = [ "windows-link", ] +[[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" @@ -424,6 +608,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + [[package]] name = "color_quant" version = "1.1.0" @@ -562,7 +755,7 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ - "bitflags", + "bitflags 2.11.0", "crossterm_winapi", "mio", "parking_lot", @@ -835,6 +1028,12 @@ dependencies = [ "zune-inflate", ] +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + [[package]] name = "fallible-iterator" version = "0.3.0" @@ -968,6 +1167,21 @@ dependencies = [ "percent-encoding", ] +[[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" @@ -975,6 +1189,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -983,6 +1198,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" @@ -1018,6 +1244,7 @@ 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", @@ -1084,6 +1311,31 @@ dependencies = [ "weezl", ] +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "h2" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap 2.13.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "h2" version = "0.4.13" @@ -1095,8 +1347,8 @@ dependencies = [ "fnv", "futures-core", "futures-sink", - "http", - "indexmap", + "http 1.4.0", + "indexmap 2.13.0", "slab", "tokio", "tokio-util", @@ -1111,7 +1363,23 @@ checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ "cfg-if", "crunchy", - "zerocopy", + "zerocopy 0.8.40", +] + +[[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]] @@ -1142,6 +1410,15 @@ dependencies = [ "foldhash 0.2.0", ] +[[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 = "hashlink" version = "0.10.0" @@ -1164,20 +1441,40 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "629d8f3bbeda9d148036d6b0de0a3ab947abd08ce90626327fc3547a49d59d97" dependencies = [ "dirs", - "http", + "http 1.4.0", "indicatif", "libc", "log", "native-tls", - "rand", + "rand 0.9.2", "reqwest", "serde", "serde_json", - "thiserror", + "thiserror 2.0.18", "ureq", "windows-sys 0.60.2", ] +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http" version = "1.4.0" @@ -1188,6 +1485,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + [[package]] name = "http-body" version = "1.0.1" @@ -1195,7 +1503,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http", + "http 1.4.0", ] [[package]] @@ -1206,11 +1514,17 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http", - "http-body", + "http 1.4.0", + "http-body 1.0.1", "pin-project-lite", ] +[[package]] +name = "http-range-header" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" + [[package]] name = "httparse" version = "1.10.1" @@ -1223,6 +1537,30 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.3.27", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.5.10", + "tokio", + "tower-service", + "tracing", + "want", +] + [[package]] name = "hyper" version = "1.8.1" @@ -1233,9 +1571,9 @@ dependencies = [ "bytes", "futures-channel", "futures-core", - "h2", - "http", - "http-body", + "h2 0.4.13", + "http 1.4.0", + "http-body 1.0.1", "httparse", "httpdate", "itoa", @@ -1246,22 +1584,52 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "399c78f9338483cb7e630c8474b07268983c6bd5acee012e4211f9f7bb21b070" +dependencies = [ + "futures-util", + "http 0.2.12", + "hyper 0.14.32", + "log", + "rustls 0.22.4", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.25.0", + "webpki-roots 0.26.11", +] + [[package]] name = "hyper-rustls" version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ - "http", - "hyper", + "http 1.4.0", + "hyper 1.8.1", "hyper-util", - "rustls", + "rustls 0.23.37", "rustls-pki-types", "tokio", - "tokio-rustls", + "tokio-rustls 0.26.4", "tower-service", ] +[[package]] +name = "hyper-timeout" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +dependencies = [ + "hyper 0.14.32", + "pin-project-lite", + "tokio", + "tokio-io-timeout", +] + [[package]] name = "hyper-tls" version = "0.6.0" @@ -1270,7 +1638,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper", + "hyper 1.8.1", "hyper-util", "native-tls", "tokio", @@ -1288,14 +1656,14 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http", - "http-body", - "hyper", + "http 1.4.0", + "http-body 1.0.1", + "hyper 1.8.1", "ipnet", "libc", "percent-encoding", "pin-project-lite", - "socket2", + "socket2 0.6.2", "system-configuration", "tokio", "tower-service", @@ -1329,10 +1697,10 @@ dependencies = [ [[package]] name = "icm-cli" -version = "0.10.49" +version = "0.10.50" dependencies = [ "anyhow", - "axum", + "axum 0.8.8", "chrono", "clap", "crossterm", @@ -1356,7 +1724,7 @@ dependencies = [ "tempfile", "tokio", "toml", - "tower-http", + "tower-http 0.6.8", "tracing", "tracing-subscriber", "ureq", @@ -1372,7 +1740,7 @@ dependencies = [ "fastembed", "serde", "serde_json", - "thiserror", + "thiserror 2.0.18", "toml", "ulid", ] @@ -1397,15 +1765,19 @@ version = "0.10.34" dependencies = [ "chrono", "icm-core", + "libsql", + "libsql-ffi", "lru 0.18.0", + "once_cell", "rusqlite", "serde_json", "sha2", "sqlite-vec", "tempfile", + "tokio", "tracing", "ulid", - "zerocopy", + "zerocopy 0.8.40", ] [[package]] @@ -1562,6 +1934,16 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c5cedc30da3a610cac6b4ba17597bdf7152cf974e8aab3afb3d54455e371c8" +[[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" @@ -1596,6 +1978,16 @@ dependencies = [ "rustversion", ] +[[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 = "instability" version = "0.3.12" @@ -1642,6 +2034,15 @@ 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 = "itertools" version = "0.13.0" @@ -1692,6 +2093,12 @@ 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" @@ -1720,18 +2127,164 @@ dependencies = [ "cc", ] +[[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 = "libredox" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" dependencies = [ - "bitflags", + "bitflags 2.11.0", "libc", "plain", "redox_syscall 0.7.3", ] +[[package]] +name = "libsql" +version = "0.9.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30fe980ac5693ed1f3db490559fb578885e913a018df64af8a1a46e1959a78df" +dependencies = [ + "anyhow", + "async-stream", + "async-trait", + "base64 0.21.7", + "bincode", + "bitflags 2.11.0", + "bytes", + "chrono", + "crc32fast", + "fallible-iterator 0.3.0", + "futures", + "http 0.2.12", + "hyper 0.14.32", + "hyper-rustls 0.25.0", + "libsql-hrana", + "libsql-sqlite3-parser", + "libsql-sys", + "libsql_replication", + "parking_lot", + "serde", + "serde_json", + "thiserror 1.0.69", + "tokio", + "tokio-stream", + "tokio-util", + "tonic", + "tonic-web", + "tower 0.4.13", + "tower-http 0.4.4", + "tracing", + "uuid", + "zerocopy 0.7.35", +] + +[[package]] +name = "libsql-ffi" +version = "0.9.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0be1da6f123ceb2cd23f469883415cab9ee963286a85d61e22afb8b12e15e681" +dependencies = [ + "bindgen", + "cc", + "cmake", + "glob", +] + +[[package]] +name = "libsql-hrana" +version = "0.9.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3358538b52cfcf9af4fe7aeb57d6843aafed2e8af80807bd636fd1448e94ea7" +dependencies = [ + "base64 0.21.7", + "bytes", + "prost", + "serde", +] + +[[package]] +name = "libsql-rusqlite" +version = "0.9.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b646f94fc1d266e481c38a2d44d6d9d1be3ad04b56b90457acfb310dc450030e" +dependencies = [ + "bitflags 2.11.0", + "fallible-iterator 0.2.0", + "fallible-streaming-iterator", + "hashlink 0.8.4", + "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.9.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90725458cc4461bc82f8f7983e80b002ea4f64b5184e1462f252d0dd74b122f5" +dependencies = [ + "bytes", + "libsql-ffi", + "libsql-rusqlite", + "once_cell", + "tracing", + "zerocopy 0.7.35", +] + +[[package]] +name = "libsql_replication" +version = "0.9.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bba5c9b3a26aca06d70f6a3646ba341cf574a548355353fe135af524b1b77cc" +dependencies = [ + "aes", + "async-stream", + "async-trait", + "bytes", + "cbc", + "libsql-rusqlite", + "libsql-sys", + "parking_lot", + "prost", + "serde", + "thiserror 1.0.69", + "tokio", + "tokio-stream", + "tokio-util", + "tonic", + "tracing", + "uuid", + "zerocopy 0.7.35", +] + [[package]] name = "libsqlite3-sys" version = "0.32.0" @@ -1828,6 +2381,12 @@ dependencies = [ "regex-automata", ] +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + [[package]] name = "matchit" version = "0.8.4" @@ -1945,10 +2504,10 @@ dependencies = [ "libc", "log", "openssl", - "openssl-probe", + "openssl-probe 0.2.1", "openssl-sys", "schannel", - "security-framework", + "security-framework 3.7.0", "security-framework-sys", "tempfile", ] @@ -2091,7 +2650,7 @@ version = "6.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "336b9c63443aceef14bea841b899035ae3abe89b7c486aaf4c5bd8aafedac3f0" dependencies = [ - "bitflags", + "bitflags 2.11.0", "libc", "once_cell", "onig_sys", @@ -2113,7 +2672,7 @@ version = "0.10.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" dependencies = [ - "bitflags", + "bitflags 2.11.0", "cfg-if", "foreign-types", "libc", @@ -2133,6 +2692,12 @@ dependencies = [ "syn", ] +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + [[package]] name = "openssl-probe" version = "0.2.1" @@ -2215,22 +2780,87 @@ dependencies = [ ] [[package]] -name = "paste" -version = "1.0.15" +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pastey" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" + +[[package]] +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 = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "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 0.8.6", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", + "uncased", +] [[package]] -name = "pastey" -version = "0.1.1" +name = "pin-project" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" +dependencies = [ + "pin-project-internal", +] [[package]] -name = "percent-encoding" -version = "2.3.2" +name = "pin-project-internal" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "pin-project-lite" @@ -2262,7 +2892,7 @@ version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" dependencies = [ - "bitflags", + "bitflags 2.11.0", "crc32fast", "fdeflate", "flate2", @@ -2299,7 +2929,7 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "zerocopy", + "zerocopy 0.8.40", ] [[package]] @@ -2340,6 +2970,29 @@ dependencies = [ "syn", ] +[[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 0.12.1", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "pxfm" version = "0.1.28" @@ -2382,14 +3035,35 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" +[[package]] +name = "rand" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + [[package]] name = "rand" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ - "rand_chacha", - "rand_core", + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + +[[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 0.6.4", ] [[package]] @@ -2399,7 +3073,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.9.5", +] + +[[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]] @@ -2417,7 +3100,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" dependencies = [ - "bitflags", + "bitflags 2.11.0", "cassowary", "compact_str 0.8.1", "crossterm", @@ -2459,10 +3142,10 @@ dependencies = [ "num-traits", "paste", "profiling", - "rand", - "rand_chacha", + "rand 0.9.2", + "rand_chacha 0.9.0", "simd_helpers", - "thiserror", + "thiserror 2.0.18", "v_frame", "wasm-bindgen", ] @@ -2525,7 +3208,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags", + "bitflags 2.11.0", ] [[package]] @@ -2534,7 +3217,7 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" dependencies = [ - "bitflags", + "bitflags 2.11.0", ] [[package]] @@ -2545,7 +3228,7 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.17", "libredox", - "thiserror", + "thiserror 2.0.18", ] [[package]] @@ -2588,12 +3271,12 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", - "h2", - "http", - "http-body", + "h2 0.4.13", + "http 1.4.0", + "http-body 1.0.1", "http-body-util", - "hyper", - "hyper-rustls", + "hyper 1.8.1", + "hyper-rustls 0.27.7", "hyper-tls", "hyper-util", "js-sys", @@ -2606,12 +3289,12 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "sync_wrapper", + "sync_wrapper 1.0.2", "tokio", "tokio-native-tls", "tokio-util", - "tower", - "tower-http", + "tower 0.5.3", + "tower-http 0.6.8", "tower-service", "url", "wasm-bindgen", @@ -2656,10 +3339,10 @@ version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37e34486da88d8e051c7c0e23c3f15fd806ea8546260aa2fec247e97242ec143" dependencies = [ - "bitflags", - "fallible-iterator", + "bitflags 2.11.0", + "fallible-iterator 0.3.0", "fallible-streaming-iterator", - "hashlink", + "hashlink 0.10.0", "libsqlite3-sys", "smallvec", ] @@ -2698,13 +3381,19 @@ dependencies = [ "walkdir", ] +[[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", + "bitflags 2.11.0", "errno", "libc", "linux-raw-sys 0.4.15", @@ -2717,13 +3406,27 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags", + "bitflags 2.11.0", "errno", "libc", "linux-raw-sys 0.12.1", "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 0.102.8", + "subtle", + "zeroize", +] + [[package]] name = "rustls" version = "0.23.37" @@ -2734,11 +3437,33 @@ dependencies = [ "once_cell", "ring", "rustls-pki-types", - "rustls-webpki", + "rustls-webpki 0.103.13", "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 0.1.6", + "rustls-pemfile", + "rustls-pki-types", + "schannel", + "security-framework 2.11.1", +] + +[[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" @@ -2748,6 +3473,17 @@ 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]] name = "rustls-webpki" version = "0.103.13" @@ -2795,13 +3531,26 @@ 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 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + [[package]] name = "security-framework" version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags", + "bitflags 2.11.0", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -2860,7 +3609,7 @@ version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ - "indexmap", + "indexmap 2.13.0", "itoa", "memchr", "serde", @@ -2874,7 +3623,7 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e033097bf0d2b59a62b42c18ebbb797503839b26afdda2c4e1415cb6c813540" dependencies = [ - "indexmap", + "indexmap 2.13.0", "itoa", "memchr", "ryu", @@ -2985,6 +3734,12 @@ dependencies = [ "quote", ] +[[package]] +name = "siphasher" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" + [[package]] name = "slab" version = "0.4.12" @@ -2997,6 +3752,16 @@ 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" @@ -3096,6 +3861,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "sync_wrapper" version = "1.0.2" @@ -3122,7 +3893,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "bitflags", + "bitflags 2.11.0", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -3161,13 +3932,33 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + [[package]] name = "thiserror" version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -3233,7 +4024,7 @@ dependencies = [ "monostate", "onig", "paste", - "rand", + "rand 0.9.2", "rayon", "rayon-cond", "regex", @@ -3241,7 +4032,7 @@ dependencies = [ "serde", "serde_json", "spm_precompiled", - "thiserror", + "thiserror 2.0.18", "unicode-normalization-alignments", "unicode-segmentation", "unicode_categories", @@ -3256,12 +4047,24 @@ dependencies = [ "bytes", "libc", "mio", + "parking_lot", "pin-project-lite", - "socket2", + "signal-hook-registry", + "socket2 0.6.2", "tokio-macros", "windows-sys 0.61.2", ] +[[package]] +name = "tokio-io-timeout" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bd86198d9ee903fedd2f9a2e72014287c0d9167e4ae43b5853007205dda1b76" +dependencies = [ + "pin-project-lite", + "tokio", +] + [[package]] name = "tokio-macros" version = "2.6.1" @@ -3283,13 +4086,35 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +dependencies = [ + "rustls 0.22.4", + "rustls-pki-types", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "rustls", + "rustls 0.23.37", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", "tokio", ] @@ -3333,7 +4158,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", @@ -3347,6 +4172,73 @@ 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 0.6.20", + "base64 0.21.7", + "bytes", + "h2 0.3.27", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.32", + "hyper-timeout", + "percent-encoding", + "pin-project", + "prost", + "tokio", + "tokio-stream", + "tower 0.4.13", + "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 0.21.7", + "bytes", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.32", + "pin-project", + "tokio-stream", + "tonic", + "tower-http 0.4.4", + "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 0.8.6", + "slab", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "tower" version = "0.5.3" @@ -3356,27 +4248,47 @@ dependencies = [ "futures-core", "futures-util", "pin-project-lite", - "sync_wrapper", + "sync_wrapper 1.0.2", "tokio", "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 0.2.12", + "http-body 0.4.6", + "http-range-header", + "pin-project-lite", + "tower 0.4.13", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "tower-http" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ - "bitflags", + "bitflags 2.11.0", "bytes", "futures-util", - "http", - "http-body", + "http 1.4.0", + "http-body 1.0.1", "iri-string", "pin-project-lite", - "tower", + "tower 0.5.3", "tower-layer", "tower-service", "tracing", @@ -3474,10 +4386,19 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "470dbf6591da1b39d43c14523b2b469c86879a53e8b758c8e090a470fe7b1fbe" dependencies = [ - "rand", + "rand 0.9.2", "web-time", ] +[[package]] +name = "uncased" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697" +dependencies = [ + "version_check", +] + [[package]] name = "unicase" version = "2.9.0" @@ -3557,7 +4478,7 @@ dependencies = [ "log", "native-tls", "once_cell", - "rustls", + "rustls 0.23.37", "rustls-pki-types", "serde", "serde_json", @@ -3590,6 +4511,18 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "1.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d258b83ceec21034727ecee8c382cfa6c3e133699b0742c64571814fb420c9f7" +dependencies = [ + "getrandom 0.4.2", + "js-sys", + "serde_core", + "wasm-bindgen", +] + [[package]] name = "v_frame" version = "0.3.9" @@ -3738,7 +4671,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" dependencies = [ "anyhow", - "indexmap", + "indexmap 2.13.0", "wasm-encoder", "wasmparser", ] @@ -3762,9 +4695,9 @@ 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", ] @@ -3812,6 +4745,18 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" +[[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]] name = "winapi" version = "0.3.9" @@ -4115,7 +5060,7 @@ checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" dependencies = [ "anyhow", "heck", - "indexmap", + "indexmap 2.13.0", "prettyplease", "syn", "wasm-metadata", @@ -4145,8 +5090,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", @@ -4165,7 +5110,7 @@ checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" dependencies = [ "anyhow", "id-arena", - "indexmap", + "indexmap 2.13.0", "log", "semver", "serde", @@ -4220,13 +5165,34 @@ dependencies = [ "synstructure", ] +[[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.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a789c6e490b576db9f7e6b6d661bcc9799f7c0ac8352f56ea20193b2681532e5" dependencies = [ - "zerocopy-derive", + "zerocopy-derive 0.8.40", +] + +[[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]] diff --git a/Cargo.toml b/Cargo.toml index 5224295..ebe3b94 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,10 @@ strip = true rusqlite = { version = "0.34", features = ["bundled", "modern_sqlite"] } sqlite-vec = "0.1" zerocopy = { version = "0.8", features = ["derive"] } +# libSQL / Turso backend (fork): remote (sqld) + embedded replica + sync. +libsql = { version = "0.9", default-features = false, features = ["core", "replication", "remote", "sync", "tls"] } +libsql-ffi = "0.9" +once_cell = "1" # Embeddings (optional) fastembed = "4" diff --git a/crates/icm-cli/Cargo.toml b/crates/icm-cli/Cargo.toml index fc522f7..3121acf 100644 --- a/crates/icm-cli/Cargo.toml +++ b/crates/icm-cli/Cargo.toml @@ -13,9 +13,13 @@ name = "icm" path = "src/main.rs" [features] -default = ["embeddings", "tui"] +default = ["embeddings", "tui", "backend-rusqlite"] embeddings = ["icm-core/embeddings", "icm-mcp/embeddings"] tui = ["dep:ratatui", "dep:crossterm"] +backend-rusqlite = ["icm-store/backend-rusqlite", "icm-mcp/backend-rusqlite"] +# Opt-in libSQL/Turso backend: build with +# `--no-default-features --features turso,embeddings,tui`. +turso = ["icm-store/turso", "icm-mcp/turso"] web = ["dep:axum", "dep:tokio", "dep:tower-http", "dep:rust-embed", "dep:mime_guess", "dep:getrandom"] # `openssl` is not used directly in our source code, but it gets pulled # transitively via reqwest / hyper-tls / etc. (depending on the active @@ -30,8 +34,8 @@ vendored-openssl = ["openssl/vendored"] [dependencies] icm-core = { path = "../icm-core" } -icm-store = { path = "../icm-store" } -icm-mcp = { path = "../icm-mcp" } +icm-store = { path = "../icm-store", default-features = false } +icm-mcp = { path = "../icm-mcp", default-features = false } anyhow = { workspace = true } clap = { workspace = true } chrono = { workspace = true } diff --git a/crates/icm-mcp/Cargo.toml b/crates/icm-mcp/Cargo.toml index 137ff3a..83eda5e 100644 --- a/crates/icm-mcp/Cargo.toml +++ b/crates/icm-mcp/Cargo.toml @@ -4,12 +4,14 @@ version = "0.10.34" edition = "2021" [features] -default = [] +default = ["backend-rusqlite"] +backend-rusqlite = ["icm-store/backend-rusqlite"] +turso = ["icm-store/turso"] embeddings = ["icm-core/embeddings"] [dependencies] icm-core = { path = "../icm-core" } -icm-store = { path = "../icm-store" } +icm-store = { path = "../icm-store", default-features = false } chrono = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } diff --git a/crates/icm-store/Cargo.toml b/crates/icm-store/Cargo.toml index ada1f73..58b9a33 100644 --- a/crates/icm-store/Cargo.toml +++ b/crates/icm-store/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" [dependencies] icm-core = { path = "../icm-core" } -rusqlite = { workspace = true } +rusqlite = { workspace = true, optional = true } sqlite-vec = { workspace = true } zerocopy = { workspace = true } serde_json = { workspace = true } @@ -15,5 +15,20 @@ lru = { workspace = true } sha2 = { workspace = true } ulid = { workspace = true } +# Optional libSQL/Turso backend. With `--features turso` the store runs over the +# async libsql client (local file, remote sqld/Turso server, or embedded +# replica); without it, behaviour is exactly upstream rusqlite. +libsql = { workspace = true, optional = true } +libsql-ffi = { workspace = true, optional = true } +once_cell = { workspace = true, optional = true } +tokio = { workspace = true, optional = true } + +[features] +# Exactly one backend. Default is rusqlite (unchanged upstream); for libSQL/Turso +# build with `--no-default-features --features turso`. +default = ["backend-rusqlite"] +backend-rusqlite = ["dep:rusqlite"] +turso = ["dep:libsql", "dep:libsql-ffi", "dep:once_cell", "dep:tokio"] + [dev-dependencies] tempfile = "3" diff --git a/crates/icm-store/src/dbcompat.rs b/crates/icm-store/src/dbcompat.rs new file mode 100644 index 0000000..c89bf93 --- /dev/null +++ b/crates/icm-store/src/dbcompat.rs @@ -0,0 +1,488 @@ +//! dbcompat — a synchronous, **rusqlite-0.34-shaped** facade over the async +//! `libsql` client. It mirrors the exact rusqlite API surface `store.rs` / +//! `schema.rs` use (`Connection`, `Statement`, `Row<'_>`, `params!`, `ToSql`, +//! `types::ToSql`, `OptionalExtension`, `MappedRows`, `Error`, the `[]` / `[T;N]` +//! / `&[&dyn ToSql]` `Params` forms) so those files stay byte-for-byte upstream +//! except for one `use crate::dbcompat as rusqlite;` alias and the connection-open +//! path. The real database can be a local file, a remote libSQL/Turso server, or +//! an embedded replica — see `Connection`. +//! +//! Keeping the store code unchanged is deliberate: `store.rs` is upstream's most +//! actively edited file, so a minimal diff means near-conflict-free rebases. + +use std::marker::PhantomData; +use std::path::Path; +use std::sync::Arc; + +use libsql::Builder; +use once_cell::sync::Lazy; + +pub use libsql::Value; + +// ───────────────────────────── runtime ───────────────────────────── + +static RT: Lazy = Lazy::new(|| { + tokio::runtime::Builder::new_multi_thread() + .worker_threads(2) + .enable_all() + .build() + .expect("BUG: failed to build dbcompat tokio runtime") +}); + +fn block_on(fut: F) -> F::Output { + match tokio::runtime::Handle::try_current() { + Ok(handle) => tokio::task::block_in_place(|| handle.block_on(fut)), + Err(_) => RT.block_on(fut), + } +} + +// ───────────────────────────── errors ───────────────────────────── + +#[derive(Debug)] +pub enum Error { + QueryReturnedNoRows, + FromSqlConversion(String), + Libsql(libsql::Error), +} + +pub type Result = std::result::Result; + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::QueryReturnedNoRows => write!(f, "Query returned no rows"), + Error::FromSqlConversion(s) => write!(f, "from-sql conversion error: {s}"), + Error::Libsql(e) => write!(f, "{e}"), + } + } +} +impl std::error::Error for Error {} +impl From for Error { + fn from(e: libsql::Error) -> Self { + Error::Libsql(e) + } +} + +// ───────────────────────────── ToSql ───────────────────────────── + +/// Object-safe value conversion (mirrors `rusqlite::ToSql` / `rusqlite::types::ToSql`). +pub trait ToSql { + fn to_value(&self) -> Value; +} + +macro_rules! to_sql_int { + ($($t:ty),*) => {$( impl ToSql for $t { + fn to_value(&self) -> Value { Value::Integer(*self as i64) } + } )*}; +} +to_sql_int!(i8, i16, i32, i64, u8, u16, u32, u64, usize, isize); + +impl ToSql for f32 { + fn to_value(&self) -> Value { Value::Real(*self as f64) } +} +impl ToSql for f64 { + fn to_value(&self) -> Value { Value::Real(*self) } +} +impl ToSql for bool { + fn to_value(&self) -> Value { Value::Integer(if *self { 1 } else { 0 }) } +} +impl ToSql for String { + fn to_value(&self) -> Value { Value::Text(self.clone()) } +} +impl ToSql for str { + fn to_value(&self) -> Value { Value::Text(self.to_string()) } +} +impl ToSql for Vec { + fn to_value(&self) -> Value { Value::Blob(self.clone()) } +} +impl ToSql for [u8] { + fn to_value(&self) -> Value { Value::Blob(self.to_vec()) } +} +impl ToSql for Value { + fn to_value(&self) -> Value { self.clone() } +} +impl ToSql for &T { + fn to_value(&self) -> Value { (**self).to_value() } +} +impl ToSql for Box { + fn to_value(&self) -> Value { (**self).to_value() } +} +impl ToSql for Option { + fn to_value(&self) -> Value { + match self { + Some(v) => v.to_value(), + None => Value::Null, + } + } +} + +/// `rusqlite::types::ToSql` lives under a `types` module upstream. +pub mod types { + pub use super::ToSql; +} + +// ───────────────────────────── Params ───────────────────────────── +// +// Mirrors rusqlite 0.34's Params impls so both `[]` (empty) and `[x]` +// (non-empty) and `params![..]` (-> `&[&dyn ToSql]`) work unchanged. The empty +// array has its own `[&dyn …; 0]` impl, which forces a per-N macro for the +// non-empty `[T; N]` case (a generic `[T; N]` would clash on N=0). + +pub trait Params { + fn into_values(self) -> Vec; +} + +impl Params for () { + fn into_values(self) -> Vec { Vec::new() } +} +// bare `[]` — the only [_; 0] impl (so it's unambiguous), like rusqlite. +impl Params for [&(dyn ToSql + Send + Sync); 0] { + fn into_values(self) -> Vec { Vec::new() } +} +impl Params for &[&dyn ToSql] { + fn into_values(self) -> Vec { self.iter().map(|v| v.to_value()).collect() } +} +impl Params for &Vec<&dyn ToSql> { + fn into_values(self) -> Vec { self.iter().map(|v| v.to_value()).collect() } +} +impl Params for &[Box] { + fn into_values(self) -> Vec { self.iter().map(|v| v.to_value()).collect() } +} +impl Params for &Vec> { + fn into_values(self) -> Vec { self.iter().map(|v| v.to_value()).collect() } +} +macro_rules! array_params { + ($($N:literal)+) => {$( + impl Params for [T; $N] { + fn into_values(self) -> Vec { self.iter().map(ToSql::to_value).collect() } + } + impl Params for &[&T; $N] { + fn into_values(self) -> Vec { self.iter().map(|v| v.to_value()).collect() } + } + )+}; +} +array_params!(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32); + +/// `params![a, b]` → `&[&dyn ToSql]`, matching `rusqlite::params!`. +#[macro_export] +macro_rules! dbcompat_params { + () => { (&[] as &[&dyn $crate::dbcompat::ToSql]) }; + ($($x:expr),+ $(,)?) => { + (&[$(&($x) as &dyn $crate::dbcompat::ToSql),+] as &[&dyn $crate::dbcompat::ToSql]) + }; +} +pub use crate::dbcompat_params as params; + +// ───────────────────────────── FromSql / Row ───────────────────────────── + +pub trait FromSql: Sized { + fn from_value(v: Value) -> Result; +} + +fn conv(what: &str, v: &Value) -> Error { + Error::FromSqlConversion(format!("cannot read {what} from {v:?}")) +} + +macro_rules! from_sql_int { + ($($t:ty),*) => {$( impl FromSql for $t { + fn from_value(v: Value) -> Result { + match v { + Value::Integer(i) => Ok(i as $t), + Value::Real(r) => Ok(r as $t), + ref o => Err(conv(stringify!($t), o)), + } + } + } )*}; +} +from_sql_int!(i8, i16, i32, i64, u8, u16, u32, u64, usize, isize); + +impl FromSql for f64 { + fn from_value(v: Value) -> Result { + match v { + Value::Real(r) => Ok(r), + Value::Integer(i) => Ok(i as f64), + ref o => Err(conv("f64", o)), + } + } +} +impl FromSql for f32 { + fn from_value(v: Value) -> Result { f64::from_value(v).map(|r| r as f32) } +} +impl FromSql for bool { + fn from_value(v: Value) -> Result { + match v { + Value::Integer(i) => Ok(i != 0), + ref o => Err(conv("bool", o)), + } + } +} +impl FromSql for String { + fn from_value(v: Value) -> Result { + match v { + Value::Text(s) => Ok(s), + Value::Blob(b) => String::from_utf8(b).map_err(|e| Error::FromSqlConversion(e.to_string())), + ref o => Err(conv("String", o)), + } + } +} +impl FromSql for Vec { + fn from_value(v: Value) -> Result { + match v { + Value::Blob(b) => Ok(b), + Value::Text(s) => Ok(s.into_bytes()), + ref o => Err(conv("Vec", o)), + } + } +} +impl FromSql for Value { + fn from_value(v: Value) -> Result { Ok(v) } +} +impl FromSql for Option { + fn from_value(v: Value) -> Result { + match v { + Value::Null => Ok(None), + other => T::from_value(other).map(Some), + } + } +} + +pub trait RowIndex { + fn idx(&self, row: &Row) -> Result; +} +impl RowIndex for usize { + fn idx(&self, _row: &Row) -> Result { Ok(*self) } +} +impl RowIndex for &str { + fn idx(&self, row: &Row) -> Result { + row.names + .iter() + .position(|n| n == self) + .ok_or_else(|| Error::FromSqlConversion(format!("no such column: {self}"))) + } +} + +/// A fully-materialised result row. The lifetime is phantom (values are owned), +/// kept only so upstream signatures like `&Row<'_>` compile unchanged. +pub struct Row<'a> { + names: Arc>, + values: Vec, + _marker: PhantomData<&'a ()>, +} + +impl<'a> Row<'a> { + pub fn get(&self, idx: I) -> Result { + let i = idx.idx(self)?; + let v = self + .values + .get(i) + .cloned() + .ok_or_else(|| Error::FromSqlConversion(format!("column index {i} out of range")))?; + T::from_value(v) + } +} + +async fn materialize(rows: &mut libsql::Rows) -> Result>> { + let ncols = rows.column_count(); + let names: Arc> = Arc::new( + (0..ncols) + .map(|i| rows.column_name(i).unwrap_or("").to_string()) + .collect(), + ); + let mut out = Vec::new(); + while let Some(r) = rows.next().await? { + let mut values = Vec::with_capacity(ncols as usize); + for i in 0..ncols { + values.push(r.get_value(i)?); + } + out.push(Row { + names: names.clone(), + values, + _marker: PhantomData, + }); + } + Ok(out) +} + +pub type MappedRows = std::vec::IntoIter>; + +// ───────────────────────────── Statement ───────────────────────────── + +pub struct Statement { + conn: Arc, + sql: String, +} + +impl Statement { + pub fn query_map(&mut self, params: impl Params, mut f: F) -> Result> + where + F: FnMut(&Row) -> Result, + { + let vals = params.into_values(); + let rows = block_on(async { + let mut rows = self.conn.query(&self.sql, vals).await?; + materialize(&mut rows).await + })?; + let mapped: Vec> = rows.iter().map(|r| f(r)).collect(); + Ok(mapped.into_iter()) + } + + pub fn query_row(&mut self, params: impl Params, f: F) -> Result + where + F: FnOnce(&Row) -> Result, + { + let vals = params.into_values(); + let rows = block_on(async { + let mut rows = self.conn.query(&self.sql, vals).await?; + materialize(&mut rows).await + })?; + match rows.first() { + Some(r) => f(r), + None => Err(Error::QueryReturnedNoRows), + } + } + + pub fn execute(&mut self, params: impl Params) -> Result { + let vals = params.into_values(); + Ok(block_on(self.conn.execute(&self.sql, vals))? as usize) + } +} + +// ───────────────────────────── Connection ───────────────────────────── + +#[derive(Clone)] +pub struct Connection { + db: Arc, + conn: Arc, + remote: bool, +} + +impl Connection { + pub fn open>(path: P) -> Result { + let db = block_on(async { Builder::new_local(path.as_ref().to_path_buf()).build().await })?; + // First connect runs libsql's one-time threading config + sqlite init; + // only AFTER that can sqlite-vec be registered (auto_extension itself + // initialises SQLite, which would otherwise race libsql's config). + let _warmup = db.connect()?; + register_vec_after_init(); + let conn = db.connect()?; + Ok(Self { db: Arc::new(db), conn: Arc::new(conn), remote: false }) + } + + pub fn open_in_memory() -> Result { + Self::open(":memory:") + } + + /// Remote libSQL/Turso server: every process shares it → concurrent-safe writes. + pub fn open_remote(url: String, auth_token: String) -> Result { + let db = block_on(async { Builder::new_remote(url, auth_token).build().await })?; + let conn = db.connect()?; + Ok(Self { db: Arc::new(db), conn: Arc::new(conn), remote: true }) + } + + /// Local embedded replica that syncs to a remote primary. + pub fn open_replica>(path: P, url: String, auth_token: String) -> Result { + let db = block_on(async { + Builder::new_remote_replica(path.as_ref().to_path_buf(), url, auth_token).build().await + })?; + let _warmup = db.connect()?; + register_vec_after_init(); + let conn = db.connect()?; + Ok(Self { db: Arc::new(db), conn: Arc::new(conn), remote: false }) + } + + pub fn is_remote(&self) -> bool { + self.remote + } + + pub fn sync(&self) -> Result<()> { + block_on(async { self.db.sync().await })?; + Ok(()) + } + + pub fn execute(&self, sql: &str, params: impl Params) -> Result { + let vals = params.into_values(); + Ok(block_on(self.conn.execute(sql, vals))? as usize) + } + + pub fn execute_batch(&self, sql: &str) -> Result<()> { + block_on(async { self.conn.execute_batch(sql).await })?; + Ok(()) + } + + pub fn prepare(&self, sql: &str) -> Result { + Ok(Statement { conn: self.conn.clone(), sql: sql.to_string() }) + } + + pub fn query_row(&self, sql: &str, params: impl Params, f: F) -> Result + where + F: FnOnce(&Row) -> Result, + { + self.prepare(sql)?.query_row(params, f) + } + + pub fn last_insert_rowid(&self) -> i64 { + self.conn.last_insert_rowid() + } + + /// Mirrors `rusqlite::Connection::unchecked_transaction` (rolls back on drop + /// unless committed). + pub fn unchecked_transaction(&self) -> Result> { + self.execute_batch("BEGIN")?; + Ok(Transaction { conn: self, done: false }) + } +} + +pub struct Transaction<'a> { + conn: &'a Connection, + done: bool, +} +impl<'a> Transaction<'a> { + pub fn commit(mut self) -> Result<()> { + self.conn.execute_batch("COMMIT")?; + self.done = true; + Ok(()) + } +} +impl<'a> std::ops::Deref for Transaction<'a> { + type Target = Connection; + fn deref(&self) -> &Connection { self.conn } +} +impl<'a> Drop for Transaction<'a> { + fn drop(&mut self) { + if !self.done { + let _ = self.conn.execute_batch("ROLLBACK"); + } + } +} + +// ───────────────────────────── OptionalExtension ───────────────────────────── + +pub trait OptionalExtension { + fn optional(self) -> Result>; +} +impl OptionalExtension for Result { + fn optional(self) -> Result> { + match self { + Ok(t) => Ok(Some(t)), + Err(Error::QueryReturnedNoRows) => Ok(None), + Err(e) => Err(e), + } + } +} + +// ───────────────────────────── sqlite-vec ───────────────────────────── + +/// Register sqlite-vec for the LOCAL backend. Must run AFTER libsql's first +/// `connect()` (which configures + initialises SQLite); `Connection::open` +/// arranges that via a warmup connect. No-op-safe to call repeatedly. On the +/// remote backend, vec lives on the server (`sqld --extensions-path`). +pub fn register_vec_after_init() { + use std::sync::Once; + static ONCE: Once = Once::new(); + ONCE.call_once(|| unsafe { + #[allow(clippy::missing_transmute_annotations)] + libsql_ffi::sqlite3_auto_extension(Some(std::mem::transmute( + sqlite_vec::sqlite3_vec_init as *const (), + ))); + }); +} diff --git a/crates/icm-store/src/lib.rs b/crates/icm-store/src/lib.rs index 07957bf..59b6a4f 100644 --- a/crates/icm-store/src/lib.rs +++ b/crates/icm-store/src/lib.rs @@ -1,3 +1,18 @@ +// Exactly one storage backend must be active. +#[cfg(all(feature = "backend-rusqlite", feature = "turso"))] +compile_error!( + "icm-store: enable exactly one backend — default `backend-rusqlite`, \ + OR libSQL/Turso via `--no-default-features --features turso` (not both)" +); +#[cfg(not(any(feature = "backend-rusqlite", feature = "turso")))] +compile_error!( + "icm-store: no storage backend enabled — keep the default `backend-rusqlite` \ + or build with `--features turso`" +); + +#[cfg(feature = "turso")] +#[macro_use] +pub mod dbcompat; mod schema; mod store; diff --git a/crates/icm-store/src/schema.rs b/crates/icm-store/src/schema.rs index cd1bc9a..87d422d 100644 --- a/crates/icm-store/src/schema.rs +++ b/crates/icm-store/src/schema.rs @@ -1,3 +1,7 @@ +// With `--features turso`, alias the libSQL-backed shim as `rusqlite` so this +// file is otherwise byte-identical to upstream (default builds use rusqlite). +#[cfg(feature = "turso")] +use crate::dbcompat as rusqlite; use rusqlite::Connection; use icm_core::{IcmError, IcmResult}; diff --git a/crates/icm-store/src/store.rs b/crates/icm-store/src/store.rs index c0dbff4..b01c6e5 100644 --- a/crates/icm-store/src/store.rs +++ b/crates/icm-store/src/store.rs @@ -1,11 +1,21 @@ use std::collections::{HashMap, HashSet, VecDeque}; use std::num::NonZeroUsize; use std::path::Path; -use std::sync::{Mutex, Once}; +use std::sync::Mutex; +#[cfg(feature = "backend-rusqlite")] +use std::sync::Once; use chrono::{DateTime, Utc}; use lru::LruCache; +// Default backend is rusqlite (unchanged upstream). With `--features turso`, the +// same SQL is routed through the libSQL-backed `dbcompat` shim, aliased as +// `rusqlite` so the rest of this file is byte-identical either way. +#[cfg(feature = "backend-rusqlite")] use rusqlite::{ffi::sqlite3_auto_extension, params, Connection}; +#[cfg(feature = "turso")] +use crate::dbcompat as rusqlite; +#[cfg(feature = "turso")] +use rusqlite::{params, Connection}; use sha2::{Digest, Sha256}; use zerocopy::IntoBytes; @@ -70,14 +80,21 @@ pub struct HookStatsRow { } /// Collect mapped rows into a Vec, converting rusqlite errors. +#[cfg(feature = "backend-rusqlite")] fn collect_rows( rows: rusqlite::MappedRows<'_, impl FnMut(&rusqlite::Row<'_>) -> rusqlite::Result>, ) -> IcmResult> { rows.collect::, _>>().map_err(db_err) } +#[cfg(feature = "turso")] +fn collect_rows(rows: rusqlite::MappedRows) -> IcmResult> { + rows.collect::>>().map_err(db_err) +} +/// Register sqlite-vec as a SQLite auto-extension (default rusqlite backend). +#[cfg(feature = "backend-rusqlite")] static SQLITE_VEC_INIT: Once = Once::new(); - +#[cfg(feature = "backend-rusqlite")] fn ensure_sqlite_vec() { SQLITE_VEC_INIT.call_once(|| unsafe { #[allow(clippy::missing_transmute_annotations)] @@ -86,6 +103,93 @@ fn ensure_sqlite_vec() { ))); }); } +// With `--features turso`, sqlite-vec is registered inside dbcompat's +// Connection::open (after libsql's threading init), so this is a no-op. +#[cfg(feature = "turso")] +fn ensure_sqlite_vec() {} + +/// Default backend: open the local SQLite file at `path`. +#[cfg(feature = "backend-rusqlite")] +fn open_backend(path: &Path) -> IcmResult { + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent) + .map_err(|e| IcmError::Database(format!("cannot create db directory: {e}")))?; + } + Connection::open(path).map_err(|e| IcmError::Database(format!("cannot open database: {e}"))) +} + +/// `--features turso`: pick the backend from the environment, else a local file. +/// +/// * `TURSO_DATABASE_URL` (or `LIBSQL_URL`) + `ICM_TURSO_REPLICA` truthy → local +/// embedded replica at `path` syncing to the remote primary. +/// * URL set otherwise → remote libSQL/Turso server (`sqld`/`tursodb`); every ICM +/// process shares it, so concurrent writes from multiple machines are +/// serialised by the server. +/// * no URL → local SQLite file at `path`. +/// +/// Auth token: `TURSO_AUTH_TOKEN` (or `LIBSQL_AUTH_TOKEN`); empty for an +/// unauthenticated local `sqld`. +#[cfg(feature = "turso")] +fn open_backend(path: &Path) -> IcmResult { + let url = std::env::var("TURSO_DATABASE_URL") + .or_else(|_| std::env::var("LIBSQL_URL")) + .ok() + .filter(|s| !s.trim().is_empty()); + let token = std::env::var("TURSO_AUTH_TOKEN") + .or_else(|_| std::env::var("LIBSQL_AUTH_TOKEN")) + .unwrap_or_default(); + match url { + Some(url) => { + let replica = std::env::var("ICM_TURSO_REPLICA") + .map(|v| v == "1" || v.eq_ignore_ascii_case("true")) + .unwrap_or(false); + if replica { + if let Some(parent) = path.parent() { + let _ = std::fs::create_dir_all(parent); + } + Connection::open_replica(path, url, token).map_err(|e| { + IcmError::Database(format!("cannot open Turso embedded replica: {e}")) + }) + } else { + Connection::open_remote(url, token).map_err(|e| { + IcmError::Database(format!("cannot connect to libSQL/Turso server: {e}")) + }) + } + } + None => { + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent) + .map_err(|e| IcmError::Database(format!("cannot create db directory: {e}")))?; + } + Connection::open(path) + .map_err(|e| IcmError::Database(format!("cannot open database: {e}"))) + } + } +} + +/// Apply connection PRAGMAs (WAL + foreign keys + busy timeout) for a local DB. +#[cfg(feature = "backend-rusqlite")] +fn apply_pragmas(conn: &Connection) -> IcmResult<()> { + conn.execute_batch( + "PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON; PRAGMA busy_timeout=30000;", + ) + .map_err(db_err) +} +/// `--features turso`: a remote server manages journaling/locking itself, so +/// only foreign-key enforcement is requested (best-effort); local/replica files +/// still get the full PRAGMAs. +#[cfg(feature = "turso")] +fn apply_pragmas(conn: &Connection) -> IcmResult<()> { + if conn.is_remote() { + let _ = conn.execute_batch("PRAGMA foreign_keys=ON;"); + Ok(()) + } else { + conn.execute_batch( + "PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON; PRAGMA busy_timeout=30000;", + ) + .map_err(db_err) + } +} /// In-process LRU cache size for hot memories. Each entry is one /// fully-hydrated `Memory` (incl. optional 384×f32 embedding ≈ 1.5KB), @@ -108,16 +212,8 @@ impl SqliteStore { /// Open or create a store with a specific embedding dimension. pub fn with_dims(path: &Path, embedding_dims: usize) -> IcmResult { ensure_sqlite_vec(); - if let Some(parent) = path.parent() { - std::fs::create_dir_all(parent) - .map_err(|e| IcmError::Database(format!("cannot create db directory: {e}")))?; - } - let conn = Connection::open(path) - .map_err(|e| IcmError::Database(format!("cannot open database: {e}")))?; - conn.execute_batch( - "PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON; PRAGMA busy_timeout=30000;", - ) - .map_err(db_err)?; + let conn = open_backend(path)?; + apply_pragmas(&conn)?; init_db_with_dims(&conn, embedding_dims)?; Ok(Self { conn, diff --git a/docs/turso-backend.md b/docs/turso-backend.md new file mode 100644 index 0000000..ba9d6da --- /dev/null +++ b/docs/turso-backend.md @@ -0,0 +1,72 @@ +# libSQL / Turso storage backend (opt-in) + +ICM stores memory in a single local SQLite file, so it can't be shared or written +concurrently from more than one machine/process. This **opt-in** backend runs the +existing `icm-store` SQL over the async **`libsql`** client instead, so the store +can live in a libSQL/Turso database — a local file, a remote `sqld`/Turso server, +or an embedded replica. A remote server is the multi-writer path: every ICM +process talks to the one server, which serialises writes. + +**The default build is unchanged** — it uses `rusqlite` exactly as before. The +two backends are mutually exclusive Cargo features. + +## Building + +```bash +# default — rusqlite (no change) +cargo build + +# libSQL/Turso backend +cargo build -p icm-cli --no-default-features --features turso,embeddings,tui +# or just the store crate: +cargo test -p icm-store --no-default-features --features turso +``` + +(A `flake.nix` builds the turso binary via Nix with the C deps wired up.) + +## Choosing the backend at runtime (turso build) + +| Env | Backend | +|-----|---------| +| *(none)* | local SQLite file (`--db` / default path) | +| `TURSO_DATABASE_URL` (or `LIBSQL_URL`) [+ `TURSO_AUTH_TOKEN`] | remote libSQL/Turso server — recommended for multi-writer | +| `…URL` + `ICM_TURSO_REPLICA=1` | local embedded replica syncing to the primary | + +## Self-hosted server with vector search + +ICM's schema uses the `vec0` virtual table (sqlite-vec), so the **server** must +load that extension (the client doesn't — remote queries run server-side): + +```bash +mkdir -p ~/.icm-ext && cd ~/.icm-ext +curl -fsSL https://github.com/asg017/sqlite-vec/releases/download/v0.1.6/sqlite-vec-0.1.6-loadable-linux-x86_64.tar.gz | tar xz +sha256sum vec0.so > trusted.lst +nix run nixpkgs#sqld -- --db-path ~/.icm/primary.sqld \ + --http-listen-addr 0.0.0.0:8080 --extensions-path ~/.icm-ext + +export TURSO_DATABASE_URL=http://:8080 +icm store --topic notes --content "shared across machines" +``` + +## Implementation + +`icm-store` gains a sync-over-async facade (`src/dbcompat.rs`) that mirrors the +slice of the rusqlite API the store uses. Under `--features turso` it's aliased +as `rusqlite`, so `store.rs`/`schema.rs` are byte-identical to the rusqlite build +apart from the alias and the connection-open path. + +## Verified + +- Default backend: **162/162** `icm-store` tests pass (unchanged). +- Turso backend: **161/162** (only `perf_fts_search_100` regresses — see below). +- Against a self-hosted `sqld`: store/recall, sqlite-vec server-side, and **16 + concurrent `icm` processes wrote with zero lost rows**. + +## Known limitations (turso backend only) + +- `perf_fts_search_100` regresses: the block-on-per-call bridge adds overhead + (worse over the network). Needs connection reuse or an async store path. +- Embedded replicas can't forward the `vec0` `CREATE VIRTUAL TABLE` DDL + (`unsupported statement`), so remote mode is the vector path. +- A benign `libsql::hrana … no runtime was available` line can appear at process + exit (the write already committed). diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..d1625f7 --- /dev/null +++ b/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1780336545, + "narHash": "sha256-vhVhuXzFrIOfcssC/9hDHx7MHzDKjF3keHuREOQqQiQ=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "4df1b885d76a54e1aa1a318f8d16fd6005b6401f", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..7e2e1cc --- /dev/null +++ b/flake.nix @@ -0,0 +1,71 @@ +{ + description = "ICM (fork) — permanent agent memory with a libSQL/Turso backend for concurrent multi-writer storage. See TURSO.md."; + + inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + + outputs = + { self, nixpkgs }: + let + systems = [ + "x86_64-linux" + "aarch64-linux" + ]; + forAll = nixpkgs.lib.genAttrs systems; + in + { + packages = forAll ( + system: + let + pkgs = nixpkgs.legacyPackages.${system}; + in + { + icm = pkgs.rustPlatform.buildRustPackage { + pname = "icm"; + version = "0.10.50-turso"; + + src = ./.; + # Build the crate vendor dir straight from our lockfile (which now + # includes libsql) — no vendor hash to maintain on every dep bump. + cargoLock.lockFile = ./Cargo.lock; + + # The libSQL/Turso backend is opt-in; the default build is rusqlite. + # Build the binary with the turso backend (mutually exclusive with the + # default rusqlite backend, hence --no-default-features). + buildNoDefaultFeatures = true; + buildFeatures = [ "turso" "embeddings" "tui" ]; + cargoBuildFlags = [ "-p" "icm-cli" ]; + + nativeBuildInputs = [ pkgs.pkg-config ]; + buildInputs = [ + pkgs.openssl + pkgs.onnxruntime + ]; + + env = { + OPENSSL_NO_VENDOR = "1"; + ORT_STRATEGY = "system"; + ORT_LIB_LOCATION = "${pkgs.lib.getLib pkgs.onnxruntime}/lib"; + }; + + # Workspace tests open libsql in-memory DBs; skip them in the package + # build (functionality is exercised via the CLI / TURSO.md). + doCheck = false; + + meta = { + description = "ICM fork with libSQL/Turso backend (concurrent multi-writer memory)"; + homepage = "https://github.com/rtk-ai/icm"; + mainProgram = "icm"; + }; + }; + default = self.packages.${system}.icm; + } + ); + + apps = forAll (system: { + default = { + type = "app"; + program = "${self.packages.${system}.icm}/bin/icm"; + }; + }); + }; +}