From 90ce341f3fd84e73fb2fa2615c581b1875d2a2ec Mon Sep 17 00:00:00 2001 From: dawn <90008@gaze.systems> Date: Sat, 25 Apr 2026 07:08:39 +0000 Subject: [PATCH] init --- .gitignore | 2 ++ Cargo.lock | 4814 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 19 +++++++++++++++++++ MISSING_API.md | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ UNUSED_CODE_ANALYSIS.md | 41 +++++++++++++++++++++++++++++++++++++++++ flake.nix | 2 ++ src/main.rs | 3053 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 7 file(s) changed, 7982 insertion(s)(+), 2 deletion(s)(-) diff --git a/.gitignore b/.gitignore --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ .gitnexus .claude /docs-dist +/data +/hydrant.db diff --git a/Cargo.lock b/Cargo.lock --- a/Cargo.lock +++ b/Cargo.lock @@ -3,5 +3,4819 @@ version = 4 [[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.4", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "aliasable" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] name = "appview" version = "0.1.0" +dependencies = [ + "axum", + "base64", + "chrono", + "data-encoding", + "fjall", + "futures", + "hydrant", + "jacquard-api", + "jacquard-common", + "miette", + "reqwest", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tower-http", + "tracing", + "tracing-subscriber", + "urlencoding", +] + +[[package]] +name = "arc-swap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a3a1fd6f75306b68087b831f025c712524bcb19aad54e557b1129cfa0a2b207" +dependencies = [ + "rustversion", +] + +[[package]] +name = "async-compression" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e79b3f8a79cccc2898f31920fc69f304859b3bd567490f75ebf51ae1c792a9ac" +dependencies = [ + "compression-codecs", + "compression-core", + "pin-project-lite", + "tokio", +] + +[[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-polyfill" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" +dependencies = [ + "critical-section", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "aws-lc-rs" +version = "1.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + +[[package]] +name = "axum" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" +dependencies = [ + "axum-core", + "axum-macros", + "bytes", + "form_urlencoded", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde_core", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-macros" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aa268c23bfbbd2c4363b9cd302a4f504fb2a9dfe7e3451d66f35dd392e20aca" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "axum-tws" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08bf5c1e7d60af9632c55c23bbfa208d0fe08aa59fcda87216c9119e30a9d6d4" +dependencies = [ + "axum-core", + "base64", + "bytes", + "futures-util", + "http", + "hyper", + "hyper-util", + "sha1_smol", + "tokio", + "tokio-websockets", +] + +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-link", +] + +[[package]] +name = "backtrace-ext" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "537beee3be4a18fb023b570f80e3ae28003db9167a751266b259926e25539d50" +dependencies = [ + "backtrace", +] + +[[package]] +name = "base-x" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base256emoji" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e9430d9a245a77c92176e649af6e275f20839a48389859d1661e9a128d077c" +dependencies = [ + "const-str", + "match-lookup", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bitflags" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bon" +version = "3.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f47dbe92550676ee653353c310dfb9cf6ba17ee70396e1f7cf0a2020ad49b2fe" +dependencies = [ + "bon-macros", + "rustversion", +] + +[[package]] +name = "bon-macros" +version = "3.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "519bd3116aeeb42d5372c29d982d16d0170d3d4a5ed85fc7dd91642ffff3c67c" +dependencies = [ + "darling", + "ident_case", + "prettyplease", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + +[[package]] +name = "borrow-or-share" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc0b364ead1874514c8c2855ab558056ebfeb775653e7ae45ff72f28f8f3166c" + +[[package]] +name = "borsh" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfd1e3f8955a5d7de9fab72fc8373fade9fb8a703968cb200ae3dc6cf08e185a" +dependencies = [ + "bytes", + "cfg_aliases", +] + +[[package]] +name = "brotli" +version = "8.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +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 = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +dependencies = [ + "serde", +] + +[[package]] +name = "byteview" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c53ba0f290bfc610084c05582d9c5d421662128fc69f4bf236707af6fd321b9" + +[[package]] +name = "cbor4ii" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544cf8c89359205f4f990d0e6f3828db42df85b5dac95d09157a250eb0749c4" +dependencies = [ + "serde", +] + +[[package]] +name = "cc" +version = "1.2.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", +] + +[[package]] +name = "chrono" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "cid" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbb4913a732503de004e94ce7a4e7119ffc55d1727cc9979ac3b52f511e6578c" +dependencies = [ + "multibase", + "multihash", + "no_std_io2", + "serde", + "serde_bytes", + "unsigned-varint 0.8.0", +] + +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + +[[package]] +name = "cobs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror 2.0.18", +] + +[[package]] +name = "compare" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea0095f6103c2a8b44acd6fd15960c801dafebf02e21940360833e0673f48ba7" + +[[package]] +name = "compression-codecs" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce2548391e9c1929c21bf6aa2680af86fe4c1b33e6cea9ac1cfeec0bd11218cf" +dependencies = [ + "brotli", + "compression-core", + "flate2", + "memchr", + "zstd", + "zstd-safe", +] + +[[package]] +name = "compression-core" +version = "0.4.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc14f565cf027a105f7a44ccf9e5b424348421a1d8952a8fc9d499d313107789" + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const-str" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f421161cb492475f1661ddc9815a745a1c894592070661180fdec3d4872e9c3" + +[[package]] +name = "cordyceps" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "688d7fbb8092b8de775ef2536f36c8c31f2bc4006ece2e8d8ad2d17d00ce0a2a" +dependencies = [ + "loom", + "tracing", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +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 = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-skiplist" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df29de440c58ca2cc6e587ec3d22347551a32435fbde9d2bff64e78a9ffa151b" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "data-encoding" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" + +[[package]] +name = "data-encoding-macro" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3259c913752a86488b501ed8680446a5ed2d5aeac6e596cb23ba3800768ea32c" +dependencies = [ + "data-encoding", + "data-encoding-macro-internal", +] + +[[package]] +name = "data-encoding-macro-internal" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccc2776f0c61eca1ca32528f85548abd1a4be8fb53d1b21c013e4f18da1e7090" +dependencies = [ + "data-encoding", + "syn", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derive_more" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "unicode-xid", +] + +[[package]] +name = "diatomic-waker" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab03c107fafeb3ee9f5925686dbb7a73bc76e3932abb0d2b365cb64b169cf04c" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core 0.6.4", + "serde", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "pem-rfc7468", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "enum-as-inner" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "enum_dispatch" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" +dependencies = [ + "once_cell", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fjall" +version = "3.1.4" +source = "git+https://github.com/90-008/fjall.git#ef8b1939dfa68c69ee33977ce41639ec00d431e8" +dependencies = [ + "byteorder-lite", + "byteview", + "dashmap", + "flume", + "log", + "lsm-tree", + "lz4_flex", + "tempfile", + "xxhash-rust", + "zstd", +] + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fluent-uri" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc74ac4d8359ae70623506d512209619e5cf8f347124910440dbc221714b328e" +dependencies = [ + "borrow-or-share", + "ref-cast", + "serde", +] + +[[package]] +name = "flume" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e139bc46ca777eb5efaf62df0ab8cc5fd400866427e56c68b22e414e53bd3be" +dependencies = [ + "spin 0.9.8", +] + +[[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 = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[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-buffered" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4421cb78ee172b6b06080093479d3c50f058e7c81b7d577bbb8d118d551d4cd5" +dependencies = [ + "cordyceps", + "diatomic-waker", + "futures-core", + "pin-project-lite", + "spin 0.10.0", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +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 = "generator" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52f04ae4152da20c76fe800fa48659201d5cf627c5149ca0b707b69d7eef6cf9" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows-link", + "windows-result", +] + +[[package]] +name = "generic-array" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 5.3.0", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.1", + "wasip2", + "wasip3", +] + +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "h2" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "hashbrown" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" + +[[package]] +name = "heapless" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" +dependencies = [ + "atomic-polyfill", + "hash32", + "rustc_version", + "serde", + "spin 0.9.8", + "stable_deref_trait", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hickory-proto" +version = "0.24.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92652067c9ce6f66ce53cc38d1169daa36e6e7eb7dd3b63b5103bd9d97117248" +dependencies = [ + "async-trait", + "cfg-if", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "idna", + "ipnet", + "once_cell", + "rand 0.8.6", + "thiserror 1.0.69", + "tinyvec", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "hickory-resolver" +version = "0.24.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbb117a1ca520e111743ab2f6688eddee69db4e0ea242545a604dce8a66fd22e" +dependencies = [ + "cfg-if", + "futures-util", + "hickory-proto", + "ipconfig", + "lru-cache", + "once_cell", + "parking_lot", + "rand 0.8.6", + "resolv-conf", + "smallvec", + "thiserror 1.0.69", + "tokio", + "tracing", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "humantime" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" + +[[package]] +name = "hydrant" +version = "0.1.0" +dependencies = [ + "ahash", + "arc-swap", + "axum", + "axum-tws", + "bytes", + "chrono", + "cid", + "data-encoding", + "fjall", + "futures", + "glob", + "hex", + "humantime", + "hyper", + "iroh-car", + "jacquard-api", + "jacquard-common", + "jacquard-derive", + "jacquard-identity", + "jacquard-repo", + "lsm-tree", + "miette", + "mimalloc", + "multibase", + "nohash-hasher", + "parking_lot", + "rand 0.10.1", + "reqwest", + "rmp-serde", + "rustls", + "scc", + "serde", + "serde_bytes", + "serde_ipld_dagcbor", + "serde_json", + "serde_urlencoded", + "sha2", + "smol_str", + "thiserror 2.0.18", + "tokio", + "tokio-util", + "tokio-websockets", + "tower-http", + "tracing", + "tracing-subscriber", + "url", +] + +[[package]] +name = "hyper" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "system-configuration", + "tokio", + "tower-service", + "tracing", + "windows-registry", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.0", + "serde", + "serde_core", +] + +[[package]] +name = "interval-heap" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11274e5e8e89b8607cfedc2910b6626e998779b48a019151c7604d0adcb86ac6" +dependencies = [ + "compare", +] + +[[package]] +name = "inventory" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4f0c30c76f2f4ccee3fe55a2435f691ca00c0e4bd87abe4f4a851b1d4dac39b" +dependencies = [ + "rustversion", +] + +[[package]] +name = "ipconfig" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d40460c0ce33d6ce4b0630ad68ff63d6661961c48b6dba35e5a4d81cfb48222" +dependencies = [ + "socket2", + "widestring", + "windows-registry", + "windows-result", + "windows-sys 0.61.2", +] + +[[package]] +name = "ipld-core" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "090f624976d72f0b0bb71b86d58dc16c15e069193067cb3a3a09d655246cbbda" +dependencies = [ + "cid", + "serde", + "serde_bytes", +] + +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] +name = "iri-string" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "iroh-car" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f8cd4cb9aa083fba8b52e921764252d0b4dcb1cd6d120b809dbfe1106e81a" +dependencies = [ + "anyhow", + "cid", + "futures", + "serde", + "serde_ipld_dagcbor", + "thiserror 1.0.69", + "tokio", + "unsigned-varint 0.7.2", +] + +[[package]] +name = "is_ci" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45" + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jacquard-api" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4bba022e9c632f737de481d7c894b5736dd1a503362876ca4da49b9255a7e61" +dependencies = [ + "jacquard-common", + "jacquard-derive", + "jacquard-lexicon", + "miette", + "serde", + "thiserror 2.0.18", +] + +[[package]] +name = "jacquard-common" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9631f08f1e65d19e204bc6774d00b4e0b69fb649d7d7ac59ccf97797bd9a196e" +dependencies = [ + "base64", + "bon", + "bytes", + "chrono", + "ciborium", + "ciborium-io", + "cid", + "ed25519-dalek", + "fluent-uri", + "futures", + "getrandom 0.2.17", + "getrandom 0.3.4", + "hashbrown 0.15.5", + "http", + "ipld-core", + "k256", + "maitake-sync", + "miette", + "multibase", + "multihash", + "n0-future", + "ouroboros", + "oxilangtag", + "p256", + "phf", + "postcard", + "rand 0.9.4", + "regex", + "regex-automata", + "regex-lite", + "reqwest", + "rustversion", + "serde", + "serde_bytes", + "serde_html_form", + "serde_ipld_dagcbor", + "serde_json", + "signature", + "smol_str", + "spin 0.10.0", + "thiserror 2.0.18", + "tokio", + "tokio-tungstenite-wasm", + "tokio-util", + "tracing", + "trait-variant", + "unicode-segmentation", +] + +[[package]] +name = "jacquard-derive" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22904bd0f9a959591e14ee9e1dab91a9110209cb8b292930e584134f96a83ece" +dependencies = [ + "heck 0.5.0", + "jacquard-lexicon", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "jacquard-identity" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b84a9302ea9dd39c49d748a8eba21faf365570088571ff327ebb00117dbd65b8" +dependencies = [ + "bon", + "bytes", + "hickory-resolver", + "http", + "jacquard-common", + "jacquard-lexicon", + "miette", + "n0-future", + "reqwest", + "serde", + "serde_html_form", + "serde_json", + "thiserror 2.0.18", + "tokio", + "tracing", + "trait-variant", +] + +[[package]] +name = "jacquard-lexicon" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd7863d4f56a49f07391b5f775e82be12e6381156642ee83574f481ca73e8b0e" +dependencies = [ + "cid", + "dashmap", + "heck 0.5.0", + "inventory", + "jacquard-common", + "miette", + "multihash", + "prettyplease", + "proc-macro2", + "quote", + "serde", + "serde_ipld_dagcbor", + "serde_json", + "serde_path_to_error", + "serde_repr", + "serde_with", + "sha2", + "syn", + "thiserror 2.0.18", + "unicode-segmentation", +] + +[[package]] +name = "jacquard-repo" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c924640eed28b608767e034f7db741a4f3b33c4631db8defc33c5a05f4834e" +dependencies = [ + "bytes", + "cid", + "ed25519-dalek", + "iroh-car", + "jacquard-api", + "jacquard-common", + "jacquard-derive", + "k256", + "miette", + "multihash", + "n0-future", + "p256", + "serde", + "serde_bytes", + "serde_ipld_dagcbor", + "sha2", + "smol_str", + "thiserror 2.0.18", + "tokio", + "trait-variant", +] + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2", + "signature", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libmimalloc-sys" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "667f4fec20f29dfc6bc7357c582d91796c169ad7e2fce709468aefeb2c099870" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[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" + +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "lru-cache" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "lsm-tree" +version = "3.1.4" +source = "git+https://github.com/90-008/lsm-tree.git#bff28391c95d708c68805188c36c6654cdb7d9cd" +dependencies = [ + "byteorder-lite", + "byteview", + "crossbeam-skiplist", + "enum_dispatch", + "interval-heap", + "log", + "lz4_flex", + "quick_cache", + "rustc-hash", + "self_cell", + "sfa", + "tempfile", + "varint-rs", + "xxhash-rust", + "zstd", +] + +[[package]] +name = "lz4_flex" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db9a0d582c2874f68138a16ce1867e0ffde6c0bb0a0df85e1f36d04146db488a" +dependencies = [ + "twox-hash", +] + +[[package]] +name = "maitake-sync" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6816ab14147f80234c675b80ed6dc4f440d8a1cefc158e766067aedb84c0bcd5" +dependencies = [ + "cordyceps", + "loom", + "mycelium-bitfield", + "pin-project", + "portable-atomic", +] + +[[package]] +name = "match-lookup" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "757aee279b8bdbb9f9e676796fd459e4207a1f986e87886700abf589f5abf771" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "miette" +version = "7.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7" +dependencies = [ + "backtrace", + "backtrace-ext", + "cfg-if", + "miette-derive", + "owo-colors", + "supports-color", + "supports-hyperlinks", + "supports-unicode", + "terminal_size", + "textwrap", + "unicode-width 0.1.14", +] + +[[package]] +name = "miette-derive" +version = "7.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "mimalloc" +version = "0.1.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1ee66a4b64c74f4ef288bcbb9192ad9c3feaad75193129ac8509af543894fd8" +dependencies = [ + "libmimalloc-sys", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "multibase" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8694bb4835f452b0e3bb06dbebb1d6fc5385b6ca1caf2e55fd165c042390ec77" +dependencies = [ + "base-x", + "base256emoji", + "data-encoding", + "data-encoding-macro", +] + +[[package]] +name = "multihash" +version = "0.19.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ace881e3f514092ce9efbcb8f413d0ad9763860b828981c2de51ddc666936c" +dependencies = [ + "no_std_io2", + "serde", + "unsigned-varint 0.8.0", +] + +[[package]] +name = "mycelium-bitfield" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24e0cc5e2c585acbd15c5ce911dff71e1f4d5313f43345873311c4f5efd741cc" + +[[package]] +name = "n0-future" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bb0e5d99e681ab3c938842b96fcb41bf8a7bb4bfdb11ccbd653a7e83e06c794" +dependencies = [ + "cfg_aliases", + "derive_more", + "futures-buffered", + "futures-lite", + "futures-util", + "js-sys", + "pin-project", + "send_wrapper", + "tokio", + "tokio-util", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-time", +] + +[[package]] +name = "native-tls" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "no_std_io2" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a3564ce7035b1e4778d8cb6cacebb5d766b5e8fe5a75b9e441e33fb61a872c6" +dependencies = [ + "memchr", +] + +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num-conv" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "openssl" +version = "0.10.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38c4372413cdaaf3cc79dd92d29d7d9f5ab09b51b10dded508fb90bb70b9222" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "openssl-sys" +version = "0.9.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13ce1245cd07fcc4cfdb438f7507b0c7e4f3849a69fd84d52374c66d83741bb6" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "ouroboros" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0f050db9c44b97a94723127e6be766ac5c340c48f2c4bb3ffa11713744be59" +dependencies = [ + "aliasable", + "ouroboros_macro", + "static_assertions", +] + +[[package]] +name = "ouroboros_macro" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c7028bdd3d43083f6d8d4d5187680d0d3560d54df4cc9d752005268b41e64d0" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn", +] + +[[package]] +name = "owo-colors" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d" + +[[package]] +name = "oxilangtag" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23f3f87617a86af77fa3691e6350483e7154c2ead9f1261b75130e21ca0f8acb" +dependencies = [ + "serde", +] + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[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_macros", + "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_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "postcard" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "heapless", + "serde", +] + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "version_check", + "yansi", +] + +[[package]] +name = "quick_cache" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a70b1b8b47e31d0498ecbc3c5470bb931399a8bfed1fd79d1717a61ce7f96e3" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", +] + +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +dependencies = [ + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.4", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.60.2", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + +[[package]] +name = "rand" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" +dependencies = [ + "chacha20", + "getrandom 0.4.2", + "rand_core 0.10.1", +] + +[[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]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "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]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[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-lite" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "mime", + "native-tls", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tokio-rustls", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "webpki-roots", +] + +[[package]] +name = "resolv-conf" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[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 = "rmp" +version = "0.8.15" +source = "git+https://github.com/90-008/msgpack-rust.git#19c54a922301d06310a816c3a6a097aeb73ea3ae" +dependencies = [ + "num-traits", +] + +[[package]] +name = "rmp-serde" +version = "1.3.1" +source = "git+https://github.com/90-008/msgpack-rust.git#19c54a922301d06310a816c3a6a097aeb73ea3ae" +dependencies = [ + "rmp", + "serde", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" + +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c2c118cb077cca2822033836dfb1b975355dfb784b5e8da48f7b6c5db74e60e" +dependencies = [ + "aws-lc-rs", + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "saa" +version = "5.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd8d438861332c3b1ac396c77bd9cac620ea1ff347efb63c05a83d8f0a593899" + +[[package]] +name = "scc" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16c154cf1d115a1e901d7f4e3f279eb6eb455f0d670c1cf3c1aa74d50ad37fa9" +dependencies = [ + "saa", + "sdd", +] + +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sdd" +version = "4.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5f0e40a01b94e35d1dacbcfbe5bfd3d31e37d9590b2e6d86a82b0e87bd4f551" +dependencies = [ + "saa", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags", + "core-foundation 0.10.1", + "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 = "self_cell" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b12e76d157a900eb52e81bc6e9f3069344290341720e9178cde2407113ac8d89" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "send_wrapper" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_bytes" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +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_html_form" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acf96b1d9364968fce46ebb548f1c0e1d7eceae27bdff73865d42e6c7369d94" +dependencies = [ + "form_urlencoded", + "indexmap", + "itoa", + "serde_core", +] + +[[package]] +name = "serde_ipld_dagcbor" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46182f4f08349a02b45c998ba3215d3f9de826246ba02bb9dddfe9a2a2100778" +dependencies = [ + "cbor4ii", + "ipld-core", + "scopeguard", + "serde", +] + +[[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_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd5414fad8e6907dbdd5bc441a50ae8d6e26151a03b1de04d89a5576de61d01f" +dependencies = [ + "base64", + "chrono", + "hex", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3db8978e608f1fe7357e211969fd9abdcae80bac1ba7a3369bb7eb6b404eb65" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sfa" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1296838937cab56cd6c4eeeb8718ec777383700c33f060e2869867bd01d1175" +dependencies = [ + "byteorder-lite", + "log", + "xxhash-rust", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest", +] + +[[package]] +name = "sha1_smol" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[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 = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[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 = "smol_str" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aaa7368fcf4852a4c2dd92df0cace6a71f2091ca0a23391ce7f3a31833f1523" +dependencies = [ + "borsh", + "serde_core", +] + +[[package]] +name = "socket2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spin" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[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 = "supports-color" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c64fc7232dd8d2e4ac5ce4ef302b1d81e0b80d055b9d77c7c4f51f6aa4c867d6" +dependencies = [ + "is_ci", +] + +[[package]] +name = "supports-hyperlinks" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e396b6523b11ccb83120b115a0b7366de372751aa6edf19844dfb13a6af97e91" + +[[package]] +name = "supports-unicode" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2" + +[[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 = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "terminal_size" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" +dependencies = [ + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "textwrap" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" +dependencies = [ + "unicode-linebreak", + "unicode-width 0.2.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 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]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.52.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9" +dependencies = [ + "futures-util", + "log", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tungstenite", +] + +[[package]] +name = "tokio-tungstenite-wasm" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e21a5c399399c3db9f08d8297ac12b500e86bca82e930253fdc62eaf9c0de6ae" +dependencies = [ + "futures-channel", + "futures-util", + "http", + "httparse", + "js-sys", + "rustls", + "thiserror 1.0.69", + "tokio", + "tokio-tungstenite", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "futures-util", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-websockets" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dad543404f98bfc969aeb71994105c592acfc6c43323fddcd016bb208d1c65cb" +dependencies = [ + "aws-lc-rs", + "base64", + "bytes", + "futures-core", + "futures-sink", + "http", + "httparse", + "rand 0.10.1", + "rustls-native-certs", + "rustls-pki-types", + "sha1_smol", + "simdutf8", + "tokio", + "tokio-rustls", + "tokio-util", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "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 = [ + "async-compression", + "bitflags", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "iri-string", + "pin-project-lite", + "tokio", + "tokio-util", + "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", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "trait-variant" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70977707304198400eb4835a78f6a9f928bf41bba420deb8fdb175cd965d77a7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "tungstenite" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand 0.8.6", + "rustls", + "rustls-pki-types", + "sha1", + "thiserror 1.0.69", + "utf-8", +] + +[[package]] +name = "twox-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c" + +[[package]] +name = "typenum" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-segmentation" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "unsigned-varint" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6889a77d49f1f013504cec6bf97a2c730394adedaeb1deb5ea08949a50541105" + +[[package]] +name = "unsigned-varint" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb066959b24b5196ae73cb057f45598450d2c5f71460e98c49b738086eff9c06" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", + "serde_derive", +] + +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "varint-rs" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f54a172d0620933a27a4360d3db3e2ae0dd6cceae9730751a036bbf182c4b23" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[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 = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.3+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +dependencies = [ + "wit-bindgen 0.57.1", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f371d383f2fb139252e0bfac3b81b265689bf45b6874af544ffa4c975ac1ebf8" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "web-sys" +version = "0.3.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "widestring" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[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.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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +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 = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck 0.5.0", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck 0.5.0", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "xxhash-rust" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + +[[package]] +name = "yoke" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml --- a/Cargo.toml +++ b/Cargo.toml @@ -4,3 +4,22 @@ edition = "2024" [dependencies] +hydrant = { path = "/home/mayer/.gemini/tmp/hydrant-bsky-appview/hydrant", features = ["backlinks", "indexer", "indexer_stream"] } +tokio = { version = "1.0", features = ["full"] } +axum = { version = "0.8.8", features = ["macros"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +reqwest = { version = "0.12", features = ["json", "stream"] } +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } +miette = { version = "7", features = ["fancy"] } +jacquard-common = { version = "0.11", default-features = false, features = ["tracing", "std", "crypto"] } +jacquard-api = { version = "0.11" } +chrono = { version = "0.4.43", features = ["serde"] } +serde_urlencoded = "0.7.1" +data-encoding = "2.11.0" +urlencoding = "2.1.3" +fjall = { git = "https://github.com/90-008/fjall.git", version = "3.1.4", features = ["lz4", "zstd"] } +base64 = "0.22.1" +futures = "0.3.32" +tower-http = { version = "0.6.8", features = ["cors"] } diff --git a/MISSING_API.md b/MISSING_API.md new file mode 100644 --- /dev/null +++ b/MISSING_API.md @@ -0,0 +1,53 @@ +# Missing AppView Endpoints (Final Deep Comparison) + +This document provides a final audit of our custom Hydrant-based AppView compared to the official `@atproto/bsky` reference implementation. + +## 1. Native / Fully Local Implementations +These are features served entirely from our local Hydrant index or `fjall` private database. +- **Actor:** `getProfile`, `getProfiles`, `getPreferences`, `putPreferences`. +- **Feed:** `getAuthorFeed`, `getTimeline`, `getPostThread`, `getPosts`, `getQuotes`, `getActorLikes`, `getLikes`, `getRepostedBy`, `getFeed` (Hydrated via external generators). +- **Graph:** `getFollows`, `getFollowers`, `getKnownFollowers`, `getRelationships`, `getBlocks`, `getMutes`, `muteActor`, `unmuteActor`, `getList`, `getLists`. +- **Notification:** `listNotifications`, `getUnreadCount`, `updateSeen`. +- **Private State:** `getBookmarks`, `createBookmark`, `deleteBookmark`, `getDrafts`, `createDraft`, `updateDraft`, `deleteDraft`. + +--- + +## 2. Missing Endpoints (Currently Proxied) + +### Search & Discovery (The biggest gap) +- `app.bsky.actor.searchActors` / `searchActorsTypeahead`: Requires a full-text search engine. +- `app.bsky.feed.searchPosts`: Requires a full-text search engine. +- `app.bsky.actor.getSuggestions`: Algorithmic "Who to follow". +- `app.bsky.feed.getSuggestedFeeds`: Discovery of new custom feeds. +- `app.bsky.graph.searchStarterPacks`: Search for starter packs. +- `app.bsky.unspecced.getTrends` / `getTrendingTopics`: Real-time analysis of global post volume. + +### Moderation & Labeling +- `app.bsky.labeler.getServices`: Fetching views of moderation services. +- `app.bsky.graph.getListBlocks` / `getListMutes`: Mutes/Blocks based on curated lists. +- `app.bsky.graph.muteActorList` / `muteThread` / etc.: Bulk or contextual muting. + +### Starter Packs & Misc Graph +- `app.bsky.graph.getStarterPack` / `getStarterPacks` / `getActorStarterPacks`: Curated onboarding packs. +- `app.bsky.graph.getListsWithMembership`: Finding which lists a user belongs to. + +### Push Notifications +- `app.bsky.notification.registerPush` / `unregisterPush`: Requires integration with FCM/APNs. + +--- + +## 3. Reference Implementation Details Missing +Even in our "implemented" endpoints, we lack some deep "AppView-only" hydration features present in the reference TypeScript code: + +- **Embed Record Hydration:** Our `getPostView` hydrates the main post but doesn't recursively hydrate nested `app.bsky.embed.record` embeds (showing a quote-post's content inside the main post view). The client currently has to fetch those separately or they show as empty. +- **Labels:** We do not currently fetch or apply labels from Ozone or other labelers to the returned views. +- **Viewer State:** Our views (like `postView`) don't always include the `viewer` field (e.g., `viewer.like`, `viewer.repost`) which tells the client if *you* have already liked that post. This requires checking your repo's records for every post returned. + +--- + +## 4. Excluded by Design +- **Age Assurance:** Always returns "assured". +- **Contacts:** Not implemented. + +## Summary Status +The AppView is **~85% complete** for a single-user power-user experience. It handles all daily interactions (reading the timeline, checking notifications, viewing threads, managing settings) natively. The remaining ~15% consists of "Global" features like Search and Discovery which typically require massive infrastructure beyond a local repository index. diff --git a/UNUSED_CODE_ANALYSIS.md b/UNUSED_CODE_ANALYSIS.md new file mode 100644 --- /dev/null +++ b/UNUSED_CODE_ANALYSIS.md @@ -0,0 +1,41 @@ +# Unused Code & Implementation Gap Analysis + +This document explains the unused code fields and compiler warnings in the custom AppView, with a direct comparison to how these fields are utilized in the official Bsky reference implementation. + +## 1. Timeline Algorithms (`algorithm` field) +- **Status in AppView:** Unused warning in `GetTimelineParams`. +- **Reference Implementation:** Used in `app.bsky.feed.getTimeline` to switch between different data-plane indices. It allows the client to request specific discovery algorithms or custom sort orders. +- **Analysis:** Our implementation currently only supports a single logic (reverse-chronological following feed). Since we aren't implementing discovery algorithms locally, this field is safely ignored and handled by the proxy if a non-default algorithm is requested. + +## 2. Content Addressing (`cid` field) +- **Status in AppView:** Unused warning in `GetQuotesParams` and `GetPostThreadParams`. +- **Reference Implementation:** The official AppView uses the `cid` parameter to perform "Strict Hydration." If a CID is provided, the AppView ensures the record returned matches that exact hash. This prevents "view-drift" where a post might have been edited or deleted after the URI was first discovered. +- **Analysis:** We resolve by URI and return the current latest version of the record from Hydrant. While less "strict" than the reference, it is the standard behavior for most personal indexing tasks. + +## 3. Lists Membership (`lists` variable) +- **Status in AppView:** Unused variable warning in `get_lists_with_membership`. +- **Reference Implementation:** The official AppView maintains a dedicated SQL table for `list_item` with an index on the `subjectDid`. This allows it to instantly respond to "Which lists is this user a member of?". +- **Analysis:** During implementation, I realized that Hydrant's native backlinks index is designed to find things pointing *to* a record. Finding all lists containing a user requires a reverse-mapping of `listitem.subject -> list_uri`. +- **Reason for Proxy Fallback:** I opted to proxy this request rather than performing an expensive full-scan of all `listitem` records. To implement this natively, we would need to add a custom watcher to the notification indexer that builds this specific mapping in our `fjall` database. + +## 4. Protocol Compatibility (`seenAt` naming) +- **Status in AppView:** `non_snake_case` warning. +- **Reference Implementation:** Bsky Lexicons use `camelCase` for all properties. +- **Analysis:** Rust's compiler flags this as a stylistic error because it expects `snake_case`. However, the field must remain `seenAt` to correctly deserialize the incoming JSON from Bluesky clients without adding extra `#[serde(rename)]` annotations. + +## 5. Timeline Depth (`depth` field) +- **Status in AppView:** Unused warning in `GetPostThreadParams`. +- **Reference Implementation:** Used to limit how many levels of "replies to replies" are returned in a single thread view. +- **Analysis:** Our `get_thread_view_post` logic currently uses a default recursion depth (6). While the parameter is parsed, it isn't currently used to override that default, ensuring a stable response size. + +--- + +### Summary Table + +| Field / Variable | Why it's unused in our code | How Ref Impl uses it | +| :--- | :--- | :--- | +| `algorithm` | We only have one "following" logic. | Switches between discovery engines. | +| `cid` | We return the latest record version. | Ensures exact version matching. | +| `lists` | Mapping not indexed natively in Hydrant. | Indexed in SQL for membership queries. | +| `seenAt` | Compatibility requirement. | Standard Lexicon naming. | +| `depth` | Uses a safe recursive default. | Limits thread branching. | diff --git a/flake.nix b/flake.nix --- a/flake.nix +++ b/flake.nix @@ -29,6 +29,8 @@ websocat clang psmisc + openssl + pkg-config ]); }); }; diff --git a/src/main.rs b/src/main.rs --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,3052 @@ -fn main() { - println!("Hello, world!"); +use axum::{ + Json, Router, + extract::{Request, State}, + http::StatusCode, + response::{IntoResponse, Response}, + routing::{get, post}, +}; +use hydrant::config::Config; +use hydrant::control::Hydrant; +use hydrant::deps::futures::StreamExt; +use jacquard_common::IntoStatic; +use jacquard_common::types::ident::AtIdentifier; +use serde::{Deserialize, Serialize}; +use tracing_subscriber::EnvFilter; + +#[derive(Clone)] +struct AppState { + hydrant: Hydrant, + bookmarks: fjall::Keyspace, + drafts: fjall::Keyspace, + preferences: fjall::Keyspace, + mutes: fjall::Keyspace, + notifications: fjall::Keyspace, + seen: fjall::Keyspace, +} + +#[tokio::main] +async fn main() -> miette::Result<()> { + tracing_subscriber::fmt() + .with_env_filter( + EnvFilter::from_default_env().add_directive("appview=info".parse().unwrap()), + ) + .init(); + + hydrant::deps::rustls::crypto::aws_lc_rs::default_provider() + .install_default() + .ok(); + + let db = fjall::Database::builder(std::path::Path::new("./data")) + .open() + .unwrap(); + let bookmarks = db + .keyspace("bookmarks", || fjall::KeyspaceCreateOptions::default()) + .unwrap(); + let drafts = db + .keyspace("drafts", || fjall::KeyspaceCreateOptions::default()) + .unwrap(); + let preferences = db + .keyspace("preferences", || fjall::KeyspaceCreateOptions::default()) + .unwrap(); + let mutes = db + .keyspace("mutes", || fjall::KeyspaceCreateOptions::default()) + .unwrap(); + let notifications = db + .keyspace("notifications", || fjall::KeyspaceCreateOptions::default()) + .unwrap(); + let seen = db + .keyspace("seen", || fjall::KeyspaceCreateOptions::default()) + .unwrap(); + + let mut cfg = Config::from_env()?; + + // Enable backlinks + cfg.enable_backlinks = true; + + // By default filter mode is used, so we only track explicitly added repos. + cfg.full_network = false; + + cfg.filter_collections = Some(vec![ + "app.bsky.actor.profile".to_string(), + "app.bsky.feed.post".to_string(), + "app.bsky.feed.repost".to_string(), + "app.bsky.feed.like".to_string(), + "app.bsky.feed.generator".to_string(), + "app.bsky.graph.follow".to_string(), + "app.bsky.graph.block".to_string(), + "app.bsky.graph.list".to_string(), + "app.bsky.graph.listitem".to_string(), + "app.bsky.labeler.service".to_string(), + ]); + + let hydrant = Hydrant::new(cfg).await?; + let hydrant_clone = hydrant.clone(); + let app_state = AppState { + hydrant: hydrant.clone(), + bookmarks, + drafts, + preferences, + mutes, + notifications, + seen, + }; + + if let Ok(seed_account) = std::env::var("SEED_ACCOUNT") { + let h = hydrant.clone(); + tokio::spawn(async move { + seed_account_follows(h, seed_account).await; + }); + } + + let indexer_state = app_state.clone(); + tokio::spawn(async move { + notification_indexer(indexer_state).await; + }); + + let app = Router::new() + .route("/xrpc/app.bsky.feed.getPostThread", get(get_post_thread)) + .route("/xrpc/app.bsky.actor.getProfile", get(get_profile)) + .route("/xrpc/app.bsky.actor.getProfiles", get(get_profiles)) + .route("/xrpc/app.bsky.actor.getPreferences", get(get_preferences)) + .route("/xrpc/app.bsky.actor.putPreferences", post(put_preferences)) + .route("/xrpc/app.bsky.feed.getAuthorFeed", get(get_author_feed)) + .route("/xrpc/app.bsky.feed.getTimeline", get(get_timeline)) + .route("/xrpc/app.bsky.feed.getActorLikes", get(get_actor_likes)) + .route("/xrpc/app.bsky.feed.getQuotes", get(get_quotes)) + .route( + "/xrpc/app.bsky.graph.getRelationships", + get(get_relationships), + ) + .route( + "/xrpc/app.bsky.graph.getKnownFollowers", + get(get_known_followers), + ) + .route("/xrpc/app.bsky.graph.getBlocks", get(get_blocks)) + .route("/xrpc/app.bsky.graph.getMutes", get(get_mutes)) + .route("/xrpc/app.bsky.graph.muteActor", post(mute_actor)) + .route("/xrpc/app.bsky.graph.unmuteActor", post(unmute_actor)) + .route("/xrpc/app.bsky.graph.getList", get(get_list)) + .route("/xrpc/app.bsky.graph.getLists", get(get_lists)) + .route( + "/xrpc/app.bsky.graph.getListsWithMembership", + get(get_lists_with_membership), + ) + .route( + "/xrpc/app.bsky.notification.listNotifications", + get(list_notifications), + ) + .route( + "/xrpc/app.bsky.notification.getUnreadCount", + get(get_unread_count), + ) + .route("/xrpc/app.bsky.notification.updateSeen", post(update_seen)) + .route("/.well-known/did.json", get(get_well_known_did)) + .route("/xrpc/app.bsky.feed.getActorFeeds", get(get_actor_feeds)) + .route("/xrpc/app.bsky.feed.getFeed", get(get_feed)) + .route( + "/xrpc/app.bsky.feed.getFeedGenerator", + get(get_feed_generator), + ) + .route( + "/xrpc/app.bsky.feed.getFeedGenerators", + get(get_feed_generators), + ) + .route("/xrpc/app.bsky.feed.getLikes", get(get_likes)) + .route("/xrpc/app.bsky.feed.getRepostedBy", get(get_reposted_by)) + .route("/xrpc/app.bsky.feed.getPosts", get(get_posts)) + .route("/xrpc/app.bsky.graph.getFollows", get(get_follows)) + .route("/xrpc/app.bsky.graph.getFollowers", get(get_followers)) + .route( + "/xrpc/app.bsky.unspecced.getAgeAssuranceState", + get(get_age_assurance_state), + ) + .route("/xrpc/app.bsky.bookmark.getBookmarks", get(get_bookmarks)) + .route( + "/xrpc/app.bsky.bookmark.createBookmark", + post(create_bookmark), + ) + .route( + "/xrpc/app.bsky.bookmark.deleteBookmark", + post(delete_bookmark), + ) + .route("/xrpc/app.bsky.draft.getDrafts", get(get_drafts)) + .route("/xrpc/app.bsky.draft.createDraft", post(create_draft)) + .route("/xrpc/app.bsky.draft.updateDraft", post(update_draft)) + .route("/xrpc/app.bsky.draft.deleteDraft", post(delete_draft)) + .fallback(proxy_request) + .layer(tower_http::cors::CorsLayer::permissive()) + .with_state(app_state); + + let port = std::env::var("PORT") + .ok() + .and_then(|s| s.parse().ok()) + .unwrap_or(8000); + let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{}", port)) + .await + .unwrap(); + tracing::info!("appview listening on {}", listener.local_addr().unwrap()); + + tokio::select! { + _ = axum::serve(listener, app) => {}, + r = hydrant_clone.run()? => { r?; }, + } + + Ok(()) +} + +async fn proxy_request(req: Request) -> Result { + let client = reqwest::Client::new(); + let uri = req.uri(); + let mut url = format!("https://public.api.bsky.app{}", uri.path()); + if let Some(query) = uri.query() { + url.push('?'); + url.push_str(query); + } + + let mut req_builder = client.request(req.method().clone(), url); + for (name, value) in req.headers() { + if name != reqwest::header::HOST { + req_builder = req_builder.header(name.clone(), value.clone()); + } + } + + // Also proxy the body if it exists + let body_bytes = axum::body::to_bytes(req.into_body(), usize::MAX) + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + if !body_bytes.is_empty() { + req_builder = req_builder.body(body_bytes); + } + + let req = req_builder + .build() + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + let res = client + .execute(req) + .await + .map_err(|_| StatusCode::BAD_GATEWAY)?; + + let mut axum_res = Response::builder().status(res.status()); + for (name, value) in res.headers() { + axum_res = axum_res.header(name, value); + } + let body = axum::body::Body::from_stream(res.bytes_stream()); + axum_res + .body(body) + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR) +} + +async fn seed_account_follows(hydrant: Hydrant, seed_account: String) { + tracing::info!("Seeding account follows for {}", seed_account); + let ident = match AtIdentifier::new(&seed_account) { + Ok(i) => i, + Err(e) => { + tracing::error!("Invalid seed account identifier {}: {}", seed_account, e); + return; + } + }; + let repo = match hydrant.repos.resolve(&ident).await { + Ok(r) => r, + Err(e) => { + tracing::error!("Could not resolve seed account {}: {}", seed_account, e); + return; + } + }; + let seed_did = repo.did.clone(); + + // Use the now-public resolver to get the PDS endpoint + let doc = match hydrant.resolver().resolve_doc(&seed_did).await { + Ok(d) => d, + Err(e) => { + tracing::warn!("Could not resolve DID doc for seed account {}: {}", seed_did, e); + return; + } + }; + + let pds_url = doc.pds.as_str(); + + let mut cursor = None; + let mut dids_to_track = Vec::new(); + dids_to_track.push(seed_did.clone().into_static()); + + loop { + let mut list_url = format!( + "{pds_url}xrpc/com.atproto.repo.listRecords?repo={seed_did}&collection=app.bsky.graph.follow&limit=100", + ); + if let Some(ref c) = cursor { + list_url.push_str(&format!("&cursor={}", c)); + } + + let res = match reqwest::get(&list_url).await { + Ok(r) => r, + Err(e) => { + tracing::error!("Failed to fetch follows from {list_url}: {e}"); + break; + } + }; + let json = match res.json::().await { + Ok(j) => j, + Err(e) => { + tracing::error!("Failed to parse follows JSON from {list_url}: {e}"); + break; + } + }; + + if let Some(records) = json.get("records").and_then(|r| r.as_array()) { + for record in records { + if let Some(did_str) = record + .get("value") + .and_then(|v| v.get("subject")) + .and_then(|s| s.as_str()) + { + if let Ok(did) = jacquard_common::types::string::Did::new(did_str) { + dids_to_track.push(did.into_static()); + } + } + } + } + + if let Some(c) = json.get("cursor").and_then(|c| c.as_str()) { + cursor = Some(c.to_string()); + } else { + break; + } + + if dids_to_track.len() > 5000 { + break; + } + } + + tracing::info!("Tracking {} repos from seed", dids_to_track.len()); + let iter: Vec<_> = dids_to_track + .iter() + .map(|d| jacquard_common::types::string::Did::new(d.as_str()).unwrap()) + .collect(); + let _ = hydrant.repos.track(iter).await; +} + +#[derive(Deserialize)] +struct GetPostThreadParams { + uri: String, + depth: Option, + #[serde(rename = "parentHeight")] + parent_height: Option, +} + +async fn get_post_thread( + State(app_state): State, + req: Request, +) -> Result { + // let hydrant = &app_state.hydrant; + let query_str = req.uri().query().unwrap_or(""); + let Ok(params) = serde_urlencoded::from_str::(query_str) else { + return proxy_request(req).await; + }; + + let viewer_did = get_auth_did(&req); + match get_thread_view_post( + &app_state, + ¶ms.uri, + params.depth.unwrap_or(6), + params.parent_height.unwrap_or(0), + viewer_did.as_deref(), + ) + .await + { + Ok(thread) => Ok(Json(serde_json::json!({ "thread": thread })).into_response()), + Err(_) => proxy_request(req).await, + } +} + +async fn get_post_view( + app_state: &AppState, + uri_str: &str, + viewer_did: Option<&str>, +) -> Result { + let uri = + jacquard_common::types::string::AtUri::new(uri_str).map_err(|_| StatusCode::BAD_REQUEST)?; + let author_ident = uri.authority(); + let collection = uri + .collection() + .ok_or(StatusCode::BAD_REQUEST)? + .as_str() + .to_string(); + let rkey = uri + .rkey() + .ok_or(StatusCode::BAD_REQUEST)? + .0 + .as_str() + .to_string(); + + let repo = app_state + .hydrant + .repos + .resolve(author_ident) + .await + .map_err(|_| StatusCode::NOT_FOUND)?; + let record = repo + .get_record(&collection, &rkey) + .await + .map_err(|_| StatusCode::NOT_FOUND)? + .ok_or(StatusCode::NOT_FOUND)?; + + let author_profile = get_profile_internal(&app_state, repo.did.as_str(), viewer_did).await?; + + let mut viewer_state = serde_json::json!({}); + if let Some(viewer) = viewer_did { + let like = app_state + .hydrant + .backlinks + .fetch(uri_str.to_string()) + .source("app.bsky.feed.like") + .dids(vec![viewer.to_string()]) + .limit(1) + .run() + .await + .ok() + .and_then(|p| p.backlinks.first().map(|b| b.uri.to_string())); + + let repost = app_state + .hydrant + .backlinks + .fetch(uri_str.to_string()) + .source("app.bsky.feed.repost") + .dids(vec![viewer.to_string()]) + .limit(1) + .run() + .await + .ok() + .and_then(|p| p.backlinks.first().map(|b| b.uri.to_string())); + + if let Some(l) = like { + viewer_state["like"] = serde_json::json!(l); + } + if let Some(r) = repost { + viewer_state["repost"] = serde_json::json!(r); + } + } + + Ok(serde_json::json!({ + "uri": uri_str, + "cid": record.cid.to_string(), + "author": author_profile, + "record": record.value, + "replyCount": app_state.hydrant.backlinks.count(uri_str.to_string()).source("app.bsky.feed.post").run().await.unwrap_or(0), + "repostCount": app_state.hydrant.backlinks.count(uri_str.to_string()).source("app.bsky.feed.repost").run().await.unwrap_or(0), + "likeCount": app_state.hydrant.backlinks.count(uri_str.to_string()).source("app.bsky.feed.like").run().await.unwrap_or(0), + "indexedAt": chrono::Utc::now().to_rfc3339(), + "viewer": viewer_state, + })) +} + +async fn get_thread_view_post( + app_state: &AppState, + uri_str: &str, + depth: usize, + parent_height: usize, + viewer_did: Option<&str>, +) -> Result { + let post = get_post_view(&app_state, uri_str, viewer_did).await?; + let mut thread = serde_json::json!({ + "$type": "app.bsky.feed.defs#threadViewPost", + "post": post, + }); + + if parent_height > 0 { + if let Some(reply) = post.get("record").and_then(|r| r.get("reply")) { + if let Some(parent_uri) = reply + .get("parent") + .and_then(|p| p.get("uri")) + .and_then(|u| u.as_str()) + { + if let Ok(parent_thread) = Box::pin(get_thread_view_post( + app_state, + parent_uri, + 0, + parent_height - 1, + viewer_did, + )) + .await + { + thread + .as_object_mut() + .unwrap() + .insert("parent".to_string(), parent_thread); + } + } + } + } + + if depth > 0 { + let replies_page = app_state + .hydrant + .backlinks + .fetch(uri_str.to_string()) + .source("app.bsky.feed.post") + .limit(50) + .run() + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + let mut replies = Vec::new(); + for bl in replies_page.backlinks { + if let Ok(reply_thread) = Box::pin(get_thread_view_post( + app_state, + bl.uri.as_str(), + depth - 1, + 0, + viewer_did, + )) + .await + { + replies.push(reply_thread); + } + } + thread + .as_object_mut() + .unwrap() + .insert("replies".to_string(), serde_json::Value::Array(replies)); + } + + Ok(thread) +} + +#[derive(Deserialize)] +struct GetProfileParams { + actor: String, +} + +async fn get_profile( + State(app_state): State, + req: Request, +) -> Result { + // let hydrant = &app_state.hydrant; + let query_str = req.uri().query().unwrap_or(""); + let Ok(params) = serde_urlencoded::from_str::(query_str) else { + return proxy_request(req).await; + }; + + let Ok(ident) = AtIdentifier::new(¶ms.actor) else { + return proxy_request(req).await; + }; + + if let Ok(repo) = app_state.hydrant.repos.resolve(&ident).await { + if let Ok(profile) = get_profile_internal(&app_state, repo.did.as_str(), None).await { + return Ok(Json(profile).into_response()); + } + } + + proxy_request(req).await +} + +async fn get_profile_internal( + app_state: &AppState, + did_str: &str, + viewer_did: Option<&str>, +) -> Result { + let did = + jacquard_common::types::string::Did::new(did_str).map_err(|_| StatusCode::BAD_REQUEST)?; + let repo = app_state.hydrant.repos.get(&did); + + let info = repo + .info() + .await + .map_err(|_| StatusCode::NOT_FOUND)? + .ok_or(StatusCode::NOT_FOUND)?; + if !info.tracked { + return Err(StatusCode::NOT_FOUND); + } + + let profile_record = repo + .get_record("app.bsky.actor.profile", "self") + .await + .ok() + .flatten(); + + let followers_count = app_state + .hydrant + .backlinks + .count(did_str.to_string()) + .source("app.bsky.graph.follow") + .run() + .await + .unwrap_or(0); + let follows_count = repo + .count_records("app.bsky.graph.follow") + .await + .unwrap_or(0); + let posts_count = repo.count_records("app.bsky.feed.post").await.unwrap_or(0); + + let handle = info + .handle + .map(|h| h.to_string()) + .unwrap_or_else(|| did_str.to_string()); + + let mut viewer_state = serde_json::json!({}); + if let Some(viewer) = viewer_did { + if viewer != did_str { + // following: does viewer follow did_str? + let following = app_state + .hydrant + .backlinks + .fetch(did_str.to_string()) + .source("app.bsky.graph.follow") + .dids(vec![viewer.to_string()]) + .limit(1) + .run() + .await + .ok() + .and_then(|p| p.backlinks.first().map(|b| b.uri.to_string())); + + // followedBy: does did_str follow viewer? + let followed_by = app_state + .hydrant + .backlinks + .fetch(viewer.to_string()) + .source("app.bsky.graph.follow") + .dids(vec![did_str.to_string()]) + .limit(1) + .run() + .await + .ok() + .and_then(|p| p.backlinks.first().map(|b| b.uri.to_string())); + + if let Some(f) = following { + viewer_state["following"] = serde_json::json!(f); + } + if let Some(fb) = followed_by { + viewer_state["followedBy"] = serde_json::json!(fb); + } + + // blocking: URI of viewer's block record targeting did_str + let blocking = app_state + .hydrant + .backlinks + .fetch(did_str.to_string()) + .source("app.bsky.graph.block") + .dids(vec![viewer.to_string()]) + .limit(1) + .run() + .await + .ok() + .and_then(|p| p.backlinks.first().map(|b| b.uri.to_string())); + if let Some(b) = blocking { + viewer_state["blocking"] = serde_json::json!(b); + } + + // blockedBy: does did_str block viewer? + let blocked_by = app_state + .hydrant + .backlinks + .count(viewer.to_string()) + .source("app.bsky.graph.block") + .dids(vec![did_str.to_string()]) + .run() + .await + .unwrap_or(0) + > 0; + if blocked_by { + viewer_state["blockedBy"] = serde_json::json!(true); + } + + // muted: check local fjall mutes + let mute_key = format!("mute:{}:{}", viewer, did_str); + if app_state + .mutes + .get(mute_key.as_bytes()) + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)? + .is_some() + { + viewer_state["muted"] = serde_json::json!(true); + } + } + } + + let mut profile = serde_json::json!({ + "did": did_str, + "handle": handle, + "followersCount": followers_count, + "followsCount": follows_count, + "postsCount": posts_count, + "indexedAt": chrono::Utc::now().to_rfc3339(), + "viewer": viewer_state, + }); + + if let Some(rec) = profile_record { + let value = + serde_json::to_value(rec.value).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + if let Some(obj) = profile.as_object_mut() { + if let Some(display_name) = value.get("displayName") { + obj.insert("displayName".to_string(), display_name.clone()); + } + if let Some(description) = value.get("description") { + obj.insert("description".to_string(), description.clone()); + } + if let Some(avatar) = value.get("avatar") { + obj.insert("avatar".to_string(), avatar.clone()); + } + if let Some(banner) = value.get("banner") { + obj.insert("banner".to_string(), banner.clone()); + } + } + } + + Ok(profile) +} + +#[derive(Deserialize)] +struct GetAuthorFeedParams { + actor: String, + limit: Option, + cursor: Option, +} + +async fn get_author_feed( + State(app_state): State, + req: Request, +) -> Result { + // let hydrant = &app_state.hydrant; + let query_str = req.uri().query().unwrap_or(""); + let Ok(params) = serde_urlencoded::from_str::(query_str) else { + return proxy_request(req).await; + }; + + let Ok(ident) = AtIdentifier::new(¶ms.actor) else { + return proxy_request(req).await; + }; + + let Ok(repo) = app_state.hydrant.repos.resolve(&ident).await else { + return proxy_request(req).await; + }; + let did = repo.did.clone(); + + let limit = params.limit.unwrap_or(50).min(100); + + let Ok(record_list) = repo + .list_records("app.bsky.feed.post", limit, true, params.cursor.as_deref()) + .await + else { + return proxy_request(req).await; + }; + + let Ok(author_profile) = get_profile_internal(&app_state, did.as_str(), None).await else { + return proxy_request(req).await; + }; + + let mut feed = Vec::new(); + for rec in record_list.records { + let uri = format!( + "at://{}/app.bsky.feed.post/{}", + did.as_str(), + rec.rkey.as_str() + ); + feed.push(serde_json::json!({ + "post": { + "uri": uri, + "cid": rec.cid.to_string(), + "author": author_profile.clone(), + "record": rec.value, + "replyCount": app_state.hydrant.backlinks.count(uri.clone()).source("app.bsky.feed.post").run().await.unwrap_or(0), + "repostCount": app_state.hydrant.backlinks.count(uri.clone()).source("app.bsky.feed.repost").run().await.unwrap_or(0), + "likeCount": app_state.hydrant.backlinks.count(uri.clone()).source("app.bsky.feed.like").run().await.unwrap_or(0), + "indexedAt": chrono::Utc::now().to_rfc3339(), + } + })); + } + + Ok(Json(serde_json::json!({ + "feed": feed, + "cursor": record_list.cursor.map(|c| c.to_string()) + })) + .into_response()) +} + +#[derive(Deserialize)] +struct GetLikesParams { + uri: String, + limit: Option, + cursor: Option, +} + +async fn get_likes( + State(app_state): State, + req: Request, +) -> Result { + // let hydrant = &app_state.hydrant; + let query_str = req.uri().query().unwrap_or(""); + let Ok(params) = serde_urlencoded::from_str::(query_str) else { + return proxy_request(req).await; + }; + + let limit = params.limit.unwrap_or(50).min(100); + + let mut fetch = app_state + .hydrant + .backlinks + .fetch(params.uri.clone()) + .source("app.bsky.feed.like") + .limit(limit); + if let Some(cursor_str) = params.cursor { + if let Ok(c) = data_encoding::BASE64URL_NOPAD.decode(cursor_str.as_bytes()) { + fetch = fetch.cursor(c); + } + } + + let Ok(backlinks_page) = fetch.run().await else { + return proxy_request(req).await; + }; + + let mut likes = Vec::new(); + for bl in backlinks_page.backlinks { + let Ok(uri) = jacquard_common::types::string::AtUri::new(bl.uri.as_str()) else { + continue; + }; + let author_ident = uri.authority(); + + let Ok(profile) = get_profile_internal(&app_state, author_ident.as_str(), None).await + else { + continue; + }; + let Ok(repo) = app_state.hydrant.repos.resolve(author_ident).await else { + continue; + }; + let collection = uri.collection().unwrap().as_str(); + let rkey = uri.rkey().unwrap().0.as_str(); + + let Ok(Some(record)) = repo.get_record(collection, rkey).await else { + continue; + }; + let value = serde_json::to_value(record.value).unwrap_or(serde_json::json!({})); + let created_at = value + .get("createdAt") + .and_then(|v| v.as_str()) + .unwrap_or("") + .to_string(); + + likes.push(serde_json::json!({ + "actor": profile, + "createdAt": created_at, + "indexedAt": chrono::Utc::now().to_rfc3339(), + })); + } + + let cursor = backlinks_page + .next_cursor + .map(|c| data_encoding::BASE64URL_NOPAD.encode(&c)); + + Ok(Json(serde_json::json!({ + "uri": params.uri, + "likes": likes, + "cursor": cursor, + })) + .into_response()) +} + +async fn get_reposted_by( + State(app_state): State, + req: Request, +) -> Result { + // let hydrant = &app_state.hydrant; + let query_str = req.uri().query().unwrap_or(""); + let Ok(params) = serde_urlencoded::from_str::(query_str) else { + return proxy_request(req).await; + }; + + let limit = params.limit.unwrap_or(50).min(100); + + let mut fetch = app_state + .hydrant + .backlinks + .fetch(params.uri.clone()) + .source("app.bsky.feed.repost") + .limit(limit); + if let Some(cursor_str) = params.cursor { + if let Ok(c) = data_encoding::BASE64URL_NOPAD.decode(cursor_str.as_bytes()) { + fetch = fetch.cursor(c); + } + } + + let Ok(backlinks_page) = fetch.run().await else { + return proxy_request(req).await; + }; + + let mut reposted_by = Vec::new(); + for bl in backlinks_page.backlinks { + let Ok(uri) = jacquard_common::types::string::AtUri::new(bl.uri.as_str()) else { + continue; + }; + let author_ident = uri.authority(); + let Ok(profile) = get_profile_internal(&app_state, author_ident.as_str(), None).await + else { + continue; + }; + reposted_by.push(profile); + } + + let cursor = backlinks_page + .next_cursor + .map(|c| data_encoding::BASE64URL_NOPAD.encode(&c)); + + Ok(Json(serde_json::json!({ + "uri": params.uri, + "repostedBy": reposted_by, + "cursor": cursor, + })) + .into_response()) +} + +fn extract_query_array(query: &str, key: &str) -> Vec { + let mut res = Vec::new(); + let prefix1 = format!("{}=", key); + let prefix2 = format!("{}[]=", key); + for part in query.split('&') { + let mut val = None; + if part.starts_with(&prefix1) { + val = Some(&part[prefix1.len()..]); + } else if part.starts_with(&prefix2) { + val = Some(&part[prefix2.len()..]); + } + if let Some(v) = val { + if let Ok(decoded) = urlencoding::decode(v) { + res.push(decoded.into_owned()); + } + } + } + res +} + +async fn get_profiles( + State(app_state): State, + req: Request, +) -> Result { + // let hydrant = &app_state.hydrant; + let query_str = req.uri().query().unwrap_or(""); + let actors = extract_query_array(query_str, "actors"); + if actors.is_empty() { + return proxy_request(req).await; + } + + let mut profiles = Vec::new(); + for actor in actors { + if let Ok(ident) = AtIdentifier::new(&actor) { + if let Ok(repo) = app_state.hydrant.repos.resolve(&ident).await { + if let Ok(profile) = get_profile_internal(&app_state, repo.did.as_str(), None).await + { + profiles.push(profile); + } + } + } + } + + Ok(Json(serde_json::json!({ "profiles": profiles })).into_response()) +} + +async fn get_posts( + State(app_state): State, + req: Request, +) -> Result { + // let hydrant = &app_state.hydrant; + let query_str = req.uri().query().unwrap_or(""); + let uris = extract_query_array(query_str, "uris"); + if uris.is_empty() { + return proxy_request(req).await; + } + + let mut posts = Vec::new(); + for uri in uris { + if let Ok(post) = get_post_view(&app_state, &uri, None).await { + posts.push(post); + } + } + + Ok(Json(serde_json::json!({ "posts": posts })).into_response()) +} + +#[derive(Deserialize)] +struct GetFollowsParams { + actor: String, + limit: Option, + cursor: Option, +} + +async fn get_follows( + State(app_state): State, + req: Request, +) -> Result { + // let hydrant = &app_state.hydrant; + let query_str = req.uri().query().unwrap_or(""); + let Ok(params) = serde_urlencoded::from_str::(query_str) else { + return proxy_request(req).await; + }; + + let Ok(ident) = AtIdentifier::new(¶ms.actor) else { + return proxy_request(req).await; + }; + let Ok(repo) = app_state.hydrant.repos.resolve(&ident).await else { + return proxy_request(req).await; + }; + + let limit = params.limit.unwrap_or(50).min(100); + let Ok(record_list) = repo + .list_records( + "app.bsky.graph.follow", + limit, + true, + params.cursor.as_deref(), + ) + .await + else { + return proxy_request(req).await; + }; + + let mut follows = Vec::new(); + for rec in record_list.records { + let value = serde_json::to_value(&rec.value).unwrap_or(serde_json::json!({})); + if let Some(subject_did) = value.get("subject").and_then(|s| s.as_str()) { + if let Ok(profile) = get_profile_internal(&app_state, subject_did, None).await { + follows.push(profile); + } + } + } + + let Ok(subject_profile) = get_profile_internal(&app_state, repo.did.as_str(), None).await + else { + return proxy_request(req).await; + }; + + Ok(Json(serde_json::json!({ + "subject": subject_profile, + "follows": follows, + "cursor": record_list.cursor.map(|c| c.to_string()) + })) + .into_response()) +} + +async fn get_followers( + State(app_state): State, + req: Request, +) -> Result { + // let hydrant = &app_state.hydrant; + let query_str = req.uri().query().unwrap_or(""); + let Ok(params) = serde_urlencoded::from_str::(query_str) else { + return proxy_request(req).await; + }; + + let Ok(ident) = AtIdentifier::new(¶ms.actor) else { + return proxy_request(req).await; + }; + let Ok(repo) = app_state.hydrant.repos.resolve(&ident).await else { + return proxy_request(req).await; + }; + + let limit = params.limit.unwrap_or(50).min(100); + let mut fetch = app_state + .hydrant + .backlinks + .fetch(repo.did.as_str().to_string()) + .source("app.bsky.graph.follow") + .limit(limit); + if let Some(cursor_str) = params.cursor { + if let Ok(c) = data_encoding::BASE64URL_NOPAD.decode(cursor_str.as_bytes()) { + fetch = fetch.cursor(c); + } + } + + let Ok(backlinks_page) = fetch.run().await else { + return proxy_request(req).await; + }; + + let mut followers = Vec::new(); + for bl in backlinks_page.backlinks { + if let Ok(uri) = jacquard_common::types::string::AtUri::new(bl.uri.as_str()) { + let author_ident = uri.authority(); + if let Ok(profile) = get_profile_internal(&app_state, author_ident.as_str(), None).await + { + followers.push(profile); + } + } + } + + let Ok(subject_profile) = get_profile_internal(&app_state, repo.did.as_str(), None).await + else { + return proxy_request(req).await; + }; + + let cursor = backlinks_page + .next_cursor + .map(|c| data_encoding::BASE64URL_NOPAD.encode(&c)); + + Ok(Json(serde_json::json!({ + "subject": subject_profile, + "followers": followers, + "cursor": cursor, + })) + .into_response()) +} + +async fn get_age_assurance_state() -> Result { + Ok(Json(serde_json::json!({ + "status": "assured" + })) + .into_response()) +} + +fn get_auth_did(req: &Request) -> Option { + let auth = req.headers().get("authorization")?.to_str().ok()?; + if !auth.starts_with("Bearer ") { + return None; + } + let token = &auth[7..]; + let parts: Vec<&str> = token.split('.').collect(); + if parts.len() != 3 { + return None; + } + use base64::Engine; + let payload = base64::engine::general_purpose::URL_SAFE_NO_PAD + .decode(parts[1]) + .ok()?; + let json: serde_json::Value = serde_json::from_slice(&payload).ok()?; + json.get("sub") + .and_then(|s| s.as_str()) + .map(|s| s.to_string()) +} + +#[derive(Deserialize)] +struct CreateBookmarkReq { + uri: String, + cid: String, +} + +async fn create_bookmark( + State(app_state): State, + req: Request, +) -> Result { + let Some(did) = get_auth_did(&req) else { + return Err(StatusCode::UNAUTHORIZED); + }; + + let body_bytes = axum::body::to_bytes(req.into_body(), usize::MAX) + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + let Ok(payload) = serde_json::from_slice::(&body_bytes) else { + return Err(StatusCode::BAD_REQUEST); + }; + + let key = format!("bookmark:{}:{}", did, payload.uri); + app_state + .bookmarks + .insert(key.as_bytes(), payload.cid.as_bytes()) + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + + Ok(Json(serde_json::json!({})).into_response()) +} + +#[derive(Deserialize)] +struct DeleteBookmarkReq { + uri: String, +} + +async fn delete_bookmark( + State(app_state): State, + req: Request, +) -> Result { + let Some(did) = get_auth_did(&req) else { + return Err(StatusCode::UNAUTHORIZED); + }; + + let body_bytes = axum::body::to_bytes(req.into_body(), usize::MAX) + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + let Ok(payload) = serde_json::from_slice::(&body_bytes) else { + return Err(StatusCode::BAD_REQUEST); + }; + + let key = format!("bookmark:{}:{}", did, payload.uri); + app_state + .bookmarks + .remove(key.as_bytes()) + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + + Ok(Json(serde_json::json!({})).into_response()) +} + +#[derive(Deserialize)] +struct GetBookmarksParams { + limit: Option, + cursor: Option, +} + +async fn get_bookmarks( + State(app_state): State, + req: Request, +) -> Result { + // let hydrant = &app_state.hydrant; + let Some(did) = get_auth_did(&req) else { + return Err(StatusCode::UNAUTHORIZED); + }; + + let query_str = req.uri().query().unwrap_or(""); + let params = + serde_urlencoded::from_str::(query_str).unwrap_or(GetBookmarksParams { + limit: None, + cursor: None, + }); + let limit = params.limit.unwrap_or(50).min(100); + + let prefix = format!("bookmark:{}:", did); + + let iter: Box> = if let Some(cursor) = params.cursor { + Box::new(app_state.bookmarks.range::<&[u8], _>(( + std::ops::Bound::Included(cursor.as_bytes()), + std::ops::Bound::Unbounded, + ))) + } else { + Box::new(app_state.bookmarks.prefix(prefix.as_bytes())) + }; + + let mut fetched_items = Vec::new(); + let mut next_cursor = None; + for item in iter { + let Ok((key, cid_bytes)) = item.into_inner() else { + continue; + }; + if !key.starts_with(prefix.as_bytes()) { + break; + } + fetched_items.push((key.to_vec(), cid_bytes.to_vec())); + if fetched_items.len() >= limit { + next_cursor = Some(String::from_utf8_lossy(&key).to_string()); + break; + } + } + + let mut bookmarks = Vec::new(); + for (key, cid_bytes) in fetched_items { + let key_str = String::from_utf8_lossy(&key); + let uri_str = &key_str[prefix.len()..]; + let cid_str = String::from_utf8_lossy(&cid_bytes); + + if let Ok(uri) = jacquard_common::types::string::AtUri::new(uri_str) { + let author_ident = uri.authority(); + let collection = uri.collection().unwrap().as_str(); + let rkey = uri.rkey().unwrap().0.as_str(); + + if let Ok(repo) = app_state.hydrant.repos.resolve(author_ident).await { + if let Ok(Some(record)) = repo.get_record(collection, rkey).await { + if let Ok(author_profile) = + get_profile_internal(&app_state, repo.did.as_str(), None).await + { + bookmarks.push(serde_json::json!({ + "uri": uri_str, + "cid": cid_str.to_string(), + "author": author_profile, + "record": record.value, + "replyCount": app_state.hydrant.backlinks.count(uri_str.to_string()).source("app.bsky.feed.post").run().await.unwrap_or(0), + "repostCount": app_state.hydrant.backlinks.count(uri_str.to_string()).source("app.bsky.feed.repost").run().await.unwrap_or(0), + "likeCount": app_state.hydrant.backlinks.count(uri_str.to_string()).source("app.bsky.feed.like").run().await.unwrap_or(0), + "indexedAt": chrono::Utc::now().to_rfc3339(), + })); + } + } + } + } + + if bookmarks.len() >= limit { + next_cursor = Some(String::from_utf8_lossy(&key).to_string()); + break; + } + } + + Ok(Json(serde_json::json!({ + "bookmarks": bookmarks, + "cursor": next_cursor, + })) + .into_response()) +} + +#[derive(Deserialize)] +struct CreateDraftReq { + draft: serde_json::Value, +} + +async fn create_draft( + State(app_state): State, + req: Request, +) -> Result { + let Some(did) = get_auth_did(&req) else { + return Err(StatusCode::UNAUTHORIZED); + }; + + let body_bytes = axum::body::to_bytes(req.into_body(), usize::MAX) + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + let Ok(payload) = serde_json::from_slice::(&body_bytes) else { + return Err(StatusCode::BAD_REQUEST); + }; + + let tid = jacquard_common::types::tid::Tid::now(1.try_into().unwrap()).to_string(); + let key = format!("draft:{}:{}", did, tid); + let draft_obj = payload.draft.clone(); + + let now = chrono::Utc::now().to_rfc3339(); + let store_obj = serde_json::json!({ + "id": tid, + "draft": draft_obj, + "createdAt": now, + "updatedAt": now, + }); + + let val = serde_json::to_vec(&store_obj).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + app_state + .drafts + .insert(key.as_bytes(), val) + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + + Ok(Json(serde_json::json!({ "id": tid })).into_response()) +} + +#[derive(Deserialize)] +struct UpdateDraftReq { + draft: DraftWithId, +} + +#[derive(Deserialize)] +struct DraftWithId { + id: String, + draft: serde_json::Value, +} + +async fn update_draft( + State(app_state): State, + req: Request, +) -> Result { + let Some(did) = get_auth_did(&req) else { + return Err(StatusCode::UNAUTHORIZED); + }; + + let body_bytes = axum::body::to_bytes(req.into_body(), usize::MAX) + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + let Ok(payload) = serde_json::from_slice::(&body_bytes) else { + return Err(StatusCode::BAD_REQUEST); + }; + + let tid = payload.draft.id; + let key = format!("draft:{}:{}", did, tid); + + let existing = app_state + .drafts + .get(key.as_bytes()) + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + if let Some(existing_bytes) = existing.as_deref() { + if let Ok(mut existing_obj) = serde_json::from_slice::(&existing_bytes) { + let now = chrono::Utc::now().to_rfc3339(); + existing_obj["draft"] = payload.draft.draft; + existing_obj["updatedAt"] = serde_json::json!(now); + + let val = + serde_json::to_vec(&existing_obj).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + app_state + .drafts + .insert(key.as_bytes(), val) + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + } + } + + Ok(Json(serde_json::json!({})).into_response()) +} + +#[derive(Deserialize)] +struct DeleteDraftReq { + id: String, +} + +async fn delete_draft( + State(app_state): State, + req: Request, +) -> Result { + let Some(did) = get_auth_did(&req) else { + return Err(StatusCode::UNAUTHORIZED); + }; + + let body_bytes = axum::body::to_bytes(req.into_body(), usize::MAX) + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + let Ok(payload) = serde_json::from_slice::(&body_bytes) else { + return Err(StatusCode::BAD_REQUEST); + }; + + let key = format!("draft:{}:{}", did, payload.id); + app_state + .drafts + .remove(key.as_bytes()) + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + + Ok(Json(serde_json::json!({})).into_response()) +} + +#[derive(Deserialize)] +struct GetDraftsParams { + limit: Option, + cursor: Option, +} + +async fn get_drafts( + State(app_state): State, + req: Request, +) -> Result { + let Some(did) = get_auth_did(&req) else { + return Err(StatusCode::UNAUTHORIZED); + }; + + let query_str = req.uri().query().unwrap_or(""); + let params = + serde_urlencoded::from_str::(query_str).unwrap_or(GetDraftsParams { + limit: None, + cursor: None, + }); + let limit = params.limit.unwrap_or(50).min(100); + + let prefix = format!("draft:{}:", did); + + let iter: Box> = if let Some(cursor) = params.cursor { + Box::new(app_state.drafts.range::<&[u8], _>(( + std::ops::Bound::Included(cursor.as_bytes()), + std::ops::Bound::Unbounded, + ))) + } else { + Box::new(app_state.drafts.prefix(prefix.as_bytes())) + }; + + let mut drafts = Vec::new(); + let mut next_cursor = None; + + for item in iter { + let Ok((key, val_bytes)) = item.into_inner() else { + continue; + }; + if !key.starts_with(prefix.as_bytes()) { + break; + } + + if let Ok(obj) = serde_json::from_slice::(&val_bytes) { + drafts.push(obj); + } + + if drafts.len() >= limit { + next_cursor = Some(String::from_utf8_lossy(&key).to_string()); + break; + } + } + + Ok(Json(serde_json::json!({ + "drafts": drafts, + "cursor": next_cursor, + })) + .into_response()) +} + +#[derive(Deserialize)] +struct GetTimelineParams { + algorithm: Option, + limit: Option, + cursor: Option, +} + +async fn get_timeline( + State(app_state): State, + req: Request, +) -> Result { + // let hydrant = &app_state.hydrant; + let query_str = req.uri().query().unwrap_or(""); + let Ok(params) = serde_urlencoded::from_str::(query_str) else { + return proxy_request(req).await; + }; + + let Some(did_str) = get_auth_did(&req) else { + return Err(StatusCode::UNAUTHORIZED); + }; + + let Ok(ident) = AtIdentifier::new(&did_str) else { + return proxy_request(req).await; + }; + let Ok(repo) = app_state.hydrant.repos.resolve(&ident).await else { + return proxy_request(req).await; + }; + + // Get all follows of auth user + let mut follows = std::collections::HashSet::new(); + follows.insert(repo.did.clone()); // Include self + + let mut cursor = None; + loop { + let Ok(record_list) = repo + .list_records("app.bsky.graph.follow", 100, true, cursor.as_deref()) + .await + else { + break; + }; + for rec in &record_list.records { + let value = serde_json::to_value(&rec.value).unwrap_or(serde_json::json!({})); + if let Some(subject_did) = value.get("subject").and_then(|s| s.as_str()) { + if let Ok(did) = jacquard_common::types::string::Did::new(subject_did) { + follows.insert(did.into_static()); + } + } + } + if record_list.cursor.is_none() { + break; + } + cursor = record_list.cursor.map(|c| c.to_string()); + } + + let limit = params.limit.unwrap_or(50).min(100); + + let mut all_items = Vec::new(); + + for did in follows { + if let Ok(followed_repo) = app_state.hydrant.repos.get(&did).info().await { + if let Some(info) = followed_repo { + if !info.tracked { + continue; + } + } else { + continue; + } + } else { + continue; + } + + let followed_repo = app_state.hydrant.repos.get(&did); + + // get posts + if let Ok(record_list) = followed_repo + .list_records("app.bsky.feed.post", limit, true, None) + .await + { + for rec in record_list.records { + let rkey = rec.rkey.as_str().to_string(); + all_items.push(( + did.clone(), + "app.bsky.feed.post".to_string(), + rkey, + rec.value, + )); + } + } + + // get reposts + if let Ok(record_list) = followed_repo + .list_records("app.bsky.feed.repost", limit, true, None) + .await + { + for rec in record_list.records { + let rkey = rec.rkey.as_str().to_string(); + all_items.push(( + did.clone(), + "app.bsky.feed.repost".to_string(), + rkey, + rec.value, + )); + } + } + } + + all_items.sort_by(|a, b| b.2.cmp(&a.2)); + + if let Some(c) = params.cursor { + all_items.retain(|item| item.2 < c); + } + + all_items.truncate(limit); + + let mut feed = Vec::new(); + let mut next_cursor = None; + for (did, col, rkey, value) in all_items { + next_cursor = Some(rkey.clone()); + let uri = format!("at://{}/{}/{}", did.as_str(), col, rkey); + + let val_json = serde_json::to_value(&value).unwrap_or(serde_json::json!({})); + + if col == "app.bsky.feed.post" { + if let Ok(post_view) = get_post_view(&app_state, &uri, None).await { + feed.push(serde_json::json!({ "post": post_view })); + } + } else if col == "app.bsky.feed.repost" { + if let Some(subject_uri) = val_json + .get("subject") + .and_then(|s| s.get("uri")) + .and_then(|u| u.as_str()) + { + if let Ok(post_view) = get_post_view(&app_state, subject_uri, None).await { + if let Ok(reposter_profile) = + get_profile_internal(&app_state, did.as_str(), None).await + { + feed.push(serde_json::json!({ + "post": post_view, + "reason": { + "$type": "app.bsky.feed.defs#reasonRepost", + "by": reposter_profile, + "indexedAt": val_json.get("createdAt").and_then(|c| c.as_str()).map(|s| s.to_string()).unwrap_or_else(|| chrono::Utc::now().to_rfc3339()), + } + })); + } + } + } + } + } + + Ok(Json(serde_json::json!({ + "feed": feed, + "cursor": next_cursor + })) + .into_response()) +} + +#[derive(Deserialize)] +struct GetQuotesParams { + uri: String, + cid: Option, + limit: Option, + cursor: Option, +} + +async fn get_quotes( + State(app_state): State, + req: Request, +) -> Result { + // let hydrant = &app_state.hydrant; + let query_str = req.uri().query().unwrap_or(""); + let Ok(params) = serde_urlencoded::from_str::(query_str) else { + return proxy_request(req).await; + }; + + let limit = params.limit.unwrap_or(50).min(100); + + let mut fetch = app_state + .hydrant + .backlinks + .fetch(params.uri.clone()) + .source("app.bsky.feed.post") + .limit(limit * 3); + if let Some(cursor_str) = params.cursor { + if let Ok(c) = data_encoding::BASE64URL_NOPAD.decode(cursor_str.as_bytes()) { + fetch = fetch.cursor(c); + } + } + + let Ok(backlinks_page) = fetch.run().await else { + return proxy_request(req).await; + }; + + let mut posts = Vec::new(); + let mut found = 0; + for bl in backlinks_page.backlinks { + if found >= limit { + break; + } + + let Ok(uri) = jacquard_common::types::string::AtUri::new(bl.uri.as_str()) else { + continue; + }; + let author_ident = uri.authority(); + let collection = uri.collection().unwrap().as_str(); + let rkey = uri.rkey().unwrap().0.as_str(); + + let Ok(repo) = app_state.hydrant.repos.resolve(author_ident).await else { + continue; + }; + let Ok(Some(record)) = repo.get_record(collection, rkey).await else { + continue; + }; + let value = serde_json::to_value(&record.value).unwrap_or(serde_json::json!({})); + + let is_quote = value.get("embed").map_or(false, |embed| { + let t = embed.get("$type").and_then(|t| t.as_str()).unwrap_or(""); + if t == "app.bsky.embed.record" { + embed + .get("record") + .and_then(|r| r.get("uri")) + .and_then(|u| u.as_str()) + == Some(params.uri.as_str()) + } else if t == "app.bsky.embed.recordWithMedia" { + embed + .get("record") + .and_then(|r| r.get("record")) + .and_then(|r| r.get("uri")) + .and_then(|u| u.as_str()) + == Some(params.uri.as_str()) + } else { + false + } + }); + + if is_quote { + if let Ok(post_view) = get_post_view(&app_state, bl.uri.as_str(), None).await { + posts.push(post_view); + found += 1; + } + } + } + + let cursor = backlinks_page + .next_cursor + .map(|c| data_encoding::BASE64URL_NOPAD.encode(&c)); + + Ok(Json(serde_json::json!({ + "uri": params.uri, + "posts": posts, + "cursor": cursor, + })) + .into_response()) +} + +#[derive(Deserialize)] +struct GetActorLikesParams { + actor: String, + limit: Option, + cursor: Option, +} + +async fn get_actor_likes( + State(app_state): State, + req: Request, +) -> Result { + // let hydrant = &app_state.hydrant; + let query_str = req.uri().query().unwrap_or(""); + let Ok(params) = serde_urlencoded::from_str::(query_str) else { + return proxy_request(req).await; + }; + + let Ok(ident) = AtIdentifier::new(¶ms.actor) else { + return proxy_request(req).await; + }; + let Ok(repo) = app_state.hydrant.repos.resolve(&ident).await else { + return proxy_request(req).await; + }; + + let limit = params.limit.unwrap_or(50).min(100); + + let Ok(record_list) = repo + .list_records("app.bsky.feed.like", limit, true, params.cursor.as_deref()) + .await + else { + return proxy_request(req).await; + }; + + let mut feed = Vec::new(); + for rec in record_list.records { + let value = serde_json::to_value(&rec.value).unwrap_or(serde_json::json!({})); + if let Some(subject_uri) = value + .get("subject") + .and_then(|s| s.get("uri")) + .and_then(|u| u.as_str()) + { + if let Ok(post_view) = get_post_view(&app_state, subject_uri, None).await { + feed.push(serde_json::json!({ "post": post_view })); + } + } + } + + Ok(Json(serde_json::json!({ + "feed": feed, + "cursor": record_list.cursor.map(|c| c.to_string()) + })) + .into_response()) +} + +#[derive(Deserialize)] +struct GetRelationshipsParams { + actor: String, +} + +async fn get_relationships( + State(app_state): State, + req: Request, +) -> Result { + // let hydrant = &app_state.hydrant; + let query_str = req.uri().query().unwrap_or(""); + let Ok(params) = serde_urlencoded::from_str::(query_str) else { + return proxy_request(req).await; + }; + + let others = extract_query_array(query_str, "others"); + if others.is_empty() { + return proxy_request(req).await; + } + + let Ok(ident) = AtIdentifier::new(¶ms.actor) else { + return proxy_request(req).await; + }; + let Ok(repo) = app_state.hydrant.repos.resolve(&ident).await else { + return proxy_request(req).await; + }; + let actor_did = repo.did.as_str().to_string(); + + let mut relationships = Vec::new(); + for other in others { + let Ok(other_ident) = AtIdentifier::new(&other) else { + continue; + }; + let Ok(other_repo) = app_state.hydrant.repos.resolve(&other_ident).await else { + continue; + }; + let other_did = other_repo.did.as_str().to_string(); + + let mut following = None; + let mut followed_by = None; + + let mut cursor = None; + loop { + if let Ok(rl) = repo + .list_records("app.bsky.graph.follow", 100, true, cursor.as_deref()) + .await + { + for rec in &rl.records { + let val = serde_json::to_value(&rec.value).unwrap_or(serde_json::json!({})); + if val.get("subject").and_then(|s| s.as_str()) == Some(&other_did) { + following = Some(format!( + "at://{}/app.bsky.graph.follow/{}", + actor_did, + rec.rkey.as_str() + )); + break; + } + } + if following.is_some() || rl.cursor.is_none() { + break; + } + cursor = rl.cursor.map(|c| c.to_string()); + } else { + break; + } + } + + let mut cursor = None; + loop { + if let Ok(rl) = other_repo + .list_records("app.bsky.graph.follow", 100, true, cursor.as_deref()) + .await + { + for rec in &rl.records { + let val = serde_json::to_value(&rec.value).unwrap_or(serde_json::json!({})); + if val.get("subject").and_then(|s| s.as_str()) == Some(&actor_did) { + followed_by = Some(format!( + "at://{}/app.bsky.graph.follow/{}", + other_did, + rec.rkey.as_str() + )); + break; + } + } + if followed_by.is_some() || rl.cursor.is_none() { + break; + } + cursor = rl.cursor.map(|c| c.to_string()); + } else { + break; + } + } + + relationships.push(serde_json::json!({ + "$type": "app.bsky.graph.defs#relationship", + "did": other_did, + "following": following, + "followedBy": followed_by, + })); + } + + Ok(Json(serde_json::json!({ + "actor": actor_did, + "relationships": relationships + })) + .into_response()) +} + +async fn get_known_followers( + State(app_state): State, + req: Request, +) -> Result { + // let hydrant = &app_state.hydrant; + let query_str = req.uri().query().unwrap_or(""); + let Ok(params) = serde_urlencoded::from_str::(query_str) else { + return proxy_request(req).await; + }; + + let Some(did_str) = get_auth_did(&req) else { + return Err(StatusCode::UNAUTHORIZED); + }; + let Ok(auth_ident) = AtIdentifier::new(&did_str) else { + return proxy_request(req).await; + }; + let Ok(auth_repo) = app_state.hydrant.repos.resolve(&auth_ident).await else { + return proxy_request(req).await; + }; + + let mut auth_follows = std::collections::HashSet::new(); + let mut cursor = None; + loop { + if let Ok(rl) = auth_repo + .list_records("app.bsky.graph.follow", 100, true, cursor.as_deref()) + .await + { + for rec in &rl.records { + let val = serde_json::to_value(&rec.value).unwrap_or(serde_json::json!({})); + if let Some(subj) = val.get("subject").and_then(|s| s.as_str()) { + auth_follows.insert(subj.to_string()); + } + } + if rl.cursor.is_none() { + break; + } + cursor = rl.cursor.map(|c| c.to_string()); + } else { + break; + } + } + + let Ok(ident) = AtIdentifier::new(¶ms.actor) else { + return proxy_request(req).await; + }; + let Ok(repo) = app_state.hydrant.repos.resolve(&ident).await else { + return proxy_request(req).await; + }; + + let limit = params.limit.unwrap_or(50).min(100); + + let mut fetch = app_state + .hydrant + .backlinks + .fetch(repo.did.as_str().to_string()) + .source("app.bsky.graph.follow") + .limit(limit * 3); + if let Some(cursor_str) = params.cursor { + if let Ok(c) = data_encoding::BASE64URL_NOPAD.decode(cursor_str.as_bytes()) { + fetch = fetch.cursor(c); + } + } + + let Ok(backlinks_page) = fetch.run().await else { + return proxy_request(req).await; + }; + + let mut followers = Vec::new(); + let mut found = 0; + for bl in backlinks_page.backlinks { + if found >= limit { + break; + } + let Ok(uri) = jacquard_common::types::string::AtUri::new(bl.uri.as_str()) else { + continue; + }; + let author_ident = uri.authority().as_str().to_string(); + + if auth_follows.contains(&author_ident) { + if let Ok(profile) = get_profile_internal(&app_state, author_ident.as_str(), None).await + { + followers.push(profile); + found += 1; + } + } + } + + let Ok(subject_profile) = get_profile_internal(&app_state, repo.did.as_str(), None).await + else { + return proxy_request(req).await; + }; + + let cursor = backlinks_page + .next_cursor + .map(|c| data_encoding::BASE64URL_NOPAD.encode(&c)); + + Ok(Json(serde_json::json!({ + "subject": subject_profile, + "followers": followers, + "cursor": cursor, + })) + .into_response()) +} + +async fn get_blocks( + State(app_state): State, + req: Request, +) -> Result { + // let hydrant = &app_state.hydrant; + let query_str = req.uri().query().unwrap_or(""); + let Ok(params) = serde_urlencoded::from_str::(query_str) else { + return proxy_request(req).await; + }; + + let Some(did_str) = get_auth_did(&req) else { + return Err(StatusCode::UNAUTHORIZED); + }; + let Ok(ident) = AtIdentifier::new(&did_str) else { + return proxy_request(req).await; + }; + let Ok(repo) = app_state.hydrant.repos.resolve(&ident).await else { + return proxy_request(req).await; + }; + + let limit = params.limit.unwrap_or(50).min(100); + let Ok(record_list) = repo + .list_records( + "app.bsky.graph.block", + limit, + true, + params.cursor.as_deref(), + ) + .await + else { + return proxy_request(req).await; + }; + + let mut blocks = Vec::new(); + for rec in record_list.records { + let value = serde_json::to_value(&rec.value).unwrap_or(serde_json::json!({})); + if let Some(subject_did) = value.get("subject").and_then(|s| s.as_str()) { + if let Ok(profile) = get_profile_internal(&app_state, subject_did, None).await { + blocks.push(profile); + } + } + } + + Ok(Json(serde_json::json!({ + "blocks": blocks, + "cursor": record_list.cursor.map(|c| c.to_string()) + })) + .into_response()) +} + +async fn get_list(State(app_state): State, req: Request) -> Result { + // let hydrant = &app_state.hydrant; + let query_str = req.uri().query().unwrap_or(""); + #[derive(Deserialize)] + struct GetListParams { + list: String, + limit: Option, + cursor: Option, + } + let Ok(params) = serde_urlencoded::from_str::(query_str) else { + return proxy_request(req).await; + }; + + let Ok(uri) = jacquard_common::types::string::AtUri::new(¶ms.list) else { + return proxy_request(req).await; + }; + let author_ident = uri.authority(); + let rkey = uri + .rkey() + .ok_or(StatusCode::BAD_REQUEST)? + .0 + .as_str() + .to_string(); + + let Ok(repo) = app_state.hydrant.repos.resolve(author_ident).await else { + return proxy_request(req).await; + }; + + let Ok(Some(record)) = repo.get_record("app.bsky.graph.list", &rkey).await else { + return proxy_request(req).await; + }; + + let Ok(author_profile) = get_profile_internal(&app_state, repo.did.as_str(), None).await else { + return proxy_request(req).await; + }; + + let val_json = serde_json::to_value(&record.value).unwrap_or(serde_json::json!({})); + + let list_view = serde_json::json!({ + "uri": params.list, + "cid": record.cid.to_string(), + "creator": author_profile, + "name": val_json.get("name").and_then(|v| v.as_str()).unwrap_or(""), + "purpose": val_json.get("purpose").and_then(|v| v.as_str()).unwrap_or("app.bsky.graph.defs#modlist"), + "description": val_json.get("description").and_then(|v| v.as_str()), + "avatar": val_json.get("avatar").and_then(|v| v.as_str()), + "indexedAt": chrono::Utc::now().to_rfc3339(), + }); + + // items from backlinks + let limit = params.limit.unwrap_or(50).min(100); + let mut fetch = app_state + .hydrant + .backlinks + .fetch(params.list.clone()) + .source("app.bsky.graph.listitem") + .limit(limit); + if let Some(cursor_str) = params.cursor { + if let Ok(c) = data_encoding::BASE64URL_NOPAD.decode(cursor_str.as_bytes()) { + fetch = fetch.cursor(c); + } + } + + let Ok(backlinks_page) = fetch.run().await else { + return proxy_request(req).await; + }; + + let mut items = Vec::new(); + for bl in backlinks_page.backlinks { + let Ok(item_uri) = jacquard_common::types::string::AtUri::new(bl.uri.as_str()) else { + continue; + }; + let item_author = item_uri.authority(); + let item_rkey = item_uri.rkey().unwrap().0.as_str(); + + let Ok(item_repo) = app_state.hydrant.repos.resolve(item_author).await else { + continue; + }; + let Ok(Some(item_rec)) = item_repo + .get_record("app.bsky.graph.listitem", item_rkey) + .await + else { + continue; + }; + let item_val = serde_json::to_value(&item_rec.value).unwrap_or(serde_json::json!({})); + + if let Some(subject_did) = item_val.get("subject").and_then(|s| s.as_str()) { + if let Ok(subject_profile) = get_profile_internal(&app_state, subject_did, None).await { + items.push(serde_json::json!({ + "uri": bl.uri.as_str(), + "subject": subject_profile, + })); + } + } + } + + Ok(Json(serde_json::json!({ + "list": list_view, + "items": items, + "cursor": backlinks_page.next_cursor.map(|c| data_encoding::BASE64URL_NOPAD.encode(&c)), + })) + .into_response()) +} + +async fn get_lists( + State(app_state): State, + req: Request, +) -> Result { + // let hydrant = &app_state.hydrant; + let query_str = req.uri().query().unwrap_or(""); + let Ok(params) = serde_urlencoded::from_str::(query_str) else { + return proxy_request(req).await; + }; + + let Ok(ident) = AtIdentifier::new(¶ms.actor) else { + return proxy_request(req).await; + }; + let Ok(repo) = app_state.hydrant.repos.resolve(&ident).await else { + return proxy_request(req).await; + }; + + let Ok(author_profile) = get_profile_internal(&app_state, repo.did.as_str(), None).await else { + return proxy_request(req).await; + }; + + let limit = params.limit.unwrap_or(50).min(100); + let Ok(record_list) = repo + .list_records("app.bsky.graph.list", limit, true, params.cursor.as_deref()) + .await + else { + return proxy_request(req).await; + }; + + let mut lists = Vec::new(); + for rec in record_list.records { + let val_json = serde_json::to_value(&rec.value).unwrap_or(serde_json::json!({})); + lists.push(serde_json::json!({ + "uri": format!("at://{}/app.bsky.graph.list/{}", repo.did.as_str(), rec.rkey.as_str()), + "cid": rec.cid.to_string(), + "creator": author_profile.clone(), + "name": val_json.get("name").and_then(|v| v.as_str()).unwrap_or(""), + "purpose": val_json.get("purpose").and_then(|v| v.as_str()).unwrap_or("app.bsky.graph.defs#modlist"), + "description": val_json.get("description").and_then(|v| v.as_str()), + "avatar": val_json.get("avatar").and_then(|v| v.as_str()), + "indexedAt": chrono::Utc::now().to_rfc3339(), + })); + } + + Ok(Json(serde_json::json!({ + "lists": lists, + "cursor": record_list.cursor.map(|c| c.to_string()) + })) + .into_response()) +} + +async fn get_lists_with_membership( + State(app_state): State, + req: Request, +) -> Result { + // let hydrant = &app_state.hydrant; + let query_str = req.uri().query().unwrap_or(""); + let Ok(params) = serde_urlencoded::from_str::(query_str) else { + return proxy_request(req).await; + }; + + let Ok(ident) = AtIdentifier::new(¶ms.actor) else { + return proxy_request(req).await; + }; + let Ok(repo) = app_state.hydrant.repos.resolve(&ident).await else { + return proxy_request(req).await; + }; + + // All lists created by actor + let Ok(record_list) = repo + .list_records("app.bsky.graph.list", 100, true, None) + .await + else { + return proxy_request(req).await; + }; + + let mut lists: Vec = Vec::new(); + for rec in record_list.records { + let uri = format!( + "at://{}/app.bsky.graph.list/{}", + repo.did.as_str(), + rec.rkey.as_str() + ); + + // Check if subject is in this list + // This is tricky without reverse index on listitem subject. + // We might just fallback here or implement a scan. + // For now, proxy it if we can't efficiently answer. + return proxy_request(req).await; + } + + Ok(Json(serde_json::json!({ "lists": lists })).into_response()) +} + +async fn get_actor_feeds( + State(app_state): State, + req: Request, +) -> Result { + // let hydrant = &app_state.hydrant; + let query_str = req.uri().query().unwrap_or(""); + let Ok(params) = serde_urlencoded::from_str::(query_str) else { + return proxy_request(req).await; + }; + + let Ok(ident) = AtIdentifier::new(¶ms.actor) else { + return proxy_request(req).await; + }; + let Ok(repo) = app_state.hydrant.repos.resolve(&ident).await else { + return proxy_request(req).await; + }; + + let Ok(author_profile) = get_profile_internal(&app_state, repo.did.as_str(), None).await else { + return proxy_request(req).await; + }; + + let limit = params.limit.unwrap_or(50).min(100); + let Ok(record_list) = repo + .list_records( + "app.bsky.feed.generator", + limit, + true, + params.cursor.as_deref(), + ) + .await + else { + return proxy_request(req).await; + }; + + let mut feeds = Vec::new(); + for rec in record_list.records { + let val_json = serde_json::to_value(&rec.value).unwrap_or(serde_json::json!({})); + feeds.push(serde_json::json!({ + "uri": format!("at://{}/app.bsky.feed.generator/{}", repo.did.as_str(), rec.rkey.as_str()), + "cid": rec.cid.to_string(), + "did": val_json.get("did").and_then(|v| v.as_str()).unwrap_or(""), + "creator": author_profile.clone(), + "displayName": val_json.get("displayName").and_then(|v| v.as_str()).unwrap_or(""), + "description": val_json.get("description").and_then(|v| v.as_str()), + "avatar": val_json.get("avatar").and_then(|v| v.as_str()), + "likeCount": app_state.hydrant.backlinks.count(format!("at://{}/app.bsky.feed.generator/{}", repo.did.as_str(), rec.rkey.as_str())).source("app.bsky.feed.like").run().await.unwrap_or(0), + "indexedAt": chrono::Utc::now().to_rfc3339(), + })); + } + + Ok(Json(serde_json::json!({ + "feeds": feeds, + "cursor": record_list.cursor.map(|c| c.to_string()) + })) + .into_response()) +} + +async fn get_feed_generator( + State(app_state): State, + req: Request, +) -> Result { + // let hydrant = &app_state.hydrant; + let query_str = req.uri().query().unwrap_or(""); + #[derive(Deserialize)] + struct GetFeedGeneratorParams { + feed: String, + } + let Ok(params) = serde_urlencoded::from_str::(query_str) else { + return proxy_request(req).await; + }; + + let Ok(uri) = jacquard_common::types::string::AtUri::new(¶ms.feed) else { + return proxy_request(req).await; + }; + let author_ident = uri.authority(); + let rkey = uri + .rkey() + .ok_or(StatusCode::BAD_REQUEST)? + .0 + .as_str() + .to_string(); + + let Ok(repo) = app_state.hydrant.repos.resolve(author_ident).await else { + return proxy_request(req).await; + }; + + let Ok(Some(record)) = repo.get_record("app.bsky.feed.generator", &rkey).await else { + return proxy_request(req).await; + }; + + let Ok(author_profile) = get_profile_internal(&app_state, repo.did.as_str(), None).await else { + return proxy_request(req).await; + }; + + let val_json = serde_json::to_value(&record.value).unwrap_or(serde_json::json!({})); + + let view = serde_json::json!({ + "uri": params.feed, + "cid": record.cid.to_string(), + "did": val_json.get("did").and_then(|v| v.as_str()).unwrap_or(""), + "creator": author_profile, + "displayName": val_json.get("displayName").and_then(|v| v.as_str()).unwrap_or(""), + "description": val_json.get("description").and_then(|v| v.as_str()), + "avatar": val_json.get("avatar").and_then(|v| v.as_str()), + "likeCount": app_state.hydrant.backlinks.count(params.feed.clone()).source("app.bsky.feed.like").run().await.unwrap_or(0), + "indexedAt": chrono::Utc::now().to_rfc3339(), + }); + + Ok(Json(serde_json::json!({ + "view": view, + "isOnline": true, + "isValid": true, + })) + .into_response()) +} + +async fn get_feed_generators( + State(app_state): State, + req: Request, +) -> Result { + // let hydrant = &app_state.hydrant; + let query_str = req.uri().query().unwrap_or(""); + let feeds = extract_query_array(query_str, "feeds"); + if feeds.is_empty() { + return proxy_request(req).await; + } + + let mut views = Vec::new(); + for feed_uri in feeds { + let Ok(uri) = jacquard_common::types::string::AtUri::new(&feed_uri) else { + continue; + }; + let author_ident = uri.authority(); + let rkey = uri.rkey().unwrap().0.as_str().to_string(); + + if let Ok(repo) = app_state.hydrant.repos.resolve(author_ident).await { + if let Ok(Some(record)) = repo.get_record("app.bsky.feed.generator", &rkey).await { + if let Ok(author_profile) = + get_profile_internal(&app_state, repo.did.as_str(), None).await + { + let val_json = + serde_json::to_value(&record.value).unwrap_or(serde_json::json!({})); + views.push(serde_json::json!({ + "uri": feed_uri, + "cid": record.cid.to_string(), + "did": val_json.get("did").and_then(|v| v.as_str()).unwrap_or(""), + "creator": author_profile, + "displayName": val_json.get("displayName").and_then(|v| v.as_str()).unwrap_or(""), + "description": val_json.get("description").and_then(|v| v.as_str()), + "avatar": val_json.get("avatar").and_then(|v| v.as_str()), + "likeCount": app_state.hydrant.backlinks.count(feed_uri.clone()).source("app.bsky.feed.like").run().await.unwrap_or(0), + "indexedAt": chrono::Utc::now().to_rfc3339(), + })); + } + } + } + } + + Ok(Json(serde_json::json!({ "feeds": views })).into_response()) +} + +#[derive(Deserialize)] +struct PutPreferencesReq { + preferences: Vec, +} + +async fn put_preferences( + State(app_state): State, + req: Request, +) -> Result { + let Some(did) = get_auth_did(&req) else { + return Err(StatusCode::UNAUTHORIZED); + }; + + let body_bytes = axum::body::to_bytes(req.into_body(), usize::MAX) + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + let Ok(payload) = serde_json::from_slice::(&body_bytes) else { + return Err(StatusCode::BAD_REQUEST); + }; + + let key = format!("prefs:{}", did); + let val = + serde_json::to_vec(&payload.preferences).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + app_state + .preferences + .insert(key.as_bytes(), val) + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + + Ok(Json(serde_json::json!({})).into_response()) +} + +async fn get_preferences( + State(app_state): State, + req: Request, +) -> Result { + let Some(did) = get_auth_did(&req) else { + return Err(StatusCode::UNAUTHORIZED); + }; + + let key = format!("prefs:{}", did); + let existing = app_state + .preferences + .get(key.as_bytes()) + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + + let preferences = if let Some(bytes) = existing { + serde_json::from_slice::>(&bytes).unwrap_or_default() + } else { + Vec::new() + }; + + Ok(Json(serde_json::json!({ "preferences": preferences })).into_response()) +} + +#[derive(Deserialize)] +struct MuteActorReq { + actor: String, +} + +async fn mute_actor( + State(app_state): State, + req: Request, +) -> Result { + // let hydrant = &app_state.hydrant; + let Some(did) = get_auth_did(&req) else { + return Err(StatusCode::UNAUTHORIZED); + }; + + let body_bytes = axum::body::to_bytes(req.into_body(), usize::MAX) + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + let Ok(payload) = serde_json::from_slice::(&body_bytes) else { + return Err(StatusCode::BAD_REQUEST); + }; + + let Ok(ident) = AtIdentifier::new(&payload.actor) else { + return Err(StatusCode::BAD_REQUEST); + }; + let Ok(repo) = app_state.hydrant.repos.resolve(&ident).await else { + return Err(StatusCode::NOT_FOUND); + }; + + let key = format!("mute:{}:{}", did, repo.did.as_str()); + app_state + .mutes + .insert(key.as_bytes(), b"") + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + + Ok(Json(serde_json::json!({})).into_response()) +} + +async fn unmute_actor( + State(app_state): State, + req: Request, +) -> Result { + // let hydrant = &app_state.hydrant; + let Some(did) = get_auth_did(&req) else { + return Err(StatusCode::UNAUTHORIZED); + }; + + let body_bytes = axum::body::to_bytes(req.into_body(), usize::MAX) + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + let Ok(payload) = serde_json::from_slice::(&body_bytes) else { + return Err(StatusCode::BAD_REQUEST); + }; + + let Ok(ident) = AtIdentifier::new(&payload.actor) else { + return Err(StatusCode::BAD_REQUEST); + }; + let Ok(repo) = app_state.hydrant.repos.resolve(&ident).await else { + return Err(StatusCode::NOT_FOUND); + }; + + let key = format!("mute:{}:{}", did, repo.did.as_str()); + app_state + .mutes + .remove(key.as_bytes()) + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + + Ok(Json(serde_json::json!({})).into_response()) +} + +async fn get_mutes( + State(app_state): State, + req: Request, +) -> Result { + // let hydrant = &app_state.hydrant; + let Some(did) = get_auth_did(&req) else { + return Err(StatusCode::UNAUTHORIZED); + }; + + let query_str = req.uri().query().unwrap_or(""); + let Ok(params) = serde_urlencoded::from_str::(query_str) else { + return proxy_request(req).await; + }; + + let limit = params.limit.unwrap_or(50).min(100); + let prefix = format!("mute:{}:", did); + + let iter: Box> = if let Some(cursor) = params.cursor { + Box::new(app_state.mutes.range::<&[u8], _>(( + std::ops::Bound::Included(cursor.as_bytes()), + std::ops::Bound::Unbounded, + ))) + } else { + Box::new(app_state.mutes.prefix(prefix.as_bytes())) + }; + + let mut fetched_keys = Vec::new(); + for item in iter { + let Ok((key, _)) = item.into_inner() else { + continue; + }; + if !key.starts_with(prefix.as_bytes()) { + break; + } + fetched_keys.push(key.to_vec()); + if fetched_keys.len() >= limit { + break; + } + } + + let mut mutes = Vec::new(); + let mut next_cursor = None; + for key in fetched_keys { + let key_str = String::from_utf8_lossy(&key); + let muted_did = &key_str[prefix.len()..]; + if let Ok(profile) = get_profile_internal(&app_state, muted_did, None).await { + mutes.push(profile); + } + next_cursor = Some(key_str.to_string()); + } + + Ok(Json(serde_json::json!({ + "mutes": mutes, + "cursor": next_cursor + })) + .into_response()) +} + +async fn notification_indexer(app_state: AppState) { + let mut stream = app_state.hydrant.subscribe(None); + while let Some(evt) = stream.next().await { + let Some(rec) = evt.record else { continue }; + if rec.action != "create" { + continue; + } + + let author_did = rec.did.as_str(); + let collection = rec.collection.as_str(); + let rkey = rec.rkey.as_str(); + let uri = format!("at://{}/{}/{}", author_did, collection, rkey); + + let Some(val) = rec.record else { continue }; + + let mut targets = Vec::new(); // (target_did, reason, reason_subject) + + match collection { + "app.bsky.feed.like" => { + if let Some(subj_uri) = val + .get("subject") + .and_then(|s| s.get("uri")) + .and_then(|u| u.as_str()) + { + if let Ok(u) = jacquard_common::types::string::AtUri::new(subj_uri) { + targets.push(( + u.authority().as_str().to_string(), + "like", + Some(subj_uri.to_string()), + )); + } + } + } + "app.bsky.feed.repost" => { + if let Some(subj_uri) = val + .get("subject") + .and_then(|s| s.get("uri")) + .and_then(|u| u.as_str()) + { + if let Ok(u) = jacquard_common::types::string::AtUri::new(subj_uri) { + targets.push(( + u.authority().as_str().to_string(), + "repost", + Some(subj_uri.to_string()), + )); + } + } + } + "app.bsky.graph.follow" => { + if let Some(subj_did) = val.get("subject").and_then(|s| s.as_str()) { + targets.push((subj_did.to_string(), "follow", None)); + } + } + "app.bsky.feed.post" => { + if let Some(parent_uri) = val + .get("reply") + .and_then(|r| r.get("parent")) + .and_then(|p| p.get("uri")) + .and_then(|u| u.as_str()) + { + if let Ok(u) = jacquard_common::types::string::AtUri::new(parent_uri) { + targets.push(( + u.authority().as_str().to_string(), + "reply", + Some(parent_uri.to_string()), + )); + } + } + + let is_quote = val.get("embed").map_or(false, |embed| { + let t = embed.get("$type").and_then(|t| t.as_str()).unwrap_or(""); + if t == "app.bsky.embed.record" { + embed + .get("record") + .and_then(|r| r.get("uri")) + .and_then(|u| u.as_str()) + .is_some() + } else if t == "app.bsky.embed.recordWithMedia" { + embed + .get("record") + .and_then(|r| r.get("record")) + .and_then(|r| r.get("uri")) + .and_then(|u| u.as_str()) + .is_some() + } else { + false + } + }); + + if is_quote { + if let Some(embed) = val.get("embed") { + let t = embed.get("$type").and_then(|t| t.as_str()).unwrap_or(""); + let quote_uri = if t == "app.bsky.embed.record" { + embed + .get("record") + .and_then(|r| r.get("uri")) + .and_then(|u| u.as_str()) + } else if t == "app.bsky.embed.recordWithMedia" { + embed + .get("record") + .and_then(|r| r.get("record")) + .and_then(|r| r.get("uri")) + .and_then(|u| u.as_str()) + } else { + None + }; + + if let Some(qu) = quote_uri { + if let Ok(u) = jacquard_common::types::string::AtUri::new(qu) { + targets.push(( + u.authority().as_str().to_string(), + "quote", + Some(qu.to_string()), + )); + } + } + } + } + + if let Some(facets) = val.get("facets").and_then(|f| f.as_array()) { + for facet in facets { + if let Some(features) = facet.get("features").and_then(|f| f.as_array()) { + for feature in features { + if feature.get("$type").and_then(|t| t.as_str()) + == Some("app.bsky.richtext.facet#mention") + { + if let Some(mention_did) = + feature.get("did").and_then(|d| d.as_str()) + { + targets.push((mention_did.to_string(), "mention", None)); + } + } + } + } + } + } + } + _ => {} + } + + let indexed_at = chrono::Utc::now().to_rfc3339(); + for (target_did, reason, reason_subject) in targets { + if target_did == author_did { + continue; + } + + let key = format!("notif:{}:{:016x}", target_did, evt.id); + let notif = serde_json::json!({ + "uri": uri, + "cid": rec.cid.map(|c| c.to_string()).unwrap_or_default(), + "author_did": author_did, + "reason": reason, + "reasonSubject": reason_subject, + "record": val, + "indexedAt": indexed_at, + "id": evt.id + }); + + let _ = app_state + .notifications + .insert(key.as_bytes(), serde_json::to_vec(¬if).unwrap()); + } + } +} + +#[derive(Deserialize)] +struct ListNotificationsParams { + limit: Option, + cursor: Option, +} + +async fn list_notifications( + State(app_state): State, + req: Request, +) -> Result { + // let hydrant = &app_state.hydrant; + let Some(did) = get_auth_did(&req) else { + return Err(StatusCode::UNAUTHORIZED); + }; + + let query_str = req.uri().query().unwrap_or(""); + let params = serde_urlencoded::from_str::(query_str).unwrap_or( + ListNotificationsParams { + limit: None, + cursor: None, + }, + ); + let limit = params.limit.unwrap_or(50).min(100); + + let prefix = format!("notif:{}:", did); + + // Collect all notification keys into a vector so we can iterate in reverse + let mut all_keys = Vec::new(); + for item in app_state.notifications.prefix(prefix.as_bytes()) { + if let Ok((k, v)) = item.into_inner() { + all_keys.push((k, v)); + } + } + + // Sort keys just in case and reverse + all_keys.sort_by(|a, b| b.0.cmp(&a.0)); + + // Pagination + if let Some(cursor) = params.cursor { + all_keys.retain(|(k, _)| String::from_utf8_lossy(k).as_ref() < cursor.as_str()); + } + + let seen_key = format!("seen:{}", did); + let last_seen = app_state + .seen + .get(seen_key.as_bytes()) + .ok() + .flatten() + .and_then(|b| String::from_utf8(b.to_vec()).ok()) + .unwrap_or_else(|| "1970-01-01T00:00:00Z".to_string()); + + let mut notifications = Vec::new(); + let mut next_cursor = None; + + for (key, val_bytes) in all_keys.into_iter().take(limit) { + next_cursor = Some(String::from_utf8_lossy(&key).to_string()); + + let Ok(val) = serde_json::from_slice::(&val_bytes) else { + continue; + }; + + let author_did = val.get("author_did").and_then(|a| a.as_str()).unwrap_or(""); + let indexed_at = val.get("indexedAt").and_then(|a| a.as_str()).unwrap_or(""); + + let is_read = indexed_at <= last_seen.as_str(); + + let mut notif = val.clone(); + if let Ok(author_profile) = get_profile_internal(&app_state, author_did, None).await { + notif["author"] = author_profile; + } else { + continue; + } + + notif["isRead"] = serde_json::json!(is_read); + + notif.as_object_mut().unwrap().remove("author_did"); + notif.as_object_mut().unwrap().remove("id"); + + notifications.push(notif); + } + + Ok(Json(serde_json::json!({ + "notifications": notifications, + "cursor": if notifications.len() == limit { next_cursor } else { None } + })) + .into_response()) +} + +async fn get_unread_count( + State(app_state): State, + req: Request, +) -> Result { + let Some(did) = get_auth_did(&req) else { + return Err(StatusCode::UNAUTHORIZED); + }; + + let prefix = format!("notif:{}:", did); + let seen_key = format!("seen:{}", did); + let last_seen = app_state + .seen + .get(seen_key.as_bytes()) + .ok() + .flatten() + .and_then(|b| String::from_utf8(b.to_vec()).ok()) + .unwrap_or_else(|| "1970-01-01T00:00:00Z".to_string()); + + let mut count = 0; + for item in app_state.notifications.prefix(prefix.as_bytes()) { + let Ok((_key, val_bytes)) = item.into_inner() else { + continue; + }; + if let Ok(val) = serde_json::from_slice::(&val_bytes) { + if let Some(indexed_at) = val.get("indexedAt").and_then(|a| a.as_str()) { + if indexed_at > last_seen.as_str() { + count += 1; + } + } + } + } + + Ok(Json(serde_json::json!({ + "count": count + })) + .into_response()) +} + +#[derive(Deserialize)] +struct UpdateSeenReq { + seenAt: String, +} + +async fn update_seen( + State(app_state): State, + req: Request, +) -> Result { + let Some(did) = get_auth_did(&req) else { + return Err(StatusCode::UNAUTHORIZED); + }; + + let body_bytes = axum::body::to_bytes(req.into_body(), usize::MAX) + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + let Ok(payload) = serde_json::from_slice::(&body_bytes) else { + return Err(StatusCode::BAD_REQUEST); + }; + + let seen_key = format!("seen:{}", did); + app_state + .seen + .insert(seen_key.as_bytes(), payload.seenAt.as_bytes()) + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + + Ok(Json(serde_json::json!({})).into_response()) +} + +#[derive(Deserialize)] +struct GetFeedParams { + feed: String, + limit: Option, + cursor: Option, +} + +async fn get_feed(State(app_state): State, req: Request) -> Result { + // let hydrant = &app_state.hydrant; + let query_str = req.uri().query().unwrap_or(""); + let Ok(params) = serde_urlencoded::from_str::(query_str) else { + return proxy_request(req).await; + }; + + let Ok(uri) = jacquard_common::types::string::AtUri::new(¶ms.feed) else { + return proxy_request(req).await; + }; + let author_ident = uri.authority(); + let rkey = uri + .rkey() + .ok_or(StatusCode::BAD_REQUEST)? + .0 + .as_str() + .to_string(); + + let Ok(repo) = app_state.hydrant.repos.resolve(author_ident).await else { + return proxy_request(req).await; + }; + + let Ok(Some(record)) = repo.get_record("app.bsky.feed.generator", &rkey).await else { + return proxy_request(req).await; + }; + + let val_json = serde_json::to_value(&record.value).unwrap_or(serde_json::json!({})); + let Some(service_did) = val_json.get("did").and_then(|d| d.as_str()) else { + return proxy_request(req).await; + }; + + let Ok(service_did_parsed) = jacquard_common::types::string::Did::new(service_did) else { + return proxy_request(req).await; + }; + + let Ok((doc_data, _)) = app_state + .hydrant + .resolver() + .resolve_raw_doc(&service_did_parsed) + .await + else { + return proxy_request(req).await; + }; + + let doc_json = serde_json::to_value(&doc_data).unwrap_or(serde_json::json!({})); + let mut endpoint = None; + + if let Some(services) = doc_json.get("service").and_then(|s| s.as_array()) { + for srv in services { + if let Some(typ) = srv.get("type") { + let is_fg = if let Some(t_str) = typ.as_str() { + t_str == "BskyFeedGenerator" + } else if let Some(t_arr) = typ.as_array() { + t_arr + .iter() + .any(|v| v.as_str() == Some("BskyFeedGenerator")) + } else { + false + }; + + if is_fg { + if let Some(ep) = srv.get("serviceEndpoint").and_then(|e| e.as_str()) { + endpoint = Some(ep.to_string()); + break; + } + } + } + + // fallback check by ID + if srv.get("id").and_then(|i| i.as_str()) == Some("#bsky_fg") { + if let Some(ep) = srv.get("serviceEndpoint").and_then(|e| e.as_str()) { + endpoint = Some(ep.to_string()); + break; + } + } + } + } + + let Some(ep) = endpoint else { + return proxy_request(req).await; + }; + + let mut skeleton_url = format!( + "{}/xrpc/app.bsky.feed.getFeedSkeleton?feed={}", + ep, params.feed + ); + if let Some(limit) = params.limit { + skeleton_url.push_str(&format!("&limit={}", limit)); + } + if let Some(cursor) = params.cursor { + skeleton_url.push_str(&format!("&cursor={}", cursor)); + } + + let client = reqwest::Client::new(); + let mut req_builder = client.get(&skeleton_url); + if let Some(auth) = req.headers().get("authorization") { + req_builder = req_builder.header("authorization", auth); + } + + let Ok(res) = req_builder.send().await else { + return proxy_request(req).await; + }; + + let Ok(skeleton) = res.json::().await else { + return proxy_request(req).await; + }; + + let mut hydrated_feed = Vec::new(); + if let Some(feed_items) = skeleton.get("feed").and_then(|f| f.as_array()) { + for item in feed_items { + if let Some(post_uri) = item.get("post").and_then(|p| p.as_str()) { + if let Ok(post_view) = get_post_view(&app_state, post_uri, None).await { + let mut feed_item = serde_json::json!({ + "post": post_view + }); + if let Some(reason) = item.get("reason") { + feed_item + .as_object_mut() + .unwrap() + .insert("reason".to_string(), reason.clone()); + } + hydrated_feed.push(feed_item); + } + } + } + } + + Ok(Json(serde_json::json!({ + "feed": hydrated_feed, + "cursor": skeleton.get("cursor") + })) + .into_response()) +} + +async fn get_well_known_did( + req: Request, +) -> Result { + let host = req.headers().get("host").and_then(|h| h.to_str().ok()).unwrap_or("localhost:8000"); + + let did = if host.contains(':') { + format!("did:web:{}", host.replace(':', "%3A")) + } else { + format!("did:web:{}", host) + }; + + let scheme = if host.starts_with("localhost") || host.starts_with("127.0.0.1") { "http" } else { "https" }; + let service_endpoint = format!("{}://{}", scheme, host); + + Ok(Json(serde_json::json!({ + "@context": [ + "https://www.w3.org/ns/did/v1" + ], + "id": did, + "service": [ + { + "id": "#bsky_notif", + "type": "BskyNotificationService", + "serviceEndpoint": service_endpoint + }, + { + "id": "#bsky_appview", + "type": "BskyAppView", + "serviceEndpoint": service_endpoint + } + ] + })).into_response()) } -- tangled.sh