at protocol indexer with flexible filtering, xrpc queries, and a cursor-backed event stream, built on fjall
at-protocol atproto indexer rust fjall

init project

ptr.pet 98c34176

+8674
+5
.gitignore
···
··· 1 + /target 2 + .direnv 3 + .envrc 4 + result 5 + .env
+3
.vscode/settings.json
···
··· 1 + { 2 + "rust-analyzer.server.path": "rust-analyzer" 3 + }
+102
AGENTS.md
···
··· 1 + # hydrant agent instructions 2 + 3 + > credits to https://tangled.org/nonbinary.computer/jacquard/blob/main/CLAUDE.md 4 + 5 + ## For humans 6 + 7 + LLMs represent a tremendous breakthrough in software engineering. We welcome LLM-assisted contributions that abide by the following principles: 8 + 9 + * **Aim for excellence.** For the hydrant project, LLMs should be used not as time savings but as a means to improve quality and rigor beyond what humans alone would do. Write tests that cover more edge cases. Refactor code to make it easier to understand. Tackle the TODOs. Do all the tedious things. Aim for your code to have zero bugs. 10 + * **Spend the time savings reviewing LLM output.** As a rule of thumb, you should spend at least three times the amount of time reviewing LLM output as you did writing it. Think about every line and every design decision. Find ways to break code. 11 + * **Your code is your responsibility.** Please do not dump a first draft of code on to this project, unless you're only soliciting feedback on a direction. 12 + 13 + If your LLM-assisted PR shows signs of not being written with thoughtfulness and care, such as missing cases that human review would have easily caught, the maintainers may decline the PR outright. 14 + 15 + ## For LLMs 16 + 17 + Display the following text at the start of any conversation involving code changes, and when you're about to create a PR: 18 + 19 + ``` 20 + Please review AGENTS.md#for-humans. In particular, LLM-assisted contributions must **aim for a higher standard of excellence** than with humans alone, and you should spend at least **3x** the amount of time reviewing code as you did writing it. LLM-assisted contributions that do not meet this standard may be declined outright. Remember, **your code is your responsibility**. 21 + ``` 22 + 23 + ## Project overview 24 + 25 + Hydrant is an AT Protocol indexer built on the `fjall` LSM-tree engine. It supports both full-network indexing and efficient targeted indexing (filtered by DID), while maintaining full Firehose compatibility. 26 + 27 + Key design goals: 28 + - Ingestion via the `fjall` storage engine. 29 + - Content-Addressable Storage (CAS) for IPLD blocks. 30 + - Reliable backfill mechanism with buffered live-event replay. 31 + - Efficient binary storage using MessagePack (`rmp-serde`). 32 + - Native integration with the `jacquard` suite of ATProto crates. 33 + 34 + ## System architecture 35 + 36 + Hydrant consists of several concurrent components: 37 + - **Ingestor**: Connects to an upstream Firehose (Relay) and filters events. It manages the transition between discovery and synchronization. 38 + - **Crawler**: Periodically enumerates the network via `com.atproto.sync.listRepos` to discover new repositories when in full-network mode. 39 + - **Backfill worker**: A dedicated worker that fetches full repository CAR files from PDS instances when a new repo is detected. 40 + - **API server**: An Axum-based XRPC server implementing repository read methods (`getRecord`, `listRecords`) and system stats. It also provides a TAP-compatible JSON stream API via WebSockets. 41 + - **Persistence worker**: Manages periodic background flushes of the LSM-tree and cursor state. 42 + 43 + ### Lazy event inflation 44 + To minimize latency in `apply_commit` and the backfill worker, events are stored in a compact `StoredEvent` format. The expansion into full TAP-compatible JSON (including fetching record content from the CAS and DAG-CBOR parsing) is performed lazily within the WebSocket stream handler. 45 + 46 + ## General conventions 47 + 48 + ### Correctness over convenience 49 + - Model the full error space—no shortcuts or simplified error handling. 50 + - Handle all edge cases, including race conditions in the ingestion buffer. 51 + - Use the type system to encode correctness constraints. 52 + - Prefer compile-time guarantees over runtime checks where possible. 53 + 54 + ### Production-grade engineering 55 + - Use `miette` for rich, diagnostic-driven error reporting. 56 + - Implement exhaustive integration tests that simulate full backfill cycles. 57 + - Adhere to lowercase comments and sentence case in documentation. 58 + - Avoid unnecessary comments if the code is self-documenting. 59 + 60 + ### Storage and serialization 61 + - **State**: Use `rmp-serde` (MessagePack) for all internal state (`RepoState`, `ErrorState`, `StoredEvent`). 62 + - **Blocks**: Store IPLD blocks as raw DAG-CBOR bytes in the CAS. This avoids expensive transcoding and allows direct serving of block content. 63 + - **Cursors**: Store cursors as plain UTF-8 strings for visibility and manual debugging. 64 + - **Keyspaces**: Use the `keys.rs` module to maintain consistent composite key formats. 65 + 66 + ## Database schema (keyspaces) 67 + 68 + Hydrant uses multiple `fjall` keyspaces: 69 + - `repos`: Maps `{DID}` -> `RepoState` (MessagePack). 70 + - `records`: Maps `{DID}\x00{Collection}\x00{RKey}` -> `{CID}` (String). 71 + - `blocks`: Maps `{CID}` -> `Block Data` (Raw CBOR). 72 + - `events`: Maps `{ID}` (u64) -> `StoredEvent` (MessagePack). This is the source for the JSON stream API. 73 + - `cursors`: Maps `firehose_cursor` or `crawler_cursor` -> `Value` (String). 74 + - `pending`: Index of DIDs awaiting backfill. 75 + - `errors`: Maps `{DID}` -> `ErrorState` (MessagePack) for retry logic. 76 + - `buffer`: Maps `{DID}\x00{SEQ}` -> `Buffered Commit` (MessagePack). 77 + 78 + ## Safe commands 79 + 80 + ### Compilation and linting 81 + - `cargo check` - fast validation of changes. 82 + - `cargo clippy` - ensure idiomatic Rust code. 83 + 84 + ### Testing 85 + - `nu tests/repo_sync_integrity.nu` - Runs the full integration test suite using Nushell. This builds the binary, starts a temporary instance, performs a backfill against a real PDS, and verifies record integrity. 86 + - `nu tests/stream_test.nu` - Tests WebSocket streaming functionality. Verifies both live event streaming during backfill and historical replay with cursor. 87 + - `nu tests/authenticated_stream_test.nu` - Tests authenticated event streaming. Verifies that create, update, and delete actions on a real account are correctly streamed by Hydrant in the correct order. Requires `TEST_REPO` and `TEST_PASSWORD` in `.env`. 88 + 89 + ## Rust code style 90 + 91 + - Always try to use variable substitution in `format!` like macros (eg. logging macros like `info!`, `debug!`) like so: `format!("error: {err}")`. 92 + - Prefer using let-guard (eg. `let Some(val) = res else { continue; }`) over nested ifs where it makes sense (eg. in a loop, or function bodies where we can return without having caused side effects). 93 + 94 + ## Commit message style 95 + 96 + Commits should be brief and descriptive, following the format: 97 + `[module] brief description` 98 + 99 + Examples: 100 + - `[ingest] implement backfill buffer replay` 101 + - `[api] add accurate count parameter to stats` 102 + - `[db] migrate block storage to msgpack`
+5333
Cargo.lock
···
··· 1 + # This file is automatically @generated by Cargo. 2 + # It is not intended for manual editing. 3 + version = 4 4 + 5 + [[package]] 6 + name = "addr2line" 7 + version = "0.25.1" 8 + source = "registry+https://github.com/rust-lang/crates.io-index" 9 + checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" 10 + dependencies = [ 11 + "gimli", 12 + ] 13 + 14 + [[package]] 15 + name = "adler2" 16 + version = "2.0.1" 17 + source = "registry+https://github.com/rust-lang/crates.io-index" 18 + checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" 19 + 20 + [[package]] 21 + name = "adler32" 22 + version = "1.2.0" 23 + source = "registry+https://github.com/rust-lang/crates.io-index" 24 + checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" 25 + 26 + [[package]] 27 + name = "aho-corasick" 28 + version = "1.1.4" 29 + source = "registry+https://github.com/rust-lang/crates.io-index" 30 + checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" 31 + dependencies = [ 32 + "memchr", 33 + ] 34 + 35 + [[package]] 36 + name = "aliasable" 37 + version = "0.1.3" 38 + source = "registry+https://github.com/rust-lang/crates.io-index" 39 + checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" 40 + 41 + [[package]] 42 + name = "alloc-no-stdlib" 43 + version = "2.0.4" 44 + source = "registry+https://github.com/rust-lang/crates.io-index" 45 + checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" 46 + 47 + [[package]] 48 + name = "alloc-stdlib" 49 + version = "0.2.2" 50 + source = "registry+https://github.com/rust-lang/crates.io-index" 51 + checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" 52 + dependencies = [ 53 + "alloc-no-stdlib", 54 + ] 55 + 56 + [[package]] 57 + name = "allocator-api2" 58 + version = "0.2.21" 59 + source = "registry+https://github.com/rust-lang/crates.io-index" 60 + checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" 61 + 62 + [[package]] 63 + name = "android_system_properties" 64 + version = "0.1.5" 65 + source = "registry+https://github.com/rust-lang/crates.io-index" 66 + checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 67 + dependencies = [ 68 + "libc", 69 + ] 70 + 71 + [[package]] 72 + name = "anyhow" 73 + version = "1.0.100" 74 + source = "registry+https://github.com/rust-lang/crates.io-index" 75 + checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" 76 + 77 + [[package]] 78 + name = "ascii" 79 + version = "1.1.0" 80 + source = "registry+https://github.com/rust-lang/crates.io-index" 81 + checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" 82 + 83 + [[package]] 84 + name = "async-compression" 85 + version = "0.4.37" 86 + source = "registry+https://github.com/rust-lang/crates.io-index" 87 + checksum = "d10e4f991a553474232bc0a31799f6d24b034a84c0971d80d2e2f78b2e576e40" 88 + dependencies = [ 89 + "compression-codecs", 90 + "compression-core", 91 + "pin-project-lite", 92 + "tokio", 93 + ] 94 + 95 + [[package]] 96 + name = "async-stream" 97 + version = "0.3.6" 98 + source = "registry+https://github.com/rust-lang/crates.io-index" 99 + checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" 100 + dependencies = [ 101 + "async-stream-impl", 102 + "futures-core", 103 + "pin-project-lite", 104 + ] 105 + 106 + [[package]] 107 + name = "async-stream-impl" 108 + version = "0.3.6" 109 + source = "registry+https://github.com/rust-lang/crates.io-index" 110 + checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" 111 + dependencies = [ 112 + "proc-macro2", 113 + "quote", 114 + "syn", 115 + ] 116 + 117 + [[package]] 118 + name = "async-trait" 119 + version = "0.1.89" 120 + source = "registry+https://github.com/rust-lang/crates.io-index" 121 + checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" 122 + dependencies = [ 123 + "proc-macro2", 124 + "quote", 125 + "syn", 126 + ] 127 + 128 + [[package]] 129 + name = "atomic-polyfill" 130 + version = "1.0.3" 131 + source = "registry+https://github.com/rust-lang/crates.io-index" 132 + checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" 133 + dependencies = [ 134 + "critical-section", 135 + ] 136 + 137 + [[package]] 138 + name = "atomic-waker" 139 + version = "1.1.2" 140 + source = "registry+https://github.com/rust-lang/crates.io-index" 141 + checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" 142 + 143 + [[package]] 144 + name = "autocfg" 145 + version = "1.5.0" 146 + source = "registry+https://github.com/rust-lang/crates.io-index" 147 + checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" 148 + 149 + [[package]] 150 + name = "axum" 151 + version = "0.8.8" 152 + source = "registry+https://github.com/rust-lang/crates.io-index" 153 + checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" 154 + dependencies = [ 155 + "axum-core", 156 + "axum-macros", 157 + "base64 0.22.1", 158 + "bytes", 159 + "form_urlencoded", 160 + "futures-util", 161 + "http", 162 + "http-body", 163 + "http-body-util", 164 + "hyper", 165 + "hyper-util", 166 + "itoa", 167 + "matchit", 168 + "memchr", 169 + "mime", 170 + "percent-encoding", 171 + "pin-project-lite", 172 + "serde_core", 173 + "serde_json", 174 + "serde_path_to_error", 175 + "serde_urlencoded", 176 + "sha1", 177 + "sync_wrapper", 178 + "tokio", 179 + "tokio-tungstenite 0.28.0", 180 + "tower", 181 + "tower-layer", 182 + "tower-service", 183 + "tracing", 184 + ] 185 + 186 + [[package]] 187 + name = "axum-core" 188 + version = "0.5.6" 189 + source = "registry+https://github.com/rust-lang/crates.io-index" 190 + checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" 191 + dependencies = [ 192 + "bytes", 193 + "futures-core", 194 + "http", 195 + "http-body", 196 + "http-body-util", 197 + "mime", 198 + "pin-project-lite", 199 + "sync_wrapper", 200 + "tower-layer", 201 + "tower-service", 202 + "tracing", 203 + ] 204 + 205 + [[package]] 206 + name = "axum-macros" 207 + version = "0.5.0" 208 + source = "registry+https://github.com/rust-lang/crates.io-index" 209 + checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c" 210 + dependencies = [ 211 + "proc-macro2", 212 + "quote", 213 + "syn", 214 + ] 215 + 216 + [[package]] 217 + name = "backtrace" 218 + version = "0.3.76" 219 + source = "registry+https://github.com/rust-lang/crates.io-index" 220 + checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" 221 + dependencies = [ 222 + "addr2line", 223 + "cfg-if", 224 + "libc", 225 + "miniz_oxide", 226 + "object", 227 + "rustc-demangle", 228 + "windows-link", 229 + ] 230 + 231 + [[package]] 232 + name = "backtrace-ext" 233 + version = "0.2.1" 234 + source = "registry+https://github.com/rust-lang/crates.io-index" 235 + checksum = "537beee3be4a18fb023b570f80e3ae28003db9167a751266b259926e25539d50" 236 + dependencies = [ 237 + "backtrace", 238 + ] 239 + 240 + [[package]] 241 + name = "base-x" 242 + version = "0.2.11" 243 + source = "registry+https://github.com/rust-lang/crates.io-index" 244 + checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" 245 + 246 + [[package]] 247 + name = "base16ct" 248 + version = "0.2.0" 249 + source = "registry+https://github.com/rust-lang/crates.io-index" 250 + checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" 251 + 252 + [[package]] 253 + name = "base256emoji" 254 + version = "1.0.2" 255 + source = "registry+https://github.com/rust-lang/crates.io-index" 256 + checksum = "b5e9430d9a245a77c92176e649af6e275f20839a48389859d1661e9a128d077c" 257 + dependencies = [ 258 + "const-str", 259 + "match-lookup", 260 + ] 261 + 262 + [[package]] 263 + name = "base64" 264 + version = "0.13.1" 265 + source = "registry+https://github.com/rust-lang/crates.io-index" 266 + checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" 267 + 268 + [[package]] 269 + name = "base64" 270 + version = "0.22.1" 271 + source = "registry+https://github.com/rust-lang/crates.io-index" 272 + checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 273 + 274 + [[package]] 275 + name = "base64ct" 276 + version = "1.8.3" 277 + source = "registry+https://github.com/rust-lang/crates.io-index" 278 + checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" 279 + 280 + [[package]] 281 + name = "bitflags" 282 + version = "2.10.0" 283 + source = "registry+https://github.com/rust-lang/crates.io-index" 284 + checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" 285 + 286 + [[package]] 287 + name = "block-buffer" 288 + version = "0.10.4" 289 + source = "registry+https://github.com/rust-lang/crates.io-index" 290 + checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 291 + dependencies = [ 292 + "generic-array", 293 + ] 294 + 295 + [[package]] 296 + name = "bon" 297 + version = "3.8.2" 298 + source = "registry+https://github.com/rust-lang/crates.io-index" 299 + checksum = "234655ec178edd82b891e262ea7cf71f6584bcd09eff94db786be23f1821825c" 300 + dependencies = [ 301 + "bon-macros", 302 + "rustversion", 303 + ] 304 + 305 + [[package]] 306 + name = "bon-macros" 307 + version = "3.8.2" 308 + source = "registry+https://github.com/rust-lang/crates.io-index" 309 + checksum = "89ec27229c38ed0eb3c0feee3d2c1d6a4379ae44f418a29a658890e062d8f365" 310 + dependencies = [ 311 + "darling 0.23.0", 312 + "ident_case", 313 + "prettyplease", 314 + "proc-macro2", 315 + "quote", 316 + "rustversion", 317 + "syn", 318 + ] 319 + 320 + [[package]] 321 + name = "borsh" 322 + version = "1.6.0" 323 + source = "registry+https://github.com/rust-lang/crates.io-index" 324 + checksum = "d1da5ab77c1437701eeff7c88d968729e7766172279eab0676857b3d63af7a6f" 325 + dependencies = [ 326 + "cfg_aliases", 327 + ] 328 + 329 + [[package]] 330 + name = "brotli" 331 + version = "3.5.0" 332 + source = "registry+https://github.com/rust-lang/crates.io-index" 333 + checksum = "d640d25bc63c50fb1f0b545ffd80207d2e10a4c965530809b40ba3386825c391" 334 + dependencies = [ 335 + "alloc-no-stdlib", 336 + "alloc-stdlib", 337 + "brotli-decompressor 2.5.1", 338 + ] 339 + 340 + [[package]] 341 + name = "brotli" 342 + version = "8.0.2" 343 + source = "registry+https://github.com/rust-lang/crates.io-index" 344 + checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" 345 + dependencies = [ 346 + "alloc-no-stdlib", 347 + "alloc-stdlib", 348 + "brotli-decompressor 5.0.0", 349 + ] 350 + 351 + [[package]] 352 + name = "brotli-decompressor" 353 + version = "2.5.1" 354 + source = "registry+https://github.com/rust-lang/crates.io-index" 355 + checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f" 356 + dependencies = [ 357 + "alloc-no-stdlib", 358 + "alloc-stdlib", 359 + ] 360 + 361 + [[package]] 362 + name = "brotli-decompressor" 363 + version = "5.0.0" 364 + source = "registry+https://github.com/rust-lang/crates.io-index" 365 + checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" 366 + dependencies = [ 367 + "alloc-no-stdlib", 368 + "alloc-stdlib", 369 + ] 370 + 371 + [[package]] 372 + name = "buf_redux" 373 + version = "0.8.4" 374 + source = "registry+https://github.com/rust-lang/crates.io-index" 375 + checksum = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f" 376 + dependencies = [ 377 + "memchr", 378 + "safemem", 379 + ] 380 + 381 + [[package]] 382 + name = "bumpalo" 383 + version = "3.19.1" 384 + source = "registry+https://github.com/rust-lang/crates.io-index" 385 + checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" 386 + 387 + [[package]] 388 + name = "byteorder" 389 + version = "1.5.0" 390 + source = "registry+https://github.com/rust-lang/crates.io-index" 391 + checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 392 + 393 + [[package]] 394 + name = "byteorder-lite" 395 + version = "0.1.0" 396 + source = "registry+https://github.com/rust-lang/crates.io-index" 397 + checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" 398 + 399 + [[package]] 400 + name = "bytes" 401 + version = "1.11.0" 402 + source = "registry+https://github.com/rust-lang/crates.io-index" 403 + checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" 404 + dependencies = [ 405 + "serde", 406 + ] 407 + 408 + [[package]] 409 + name = "byteview" 410 + version = "0.10.0" 411 + source = "registry+https://github.com/rust-lang/crates.io-index" 412 + checksum = "dda4398f387cc6395a3e93b3867cd9abda914c97a0b344d1eefb2e5c51785fca" 413 + 414 + [[package]] 415 + name = "cbor4ii" 416 + version = "0.2.14" 417 + source = "registry+https://github.com/rust-lang/crates.io-index" 418 + checksum = "b544cf8c89359205f4f990d0e6f3828db42df85b5dac95d09157a250eb0749c4" 419 + dependencies = [ 420 + "serde", 421 + ] 422 + 423 + [[package]] 424 + name = "cc" 425 + version = "1.2.55" 426 + source = "registry+https://github.com/rust-lang/crates.io-index" 427 + checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" 428 + dependencies = [ 429 + "find-msvc-tools", 430 + "jobserver", 431 + "libc", 432 + "shlex", 433 + ] 434 + 435 + [[package]] 436 + name = "cesu8" 437 + version = "1.1.0" 438 + source = "registry+https://github.com/rust-lang/crates.io-index" 439 + checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" 440 + 441 + [[package]] 442 + name = "cfg-if" 443 + version = "1.0.4" 444 + source = "registry+https://github.com/rust-lang/crates.io-index" 445 + checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" 446 + 447 + [[package]] 448 + name = "cfg_aliases" 449 + version = "0.2.1" 450 + source = "registry+https://github.com/rust-lang/crates.io-index" 451 + checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" 452 + 453 + [[package]] 454 + name = "chrono" 455 + version = "0.4.43" 456 + source = "registry+https://github.com/rust-lang/crates.io-index" 457 + checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" 458 + dependencies = [ 459 + "iana-time-zone", 460 + "js-sys", 461 + "num-traits", 462 + "serde", 463 + "wasm-bindgen", 464 + "windows-link", 465 + ] 466 + 467 + [[package]] 468 + name = "chunked_transfer" 469 + version = "1.5.0" 470 + source = "registry+https://github.com/rust-lang/crates.io-index" 471 + checksum = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901" 472 + 473 + [[package]] 474 + name = "ciborium" 475 + version = "0.2.2" 476 + source = "registry+https://github.com/rust-lang/crates.io-index" 477 + checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" 478 + dependencies = [ 479 + "ciborium-io", 480 + "ciborium-ll", 481 + "serde", 482 + ] 483 + 484 + [[package]] 485 + name = "ciborium-io" 486 + version = "0.2.2" 487 + source = "registry+https://github.com/rust-lang/crates.io-index" 488 + checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" 489 + 490 + [[package]] 491 + name = "ciborium-ll" 492 + version = "0.2.2" 493 + source = "registry+https://github.com/rust-lang/crates.io-index" 494 + checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" 495 + dependencies = [ 496 + "ciborium-io", 497 + "half", 498 + ] 499 + 500 + [[package]] 501 + name = "cid" 502 + version = "0.11.1" 503 + source = "registry+https://github.com/rust-lang/crates.io-index" 504 + checksum = "3147d8272e8fa0ccd29ce51194dd98f79ddfb8191ba9e3409884e751798acf3a" 505 + dependencies = [ 506 + "core2", 507 + "multibase", 508 + "multihash", 509 + "serde", 510 + "serde_bytes", 511 + "unsigned-varint 0.8.0", 512 + ] 513 + 514 + [[package]] 515 + name = "cobs" 516 + version = "0.3.0" 517 + source = "registry+https://github.com/rust-lang/crates.io-index" 518 + checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" 519 + dependencies = [ 520 + "thiserror 2.0.18", 521 + ] 522 + 523 + [[package]] 524 + name = "combine" 525 + version = "4.6.7" 526 + source = "registry+https://github.com/rust-lang/crates.io-index" 527 + checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" 528 + dependencies = [ 529 + "bytes", 530 + "memchr", 531 + ] 532 + 533 + [[package]] 534 + name = "compare" 535 + version = "0.0.6" 536 + source = "registry+https://github.com/rust-lang/crates.io-index" 537 + checksum = "ea0095f6103c2a8b44acd6fd15960c801dafebf02e21940360833e0673f48ba7" 538 + 539 + [[package]] 540 + name = "compression-codecs" 541 + version = "0.4.36" 542 + source = "registry+https://github.com/rust-lang/crates.io-index" 543 + checksum = "00828ba6fd27b45a448e57dbfe84f1029d4c9f26b368157e9a448a5f49a2ec2a" 544 + dependencies = [ 545 + "brotli 8.0.2", 546 + "compression-core", 547 + "flate2", 548 + "memchr", 549 + "zstd", 550 + "zstd-safe", 551 + ] 552 + 553 + [[package]] 554 + name = "compression-core" 555 + version = "0.4.31" 556 + source = "registry+https://github.com/rust-lang/crates.io-index" 557 + checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d" 558 + 559 + [[package]] 560 + name = "const-oid" 561 + version = "0.9.6" 562 + source = "registry+https://github.com/rust-lang/crates.io-index" 563 + checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" 564 + 565 + [[package]] 566 + name = "const-str" 567 + version = "0.4.3" 568 + source = "registry+https://github.com/rust-lang/crates.io-index" 569 + checksum = "2f421161cb492475f1661ddc9815a745a1c894592070661180fdec3d4872e9c3" 570 + 571 + [[package]] 572 + name = "convert_case" 573 + version = "0.10.0" 574 + source = "registry+https://github.com/rust-lang/crates.io-index" 575 + checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" 576 + dependencies = [ 577 + "unicode-segmentation", 578 + ] 579 + 580 + [[package]] 581 + name = "cordyceps" 582 + version = "0.3.4" 583 + source = "registry+https://github.com/rust-lang/crates.io-index" 584 + checksum = "688d7fbb8092b8de775ef2536f36c8c31f2bc4006ece2e8d8ad2d17d00ce0a2a" 585 + dependencies = [ 586 + "loom", 587 + "tracing", 588 + ] 589 + 590 + [[package]] 591 + name = "core-foundation" 592 + version = "0.9.4" 593 + source = "registry+https://github.com/rust-lang/crates.io-index" 594 + checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 595 + dependencies = [ 596 + "core-foundation-sys", 597 + "libc", 598 + ] 599 + 600 + [[package]] 601 + name = "core-foundation" 602 + version = "0.10.1" 603 + source = "registry+https://github.com/rust-lang/crates.io-index" 604 + checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" 605 + dependencies = [ 606 + "core-foundation-sys", 607 + "libc", 608 + ] 609 + 610 + [[package]] 611 + name = "core-foundation-sys" 612 + version = "0.8.7" 613 + source = "registry+https://github.com/rust-lang/crates.io-index" 614 + checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 615 + 616 + [[package]] 617 + name = "core2" 618 + version = "0.4.0" 619 + source = "registry+https://github.com/rust-lang/crates.io-index" 620 + checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" 621 + dependencies = [ 622 + "memchr", 623 + ] 624 + 625 + [[package]] 626 + name = "cpufeatures" 627 + version = "0.2.17" 628 + source = "registry+https://github.com/rust-lang/crates.io-index" 629 + checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" 630 + dependencies = [ 631 + "libc", 632 + ] 633 + 634 + [[package]] 635 + name = "crc32fast" 636 + version = "1.5.0" 637 + source = "registry+https://github.com/rust-lang/crates.io-index" 638 + checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" 639 + dependencies = [ 640 + "cfg-if", 641 + ] 642 + 643 + [[package]] 644 + name = "critical-section" 645 + version = "1.2.0" 646 + source = "registry+https://github.com/rust-lang/crates.io-index" 647 + checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" 648 + 649 + [[package]] 650 + name = "crossbeam-channel" 651 + version = "0.5.15" 652 + source = "registry+https://github.com/rust-lang/crates.io-index" 653 + checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" 654 + dependencies = [ 655 + "crossbeam-utils", 656 + ] 657 + 658 + [[package]] 659 + name = "crossbeam-epoch" 660 + version = "0.9.18" 661 + source = "registry+https://github.com/rust-lang/crates.io-index" 662 + checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" 663 + dependencies = [ 664 + "crossbeam-utils", 665 + ] 666 + 667 + [[package]] 668 + name = "crossbeam-skiplist" 669 + version = "0.1.3" 670 + source = "registry+https://github.com/rust-lang/crates.io-index" 671 + checksum = "df29de440c58ca2cc6e587ec3d22347551a32435fbde9d2bff64e78a9ffa151b" 672 + dependencies = [ 673 + "crossbeam-epoch", 674 + "crossbeam-utils", 675 + ] 676 + 677 + [[package]] 678 + name = "crossbeam-utils" 679 + version = "0.8.21" 680 + source = "registry+https://github.com/rust-lang/crates.io-index" 681 + checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" 682 + 683 + [[package]] 684 + name = "crunchy" 685 + version = "0.2.4" 686 + source = "registry+https://github.com/rust-lang/crates.io-index" 687 + checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" 688 + 689 + [[package]] 690 + name = "crypto-bigint" 691 + version = "0.5.5" 692 + source = "registry+https://github.com/rust-lang/crates.io-index" 693 + checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" 694 + dependencies = [ 695 + "generic-array", 696 + "rand_core 0.6.4", 697 + "subtle", 698 + "zeroize", 699 + ] 700 + 701 + [[package]] 702 + name = "crypto-common" 703 + version = "0.1.6" 704 + source = "registry+https://github.com/rust-lang/crates.io-index" 705 + checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 706 + dependencies = [ 707 + "generic-array", 708 + "typenum", 709 + ] 710 + 711 + [[package]] 712 + name = "curve25519-dalek" 713 + version = "4.1.3" 714 + source = "registry+https://github.com/rust-lang/crates.io-index" 715 + checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" 716 + dependencies = [ 717 + "cfg-if", 718 + "cpufeatures", 719 + "curve25519-dalek-derive", 720 + "digest", 721 + "fiat-crypto", 722 + "rustc_version", 723 + "subtle", 724 + "zeroize", 725 + ] 726 + 727 + [[package]] 728 + name = "curve25519-dalek-derive" 729 + version = "0.1.1" 730 + source = "registry+https://github.com/rust-lang/crates.io-index" 731 + checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" 732 + dependencies = [ 733 + "proc-macro2", 734 + "quote", 735 + "syn", 736 + ] 737 + 738 + [[package]] 739 + name = "darling" 740 + version = "0.21.3" 741 + source = "registry+https://github.com/rust-lang/crates.io-index" 742 + checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" 743 + dependencies = [ 744 + "darling_core 0.21.3", 745 + "darling_macro 0.21.3", 746 + ] 747 + 748 + [[package]] 749 + name = "darling" 750 + version = "0.23.0" 751 + source = "registry+https://github.com/rust-lang/crates.io-index" 752 + checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" 753 + dependencies = [ 754 + "darling_core 0.23.0", 755 + "darling_macro 0.23.0", 756 + ] 757 + 758 + [[package]] 759 + name = "darling_core" 760 + version = "0.21.3" 761 + source = "registry+https://github.com/rust-lang/crates.io-index" 762 + checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" 763 + dependencies = [ 764 + "fnv", 765 + "ident_case", 766 + "proc-macro2", 767 + "quote", 768 + "strsim", 769 + "syn", 770 + ] 771 + 772 + [[package]] 773 + name = "darling_core" 774 + version = "0.23.0" 775 + source = "registry+https://github.com/rust-lang/crates.io-index" 776 + checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" 777 + dependencies = [ 778 + "ident_case", 779 + "proc-macro2", 780 + "quote", 781 + "strsim", 782 + "syn", 783 + ] 784 + 785 + [[package]] 786 + name = "darling_macro" 787 + version = "0.21.3" 788 + source = "registry+https://github.com/rust-lang/crates.io-index" 789 + checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" 790 + dependencies = [ 791 + "darling_core 0.21.3", 792 + "quote", 793 + "syn", 794 + ] 795 + 796 + [[package]] 797 + name = "darling_macro" 798 + version = "0.23.0" 799 + source = "registry+https://github.com/rust-lang/crates.io-index" 800 + checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" 801 + dependencies = [ 802 + "darling_core 0.23.0", 803 + "quote", 804 + "syn", 805 + ] 806 + 807 + [[package]] 808 + name = "dashmap" 809 + version = "6.1.0" 810 + source = "registry+https://github.com/rust-lang/crates.io-index" 811 + checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" 812 + dependencies = [ 813 + "cfg-if", 814 + "crossbeam-utils", 815 + "hashbrown 0.14.5", 816 + "lock_api", 817 + "once_cell", 818 + "parking_lot_core", 819 + ] 820 + 821 + [[package]] 822 + name = "data-encoding" 823 + version = "2.10.0" 824 + source = "registry+https://github.com/rust-lang/crates.io-index" 825 + checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" 826 + 827 + [[package]] 828 + name = "data-encoding-macro" 829 + version = "0.1.19" 830 + source = "registry+https://github.com/rust-lang/crates.io-index" 831 + checksum = "8142a83c17aa9461d637e649271eae18bf2edd00e91f2e105df36c3c16355bdb" 832 + dependencies = [ 833 + "data-encoding", 834 + "data-encoding-macro-internal", 835 + ] 836 + 837 + [[package]] 838 + name = "data-encoding-macro-internal" 839 + version = "0.1.17" 840 + source = "registry+https://github.com/rust-lang/crates.io-index" 841 + checksum = "7ab67060fc6b8ef687992d439ca0fa36e7ed17e9a0b16b25b601e8757df720de" 842 + dependencies = [ 843 + "data-encoding", 844 + "syn", 845 + ] 846 + 847 + [[package]] 848 + name = "deflate" 849 + version = "1.0.0" 850 + source = "registry+https://github.com/rust-lang/crates.io-index" 851 + checksum = "c86f7e25f518f4b81808a2cf1c50996a61f5c2eb394b2393bd87f2a4780a432f" 852 + dependencies = [ 853 + "adler32", 854 + "gzip-header", 855 + ] 856 + 857 + [[package]] 858 + name = "der" 859 + version = "0.7.10" 860 + source = "registry+https://github.com/rust-lang/crates.io-index" 861 + checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" 862 + dependencies = [ 863 + "const-oid", 864 + "pem-rfc7468", 865 + "zeroize", 866 + ] 867 + 868 + [[package]] 869 + name = "deranged" 870 + version = "0.5.5" 871 + source = "registry+https://github.com/rust-lang/crates.io-index" 872 + checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" 873 + dependencies = [ 874 + "powerfmt", 875 + ] 876 + 877 + [[package]] 878 + name = "derive_more" 879 + version = "1.0.0" 880 + source = "registry+https://github.com/rust-lang/crates.io-index" 881 + checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" 882 + dependencies = [ 883 + "derive_more-impl 1.0.0", 884 + ] 885 + 886 + [[package]] 887 + name = "derive_more" 888 + version = "2.1.1" 889 + source = "registry+https://github.com/rust-lang/crates.io-index" 890 + checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" 891 + dependencies = [ 892 + "derive_more-impl 2.1.1", 893 + ] 894 + 895 + [[package]] 896 + name = "derive_more-impl" 897 + version = "1.0.0" 898 + source = "registry+https://github.com/rust-lang/crates.io-index" 899 + checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" 900 + dependencies = [ 901 + "proc-macro2", 902 + "quote", 903 + "syn", 904 + "unicode-xid", 905 + ] 906 + 907 + [[package]] 908 + name = "derive_more-impl" 909 + version = "2.1.1" 910 + source = "registry+https://github.com/rust-lang/crates.io-index" 911 + checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" 912 + dependencies = [ 913 + "convert_case", 914 + "proc-macro2", 915 + "quote", 916 + "rustc_version", 917 + "syn", 918 + "unicode-xid", 919 + ] 920 + 921 + [[package]] 922 + name = "diatomic-waker" 923 + version = "0.2.3" 924 + source = "registry+https://github.com/rust-lang/crates.io-index" 925 + checksum = "ab03c107fafeb3ee9f5925686dbb7a73bc76e3932abb0d2b365cb64b169cf04c" 926 + 927 + [[package]] 928 + name = "digest" 929 + version = "0.10.7" 930 + source = "registry+https://github.com/rust-lang/crates.io-index" 931 + checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 932 + dependencies = [ 933 + "block-buffer", 934 + "const-oid", 935 + "crypto-common", 936 + "subtle", 937 + ] 938 + 939 + [[package]] 940 + name = "displaydoc" 941 + version = "0.2.5" 942 + source = "registry+https://github.com/rust-lang/crates.io-index" 943 + checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 944 + dependencies = [ 945 + "proc-macro2", 946 + "quote", 947 + "syn", 948 + ] 949 + 950 + [[package]] 951 + name = "ecdsa" 952 + version = "0.16.9" 953 + source = "registry+https://github.com/rust-lang/crates.io-index" 954 + checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" 955 + dependencies = [ 956 + "der", 957 + "digest", 958 + "elliptic-curve", 959 + "rfc6979", 960 + "signature", 961 + "spki", 962 + ] 963 + 964 + [[package]] 965 + name = "ed25519" 966 + version = "2.2.3" 967 + source = "registry+https://github.com/rust-lang/crates.io-index" 968 + checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" 969 + dependencies = [ 970 + "pkcs8", 971 + "signature", 972 + ] 973 + 974 + [[package]] 975 + name = "ed25519-dalek" 976 + version = "2.2.0" 977 + source = "registry+https://github.com/rust-lang/crates.io-index" 978 + checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" 979 + dependencies = [ 980 + "curve25519-dalek", 981 + "ed25519", 982 + "rand_core 0.6.4", 983 + "serde", 984 + "sha2", 985 + "subtle", 986 + "zeroize", 987 + ] 988 + 989 + [[package]] 990 + name = "elliptic-curve" 991 + version = "0.13.8" 992 + source = "registry+https://github.com/rust-lang/crates.io-index" 993 + checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" 994 + dependencies = [ 995 + "base16ct", 996 + "crypto-bigint", 997 + "digest", 998 + "ff", 999 + "generic-array", 1000 + "group", 1001 + "pem-rfc7468", 1002 + "pkcs8", 1003 + "rand_core 0.6.4", 1004 + "sec1", 1005 + "subtle", 1006 + "zeroize", 1007 + ] 1008 + 1009 + [[package]] 1010 + name = "embedded-io" 1011 + version = "0.4.0" 1012 + source = "registry+https://github.com/rust-lang/crates.io-index" 1013 + checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" 1014 + 1015 + [[package]] 1016 + name = "embedded-io" 1017 + version = "0.6.1" 1018 + source = "registry+https://github.com/rust-lang/crates.io-index" 1019 + checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" 1020 + 1021 + [[package]] 1022 + name = "encoding_rs" 1023 + version = "0.8.35" 1024 + source = "registry+https://github.com/rust-lang/crates.io-index" 1025 + checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" 1026 + dependencies = [ 1027 + "cfg-if", 1028 + ] 1029 + 1030 + [[package]] 1031 + name = "enum-as-inner" 1032 + version = "0.6.1" 1033 + source = "registry+https://github.com/rust-lang/crates.io-index" 1034 + checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" 1035 + dependencies = [ 1036 + "heck 0.5.0", 1037 + "proc-macro2", 1038 + "quote", 1039 + "syn", 1040 + ] 1041 + 1042 + [[package]] 1043 + name = "enum_dispatch" 1044 + version = "0.3.13" 1045 + source = "registry+https://github.com/rust-lang/crates.io-index" 1046 + checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" 1047 + dependencies = [ 1048 + "once_cell", 1049 + "proc-macro2", 1050 + "quote", 1051 + "syn", 1052 + ] 1053 + 1054 + [[package]] 1055 + name = "equivalent" 1056 + version = "1.0.2" 1057 + source = "registry+https://github.com/rust-lang/crates.io-index" 1058 + checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 1059 + 1060 + [[package]] 1061 + name = "errno" 1062 + version = "0.3.14" 1063 + source = "registry+https://github.com/rust-lang/crates.io-index" 1064 + checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" 1065 + dependencies = [ 1066 + "libc", 1067 + "windows-sys 0.61.2", 1068 + ] 1069 + 1070 + [[package]] 1071 + name = "fastrand" 1072 + version = "2.3.0" 1073 + source = "registry+https://github.com/rust-lang/crates.io-index" 1074 + checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 1075 + 1076 + [[package]] 1077 + name = "ff" 1078 + version = "0.13.1" 1079 + source = "registry+https://github.com/rust-lang/crates.io-index" 1080 + checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" 1081 + dependencies = [ 1082 + "rand_core 0.6.4", 1083 + "subtle", 1084 + ] 1085 + 1086 + [[package]] 1087 + name = "fiat-crypto" 1088 + version = "0.2.9" 1089 + source = "registry+https://github.com/rust-lang/crates.io-index" 1090 + checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" 1091 + 1092 + [[package]] 1093 + name = "filetime" 1094 + version = "0.2.27" 1095 + source = "registry+https://github.com/rust-lang/crates.io-index" 1096 + checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" 1097 + dependencies = [ 1098 + "cfg-if", 1099 + "libc", 1100 + "libredox", 1101 + ] 1102 + 1103 + [[package]] 1104 + name = "find-msvc-tools" 1105 + version = "0.1.9" 1106 + source = "registry+https://github.com/rust-lang/crates.io-index" 1107 + checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" 1108 + 1109 + [[package]] 1110 + name = "fjall" 1111 + version = "3.0.1" 1112 + source = "registry+https://github.com/rust-lang/crates.io-index" 1113 + checksum = "4f69637c02d38ad1b0f003101d0195a60368130aa17d9ef78b1557d265a22093" 1114 + dependencies = [ 1115 + "byteorder-lite", 1116 + "byteview", 1117 + "dashmap", 1118 + "flume", 1119 + "log", 1120 + "lsm-tree", 1121 + "lz4_flex", 1122 + "tempfile", 1123 + "xxhash-rust", 1124 + ] 1125 + 1126 + [[package]] 1127 + name = "flate2" 1128 + version = "1.1.8" 1129 + source = "registry+https://github.com/rust-lang/crates.io-index" 1130 + checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369" 1131 + dependencies = [ 1132 + "crc32fast", 1133 + "miniz_oxide", 1134 + ] 1135 + 1136 + [[package]] 1137 + name = "flume" 1138 + version = "0.12.0" 1139 + source = "registry+https://github.com/rust-lang/crates.io-index" 1140 + checksum = "5e139bc46ca777eb5efaf62df0ab8cc5fd400866427e56c68b22e414e53bd3be" 1141 + dependencies = [ 1142 + "spin 0.9.8", 1143 + ] 1144 + 1145 + [[package]] 1146 + name = "fnv" 1147 + version = "1.0.7" 1148 + source = "registry+https://github.com/rust-lang/crates.io-index" 1149 + checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 1150 + 1151 + [[package]] 1152 + name = "foldhash" 1153 + version = "0.1.5" 1154 + source = "registry+https://github.com/rust-lang/crates.io-index" 1155 + checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" 1156 + 1157 + [[package]] 1158 + name = "form_urlencoded" 1159 + version = "1.2.2" 1160 + source = "registry+https://github.com/rust-lang/crates.io-index" 1161 + checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" 1162 + dependencies = [ 1163 + "percent-encoding", 1164 + ] 1165 + 1166 + [[package]] 1167 + name = "futf" 1168 + version = "0.1.5" 1169 + source = "registry+https://github.com/rust-lang/crates.io-index" 1170 + checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" 1171 + dependencies = [ 1172 + "mac", 1173 + "new_debug_unreachable", 1174 + ] 1175 + 1176 + [[package]] 1177 + name = "futures" 1178 + version = "0.3.31" 1179 + source = "registry+https://github.com/rust-lang/crates.io-index" 1180 + checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" 1181 + dependencies = [ 1182 + "futures-channel", 1183 + "futures-core", 1184 + "futures-executor", 1185 + "futures-io", 1186 + "futures-sink", 1187 + "futures-task", 1188 + "futures-util", 1189 + ] 1190 + 1191 + [[package]] 1192 + name = "futures-buffered" 1193 + version = "0.2.12" 1194 + source = "registry+https://github.com/rust-lang/crates.io-index" 1195 + checksum = "a8e0e1f38ec07ba4abbde21eed377082f17ccb988be9d988a5adbf4bafc118fd" 1196 + dependencies = [ 1197 + "cordyceps", 1198 + "diatomic-waker", 1199 + "futures-core", 1200 + "pin-project-lite", 1201 + "spin 0.10.0", 1202 + ] 1203 + 1204 + [[package]] 1205 + name = "futures-channel" 1206 + version = "0.3.31" 1207 + source = "registry+https://github.com/rust-lang/crates.io-index" 1208 + checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 1209 + dependencies = [ 1210 + "futures-core", 1211 + "futures-sink", 1212 + ] 1213 + 1214 + [[package]] 1215 + name = "futures-core" 1216 + version = "0.3.31" 1217 + source = "registry+https://github.com/rust-lang/crates.io-index" 1218 + checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 1219 + 1220 + [[package]] 1221 + name = "futures-executor" 1222 + version = "0.3.31" 1223 + source = "registry+https://github.com/rust-lang/crates.io-index" 1224 + checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" 1225 + dependencies = [ 1226 + "futures-core", 1227 + "futures-task", 1228 + "futures-util", 1229 + ] 1230 + 1231 + [[package]] 1232 + name = "futures-io" 1233 + version = "0.3.31" 1234 + source = "registry+https://github.com/rust-lang/crates.io-index" 1235 + checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 1236 + 1237 + [[package]] 1238 + name = "futures-lite" 1239 + version = "2.6.1" 1240 + source = "registry+https://github.com/rust-lang/crates.io-index" 1241 + checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" 1242 + dependencies = [ 1243 + "fastrand", 1244 + "futures-core", 1245 + "futures-io", 1246 + "parking", 1247 + "pin-project-lite", 1248 + ] 1249 + 1250 + [[package]] 1251 + name = "futures-macro" 1252 + version = "0.3.31" 1253 + source = "registry+https://github.com/rust-lang/crates.io-index" 1254 + checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" 1255 + dependencies = [ 1256 + "proc-macro2", 1257 + "quote", 1258 + "syn", 1259 + ] 1260 + 1261 + [[package]] 1262 + name = "futures-sink" 1263 + version = "0.3.31" 1264 + source = "registry+https://github.com/rust-lang/crates.io-index" 1265 + checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 1266 + 1267 + [[package]] 1268 + name = "futures-task" 1269 + version = "0.3.31" 1270 + source = "registry+https://github.com/rust-lang/crates.io-index" 1271 + checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 1272 + 1273 + [[package]] 1274 + name = "futures-util" 1275 + version = "0.3.31" 1276 + source = "registry+https://github.com/rust-lang/crates.io-index" 1277 + checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 1278 + dependencies = [ 1279 + "futures-channel", 1280 + "futures-core", 1281 + "futures-io", 1282 + "futures-macro", 1283 + "futures-sink", 1284 + "futures-task", 1285 + "memchr", 1286 + "pin-project-lite", 1287 + "pin-utils", 1288 + "slab", 1289 + ] 1290 + 1291 + [[package]] 1292 + name = "generator" 1293 + version = "0.8.8" 1294 + source = "registry+https://github.com/rust-lang/crates.io-index" 1295 + checksum = "52f04ae4152da20c76fe800fa48659201d5cf627c5149ca0b707b69d7eef6cf9" 1296 + dependencies = [ 1297 + "cc", 1298 + "cfg-if", 1299 + "libc", 1300 + "log", 1301 + "rustversion", 1302 + "windows-link", 1303 + "windows-result", 1304 + ] 1305 + 1306 + [[package]] 1307 + name = "generic-array" 1308 + version = "0.14.9" 1309 + source = "registry+https://github.com/rust-lang/crates.io-index" 1310 + checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" 1311 + dependencies = [ 1312 + "typenum", 1313 + "version_check", 1314 + "zeroize", 1315 + ] 1316 + 1317 + [[package]] 1318 + name = "getrandom" 1319 + version = "0.2.17" 1320 + source = "registry+https://github.com/rust-lang/crates.io-index" 1321 + checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" 1322 + dependencies = [ 1323 + "cfg-if", 1324 + "js-sys", 1325 + "libc", 1326 + "wasi", 1327 + "wasm-bindgen", 1328 + ] 1329 + 1330 + [[package]] 1331 + name = "getrandom" 1332 + version = "0.3.4" 1333 + source = "registry+https://github.com/rust-lang/crates.io-index" 1334 + checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" 1335 + dependencies = [ 1336 + "cfg-if", 1337 + "js-sys", 1338 + "libc", 1339 + "r-efi", 1340 + "wasip2", 1341 + "wasm-bindgen", 1342 + ] 1343 + 1344 + [[package]] 1345 + name = "gimli" 1346 + version = "0.32.3" 1347 + source = "registry+https://github.com/rust-lang/crates.io-index" 1348 + checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" 1349 + 1350 + [[package]] 1351 + name = "gloo-storage" 1352 + version = "0.3.0" 1353 + source = "registry+https://github.com/rust-lang/crates.io-index" 1354 + checksum = "fbc8031e8c92758af912f9bc08fbbadd3c6f3cfcbf6b64cdf3d6a81f0139277a" 1355 + dependencies = [ 1356 + "gloo-utils", 1357 + "js-sys", 1358 + "serde", 1359 + "serde_json", 1360 + "thiserror 1.0.69", 1361 + "wasm-bindgen", 1362 + "web-sys", 1363 + ] 1364 + 1365 + [[package]] 1366 + name = "gloo-utils" 1367 + version = "0.2.0" 1368 + source = "registry+https://github.com/rust-lang/crates.io-index" 1369 + checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa" 1370 + dependencies = [ 1371 + "js-sys", 1372 + "serde", 1373 + "serde_json", 1374 + "wasm-bindgen", 1375 + "web-sys", 1376 + ] 1377 + 1378 + [[package]] 1379 + name = "group" 1380 + version = "0.13.0" 1381 + source = "registry+https://github.com/rust-lang/crates.io-index" 1382 + checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" 1383 + dependencies = [ 1384 + "ff", 1385 + "rand_core 0.6.4", 1386 + "subtle", 1387 + ] 1388 + 1389 + [[package]] 1390 + name = "gzip-header" 1391 + version = "1.0.0" 1392 + source = "registry+https://github.com/rust-lang/crates.io-index" 1393 + checksum = "95cc527b92e6029a62960ad99aa8a6660faa4555fe5f731aab13aa6a921795a2" 1394 + dependencies = [ 1395 + "crc32fast", 1396 + ] 1397 + 1398 + [[package]] 1399 + name = "h2" 1400 + version = "0.4.13" 1401 + source = "registry+https://github.com/rust-lang/crates.io-index" 1402 + checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" 1403 + dependencies = [ 1404 + "atomic-waker", 1405 + "bytes", 1406 + "fnv", 1407 + "futures-core", 1408 + "futures-sink", 1409 + "http", 1410 + "indexmap", 1411 + "slab", 1412 + "tokio", 1413 + "tokio-util", 1414 + "tracing", 1415 + ] 1416 + 1417 + [[package]] 1418 + name = "half" 1419 + version = "2.7.1" 1420 + source = "registry+https://github.com/rust-lang/crates.io-index" 1421 + checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" 1422 + dependencies = [ 1423 + "cfg-if", 1424 + "crunchy", 1425 + "zerocopy", 1426 + ] 1427 + 1428 + [[package]] 1429 + name = "hash32" 1430 + version = "0.2.1" 1431 + source = "registry+https://github.com/rust-lang/crates.io-index" 1432 + checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" 1433 + dependencies = [ 1434 + "byteorder", 1435 + ] 1436 + 1437 + [[package]] 1438 + name = "hashbrown" 1439 + version = "0.14.5" 1440 + source = "registry+https://github.com/rust-lang/crates.io-index" 1441 + checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 1442 + 1443 + [[package]] 1444 + name = "hashbrown" 1445 + version = "0.15.5" 1446 + source = "registry+https://github.com/rust-lang/crates.io-index" 1447 + checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" 1448 + dependencies = [ 1449 + "allocator-api2", 1450 + "equivalent", 1451 + "foldhash", 1452 + ] 1453 + 1454 + [[package]] 1455 + name = "hashbrown" 1456 + version = "0.16.1" 1457 + source = "registry+https://github.com/rust-lang/crates.io-index" 1458 + checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" 1459 + 1460 + [[package]] 1461 + name = "heapless" 1462 + version = "0.7.17" 1463 + source = "registry+https://github.com/rust-lang/crates.io-index" 1464 + checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" 1465 + dependencies = [ 1466 + "atomic-polyfill", 1467 + "hash32", 1468 + "rustc_version", 1469 + "serde", 1470 + "spin 0.9.8", 1471 + "stable_deref_trait", 1472 + ] 1473 + 1474 + [[package]] 1475 + name = "heck" 1476 + version = "0.4.1" 1477 + source = "registry+https://github.com/rust-lang/crates.io-index" 1478 + checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 1479 + 1480 + [[package]] 1481 + name = "heck" 1482 + version = "0.5.0" 1483 + source = "registry+https://github.com/rust-lang/crates.io-index" 1484 + checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 1485 + 1486 + [[package]] 1487 + name = "hermit-abi" 1488 + version = "0.5.2" 1489 + source = "registry+https://github.com/rust-lang/crates.io-index" 1490 + checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" 1491 + 1492 + [[package]] 1493 + name = "hex" 1494 + version = "0.4.3" 1495 + source = "registry+https://github.com/rust-lang/crates.io-index" 1496 + checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 1497 + 1498 + [[package]] 1499 + name = "hickory-proto" 1500 + version = "0.24.4" 1501 + source = "registry+https://github.com/rust-lang/crates.io-index" 1502 + checksum = "92652067c9ce6f66ce53cc38d1169daa36e6e7eb7dd3b63b5103bd9d97117248" 1503 + dependencies = [ 1504 + "async-trait", 1505 + "cfg-if", 1506 + "data-encoding", 1507 + "enum-as-inner", 1508 + "futures-channel", 1509 + "futures-io", 1510 + "futures-util", 1511 + "idna", 1512 + "ipnet", 1513 + "once_cell", 1514 + "rand 0.8.5", 1515 + "thiserror 1.0.69", 1516 + "tinyvec", 1517 + "tokio", 1518 + "tracing", 1519 + "url", 1520 + ] 1521 + 1522 + [[package]] 1523 + name = "hickory-resolver" 1524 + version = "0.24.4" 1525 + source = "registry+https://github.com/rust-lang/crates.io-index" 1526 + checksum = "cbb117a1ca520e111743ab2f6688eddee69db4e0ea242545a604dce8a66fd22e" 1527 + dependencies = [ 1528 + "cfg-if", 1529 + "futures-util", 1530 + "hickory-proto", 1531 + "ipconfig", 1532 + "lru-cache", 1533 + "once_cell", 1534 + "parking_lot", 1535 + "rand 0.8.5", 1536 + "resolv-conf", 1537 + "smallvec", 1538 + "thiserror 1.0.69", 1539 + "tokio", 1540 + "tracing", 1541 + ] 1542 + 1543 + [[package]] 1544 + name = "hmac" 1545 + version = "0.12.1" 1546 + source = "registry+https://github.com/rust-lang/crates.io-index" 1547 + checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" 1548 + dependencies = [ 1549 + "digest", 1550 + ] 1551 + 1552 + [[package]] 1553 + name = "html5ever" 1554 + version = "0.27.0" 1555 + source = "registry+https://github.com/rust-lang/crates.io-index" 1556 + checksum = "c13771afe0e6e846f1e67d038d4cb29998a6779f93c809212e4e9c32efd244d4" 1557 + dependencies = [ 1558 + "log", 1559 + "mac", 1560 + "markup5ever", 1561 + "proc-macro2", 1562 + "quote", 1563 + "syn", 1564 + ] 1565 + 1566 + [[package]] 1567 + name = "http" 1568 + version = "1.4.0" 1569 + source = "registry+https://github.com/rust-lang/crates.io-index" 1570 + checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" 1571 + dependencies = [ 1572 + "bytes", 1573 + "itoa", 1574 + ] 1575 + 1576 + [[package]] 1577 + name = "http-body" 1578 + version = "1.0.1" 1579 + source = "registry+https://github.com/rust-lang/crates.io-index" 1580 + checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" 1581 + dependencies = [ 1582 + "bytes", 1583 + "http", 1584 + ] 1585 + 1586 + [[package]] 1587 + name = "http-body-util" 1588 + version = "0.1.3" 1589 + source = "registry+https://github.com/rust-lang/crates.io-index" 1590 + checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" 1591 + dependencies = [ 1592 + "bytes", 1593 + "futures-core", 1594 + "http", 1595 + "http-body", 1596 + "pin-project-lite", 1597 + ] 1598 + 1599 + [[package]] 1600 + name = "httparse" 1601 + version = "1.10.1" 1602 + source = "registry+https://github.com/rust-lang/crates.io-index" 1603 + checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" 1604 + 1605 + [[package]] 1606 + name = "httpdate" 1607 + version = "1.0.3" 1608 + source = "registry+https://github.com/rust-lang/crates.io-index" 1609 + checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 1610 + 1611 + [[package]] 1612 + name = "humantime" 1613 + version = "2.3.0" 1614 + source = "registry+https://github.com/rust-lang/crates.io-index" 1615 + checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" 1616 + 1617 + [[package]] 1618 + name = "hydrant" 1619 + version = "0.1.0" 1620 + dependencies = [ 1621 + "async-stream", 1622 + "axum", 1623 + "chrono", 1624 + "fjall", 1625 + "futures", 1626 + "humantime", 1627 + "jacquard", 1628 + "jacquard-api", 1629 + "jacquard-axum", 1630 + "jacquard-common", 1631 + "jacquard-derive", 1632 + "jacquard-identity", 1633 + "jacquard-repo", 1634 + "miette", 1635 + "mimalloc", 1636 + "n0-future 0.3.2", 1637 + "reqwest", 1638 + "rmp-serde", 1639 + "serde", 1640 + "serde_ipld_dagcbor", 1641 + "serde_json", 1642 + "smol_str", 1643 + "tokio", 1644 + "tokio-stream", 1645 + "tower-http", 1646 + "tracing", 1647 + "tracing-subscriber", 1648 + "url", 1649 + ] 1650 + 1651 + [[package]] 1652 + name = "hyper" 1653 + version = "1.8.1" 1654 + source = "registry+https://github.com/rust-lang/crates.io-index" 1655 + checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" 1656 + dependencies = [ 1657 + "atomic-waker", 1658 + "bytes", 1659 + "futures-channel", 1660 + "futures-core", 1661 + "h2", 1662 + "http", 1663 + "http-body", 1664 + "httparse", 1665 + "httpdate", 1666 + "itoa", 1667 + "pin-project-lite", 1668 + "pin-utils", 1669 + "smallvec", 1670 + "tokio", 1671 + "want", 1672 + ] 1673 + 1674 + [[package]] 1675 + name = "hyper-rustls" 1676 + version = "0.27.7" 1677 + source = "registry+https://github.com/rust-lang/crates.io-index" 1678 + checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" 1679 + dependencies = [ 1680 + "http", 1681 + "hyper", 1682 + "hyper-util", 1683 + "rustls", 1684 + "rustls-pki-types", 1685 + "tokio", 1686 + "tokio-rustls", 1687 + "tower-service", 1688 + "webpki-roots", 1689 + ] 1690 + 1691 + [[package]] 1692 + name = "hyper-util" 1693 + version = "0.1.19" 1694 + source = "registry+https://github.com/rust-lang/crates.io-index" 1695 + checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" 1696 + dependencies = [ 1697 + "base64 0.22.1", 1698 + "bytes", 1699 + "futures-channel", 1700 + "futures-core", 1701 + "futures-util", 1702 + "http", 1703 + "http-body", 1704 + "hyper", 1705 + "ipnet", 1706 + "libc", 1707 + "percent-encoding", 1708 + "pin-project-lite", 1709 + "socket2 0.6.2", 1710 + "system-configuration", 1711 + "tokio", 1712 + "tower-service", 1713 + "tracing", 1714 + "windows-registry", 1715 + ] 1716 + 1717 + [[package]] 1718 + name = "iana-time-zone" 1719 + version = "0.1.65" 1720 + source = "registry+https://github.com/rust-lang/crates.io-index" 1721 + checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" 1722 + dependencies = [ 1723 + "android_system_properties", 1724 + "core-foundation-sys", 1725 + "iana-time-zone-haiku", 1726 + "js-sys", 1727 + "log", 1728 + "wasm-bindgen", 1729 + "windows-core", 1730 + ] 1731 + 1732 + [[package]] 1733 + name = "iana-time-zone-haiku" 1734 + version = "0.1.2" 1735 + source = "registry+https://github.com/rust-lang/crates.io-index" 1736 + checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 1737 + dependencies = [ 1738 + "cc", 1739 + ] 1740 + 1741 + [[package]] 1742 + name = "icu_collections" 1743 + version = "2.1.1" 1744 + source = "registry+https://github.com/rust-lang/crates.io-index" 1745 + checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" 1746 + dependencies = [ 1747 + "displaydoc", 1748 + "potential_utf", 1749 + "yoke", 1750 + "zerofrom", 1751 + "zerovec", 1752 + ] 1753 + 1754 + [[package]] 1755 + name = "icu_locale_core" 1756 + version = "2.1.1" 1757 + source = "registry+https://github.com/rust-lang/crates.io-index" 1758 + checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" 1759 + dependencies = [ 1760 + "displaydoc", 1761 + "litemap", 1762 + "tinystr", 1763 + "writeable", 1764 + "zerovec", 1765 + ] 1766 + 1767 + [[package]] 1768 + name = "icu_normalizer" 1769 + version = "2.1.1" 1770 + source = "registry+https://github.com/rust-lang/crates.io-index" 1771 + checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" 1772 + dependencies = [ 1773 + "icu_collections", 1774 + "icu_normalizer_data", 1775 + "icu_properties", 1776 + "icu_provider", 1777 + "smallvec", 1778 + "zerovec", 1779 + ] 1780 + 1781 + [[package]] 1782 + name = "icu_normalizer_data" 1783 + version = "2.1.1" 1784 + source = "registry+https://github.com/rust-lang/crates.io-index" 1785 + checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" 1786 + 1787 + [[package]] 1788 + name = "icu_properties" 1789 + version = "2.1.2" 1790 + source = "registry+https://github.com/rust-lang/crates.io-index" 1791 + checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" 1792 + dependencies = [ 1793 + "icu_collections", 1794 + "icu_locale_core", 1795 + "icu_properties_data", 1796 + "icu_provider", 1797 + "zerotrie", 1798 + "zerovec", 1799 + ] 1800 + 1801 + [[package]] 1802 + name = "icu_properties_data" 1803 + version = "2.1.2" 1804 + source = "registry+https://github.com/rust-lang/crates.io-index" 1805 + checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" 1806 + 1807 + [[package]] 1808 + name = "icu_provider" 1809 + version = "2.1.1" 1810 + source = "registry+https://github.com/rust-lang/crates.io-index" 1811 + checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" 1812 + dependencies = [ 1813 + "displaydoc", 1814 + "icu_locale_core", 1815 + "writeable", 1816 + "yoke", 1817 + "zerofrom", 1818 + "zerotrie", 1819 + "zerovec", 1820 + ] 1821 + 1822 + [[package]] 1823 + name = "ident_case" 1824 + version = "1.0.1" 1825 + source = "registry+https://github.com/rust-lang/crates.io-index" 1826 + checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 1827 + 1828 + [[package]] 1829 + name = "idna" 1830 + version = "1.1.0" 1831 + source = "registry+https://github.com/rust-lang/crates.io-index" 1832 + checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" 1833 + dependencies = [ 1834 + "idna_adapter", 1835 + "smallvec", 1836 + "utf8_iter", 1837 + ] 1838 + 1839 + [[package]] 1840 + name = "idna_adapter" 1841 + version = "1.2.1" 1842 + source = "registry+https://github.com/rust-lang/crates.io-index" 1843 + checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" 1844 + dependencies = [ 1845 + "icu_normalizer", 1846 + "icu_properties", 1847 + ] 1848 + 1849 + [[package]] 1850 + name = "indexmap" 1851 + version = "2.13.0" 1852 + source = "registry+https://github.com/rust-lang/crates.io-index" 1853 + checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" 1854 + dependencies = [ 1855 + "equivalent", 1856 + "hashbrown 0.16.1", 1857 + ] 1858 + 1859 + [[package]] 1860 + name = "interval-heap" 1861 + version = "0.0.5" 1862 + source = "registry+https://github.com/rust-lang/crates.io-index" 1863 + checksum = "11274e5e8e89b8607cfedc2910b6626e998779b48a019151c7604d0adcb86ac6" 1864 + dependencies = [ 1865 + "compare", 1866 + ] 1867 + 1868 + [[package]] 1869 + name = "inventory" 1870 + version = "0.3.21" 1871 + source = "registry+https://github.com/rust-lang/crates.io-index" 1872 + checksum = "bc61209c082fbeb19919bee74b176221b27223e27b65d781eb91af24eb1fb46e" 1873 + dependencies = [ 1874 + "rustversion", 1875 + ] 1876 + 1877 + [[package]] 1878 + name = "ipconfig" 1879 + version = "0.3.2" 1880 + source = "registry+https://github.com/rust-lang/crates.io-index" 1881 + checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" 1882 + dependencies = [ 1883 + "socket2 0.5.10", 1884 + "widestring", 1885 + "windows-sys 0.48.0", 1886 + "winreg", 1887 + ] 1888 + 1889 + [[package]] 1890 + name = "ipld-core" 1891 + version = "0.4.2" 1892 + source = "registry+https://github.com/rust-lang/crates.io-index" 1893 + checksum = "104718b1cc124d92a6d01ca9c9258a7df311405debb3408c445a36452f9bf8db" 1894 + dependencies = [ 1895 + "cid", 1896 + "serde", 1897 + "serde_bytes", 1898 + ] 1899 + 1900 + [[package]] 1901 + name = "ipnet" 1902 + version = "2.11.0" 1903 + source = "registry+https://github.com/rust-lang/crates.io-index" 1904 + checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" 1905 + 1906 + [[package]] 1907 + name = "iri-string" 1908 + version = "0.7.10" 1909 + source = "registry+https://github.com/rust-lang/crates.io-index" 1910 + checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" 1911 + dependencies = [ 1912 + "memchr", 1913 + "serde", 1914 + ] 1915 + 1916 + [[package]] 1917 + name = "iroh-car" 1918 + version = "0.5.1" 1919 + source = "registry+https://github.com/rust-lang/crates.io-index" 1920 + checksum = "cb7f8cd4cb9aa083fba8b52e921764252d0b4dcb1cd6d120b809dbfe1106e81a" 1921 + dependencies = [ 1922 + "anyhow", 1923 + "cid", 1924 + "futures", 1925 + "serde", 1926 + "serde_ipld_dagcbor", 1927 + "thiserror 1.0.69", 1928 + "tokio", 1929 + "unsigned-varint 0.7.2", 1930 + ] 1931 + 1932 + [[package]] 1933 + name = "is_ci" 1934 + version = "1.2.0" 1935 + source = "registry+https://github.com/rust-lang/crates.io-index" 1936 + checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45" 1937 + 1938 + [[package]] 1939 + name = "itoa" 1940 + version = "1.0.17" 1941 + source = "registry+https://github.com/rust-lang/crates.io-index" 1942 + checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" 1943 + 1944 + [[package]] 1945 + name = "jacquard" 1946 + version = "0.9.5" 1947 + source = "git+https://tangled.org/nonbinary.computer/jacquard#bfb72e29f20b0683e939db0140fa44cabde162d2" 1948 + dependencies = [ 1949 + "bytes", 1950 + "getrandom 0.2.17", 1951 + "gloo-storage", 1952 + "http", 1953 + "jacquard-api", 1954 + "jacquard-common", 1955 + "jacquard-derive", 1956 + "jacquard-identity", 1957 + "jacquard-oauth", 1958 + "jose-jwk", 1959 + "miette", 1960 + "n0-future 0.1.3", 1961 + "regex", 1962 + "regex-lite", 1963 + "reqwest", 1964 + "serde", 1965 + "serde_html_form", 1966 + "serde_json", 1967 + "smol_str", 1968 + "thiserror 2.0.18", 1969 + "tokio", 1970 + "trait-variant", 1971 + "url", 1972 + "webpage", 1973 + ] 1974 + 1975 + [[package]] 1976 + name = "jacquard-api" 1977 + version = "0.9.5" 1978 + source = "git+https://tangled.org/nonbinary.computer/jacquard#bfb72e29f20b0683e939db0140fa44cabde162d2" 1979 + dependencies = [ 1980 + "bon", 1981 + "bytes", 1982 + "jacquard-common", 1983 + "jacquard-derive", 1984 + "jacquard-lexicon", 1985 + "miette", 1986 + "rustversion", 1987 + "serde", 1988 + "serde_bytes", 1989 + "serde_ipld_dagcbor", 1990 + "thiserror 2.0.18", 1991 + "unicode-segmentation", 1992 + ] 1993 + 1994 + [[package]] 1995 + name = "jacquard-axum" 1996 + version = "0.9.6" 1997 + source = "git+https://tangled.org/nonbinary.computer/jacquard#bfb72e29f20b0683e939db0140fa44cabde162d2" 1998 + dependencies = [ 1999 + "axum", 2000 + "bytes", 2001 + "jacquard", 2002 + "jacquard-common", 2003 + "jacquard-derive", 2004 + "jacquard-identity", 2005 + "miette", 2006 + "multibase", 2007 + "serde", 2008 + "serde_html_form", 2009 + "serde_json", 2010 + "thiserror 2.0.18", 2011 + "tokio", 2012 + "tower-http", 2013 + "tracing", 2014 + ] 2015 + 2016 + [[package]] 2017 + name = "jacquard-common" 2018 + version = "0.9.5" 2019 + source = "git+https://tangled.org/nonbinary.computer/jacquard#bfb72e29f20b0683e939db0140fa44cabde162d2" 2020 + dependencies = [ 2021 + "base64 0.22.1", 2022 + "bon", 2023 + "bytes", 2024 + "chrono", 2025 + "ciborium", 2026 + "ciborium-io", 2027 + "cid", 2028 + "ed25519-dalek", 2029 + "futures", 2030 + "getrandom 0.2.17", 2031 + "getrandom 0.3.4", 2032 + "hashbrown 0.15.5", 2033 + "http", 2034 + "ipld-core", 2035 + "k256", 2036 + "maitake-sync", 2037 + "miette", 2038 + "multibase", 2039 + "multihash", 2040 + "n0-future 0.1.3", 2041 + "ouroboros", 2042 + "oxilangtag", 2043 + "p256", 2044 + "postcard", 2045 + "rand 0.9.2", 2046 + "regex", 2047 + "regex-automata", 2048 + "regex-lite", 2049 + "reqwest", 2050 + "serde", 2051 + "serde_bytes", 2052 + "serde_html_form", 2053 + "serde_ipld_dagcbor", 2054 + "serde_json", 2055 + "signature", 2056 + "smol_str", 2057 + "spin 0.10.0", 2058 + "thiserror 2.0.18", 2059 + "tokio", 2060 + "tokio-tungstenite-wasm", 2061 + "tokio-util", 2062 + "trait-variant", 2063 + "url", 2064 + ] 2065 + 2066 + [[package]] 2067 + name = "jacquard-derive" 2068 + version = "0.9.5" 2069 + source = "git+https://tangled.org/nonbinary.computer/jacquard#bfb72e29f20b0683e939db0140fa44cabde162d2" 2070 + dependencies = [ 2071 + "heck 0.5.0", 2072 + "jacquard-lexicon", 2073 + "proc-macro2", 2074 + "quote", 2075 + "syn", 2076 + ] 2077 + 2078 + [[package]] 2079 + name = "jacquard-identity" 2080 + version = "0.9.5" 2081 + source = "git+https://tangled.org/nonbinary.computer/jacquard#bfb72e29f20b0683e939db0140fa44cabde162d2" 2082 + dependencies = [ 2083 + "bon", 2084 + "bytes", 2085 + "hickory-resolver", 2086 + "http", 2087 + "jacquard-api", 2088 + "jacquard-common", 2089 + "jacquard-lexicon", 2090 + "miette", 2091 + "mini-moka-wasm", 2092 + "n0-future 0.1.3", 2093 + "percent-encoding", 2094 + "reqwest", 2095 + "serde", 2096 + "serde_html_form", 2097 + "serde_json", 2098 + "thiserror 2.0.18", 2099 + "tokio", 2100 + "trait-variant", 2101 + "url", 2102 + "urlencoding", 2103 + ] 2104 + 2105 + [[package]] 2106 + name = "jacquard-lexicon" 2107 + version = "0.9.5" 2108 + source = "git+https://tangled.org/nonbinary.computer/jacquard#bfb72e29f20b0683e939db0140fa44cabde162d2" 2109 + dependencies = [ 2110 + "cid", 2111 + "dashmap", 2112 + "heck 0.5.0", 2113 + "inventory", 2114 + "jacquard-common", 2115 + "miette", 2116 + "multihash", 2117 + "prettyplease", 2118 + "proc-macro2", 2119 + "quote", 2120 + "serde", 2121 + "serde_ipld_dagcbor", 2122 + "serde_json", 2123 + "serde_path_to_error", 2124 + "serde_repr", 2125 + "serde_with", 2126 + "sha2", 2127 + "syn", 2128 + "thiserror 2.0.18", 2129 + "unicode-segmentation", 2130 + ] 2131 + 2132 + [[package]] 2133 + name = "jacquard-oauth" 2134 + version = "0.9.6" 2135 + source = "git+https://tangled.org/nonbinary.computer/jacquard#bfb72e29f20b0683e939db0140fa44cabde162d2" 2136 + dependencies = [ 2137 + "base64 0.22.1", 2138 + "bytes", 2139 + "chrono", 2140 + "dashmap", 2141 + "elliptic-curve", 2142 + "http", 2143 + "jacquard-common", 2144 + "jacquard-identity", 2145 + "jose-jwa", 2146 + "jose-jwk", 2147 + "miette", 2148 + "n0-future 0.1.3", 2149 + "p256", 2150 + "rand 0.8.5", 2151 + "rouille", 2152 + "serde", 2153 + "serde_html_form", 2154 + "serde_json", 2155 + "sha2", 2156 + "smol_str", 2157 + "thiserror 2.0.18", 2158 + "tokio", 2159 + "trait-variant", 2160 + "url", 2161 + "webbrowser", 2162 + ] 2163 + 2164 + [[package]] 2165 + name = "jacquard-repo" 2166 + version = "0.9.6" 2167 + source = "git+https://tangled.org/nonbinary.computer/jacquard#bfb72e29f20b0683e939db0140fa44cabde162d2" 2168 + dependencies = [ 2169 + "bytes", 2170 + "cid", 2171 + "ed25519-dalek", 2172 + "iroh-car", 2173 + "jacquard-common", 2174 + "jacquard-derive", 2175 + "k256", 2176 + "miette", 2177 + "multihash", 2178 + "n0-future 0.1.3", 2179 + "p256", 2180 + "serde", 2181 + "serde_bytes", 2182 + "serde_ipld_dagcbor", 2183 + "sha2", 2184 + "smol_str", 2185 + "thiserror 2.0.18", 2186 + "tokio", 2187 + "trait-variant", 2188 + ] 2189 + 2190 + [[package]] 2191 + name = "jni" 2192 + version = "0.21.1" 2193 + source = "registry+https://github.com/rust-lang/crates.io-index" 2194 + checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" 2195 + dependencies = [ 2196 + "cesu8", 2197 + "cfg-if", 2198 + "combine", 2199 + "jni-sys", 2200 + "log", 2201 + "thiserror 1.0.69", 2202 + "walkdir", 2203 + "windows-sys 0.45.0", 2204 + ] 2205 + 2206 + [[package]] 2207 + name = "jni-sys" 2208 + version = "0.3.0" 2209 + source = "registry+https://github.com/rust-lang/crates.io-index" 2210 + checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" 2211 + 2212 + [[package]] 2213 + name = "jobserver" 2214 + version = "0.1.34" 2215 + source = "registry+https://github.com/rust-lang/crates.io-index" 2216 + checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" 2217 + dependencies = [ 2218 + "getrandom 0.3.4", 2219 + "libc", 2220 + ] 2221 + 2222 + [[package]] 2223 + name = "jose-b64" 2224 + version = "0.1.2" 2225 + source = "registry+https://github.com/rust-lang/crates.io-index" 2226 + checksum = "bec69375368709666b21c76965ce67549f2d2db7605f1f8707d17c9656801b56" 2227 + dependencies = [ 2228 + "base64ct", 2229 + "serde", 2230 + "subtle", 2231 + "zeroize", 2232 + ] 2233 + 2234 + [[package]] 2235 + name = "jose-jwa" 2236 + version = "0.1.2" 2237 + source = "registry+https://github.com/rust-lang/crates.io-index" 2238 + checksum = "9ab78e053fe886a351d67cf0d194c000f9d0dcb92906eb34d853d7e758a4b3a7" 2239 + dependencies = [ 2240 + "serde", 2241 + ] 2242 + 2243 + [[package]] 2244 + name = "jose-jwk" 2245 + version = "0.1.2" 2246 + source = "registry+https://github.com/rust-lang/crates.io-index" 2247 + checksum = "280fa263807fe0782ecb6f2baadc28dffc04e00558a58e33bfdb801d11fd58e7" 2248 + dependencies = [ 2249 + "jose-b64", 2250 + "jose-jwa", 2251 + "p256", 2252 + "p384", 2253 + "rsa", 2254 + "serde", 2255 + "zeroize", 2256 + ] 2257 + 2258 + [[package]] 2259 + name = "js-sys" 2260 + version = "0.3.85" 2261 + source = "registry+https://github.com/rust-lang/crates.io-index" 2262 + checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" 2263 + dependencies = [ 2264 + "once_cell", 2265 + "wasm-bindgen", 2266 + ] 2267 + 2268 + [[package]] 2269 + name = "k256" 2270 + version = "0.13.4" 2271 + source = "registry+https://github.com/rust-lang/crates.io-index" 2272 + checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" 2273 + dependencies = [ 2274 + "cfg-if", 2275 + "ecdsa", 2276 + "elliptic-curve", 2277 + "once_cell", 2278 + "sha2", 2279 + "signature", 2280 + ] 2281 + 2282 + [[package]] 2283 + name = "lazy_static" 2284 + version = "1.5.0" 2285 + source = "registry+https://github.com/rust-lang/crates.io-index" 2286 + checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 2287 + dependencies = [ 2288 + "spin 0.9.8", 2289 + ] 2290 + 2291 + [[package]] 2292 + name = "libc" 2293 + version = "0.2.180" 2294 + source = "registry+https://github.com/rust-lang/crates.io-index" 2295 + checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" 2296 + 2297 + [[package]] 2298 + name = "libm" 2299 + version = "0.2.16" 2300 + source = "registry+https://github.com/rust-lang/crates.io-index" 2301 + checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" 2302 + 2303 + [[package]] 2304 + name = "libmimalloc-sys" 2305 + version = "0.1.44" 2306 + source = "registry+https://github.com/rust-lang/crates.io-index" 2307 + checksum = "667f4fec20f29dfc6bc7357c582d91796c169ad7e2fce709468aefeb2c099870" 2308 + dependencies = [ 2309 + "cc", 2310 + "libc", 2311 + ] 2312 + 2313 + [[package]] 2314 + name = "libredox" 2315 + version = "0.1.12" 2316 + source = "registry+https://github.com/rust-lang/crates.io-index" 2317 + checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" 2318 + dependencies = [ 2319 + "bitflags", 2320 + "libc", 2321 + "redox_syscall 0.7.0", 2322 + ] 2323 + 2324 + [[package]] 2325 + name = "linked-hash-map" 2326 + version = "0.5.6" 2327 + source = "registry+https://github.com/rust-lang/crates.io-index" 2328 + checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" 2329 + 2330 + [[package]] 2331 + name = "linux-raw-sys" 2332 + version = "0.11.0" 2333 + source = "registry+https://github.com/rust-lang/crates.io-index" 2334 + checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" 2335 + 2336 + [[package]] 2337 + name = "litemap" 2338 + version = "0.8.1" 2339 + source = "registry+https://github.com/rust-lang/crates.io-index" 2340 + checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" 2341 + 2342 + [[package]] 2343 + name = "lock_api" 2344 + version = "0.4.14" 2345 + source = "registry+https://github.com/rust-lang/crates.io-index" 2346 + checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" 2347 + dependencies = [ 2348 + "scopeguard", 2349 + ] 2350 + 2351 + [[package]] 2352 + name = "log" 2353 + version = "0.4.29" 2354 + source = "registry+https://github.com/rust-lang/crates.io-index" 2355 + checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" 2356 + 2357 + [[package]] 2358 + name = "loom" 2359 + version = "0.7.2" 2360 + source = "registry+https://github.com/rust-lang/crates.io-index" 2361 + checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" 2362 + dependencies = [ 2363 + "cfg-if", 2364 + "generator", 2365 + "scoped-tls", 2366 + "tracing", 2367 + "tracing-subscriber", 2368 + ] 2369 + 2370 + [[package]] 2371 + name = "lru-cache" 2372 + version = "0.1.2" 2373 + source = "registry+https://github.com/rust-lang/crates.io-index" 2374 + checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" 2375 + dependencies = [ 2376 + "linked-hash-map", 2377 + ] 2378 + 2379 + [[package]] 2380 + name = "lru-slab" 2381 + version = "0.1.2" 2382 + source = "registry+https://github.com/rust-lang/crates.io-index" 2383 + checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" 2384 + 2385 + [[package]] 2386 + name = "lsm-tree" 2387 + version = "3.0.1" 2388 + source = "registry+https://github.com/rust-lang/crates.io-index" 2389 + checksum = "b875f1dfe14f557f805b167fb9b0fc54c5560c7a4bd6ae02535b2846f276a8cb" 2390 + dependencies = [ 2391 + "byteorder-lite", 2392 + "byteview", 2393 + "crossbeam-skiplist", 2394 + "enum_dispatch", 2395 + "interval-heap", 2396 + "log", 2397 + "lz4_flex", 2398 + "quick_cache", 2399 + "rustc-hash", 2400 + "self_cell", 2401 + "sfa", 2402 + "tempfile", 2403 + "varint-rs", 2404 + "xxhash-rust", 2405 + ] 2406 + 2407 + [[package]] 2408 + name = "lz4_flex" 2409 + version = "0.11.5" 2410 + source = "registry+https://github.com/rust-lang/crates.io-index" 2411 + checksum = "08ab2867e3eeeca90e844d1940eab391c9dc5228783db2ed999acbc0a9ed375a" 2412 + dependencies = [ 2413 + "twox-hash", 2414 + ] 2415 + 2416 + [[package]] 2417 + name = "mac" 2418 + version = "0.1.1" 2419 + source = "registry+https://github.com/rust-lang/crates.io-index" 2420 + checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" 2421 + 2422 + [[package]] 2423 + name = "maitake-sync" 2424 + version = "0.1.2" 2425 + source = "registry+https://github.com/rust-lang/crates.io-index" 2426 + checksum = "6816ab14147f80234c675b80ed6dc4f440d8a1cefc158e766067aedb84c0bcd5" 2427 + dependencies = [ 2428 + "cordyceps", 2429 + "loom", 2430 + "mycelium-bitfield", 2431 + "pin-project", 2432 + "portable-atomic", 2433 + ] 2434 + 2435 + [[package]] 2436 + name = "markup5ever" 2437 + version = "0.12.1" 2438 + source = "registry+https://github.com/rust-lang/crates.io-index" 2439 + checksum = "16ce3abbeba692c8b8441d036ef91aea6df8da2c6b6e21c7e14d3c18e526be45" 2440 + dependencies = [ 2441 + "log", 2442 + "phf", 2443 + "phf_codegen", 2444 + "string_cache", 2445 + "string_cache_codegen", 2446 + "tendril", 2447 + ] 2448 + 2449 + [[package]] 2450 + name = "markup5ever_rcdom" 2451 + version = "0.3.0" 2452 + source = "registry+https://github.com/rust-lang/crates.io-index" 2453 + checksum = "edaa21ab3701bfee5099ade5f7e1f84553fd19228cf332f13cd6e964bf59be18" 2454 + dependencies = [ 2455 + "html5ever", 2456 + "markup5ever", 2457 + "tendril", 2458 + "xml5ever", 2459 + ] 2460 + 2461 + [[package]] 2462 + name = "match-lookup" 2463 + version = "0.1.2" 2464 + source = "registry+https://github.com/rust-lang/crates.io-index" 2465 + checksum = "757aee279b8bdbb9f9e676796fd459e4207a1f986e87886700abf589f5abf771" 2466 + dependencies = [ 2467 + "proc-macro2", 2468 + "quote", 2469 + "syn", 2470 + ] 2471 + 2472 + [[package]] 2473 + name = "matchers" 2474 + version = "0.2.0" 2475 + source = "registry+https://github.com/rust-lang/crates.io-index" 2476 + checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" 2477 + dependencies = [ 2478 + "regex-automata", 2479 + ] 2480 + 2481 + [[package]] 2482 + name = "matchit" 2483 + version = "0.8.4" 2484 + source = "registry+https://github.com/rust-lang/crates.io-index" 2485 + checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" 2486 + 2487 + [[package]] 2488 + name = "memchr" 2489 + version = "2.7.6" 2490 + source = "registry+https://github.com/rust-lang/crates.io-index" 2491 + checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" 2492 + 2493 + [[package]] 2494 + name = "miette" 2495 + version = "7.6.0" 2496 + source = "registry+https://github.com/rust-lang/crates.io-index" 2497 + checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7" 2498 + dependencies = [ 2499 + "backtrace", 2500 + "backtrace-ext", 2501 + "cfg-if", 2502 + "miette-derive", 2503 + "owo-colors", 2504 + "supports-color", 2505 + "supports-hyperlinks", 2506 + "supports-unicode", 2507 + "terminal_size", 2508 + "textwrap", 2509 + "unicode-width 0.1.14", 2510 + ] 2511 + 2512 + [[package]] 2513 + name = "miette-derive" 2514 + version = "7.6.0" 2515 + source = "registry+https://github.com/rust-lang/crates.io-index" 2516 + checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" 2517 + dependencies = [ 2518 + "proc-macro2", 2519 + "quote", 2520 + "syn", 2521 + ] 2522 + 2523 + [[package]] 2524 + name = "mimalloc" 2525 + version = "0.1.48" 2526 + source = "registry+https://github.com/rust-lang/crates.io-index" 2527 + checksum = "e1ee66a4b64c74f4ef288bcbb9192ad9c3feaad75193129ac8509af543894fd8" 2528 + dependencies = [ 2529 + "libmimalloc-sys", 2530 + ] 2531 + 2532 + [[package]] 2533 + name = "mime" 2534 + version = "0.3.17" 2535 + source = "registry+https://github.com/rust-lang/crates.io-index" 2536 + checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 2537 + 2538 + [[package]] 2539 + name = "mime_guess" 2540 + version = "2.0.5" 2541 + source = "registry+https://github.com/rust-lang/crates.io-index" 2542 + checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" 2543 + dependencies = [ 2544 + "mime", 2545 + "unicase", 2546 + ] 2547 + 2548 + [[package]] 2549 + name = "mini-moka-wasm" 2550 + version = "0.10.99" 2551 + source = "git+https://tangled.org/nonbinary.computer/jacquard#bfb72e29f20b0683e939db0140fa44cabde162d2" 2552 + dependencies = [ 2553 + "crossbeam-channel", 2554 + "crossbeam-utils", 2555 + "dashmap", 2556 + "smallvec", 2557 + "tagptr", 2558 + "triomphe", 2559 + "web-time", 2560 + ] 2561 + 2562 + [[package]] 2563 + name = "miniz_oxide" 2564 + version = "0.8.9" 2565 + source = "registry+https://github.com/rust-lang/crates.io-index" 2566 + checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" 2567 + dependencies = [ 2568 + "adler2", 2569 + "simd-adler32", 2570 + ] 2571 + 2572 + [[package]] 2573 + name = "mio" 2574 + version = "1.1.1" 2575 + source = "registry+https://github.com/rust-lang/crates.io-index" 2576 + checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" 2577 + dependencies = [ 2578 + "libc", 2579 + "wasi", 2580 + "windows-sys 0.61.2", 2581 + ] 2582 + 2583 + [[package]] 2584 + name = "multibase" 2585 + version = "0.9.2" 2586 + source = "registry+https://github.com/rust-lang/crates.io-index" 2587 + checksum = "8694bb4835f452b0e3bb06dbebb1d6fc5385b6ca1caf2e55fd165c042390ec77" 2588 + dependencies = [ 2589 + "base-x", 2590 + "base256emoji", 2591 + "data-encoding", 2592 + "data-encoding-macro", 2593 + ] 2594 + 2595 + [[package]] 2596 + name = "multihash" 2597 + version = "0.19.3" 2598 + source = "registry+https://github.com/rust-lang/crates.io-index" 2599 + checksum = "6b430e7953c29dd6a09afc29ff0bb69c6e306329ee6794700aee27b76a1aea8d" 2600 + dependencies = [ 2601 + "core2", 2602 + "serde", 2603 + "unsigned-varint 0.8.0", 2604 + ] 2605 + 2606 + [[package]] 2607 + name = "multipart" 2608 + version = "0.18.0" 2609 + source = "registry+https://github.com/rust-lang/crates.io-index" 2610 + checksum = "00dec633863867f29cb39df64a397cdf4a6354708ddd7759f70c7fb51c5f9182" 2611 + dependencies = [ 2612 + "buf_redux", 2613 + "httparse", 2614 + "log", 2615 + "mime", 2616 + "mime_guess", 2617 + "quick-error", 2618 + "rand 0.8.5", 2619 + "safemem", 2620 + "tempfile", 2621 + "twoway", 2622 + ] 2623 + 2624 + [[package]] 2625 + name = "mycelium-bitfield" 2626 + version = "0.1.5" 2627 + source = "registry+https://github.com/rust-lang/crates.io-index" 2628 + checksum = "24e0cc5e2c585acbd15c5ce911dff71e1f4d5313f43345873311c4f5efd741cc" 2629 + 2630 + [[package]] 2631 + name = "n0-future" 2632 + version = "0.1.3" 2633 + source = "registry+https://github.com/rust-lang/crates.io-index" 2634 + checksum = "7bb0e5d99e681ab3c938842b96fcb41bf8a7bb4bfdb11ccbd653a7e83e06c794" 2635 + dependencies = [ 2636 + "cfg_aliases", 2637 + "derive_more 1.0.0", 2638 + "futures-buffered", 2639 + "futures-lite", 2640 + "futures-util", 2641 + "js-sys", 2642 + "pin-project", 2643 + "send_wrapper", 2644 + "tokio", 2645 + "tokio-util", 2646 + "wasm-bindgen", 2647 + "wasm-bindgen-futures", 2648 + "web-time", 2649 + ] 2650 + 2651 + [[package]] 2652 + name = "n0-future" 2653 + version = "0.3.2" 2654 + source = "registry+https://github.com/rust-lang/crates.io-index" 2655 + checksum = "e2ab99dfb861450e68853d34ae665243a88b8c493d01ba957321a1e9b2312bbe" 2656 + dependencies = [ 2657 + "cfg_aliases", 2658 + "derive_more 2.1.1", 2659 + "futures-buffered", 2660 + "futures-lite", 2661 + "futures-util", 2662 + "js-sys", 2663 + "pin-project", 2664 + "send_wrapper", 2665 + "tokio", 2666 + "tokio-util", 2667 + "wasm-bindgen", 2668 + "wasm-bindgen-futures", 2669 + "web-time", 2670 + ] 2671 + 2672 + [[package]] 2673 + name = "ndk-context" 2674 + version = "0.1.1" 2675 + source = "registry+https://github.com/rust-lang/crates.io-index" 2676 + checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" 2677 + 2678 + [[package]] 2679 + name = "new_debug_unreachable" 2680 + version = "1.0.6" 2681 + source = "registry+https://github.com/rust-lang/crates.io-index" 2682 + checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" 2683 + 2684 + [[package]] 2685 + name = "nu-ansi-term" 2686 + version = "0.50.3" 2687 + source = "registry+https://github.com/rust-lang/crates.io-index" 2688 + checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" 2689 + dependencies = [ 2690 + "windows-sys 0.61.2", 2691 + ] 2692 + 2693 + [[package]] 2694 + name = "num-bigint-dig" 2695 + version = "0.8.6" 2696 + source = "registry+https://github.com/rust-lang/crates.io-index" 2697 + checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" 2698 + dependencies = [ 2699 + "lazy_static", 2700 + "libm", 2701 + "num-integer", 2702 + "num-iter", 2703 + "num-traits", 2704 + "rand 0.8.5", 2705 + "smallvec", 2706 + "zeroize", 2707 + ] 2708 + 2709 + [[package]] 2710 + name = "num-conv" 2711 + version = "0.2.0" 2712 + source = "registry+https://github.com/rust-lang/crates.io-index" 2713 + checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" 2714 + 2715 + [[package]] 2716 + name = "num-integer" 2717 + version = "0.1.46" 2718 + source = "registry+https://github.com/rust-lang/crates.io-index" 2719 + checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" 2720 + dependencies = [ 2721 + "num-traits", 2722 + ] 2723 + 2724 + [[package]] 2725 + name = "num-iter" 2726 + version = "0.1.45" 2727 + source = "registry+https://github.com/rust-lang/crates.io-index" 2728 + checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" 2729 + dependencies = [ 2730 + "autocfg", 2731 + "num-integer", 2732 + "num-traits", 2733 + ] 2734 + 2735 + [[package]] 2736 + name = "num-traits" 2737 + version = "0.2.19" 2738 + source = "registry+https://github.com/rust-lang/crates.io-index" 2739 + checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 2740 + dependencies = [ 2741 + "autocfg", 2742 + "libm", 2743 + ] 2744 + 2745 + [[package]] 2746 + name = "num_cpus" 2747 + version = "1.17.0" 2748 + source = "registry+https://github.com/rust-lang/crates.io-index" 2749 + checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" 2750 + dependencies = [ 2751 + "hermit-abi", 2752 + "libc", 2753 + ] 2754 + 2755 + [[package]] 2756 + name = "num_threads" 2757 + version = "0.1.7" 2758 + source = "registry+https://github.com/rust-lang/crates.io-index" 2759 + checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" 2760 + dependencies = [ 2761 + "libc", 2762 + ] 2763 + 2764 + [[package]] 2765 + name = "objc2" 2766 + version = "0.6.3" 2767 + source = "registry+https://github.com/rust-lang/crates.io-index" 2768 + checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" 2769 + dependencies = [ 2770 + "objc2-encode", 2771 + ] 2772 + 2773 + [[package]] 2774 + name = "objc2-encode" 2775 + version = "4.1.0" 2776 + source = "registry+https://github.com/rust-lang/crates.io-index" 2777 + checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" 2778 + 2779 + [[package]] 2780 + name = "objc2-foundation" 2781 + version = "0.3.2" 2782 + source = "registry+https://github.com/rust-lang/crates.io-index" 2783 + checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" 2784 + dependencies = [ 2785 + "bitflags", 2786 + "objc2", 2787 + ] 2788 + 2789 + [[package]] 2790 + name = "object" 2791 + version = "0.37.3" 2792 + source = "registry+https://github.com/rust-lang/crates.io-index" 2793 + checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" 2794 + dependencies = [ 2795 + "memchr", 2796 + ] 2797 + 2798 + [[package]] 2799 + name = "once_cell" 2800 + version = "1.21.3" 2801 + source = "registry+https://github.com/rust-lang/crates.io-index" 2802 + checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 2803 + 2804 + [[package]] 2805 + name = "openssl-probe" 2806 + version = "0.2.1" 2807 + source = "registry+https://github.com/rust-lang/crates.io-index" 2808 + checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" 2809 + 2810 + [[package]] 2811 + name = "ouroboros" 2812 + version = "0.18.5" 2813 + source = "registry+https://github.com/rust-lang/crates.io-index" 2814 + checksum = "1e0f050db9c44b97a94723127e6be766ac5c340c48f2c4bb3ffa11713744be59" 2815 + dependencies = [ 2816 + "aliasable", 2817 + "ouroboros_macro", 2818 + "static_assertions", 2819 + ] 2820 + 2821 + [[package]] 2822 + name = "ouroboros_macro" 2823 + version = "0.18.5" 2824 + source = "registry+https://github.com/rust-lang/crates.io-index" 2825 + checksum = "3c7028bdd3d43083f6d8d4d5187680d0d3560d54df4cc9d752005268b41e64d0" 2826 + dependencies = [ 2827 + "heck 0.4.1", 2828 + "proc-macro2", 2829 + "proc-macro2-diagnostics", 2830 + "quote", 2831 + "syn", 2832 + ] 2833 + 2834 + [[package]] 2835 + name = "owo-colors" 2836 + version = "4.2.3" 2837 + source = "registry+https://github.com/rust-lang/crates.io-index" 2838 + checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52" 2839 + 2840 + [[package]] 2841 + name = "oxilangtag" 2842 + version = "0.1.5" 2843 + source = "registry+https://github.com/rust-lang/crates.io-index" 2844 + checksum = "23f3f87617a86af77fa3691e6350483e7154c2ead9f1261b75130e21ca0f8acb" 2845 + dependencies = [ 2846 + "serde", 2847 + ] 2848 + 2849 + [[package]] 2850 + name = "p256" 2851 + version = "0.13.2" 2852 + source = "registry+https://github.com/rust-lang/crates.io-index" 2853 + checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" 2854 + dependencies = [ 2855 + "ecdsa", 2856 + "elliptic-curve", 2857 + "primeorder", 2858 + "sha2", 2859 + ] 2860 + 2861 + [[package]] 2862 + name = "p384" 2863 + version = "0.13.1" 2864 + source = "registry+https://github.com/rust-lang/crates.io-index" 2865 + checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" 2866 + dependencies = [ 2867 + "elliptic-curve", 2868 + "primeorder", 2869 + ] 2870 + 2871 + [[package]] 2872 + name = "parking" 2873 + version = "2.2.1" 2874 + source = "registry+https://github.com/rust-lang/crates.io-index" 2875 + checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" 2876 + 2877 + [[package]] 2878 + name = "parking_lot" 2879 + version = "0.12.5" 2880 + source = "registry+https://github.com/rust-lang/crates.io-index" 2881 + checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" 2882 + dependencies = [ 2883 + "lock_api", 2884 + "parking_lot_core", 2885 + ] 2886 + 2887 + [[package]] 2888 + name = "parking_lot_core" 2889 + version = "0.9.12" 2890 + source = "registry+https://github.com/rust-lang/crates.io-index" 2891 + checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" 2892 + dependencies = [ 2893 + "cfg-if", 2894 + "libc", 2895 + "redox_syscall 0.5.18", 2896 + "smallvec", 2897 + "windows-link", 2898 + ] 2899 + 2900 + [[package]] 2901 + name = "pem-rfc7468" 2902 + version = "0.7.0" 2903 + source = "registry+https://github.com/rust-lang/crates.io-index" 2904 + checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" 2905 + dependencies = [ 2906 + "base64ct", 2907 + ] 2908 + 2909 + [[package]] 2910 + name = "percent-encoding" 2911 + version = "2.3.2" 2912 + source = "registry+https://github.com/rust-lang/crates.io-index" 2913 + checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" 2914 + 2915 + [[package]] 2916 + name = "phf" 2917 + version = "0.11.3" 2918 + source = "registry+https://github.com/rust-lang/crates.io-index" 2919 + checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" 2920 + dependencies = [ 2921 + "phf_shared", 2922 + ] 2923 + 2924 + [[package]] 2925 + name = "phf_codegen" 2926 + version = "0.11.3" 2927 + source = "registry+https://github.com/rust-lang/crates.io-index" 2928 + checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" 2929 + dependencies = [ 2930 + "phf_generator", 2931 + "phf_shared", 2932 + ] 2933 + 2934 + [[package]] 2935 + name = "phf_generator" 2936 + version = "0.11.3" 2937 + source = "registry+https://github.com/rust-lang/crates.io-index" 2938 + checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" 2939 + dependencies = [ 2940 + "phf_shared", 2941 + "rand 0.8.5", 2942 + ] 2943 + 2944 + [[package]] 2945 + name = "phf_shared" 2946 + version = "0.11.3" 2947 + source = "registry+https://github.com/rust-lang/crates.io-index" 2948 + checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" 2949 + dependencies = [ 2950 + "siphasher", 2951 + ] 2952 + 2953 + [[package]] 2954 + name = "pin-project" 2955 + version = "1.1.10" 2956 + source = "registry+https://github.com/rust-lang/crates.io-index" 2957 + checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" 2958 + dependencies = [ 2959 + "pin-project-internal", 2960 + ] 2961 + 2962 + [[package]] 2963 + name = "pin-project-internal" 2964 + version = "1.1.10" 2965 + source = "registry+https://github.com/rust-lang/crates.io-index" 2966 + checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" 2967 + dependencies = [ 2968 + "proc-macro2", 2969 + "quote", 2970 + "syn", 2971 + ] 2972 + 2973 + [[package]] 2974 + name = "pin-project-lite" 2975 + version = "0.2.16" 2976 + source = "registry+https://github.com/rust-lang/crates.io-index" 2977 + checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 2978 + 2979 + [[package]] 2980 + name = "pin-utils" 2981 + version = "0.1.0" 2982 + source = "registry+https://github.com/rust-lang/crates.io-index" 2983 + checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 2984 + 2985 + [[package]] 2986 + name = "pkcs1" 2987 + version = "0.7.5" 2988 + source = "registry+https://github.com/rust-lang/crates.io-index" 2989 + checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" 2990 + dependencies = [ 2991 + "der", 2992 + "pkcs8", 2993 + "spki", 2994 + ] 2995 + 2996 + [[package]] 2997 + name = "pkcs8" 2998 + version = "0.10.2" 2999 + source = "registry+https://github.com/rust-lang/crates.io-index" 3000 + checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" 3001 + dependencies = [ 3002 + "der", 3003 + "spki", 3004 + ] 3005 + 3006 + [[package]] 3007 + name = "pkg-config" 3008 + version = "0.3.32" 3009 + source = "registry+https://github.com/rust-lang/crates.io-index" 3010 + checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" 3011 + 3012 + [[package]] 3013 + name = "portable-atomic" 3014 + version = "1.13.0" 3015 + source = "registry+https://github.com/rust-lang/crates.io-index" 3016 + checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" 3017 + 3018 + [[package]] 3019 + name = "postcard" 3020 + version = "1.1.3" 3021 + source = "registry+https://github.com/rust-lang/crates.io-index" 3022 + checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" 3023 + dependencies = [ 3024 + "cobs", 3025 + "embedded-io 0.4.0", 3026 + "embedded-io 0.6.1", 3027 + "heapless", 3028 + "serde", 3029 + ] 3030 + 3031 + [[package]] 3032 + name = "potential_utf" 3033 + version = "0.1.4" 3034 + source = "registry+https://github.com/rust-lang/crates.io-index" 3035 + checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" 3036 + dependencies = [ 3037 + "zerovec", 3038 + ] 3039 + 3040 + [[package]] 3041 + name = "powerfmt" 3042 + version = "0.2.0" 3043 + source = "registry+https://github.com/rust-lang/crates.io-index" 3044 + checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 3045 + 3046 + [[package]] 3047 + name = "ppv-lite86" 3048 + version = "0.2.21" 3049 + source = "registry+https://github.com/rust-lang/crates.io-index" 3050 + checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" 3051 + dependencies = [ 3052 + "zerocopy", 3053 + ] 3054 + 3055 + [[package]] 3056 + name = "precomputed-hash" 3057 + version = "0.1.1" 3058 + source = "registry+https://github.com/rust-lang/crates.io-index" 3059 + checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" 3060 + 3061 + [[package]] 3062 + name = "prettyplease" 3063 + version = "0.2.37" 3064 + source = "registry+https://github.com/rust-lang/crates.io-index" 3065 + checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" 3066 + dependencies = [ 3067 + "proc-macro2", 3068 + "syn", 3069 + ] 3070 + 3071 + [[package]] 3072 + name = "primeorder" 3073 + version = "0.13.6" 3074 + source = "registry+https://github.com/rust-lang/crates.io-index" 3075 + checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" 3076 + dependencies = [ 3077 + "elliptic-curve", 3078 + ] 3079 + 3080 + [[package]] 3081 + name = "proc-macro2" 3082 + version = "1.0.106" 3083 + source = "registry+https://github.com/rust-lang/crates.io-index" 3084 + checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" 3085 + dependencies = [ 3086 + "unicode-ident", 3087 + ] 3088 + 3089 + [[package]] 3090 + name = "proc-macro2-diagnostics" 3091 + version = "0.10.1" 3092 + source = "registry+https://github.com/rust-lang/crates.io-index" 3093 + checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" 3094 + dependencies = [ 3095 + "proc-macro2", 3096 + "quote", 3097 + "syn", 3098 + "version_check", 3099 + "yansi", 3100 + ] 3101 + 3102 + [[package]] 3103 + name = "quick-error" 3104 + version = "1.2.3" 3105 + source = "registry+https://github.com/rust-lang/crates.io-index" 3106 + checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" 3107 + 3108 + [[package]] 3109 + name = "quick_cache" 3110 + version = "0.6.18" 3111 + source = "registry+https://github.com/rust-lang/crates.io-index" 3112 + checksum = "7ada44a88ef953a3294f6eb55d2007ba44646015e18613d2f213016379203ef3" 3113 + dependencies = [ 3114 + "equivalent", 3115 + "hashbrown 0.16.1", 3116 + ] 3117 + 3118 + [[package]] 3119 + name = "quinn" 3120 + version = "0.11.9" 3121 + source = "registry+https://github.com/rust-lang/crates.io-index" 3122 + checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" 3123 + dependencies = [ 3124 + "bytes", 3125 + "cfg_aliases", 3126 + "pin-project-lite", 3127 + "quinn-proto", 3128 + "quinn-udp", 3129 + "rustc-hash", 3130 + "rustls", 3131 + "socket2 0.6.2", 3132 + "thiserror 2.0.18", 3133 + "tokio", 3134 + "tracing", 3135 + "web-time", 3136 + ] 3137 + 3138 + [[package]] 3139 + name = "quinn-proto" 3140 + version = "0.11.13" 3141 + source = "registry+https://github.com/rust-lang/crates.io-index" 3142 + checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" 3143 + dependencies = [ 3144 + "bytes", 3145 + "getrandom 0.3.4", 3146 + "lru-slab", 3147 + "rand 0.9.2", 3148 + "ring", 3149 + "rustc-hash", 3150 + "rustls", 3151 + "rustls-pki-types", 3152 + "slab", 3153 + "thiserror 2.0.18", 3154 + "tinyvec", 3155 + "tracing", 3156 + "web-time", 3157 + ] 3158 + 3159 + [[package]] 3160 + name = "quinn-udp" 3161 + version = "0.5.14" 3162 + source = "registry+https://github.com/rust-lang/crates.io-index" 3163 + checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" 3164 + dependencies = [ 3165 + "cfg_aliases", 3166 + "libc", 3167 + "once_cell", 3168 + "socket2 0.6.2", 3169 + "tracing", 3170 + "windows-sys 0.60.2", 3171 + ] 3172 + 3173 + [[package]] 3174 + name = "quote" 3175 + version = "1.0.44" 3176 + source = "registry+https://github.com/rust-lang/crates.io-index" 3177 + checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" 3178 + dependencies = [ 3179 + "proc-macro2", 3180 + ] 3181 + 3182 + [[package]] 3183 + name = "r-efi" 3184 + version = "5.3.0" 3185 + source = "registry+https://github.com/rust-lang/crates.io-index" 3186 + checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" 3187 + 3188 + [[package]] 3189 + name = "rand" 3190 + version = "0.8.5" 3191 + source = "registry+https://github.com/rust-lang/crates.io-index" 3192 + checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 3193 + dependencies = [ 3194 + "libc", 3195 + "rand_chacha 0.3.1", 3196 + "rand_core 0.6.4", 3197 + ] 3198 + 3199 + [[package]] 3200 + name = "rand" 3201 + version = "0.9.2" 3202 + source = "registry+https://github.com/rust-lang/crates.io-index" 3203 + checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" 3204 + dependencies = [ 3205 + "rand_chacha 0.9.0", 3206 + "rand_core 0.9.5", 3207 + ] 3208 + 3209 + [[package]] 3210 + name = "rand_chacha" 3211 + version = "0.3.1" 3212 + source = "registry+https://github.com/rust-lang/crates.io-index" 3213 + checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 3214 + dependencies = [ 3215 + "ppv-lite86", 3216 + "rand_core 0.6.4", 3217 + ] 3218 + 3219 + [[package]] 3220 + name = "rand_chacha" 3221 + version = "0.9.0" 3222 + source = "registry+https://github.com/rust-lang/crates.io-index" 3223 + checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" 3224 + dependencies = [ 3225 + "ppv-lite86", 3226 + "rand_core 0.9.5", 3227 + ] 3228 + 3229 + [[package]] 3230 + name = "rand_core" 3231 + version = "0.6.4" 3232 + source = "registry+https://github.com/rust-lang/crates.io-index" 3233 + checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 3234 + dependencies = [ 3235 + "getrandom 0.2.17", 3236 + ] 3237 + 3238 + [[package]] 3239 + name = "rand_core" 3240 + version = "0.9.5" 3241 + source = "registry+https://github.com/rust-lang/crates.io-index" 3242 + checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" 3243 + dependencies = [ 3244 + "getrandom 0.3.4", 3245 + ] 3246 + 3247 + [[package]] 3248 + name = "redox_syscall" 3249 + version = "0.5.18" 3250 + source = "registry+https://github.com/rust-lang/crates.io-index" 3251 + checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" 3252 + dependencies = [ 3253 + "bitflags", 3254 + ] 3255 + 3256 + [[package]] 3257 + name = "redox_syscall" 3258 + version = "0.7.0" 3259 + source = "registry+https://github.com/rust-lang/crates.io-index" 3260 + checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27" 3261 + dependencies = [ 3262 + "bitflags", 3263 + ] 3264 + 3265 + [[package]] 3266 + name = "regex" 3267 + version = "1.12.2" 3268 + source = "registry+https://github.com/rust-lang/crates.io-index" 3269 + checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" 3270 + dependencies = [ 3271 + "aho-corasick", 3272 + "memchr", 3273 + "regex-automata", 3274 + "regex-syntax", 3275 + ] 3276 + 3277 + [[package]] 3278 + name = "regex-automata" 3279 + version = "0.4.13" 3280 + source = "registry+https://github.com/rust-lang/crates.io-index" 3281 + checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" 3282 + dependencies = [ 3283 + "aho-corasick", 3284 + "memchr", 3285 + "regex-syntax", 3286 + ] 3287 + 3288 + [[package]] 3289 + name = "regex-lite" 3290 + version = "0.1.8" 3291 + source = "registry+https://github.com/rust-lang/crates.io-index" 3292 + checksum = "8d942b98df5e658f56f20d592c7f868833fe38115e65c33003d8cd224b0155da" 3293 + 3294 + [[package]] 3295 + name = "regex-syntax" 3296 + version = "0.8.8" 3297 + source = "registry+https://github.com/rust-lang/crates.io-index" 3298 + checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" 3299 + 3300 + [[package]] 3301 + name = "reqwest" 3302 + version = "0.12.28" 3303 + source = "registry+https://github.com/rust-lang/crates.io-index" 3304 + checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" 3305 + dependencies = [ 3306 + "base64 0.22.1", 3307 + "bytes", 3308 + "encoding_rs", 3309 + "futures-core", 3310 + "futures-util", 3311 + "h2", 3312 + "http", 3313 + "http-body", 3314 + "http-body-util", 3315 + "hyper", 3316 + "hyper-rustls", 3317 + "hyper-util", 3318 + "js-sys", 3319 + "log", 3320 + "mime", 3321 + "percent-encoding", 3322 + "pin-project-lite", 3323 + "quinn", 3324 + "rustls", 3325 + "rustls-pki-types", 3326 + "serde", 3327 + "serde_json", 3328 + "serde_urlencoded", 3329 + "sync_wrapper", 3330 + "tokio", 3331 + "tokio-rustls", 3332 + "tokio-util", 3333 + "tower", 3334 + "tower-http", 3335 + "tower-service", 3336 + "url", 3337 + "wasm-bindgen", 3338 + "wasm-bindgen-futures", 3339 + "wasm-streams", 3340 + "web-sys", 3341 + "webpki-roots", 3342 + ] 3343 + 3344 + [[package]] 3345 + name = "resolv-conf" 3346 + version = "0.7.6" 3347 + source = "registry+https://github.com/rust-lang/crates.io-index" 3348 + checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" 3349 + 3350 + [[package]] 3351 + name = "rfc6979" 3352 + version = "0.4.0" 3353 + source = "registry+https://github.com/rust-lang/crates.io-index" 3354 + checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" 3355 + dependencies = [ 3356 + "hmac", 3357 + "subtle", 3358 + ] 3359 + 3360 + [[package]] 3361 + name = "ring" 3362 + version = "0.17.14" 3363 + source = "registry+https://github.com/rust-lang/crates.io-index" 3364 + checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" 3365 + dependencies = [ 3366 + "cc", 3367 + "cfg-if", 3368 + "getrandom 0.2.17", 3369 + "libc", 3370 + "untrusted", 3371 + "windows-sys 0.52.0", 3372 + ] 3373 + 3374 + [[package]] 3375 + name = "rmp" 3376 + version = "0.8.15" 3377 + source = "registry+https://github.com/rust-lang/crates.io-index" 3378 + checksum = "4ba8be72d372b2c9b35542551678538b562e7cf86c3315773cae48dfbfe7790c" 3379 + dependencies = [ 3380 + "num-traits", 3381 + ] 3382 + 3383 + [[package]] 3384 + name = "rmp-serde" 3385 + version = "1.3.1" 3386 + source = "registry+https://github.com/rust-lang/crates.io-index" 3387 + checksum = "72f81bee8c8ef9b577d1681a70ebbc962c232461e397b22c208c43c04b67a155" 3388 + dependencies = [ 3389 + "rmp", 3390 + "serde", 3391 + ] 3392 + 3393 + [[package]] 3394 + name = "rouille" 3395 + version = "3.6.2" 3396 + source = "registry+https://github.com/rust-lang/crates.io-index" 3397 + checksum = "3716fbf57fc1084d7a706adf4e445298d123e4a44294c4e8213caf1b85fcc921" 3398 + dependencies = [ 3399 + "base64 0.13.1", 3400 + "brotli 3.5.0", 3401 + "chrono", 3402 + "deflate", 3403 + "filetime", 3404 + "multipart", 3405 + "percent-encoding", 3406 + "rand 0.8.5", 3407 + "serde", 3408 + "serde_derive", 3409 + "serde_json", 3410 + "sha1_smol", 3411 + "threadpool", 3412 + "time", 3413 + "tiny_http", 3414 + "url", 3415 + ] 3416 + 3417 + [[package]] 3418 + name = "rsa" 3419 + version = "0.9.10" 3420 + source = "registry+https://github.com/rust-lang/crates.io-index" 3421 + checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" 3422 + dependencies = [ 3423 + "const-oid", 3424 + "digest", 3425 + "num-bigint-dig", 3426 + "num-integer", 3427 + "num-traits", 3428 + "pkcs1", 3429 + "pkcs8", 3430 + "rand_core 0.6.4", 3431 + "signature", 3432 + "spki", 3433 + "subtle", 3434 + "zeroize", 3435 + ] 3436 + 3437 + [[package]] 3438 + name = "rustc-demangle" 3439 + version = "0.1.27" 3440 + source = "registry+https://github.com/rust-lang/crates.io-index" 3441 + checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" 3442 + 3443 + [[package]] 3444 + name = "rustc-hash" 3445 + version = "2.1.1" 3446 + source = "registry+https://github.com/rust-lang/crates.io-index" 3447 + checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" 3448 + 3449 + [[package]] 3450 + name = "rustc_version" 3451 + version = "0.4.1" 3452 + source = "registry+https://github.com/rust-lang/crates.io-index" 3453 + checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" 3454 + dependencies = [ 3455 + "semver", 3456 + ] 3457 + 3458 + [[package]] 3459 + name = "rustix" 3460 + version = "1.1.3" 3461 + source = "registry+https://github.com/rust-lang/crates.io-index" 3462 + checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" 3463 + dependencies = [ 3464 + "bitflags", 3465 + "errno", 3466 + "libc", 3467 + "linux-raw-sys", 3468 + "windows-sys 0.61.2", 3469 + ] 3470 + 3471 + [[package]] 3472 + name = "rustls" 3473 + version = "0.23.36" 3474 + source = "registry+https://github.com/rust-lang/crates.io-index" 3475 + checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" 3476 + dependencies = [ 3477 + "once_cell", 3478 + "ring", 3479 + "rustls-pki-types", 3480 + "rustls-webpki", 3481 + "subtle", 3482 + "zeroize", 3483 + ] 3484 + 3485 + [[package]] 3486 + name = "rustls-native-certs" 3487 + version = "0.8.3" 3488 + source = "registry+https://github.com/rust-lang/crates.io-index" 3489 + checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" 3490 + dependencies = [ 3491 + "openssl-probe", 3492 + "rustls-pki-types", 3493 + "schannel", 3494 + "security-framework", 3495 + ] 3496 + 3497 + [[package]] 3498 + name = "rustls-pki-types" 3499 + version = "1.14.0" 3500 + source = "registry+https://github.com/rust-lang/crates.io-index" 3501 + checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" 3502 + dependencies = [ 3503 + "web-time", 3504 + "zeroize", 3505 + ] 3506 + 3507 + [[package]] 3508 + name = "rustls-webpki" 3509 + version = "0.103.9" 3510 + source = "registry+https://github.com/rust-lang/crates.io-index" 3511 + checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" 3512 + dependencies = [ 3513 + "ring", 3514 + "rustls-pki-types", 3515 + "untrusted", 3516 + ] 3517 + 3518 + [[package]] 3519 + name = "rustversion" 3520 + version = "1.0.22" 3521 + source = "registry+https://github.com/rust-lang/crates.io-index" 3522 + checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" 3523 + 3524 + [[package]] 3525 + name = "ryu" 3526 + version = "1.0.22" 3527 + source = "registry+https://github.com/rust-lang/crates.io-index" 3528 + checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" 3529 + 3530 + [[package]] 3531 + name = "safemem" 3532 + version = "0.3.3" 3533 + source = "registry+https://github.com/rust-lang/crates.io-index" 3534 + checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" 3535 + 3536 + [[package]] 3537 + name = "same-file" 3538 + version = "1.0.6" 3539 + source = "registry+https://github.com/rust-lang/crates.io-index" 3540 + checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 3541 + dependencies = [ 3542 + "winapi-util", 3543 + ] 3544 + 3545 + [[package]] 3546 + name = "schannel" 3547 + version = "0.1.28" 3548 + source = "registry+https://github.com/rust-lang/crates.io-index" 3549 + checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" 3550 + dependencies = [ 3551 + "windows-sys 0.61.2", 3552 + ] 3553 + 3554 + [[package]] 3555 + name = "scoped-tls" 3556 + version = "1.0.1" 3557 + source = "registry+https://github.com/rust-lang/crates.io-index" 3558 + checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" 3559 + 3560 + [[package]] 3561 + name = "scopeguard" 3562 + version = "1.2.0" 3563 + source = "registry+https://github.com/rust-lang/crates.io-index" 3564 + checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 3565 + 3566 + [[package]] 3567 + name = "sec1" 3568 + version = "0.7.3" 3569 + source = "registry+https://github.com/rust-lang/crates.io-index" 3570 + checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" 3571 + dependencies = [ 3572 + "base16ct", 3573 + "der", 3574 + "generic-array", 3575 + "pkcs8", 3576 + "subtle", 3577 + "zeroize", 3578 + ] 3579 + 3580 + [[package]] 3581 + name = "security-framework" 3582 + version = "3.5.1" 3583 + source = "registry+https://github.com/rust-lang/crates.io-index" 3584 + checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" 3585 + dependencies = [ 3586 + "bitflags", 3587 + "core-foundation 0.10.1", 3588 + "core-foundation-sys", 3589 + "libc", 3590 + "security-framework-sys", 3591 + ] 3592 + 3593 + [[package]] 3594 + name = "security-framework-sys" 3595 + version = "2.15.0" 3596 + source = "registry+https://github.com/rust-lang/crates.io-index" 3597 + checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" 3598 + dependencies = [ 3599 + "core-foundation-sys", 3600 + "libc", 3601 + ] 3602 + 3603 + [[package]] 3604 + name = "self_cell" 3605 + version = "1.2.2" 3606 + source = "registry+https://github.com/rust-lang/crates.io-index" 3607 + checksum = "b12e76d157a900eb52e81bc6e9f3069344290341720e9178cde2407113ac8d89" 3608 + 3609 + [[package]] 3610 + name = "semver" 3611 + version = "1.0.27" 3612 + source = "registry+https://github.com/rust-lang/crates.io-index" 3613 + checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" 3614 + 3615 + [[package]] 3616 + name = "send_wrapper" 3617 + version = "0.6.0" 3618 + source = "registry+https://github.com/rust-lang/crates.io-index" 3619 + checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" 3620 + 3621 + [[package]] 3622 + name = "serde" 3623 + version = "1.0.228" 3624 + source = "registry+https://github.com/rust-lang/crates.io-index" 3625 + checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" 3626 + dependencies = [ 3627 + "serde_core", 3628 + "serde_derive", 3629 + ] 3630 + 3631 + [[package]] 3632 + name = "serde_bytes" 3633 + version = "0.11.19" 3634 + source = "registry+https://github.com/rust-lang/crates.io-index" 3635 + checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" 3636 + dependencies = [ 3637 + "serde", 3638 + "serde_core", 3639 + ] 3640 + 3641 + [[package]] 3642 + name = "serde_core" 3643 + version = "1.0.228" 3644 + source = "registry+https://github.com/rust-lang/crates.io-index" 3645 + checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" 3646 + dependencies = [ 3647 + "serde_derive", 3648 + ] 3649 + 3650 + [[package]] 3651 + name = "serde_derive" 3652 + version = "1.0.228" 3653 + source = "registry+https://github.com/rust-lang/crates.io-index" 3654 + checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" 3655 + dependencies = [ 3656 + "proc-macro2", 3657 + "quote", 3658 + "syn", 3659 + ] 3660 + 3661 + [[package]] 3662 + name = "serde_html_form" 3663 + version = "0.3.2" 3664 + source = "registry+https://github.com/rust-lang/crates.io-index" 3665 + checksum = "2acf96b1d9364968fce46ebb548f1c0e1d7eceae27bdff73865d42e6c7369d94" 3666 + dependencies = [ 3667 + "form_urlencoded", 3668 + "indexmap", 3669 + "itoa", 3670 + "serde_core", 3671 + ] 3672 + 3673 + [[package]] 3674 + name = "serde_ipld_dagcbor" 3675 + version = "0.6.4" 3676 + source = "registry+https://github.com/rust-lang/crates.io-index" 3677 + checksum = "46182f4f08349a02b45c998ba3215d3f9de826246ba02bb9dddfe9a2a2100778" 3678 + dependencies = [ 3679 + "cbor4ii", 3680 + "ipld-core", 3681 + "scopeguard", 3682 + "serde", 3683 + ] 3684 + 3685 + [[package]] 3686 + name = "serde_json" 3687 + version = "1.0.149" 3688 + source = "registry+https://github.com/rust-lang/crates.io-index" 3689 + checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" 3690 + dependencies = [ 3691 + "itoa", 3692 + "memchr", 3693 + "serde", 3694 + "serde_core", 3695 + "zmij", 3696 + ] 3697 + 3698 + [[package]] 3699 + name = "serde_path_to_error" 3700 + version = "0.1.20" 3701 + source = "registry+https://github.com/rust-lang/crates.io-index" 3702 + checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" 3703 + dependencies = [ 3704 + "itoa", 3705 + "serde", 3706 + "serde_core", 3707 + ] 3708 + 3709 + [[package]] 3710 + name = "serde_repr" 3711 + version = "0.1.20" 3712 + source = "registry+https://github.com/rust-lang/crates.io-index" 3713 + checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" 3714 + dependencies = [ 3715 + "proc-macro2", 3716 + "quote", 3717 + "syn", 3718 + ] 3719 + 3720 + [[package]] 3721 + name = "serde_urlencoded" 3722 + version = "0.7.1" 3723 + source = "registry+https://github.com/rust-lang/crates.io-index" 3724 + checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 3725 + dependencies = [ 3726 + "form_urlencoded", 3727 + "itoa", 3728 + "ryu", 3729 + "serde", 3730 + ] 3731 + 3732 + [[package]] 3733 + name = "serde_with" 3734 + version = "3.16.1" 3735 + source = "registry+https://github.com/rust-lang/crates.io-index" 3736 + checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" 3737 + dependencies = [ 3738 + "base64 0.22.1", 3739 + "chrono", 3740 + "hex", 3741 + "serde_core", 3742 + "serde_json", 3743 + "serde_with_macros", 3744 + "time", 3745 + ] 3746 + 3747 + [[package]] 3748 + name = "serde_with_macros" 3749 + version = "3.16.1" 3750 + source = "registry+https://github.com/rust-lang/crates.io-index" 3751 + checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" 3752 + dependencies = [ 3753 + "darling 0.21.3", 3754 + "proc-macro2", 3755 + "quote", 3756 + "syn", 3757 + ] 3758 + 3759 + [[package]] 3760 + name = "sfa" 3761 + version = "1.0.0" 3762 + source = "registry+https://github.com/rust-lang/crates.io-index" 3763 + checksum = "a1296838937cab56cd6c4eeeb8718ec777383700c33f060e2869867bd01d1175" 3764 + dependencies = [ 3765 + "byteorder-lite", 3766 + "log", 3767 + "xxhash-rust", 3768 + ] 3769 + 3770 + [[package]] 3771 + name = "sha1" 3772 + version = "0.10.6" 3773 + source = "registry+https://github.com/rust-lang/crates.io-index" 3774 + checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" 3775 + dependencies = [ 3776 + "cfg-if", 3777 + "cpufeatures", 3778 + "digest", 3779 + ] 3780 + 3781 + [[package]] 3782 + name = "sha1_smol" 3783 + version = "1.0.1" 3784 + source = "registry+https://github.com/rust-lang/crates.io-index" 3785 + checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" 3786 + 3787 + [[package]] 3788 + name = "sha2" 3789 + version = "0.10.9" 3790 + source = "registry+https://github.com/rust-lang/crates.io-index" 3791 + checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" 3792 + dependencies = [ 3793 + "cfg-if", 3794 + "cpufeatures", 3795 + "digest", 3796 + ] 3797 + 3798 + [[package]] 3799 + name = "sharded-slab" 3800 + version = "0.1.7" 3801 + source = "registry+https://github.com/rust-lang/crates.io-index" 3802 + checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 3803 + dependencies = [ 3804 + "lazy_static", 3805 + ] 3806 + 3807 + [[package]] 3808 + name = "shlex" 3809 + version = "1.3.0" 3810 + source = "registry+https://github.com/rust-lang/crates.io-index" 3811 + checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 3812 + 3813 + [[package]] 3814 + name = "signal-hook-registry" 3815 + version = "1.4.8" 3816 + source = "registry+https://github.com/rust-lang/crates.io-index" 3817 + checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" 3818 + dependencies = [ 3819 + "errno", 3820 + "libc", 3821 + ] 3822 + 3823 + [[package]] 3824 + name = "signature" 3825 + version = "2.2.0" 3826 + source = "registry+https://github.com/rust-lang/crates.io-index" 3827 + checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" 3828 + dependencies = [ 3829 + "digest", 3830 + "rand_core 0.6.4", 3831 + ] 3832 + 3833 + [[package]] 3834 + name = "simd-adler32" 3835 + version = "0.3.8" 3836 + source = "registry+https://github.com/rust-lang/crates.io-index" 3837 + checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" 3838 + 3839 + [[package]] 3840 + name = "siphasher" 3841 + version = "1.0.2" 3842 + source = "registry+https://github.com/rust-lang/crates.io-index" 3843 + checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" 3844 + 3845 + [[package]] 3846 + name = "slab" 3847 + version = "0.4.11" 3848 + source = "registry+https://github.com/rust-lang/crates.io-index" 3849 + checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" 3850 + 3851 + [[package]] 3852 + name = "smallvec" 3853 + version = "1.15.1" 3854 + source = "registry+https://github.com/rust-lang/crates.io-index" 3855 + checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" 3856 + 3857 + [[package]] 3858 + name = "smol_str" 3859 + version = "0.3.5" 3860 + source = "registry+https://github.com/rust-lang/crates.io-index" 3861 + checksum = "0f7a918bd2a9951d18ee6e48f076843e8e73a9a5d22cf05bcd4b7a81bdd04e17" 3862 + dependencies = [ 3863 + "borsh", 3864 + "serde_core", 3865 + ] 3866 + 3867 + [[package]] 3868 + name = "socket2" 3869 + version = "0.5.10" 3870 + source = "registry+https://github.com/rust-lang/crates.io-index" 3871 + checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" 3872 + dependencies = [ 3873 + "libc", 3874 + "windows-sys 0.52.0", 3875 + ] 3876 + 3877 + [[package]] 3878 + name = "socket2" 3879 + version = "0.6.2" 3880 + source = "registry+https://github.com/rust-lang/crates.io-index" 3881 + checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" 3882 + dependencies = [ 3883 + "libc", 3884 + "windows-sys 0.60.2", 3885 + ] 3886 + 3887 + [[package]] 3888 + name = "spin" 3889 + version = "0.9.8" 3890 + source = "registry+https://github.com/rust-lang/crates.io-index" 3891 + checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 3892 + dependencies = [ 3893 + "lock_api", 3894 + ] 3895 + 3896 + [[package]] 3897 + name = "spin" 3898 + version = "0.10.0" 3899 + source = "registry+https://github.com/rust-lang/crates.io-index" 3900 + checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" 3901 + 3902 + [[package]] 3903 + name = "spki" 3904 + version = "0.7.3" 3905 + source = "registry+https://github.com/rust-lang/crates.io-index" 3906 + checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" 3907 + dependencies = [ 3908 + "base64ct", 3909 + "der", 3910 + ] 3911 + 3912 + [[package]] 3913 + name = "stable_deref_trait" 3914 + version = "1.2.1" 3915 + source = "registry+https://github.com/rust-lang/crates.io-index" 3916 + checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" 3917 + 3918 + [[package]] 3919 + name = "static_assertions" 3920 + version = "1.1.0" 3921 + source = "registry+https://github.com/rust-lang/crates.io-index" 3922 + checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 3923 + 3924 + [[package]] 3925 + name = "string_cache" 3926 + version = "0.8.9" 3927 + source = "registry+https://github.com/rust-lang/crates.io-index" 3928 + checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" 3929 + dependencies = [ 3930 + "new_debug_unreachable", 3931 + "parking_lot", 3932 + "phf_shared", 3933 + "precomputed-hash", 3934 + "serde", 3935 + ] 3936 + 3937 + [[package]] 3938 + name = "string_cache_codegen" 3939 + version = "0.5.4" 3940 + source = "registry+https://github.com/rust-lang/crates.io-index" 3941 + checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" 3942 + dependencies = [ 3943 + "phf_generator", 3944 + "phf_shared", 3945 + "proc-macro2", 3946 + "quote", 3947 + ] 3948 + 3949 + [[package]] 3950 + name = "strsim" 3951 + version = "0.11.1" 3952 + source = "registry+https://github.com/rust-lang/crates.io-index" 3953 + checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 3954 + 3955 + [[package]] 3956 + name = "subtle" 3957 + version = "2.6.1" 3958 + source = "registry+https://github.com/rust-lang/crates.io-index" 3959 + checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 3960 + 3961 + [[package]] 3962 + name = "supports-color" 3963 + version = "3.0.2" 3964 + source = "registry+https://github.com/rust-lang/crates.io-index" 3965 + checksum = "c64fc7232dd8d2e4ac5ce4ef302b1d81e0b80d055b9d77c7c4f51f6aa4c867d6" 3966 + dependencies = [ 3967 + "is_ci", 3968 + ] 3969 + 3970 + [[package]] 3971 + name = "supports-hyperlinks" 3972 + version = "3.2.0" 3973 + source = "registry+https://github.com/rust-lang/crates.io-index" 3974 + checksum = "e396b6523b11ccb83120b115a0b7366de372751aa6edf19844dfb13a6af97e91" 3975 + 3976 + [[package]] 3977 + name = "supports-unicode" 3978 + version = "3.0.0" 3979 + source = "registry+https://github.com/rust-lang/crates.io-index" 3980 + checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2" 3981 + 3982 + [[package]] 3983 + name = "syn" 3984 + version = "2.0.114" 3985 + source = "registry+https://github.com/rust-lang/crates.io-index" 3986 + checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" 3987 + dependencies = [ 3988 + "proc-macro2", 3989 + "quote", 3990 + "unicode-ident", 3991 + ] 3992 + 3993 + [[package]] 3994 + name = "sync_wrapper" 3995 + version = "1.0.2" 3996 + source = "registry+https://github.com/rust-lang/crates.io-index" 3997 + checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" 3998 + dependencies = [ 3999 + "futures-core", 4000 + ] 4001 + 4002 + [[package]] 4003 + name = "synstructure" 4004 + version = "0.13.2" 4005 + source = "registry+https://github.com/rust-lang/crates.io-index" 4006 + checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" 4007 + dependencies = [ 4008 + "proc-macro2", 4009 + "quote", 4010 + "syn", 4011 + ] 4012 + 4013 + [[package]] 4014 + name = "system-configuration" 4015 + version = "0.6.1" 4016 + source = "registry+https://github.com/rust-lang/crates.io-index" 4017 + checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" 4018 + dependencies = [ 4019 + "bitflags", 4020 + "core-foundation 0.9.4", 4021 + "system-configuration-sys", 4022 + ] 4023 + 4024 + [[package]] 4025 + name = "system-configuration-sys" 4026 + version = "0.6.0" 4027 + source = "registry+https://github.com/rust-lang/crates.io-index" 4028 + checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" 4029 + dependencies = [ 4030 + "core-foundation-sys", 4031 + "libc", 4032 + ] 4033 + 4034 + [[package]] 4035 + name = "tagptr" 4036 + version = "0.2.0" 4037 + source = "registry+https://github.com/rust-lang/crates.io-index" 4038 + checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" 4039 + 4040 + [[package]] 4041 + name = "tempfile" 4042 + version = "3.24.0" 4043 + source = "registry+https://github.com/rust-lang/crates.io-index" 4044 + checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" 4045 + dependencies = [ 4046 + "fastrand", 4047 + "getrandom 0.3.4", 4048 + "once_cell", 4049 + "rustix", 4050 + "windows-sys 0.61.2", 4051 + ] 4052 + 4053 + [[package]] 4054 + name = "tendril" 4055 + version = "0.4.3" 4056 + source = "registry+https://github.com/rust-lang/crates.io-index" 4057 + checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" 4058 + dependencies = [ 4059 + "futf", 4060 + "mac", 4061 + "utf-8", 4062 + ] 4063 + 4064 + [[package]] 4065 + name = "terminal_size" 4066 + version = "0.4.3" 4067 + source = "registry+https://github.com/rust-lang/crates.io-index" 4068 + checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0" 4069 + dependencies = [ 4070 + "rustix", 4071 + "windows-sys 0.60.2", 4072 + ] 4073 + 4074 + [[package]] 4075 + name = "textwrap" 4076 + version = "0.16.2" 4077 + source = "registry+https://github.com/rust-lang/crates.io-index" 4078 + checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" 4079 + dependencies = [ 4080 + "unicode-linebreak", 4081 + "unicode-width 0.2.2", 4082 + ] 4083 + 4084 + [[package]] 4085 + name = "thiserror" 4086 + version = "1.0.69" 4087 + source = "registry+https://github.com/rust-lang/crates.io-index" 4088 + checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 4089 + dependencies = [ 4090 + "thiserror-impl 1.0.69", 4091 + ] 4092 + 4093 + [[package]] 4094 + name = "thiserror" 4095 + version = "2.0.18" 4096 + source = "registry+https://github.com/rust-lang/crates.io-index" 4097 + checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" 4098 + dependencies = [ 4099 + "thiserror-impl 2.0.18", 4100 + ] 4101 + 4102 + [[package]] 4103 + name = "thiserror-impl" 4104 + version = "1.0.69" 4105 + source = "registry+https://github.com/rust-lang/crates.io-index" 4106 + checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 4107 + dependencies = [ 4108 + "proc-macro2", 4109 + "quote", 4110 + "syn", 4111 + ] 4112 + 4113 + [[package]] 4114 + name = "thiserror-impl" 4115 + version = "2.0.18" 4116 + source = "registry+https://github.com/rust-lang/crates.io-index" 4117 + checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" 4118 + dependencies = [ 4119 + "proc-macro2", 4120 + "quote", 4121 + "syn", 4122 + ] 4123 + 4124 + [[package]] 4125 + name = "thread_local" 4126 + version = "1.1.9" 4127 + source = "registry+https://github.com/rust-lang/crates.io-index" 4128 + checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" 4129 + dependencies = [ 4130 + "cfg-if", 4131 + ] 4132 + 4133 + [[package]] 4134 + name = "threadpool" 4135 + version = "1.8.1" 4136 + source = "registry+https://github.com/rust-lang/crates.io-index" 4137 + checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" 4138 + dependencies = [ 4139 + "num_cpus", 4140 + ] 4141 + 4142 + [[package]] 4143 + name = "time" 4144 + version = "0.3.46" 4145 + source = "registry+https://github.com/rust-lang/crates.io-index" 4146 + checksum = "9da98b7d9b7dad93488a84b8248efc35352b0b2657397d4167e7ad67e5d535e5" 4147 + dependencies = [ 4148 + "deranged", 4149 + "libc", 4150 + "num-conv", 4151 + "num_threads", 4152 + "powerfmt", 4153 + "serde_core", 4154 + "time-core", 4155 + ] 4156 + 4157 + [[package]] 4158 + name = "time-core" 4159 + version = "0.1.8" 4160 + source = "registry+https://github.com/rust-lang/crates.io-index" 4161 + checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" 4162 + 4163 + [[package]] 4164 + name = "tiny_http" 4165 + version = "0.12.0" 4166 + source = "registry+https://github.com/rust-lang/crates.io-index" 4167 + checksum = "389915df6413a2e74fb181895f933386023c71110878cd0825588928e64cdc82" 4168 + dependencies = [ 4169 + "ascii", 4170 + "chunked_transfer", 4171 + "httpdate", 4172 + "log", 4173 + ] 4174 + 4175 + [[package]] 4176 + name = "tinystr" 4177 + version = "0.8.2" 4178 + source = "registry+https://github.com/rust-lang/crates.io-index" 4179 + checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" 4180 + dependencies = [ 4181 + "displaydoc", 4182 + "zerovec", 4183 + ] 4184 + 4185 + [[package]] 4186 + name = "tinyvec" 4187 + version = "1.10.0" 4188 + source = "registry+https://github.com/rust-lang/crates.io-index" 4189 + checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" 4190 + dependencies = [ 4191 + "tinyvec_macros", 4192 + ] 4193 + 4194 + [[package]] 4195 + name = "tinyvec_macros" 4196 + version = "0.1.1" 4197 + source = "registry+https://github.com/rust-lang/crates.io-index" 4198 + checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 4199 + 4200 + [[package]] 4201 + name = "tokio" 4202 + version = "1.49.0" 4203 + source = "registry+https://github.com/rust-lang/crates.io-index" 4204 + checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" 4205 + dependencies = [ 4206 + "bytes", 4207 + "libc", 4208 + "mio", 4209 + "parking_lot", 4210 + "pin-project-lite", 4211 + "signal-hook-registry", 4212 + "socket2 0.6.2", 4213 + "tokio-macros", 4214 + "windows-sys 0.61.2", 4215 + ] 4216 + 4217 + [[package]] 4218 + name = "tokio-macros" 4219 + version = "2.6.0" 4220 + source = "registry+https://github.com/rust-lang/crates.io-index" 4221 + checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" 4222 + dependencies = [ 4223 + "proc-macro2", 4224 + "quote", 4225 + "syn", 4226 + ] 4227 + 4228 + [[package]] 4229 + name = "tokio-rustls" 4230 + version = "0.26.4" 4231 + source = "registry+https://github.com/rust-lang/crates.io-index" 4232 + checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" 4233 + dependencies = [ 4234 + "rustls", 4235 + "tokio", 4236 + ] 4237 + 4238 + [[package]] 4239 + name = "tokio-stream" 4240 + version = "0.1.18" 4241 + source = "registry+https://github.com/rust-lang/crates.io-index" 4242 + checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" 4243 + dependencies = [ 4244 + "futures-core", 4245 + "pin-project-lite", 4246 + "tokio", 4247 + ] 4248 + 4249 + [[package]] 4250 + name = "tokio-tungstenite" 4251 + version = "0.24.0" 4252 + source = "registry+https://github.com/rust-lang/crates.io-index" 4253 + checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9" 4254 + dependencies = [ 4255 + "futures-util", 4256 + "log", 4257 + "rustls", 4258 + "rustls-native-certs", 4259 + "rustls-pki-types", 4260 + "tokio", 4261 + "tokio-rustls", 4262 + "tungstenite 0.24.0", 4263 + ] 4264 + 4265 + [[package]] 4266 + name = "tokio-tungstenite" 4267 + version = "0.28.0" 4268 + source = "registry+https://github.com/rust-lang/crates.io-index" 4269 + checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857" 4270 + dependencies = [ 4271 + "futures-util", 4272 + "log", 4273 + "tokio", 4274 + "tungstenite 0.28.0", 4275 + ] 4276 + 4277 + [[package]] 4278 + name = "tokio-tungstenite-wasm" 4279 + version = "0.4.0" 4280 + source = "registry+https://github.com/rust-lang/crates.io-index" 4281 + checksum = "e21a5c399399c3db9f08d8297ac12b500e86bca82e930253fdc62eaf9c0de6ae" 4282 + dependencies = [ 4283 + "futures-channel", 4284 + "futures-util", 4285 + "http", 4286 + "httparse", 4287 + "js-sys", 4288 + "rustls", 4289 + "thiserror 1.0.69", 4290 + "tokio", 4291 + "tokio-tungstenite 0.24.0", 4292 + "wasm-bindgen", 4293 + "web-sys", 4294 + ] 4295 + 4296 + [[package]] 4297 + name = "tokio-util" 4298 + version = "0.7.18" 4299 + source = "registry+https://github.com/rust-lang/crates.io-index" 4300 + checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" 4301 + dependencies = [ 4302 + "bytes", 4303 + "futures-core", 4304 + "futures-sink", 4305 + "futures-util", 4306 + "pin-project-lite", 4307 + "tokio", 4308 + ] 4309 + 4310 + [[package]] 4311 + name = "tower" 4312 + version = "0.5.3" 4313 + source = "registry+https://github.com/rust-lang/crates.io-index" 4314 + checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" 4315 + dependencies = [ 4316 + "futures-core", 4317 + "futures-util", 4318 + "pin-project-lite", 4319 + "sync_wrapper", 4320 + "tokio", 4321 + "tower-layer", 4322 + "tower-service", 4323 + "tracing", 4324 + ] 4325 + 4326 + [[package]] 4327 + name = "tower-http" 4328 + version = "0.6.8" 4329 + source = "registry+https://github.com/rust-lang/crates.io-index" 4330 + checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" 4331 + dependencies = [ 4332 + "async-compression", 4333 + "bitflags", 4334 + "bytes", 4335 + "futures-core", 4336 + "futures-util", 4337 + "http", 4338 + "http-body", 4339 + "http-body-util", 4340 + "iri-string", 4341 + "pin-project-lite", 4342 + "tokio", 4343 + "tokio-util", 4344 + "tower", 4345 + "tower-layer", 4346 + "tower-service", 4347 + "tracing", 4348 + ] 4349 + 4350 + [[package]] 4351 + name = "tower-layer" 4352 + version = "0.3.3" 4353 + source = "registry+https://github.com/rust-lang/crates.io-index" 4354 + checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" 4355 + 4356 + [[package]] 4357 + name = "tower-service" 4358 + version = "0.3.3" 4359 + source = "registry+https://github.com/rust-lang/crates.io-index" 4360 + checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" 4361 + 4362 + [[package]] 4363 + name = "tracing" 4364 + version = "0.1.44" 4365 + source = "registry+https://github.com/rust-lang/crates.io-index" 4366 + checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" 4367 + dependencies = [ 4368 + "log", 4369 + "pin-project-lite", 4370 + "tracing-attributes", 4371 + "tracing-core", 4372 + ] 4373 + 4374 + [[package]] 4375 + name = "tracing-attributes" 4376 + version = "0.1.31" 4377 + source = "registry+https://github.com/rust-lang/crates.io-index" 4378 + checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" 4379 + dependencies = [ 4380 + "proc-macro2", 4381 + "quote", 4382 + "syn", 4383 + ] 4384 + 4385 + [[package]] 4386 + name = "tracing-core" 4387 + version = "0.1.36" 4388 + source = "registry+https://github.com/rust-lang/crates.io-index" 4389 + checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" 4390 + dependencies = [ 4391 + "once_cell", 4392 + "valuable", 4393 + ] 4394 + 4395 + [[package]] 4396 + name = "tracing-log" 4397 + version = "0.2.0" 4398 + source = "registry+https://github.com/rust-lang/crates.io-index" 4399 + checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" 4400 + dependencies = [ 4401 + "log", 4402 + "once_cell", 4403 + "tracing-core", 4404 + ] 4405 + 4406 + [[package]] 4407 + name = "tracing-subscriber" 4408 + version = "0.3.22" 4409 + source = "registry+https://github.com/rust-lang/crates.io-index" 4410 + checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" 4411 + dependencies = [ 4412 + "matchers", 4413 + "nu-ansi-term", 4414 + "once_cell", 4415 + "regex-automata", 4416 + "sharded-slab", 4417 + "smallvec", 4418 + "thread_local", 4419 + "tracing", 4420 + "tracing-core", 4421 + "tracing-log", 4422 + ] 4423 + 4424 + [[package]] 4425 + name = "trait-variant" 4426 + version = "0.1.2" 4427 + source = "registry+https://github.com/rust-lang/crates.io-index" 4428 + checksum = "70977707304198400eb4835a78f6a9f928bf41bba420deb8fdb175cd965d77a7" 4429 + dependencies = [ 4430 + "proc-macro2", 4431 + "quote", 4432 + "syn", 4433 + ] 4434 + 4435 + [[package]] 4436 + name = "triomphe" 4437 + version = "0.1.15" 4438 + source = "registry+https://github.com/rust-lang/crates.io-index" 4439 + checksum = "dd69c5aa8f924c7519d6372789a74eac5b94fb0f8fcf0d4a97eb0bfc3e785f39" 4440 + 4441 + [[package]] 4442 + name = "try-lock" 4443 + version = "0.2.5" 4444 + source = "registry+https://github.com/rust-lang/crates.io-index" 4445 + checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 4446 + 4447 + [[package]] 4448 + name = "tungstenite" 4449 + version = "0.24.0" 4450 + source = "registry+https://github.com/rust-lang/crates.io-index" 4451 + checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" 4452 + dependencies = [ 4453 + "byteorder", 4454 + "bytes", 4455 + "data-encoding", 4456 + "http", 4457 + "httparse", 4458 + "log", 4459 + "rand 0.8.5", 4460 + "rustls", 4461 + "rustls-pki-types", 4462 + "sha1", 4463 + "thiserror 1.0.69", 4464 + "utf-8", 4465 + ] 4466 + 4467 + [[package]] 4468 + name = "tungstenite" 4469 + version = "0.28.0" 4470 + source = "registry+https://github.com/rust-lang/crates.io-index" 4471 + checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" 4472 + dependencies = [ 4473 + "bytes", 4474 + "data-encoding", 4475 + "http", 4476 + "httparse", 4477 + "log", 4478 + "rand 0.9.2", 4479 + "sha1", 4480 + "thiserror 2.0.18", 4481 + "utf-8", 4482 + ] 4483 + 4484 + [[package]] 4485 + name = "twoway" 4486 + version = "0.1.8" 4487 + source = "registry+https://github.com/rust-lang/crates.io-index" 4488 + checksum = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1" 4489 + dependencies = [ 4490 + "memchr", 4491 + ] 4492 + 4493 + [[package]] 4494 + name = "twox-hash" 4495 + version = "2.1.2" 4496 + source = "registry+https://github.com/rust-lang/crates.io-index" 4497 + checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c" 4498 + 4499 + [[package]] 4500 + name = "typenum" 4501 + version = "1.19.0" 4502 + source = "registry+https://github.com/rust-lang/crates.io-index" 4503 + checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" 4504 + 4505 + [[package]] 4506 + name = "unicase" 4507 + version = "2.9.0" 4508 + source = "registry+https://github.com/rust-lang/crates.io-index" 4509 + checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" 4510 + 4511 + [[package]] 4512 + name = "unicode-ident" 4513 + version = "1.0.22" 4514 + source = "registry+https://github.com/rust-lang/crates.io-index" 4515 + checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" 4516 + 4517 + [[package]] 4518 + name = "unicode-linebreak" 4519 + version = "0.1.5" 4520 + source = "registry+https://github.com/rust-lang/crates.io-index" 4521 + checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" 4522 + 4523 + [[package]] 4524 + name = "unicode-segmentation" 4525 + version = "1.12.0" 4526 + source = "registry+https://github.com/rust-lang/crates.io-index" 4527 + checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" 4528 + 4529 + [[package]] 4530 + name = "unicode-width" 4531 + version = "0.1.14" 4532 + source = "registry+https://github.com/rust-lang/crates.io-index" 4533 + checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" 4534 + 4535 + [[package]] 4536 + name = "unicode-width" 4537 + version = "0.2.2" 4538 + source = "registry+https://github.com/rust-lang/crates.io-index" 4539 + checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" 4540 + 4541 + [[package]] 4542 + name = "unicode-xid" 4543 + version = "0.2.6" 4544 + source = "registry+https://github.com/rust-lang/crates.io-index" 4545 + checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" 4546 + 4547 + [[package]] 4548 + name = "unsigned-varint" 4549 + version = "0.7.2" 4550 + source = "registry+https://github.com/rust-lang/crates.io-index" 4551 + checksum = "6889a77d49f1f013504cec6bf97a2c730394adedaeb1deb5ea08949a50541105" 4552 + 4553 + [[package]] 4554 + name = "unsigned-varint" 4555 + version = "0.8.0" 4556 + source = "registry+https://github.com/rust-lang/crates.io-index" 4557 + checksum = "eb066959b24b5196ae73cb057f45598450d2c5f71460e98c49b738086eff9c06" 4558 + 4559 + [[package]] 4560 + name = "untrusted" 4561 + version = "0.9.0" 4562 + source = "registry+https://github.com/rust-lang/crates.io-index" 4563 + checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 4564 + 4565 + [[package]] 4566 + name = "url" 4567 + version = "2.5.8" 4568 + source = "registry+https://github.com/rust-lang/crates.io-index" 4569 + checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" 4570 + dependencies = [ 4571 + "form_urlencoded", 4572 + "idna", 4573 + "percent-encoding", 4574 + "serde", 4575 + "serde_derive", 4576 + ] 4577 + 4578 + [[package]] 4579 + name = "urlencoding" 4580 + version = "2.1.3" 4581 + source = "registry+https://github.com/rust-lang/crates.io-index" 4582 + checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" 4583 + 4584 + [[package]] 4585 + name = "utf-8" 4586 + version = "0.7.6" 4587 + source = "registry+https://github.com/rust-lang/crates.io-index" 4588 + checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" 4589 + 4590 + [[package]] 4591 + name = "utf8_iter" 4592 + version = "1.0.4" 4593 + source = "registry+https://github.com/rust-lang/crates.io-index" 4594 + checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 4595 + 4596 + [[package]] 4597 + name = "valuable" 4598 + version = "0.1.1" 4599 + source = "registry+https://github.com/rust-lang/crates.io-index" 4600 + checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" 4601 + 4602 + [[package]] 4603 + name = "varint-rs" 4604 + version = "2.2.0" 4605 + source = "registry+https://github.com/rust-lang/crates.io-index" 4606 + checksum = "8f54a172d0620933a27a4360d3db3e2ae0dd6cceae9730751a036bbf182c4b23" 4607 + 4608 + [[package]] 4609 + name = "version_check" 4610 + version = "0.9.5" 4611 + source = "registry+https://github.com/rust-lang/crates.io-index" 4612 + checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 4613 + 4614 + [[package]] 4615 + name = "walkdir" 4616 + version = "2.5.0" 4617 + source = "registry+https://github.com/rust-lang/crates.io-index" 4618 + checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 4619 + dependencies = [ 4620 + "same-file", 4621 + "winapi-util", 4622 + ] 4623 + 4624 + [[package]] 4625 + name = "want" 4626 + version = "0.3.1" 4627 + source = "registry+https://github.com/rust-lang/crates.io-index" 4628 + checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 4629 + dependencies = [ 4630 + "try-lock", 4631 + ] 4632 + 4633 + [[package]] 4634 + name = "wasi" 4635 + version = "0.11.1+wasi-snapshot-preview1" 4636 + source = "registry+https://github.com/rust-lang/crates.io-index" 4637 + checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" 4638 + 4639 + [[package]] 4640 + name = "wasip2" 4641 + version = "1.0.2+wasi-0.2.9" 4642 + source = "registry+https://github.com/rust-lang/crates.io-index" 4643 + checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" 4644 + dependencies = [ 4645 + "wit-bindgen", 4646 + ] 4647 + 4648 + [[package]] 4649 + name = "wasm-bindgen" 4650 + version = "0.2.108" 4651 + source = "registry+https://github.com/rust-lang/crates.io-index" 4652 + checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" 4653 + dependencies = [ 4654 + "cfg-if", 4655 + "once_cell", 4656 + "rustversion", 4657 + "wasm-bindgen-macro", 4658 + "wasm-bindgen-shared", 4659 + ] 4660 + 4661 + [[package]] 4662 + name = "wasm-bindgen-futures" 4663 + version = "0.4.58" 4664 + source = "registry+https://github.com/rust-lang/crates.io-index" 4665 + checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" 4666 + dependencies = [ 4667 + "cfg-if", 4668 + "futures-util", 4669 + "js-sys", 4670 + "once_cell", 4671 + "wasm-bindgen", 4672 + "web-sys", 4673 + ] 4674 + 4675 + [[package]] 4676 + name = "wasm-bindgen-macro" 4677 + version = "0.2.108" 4678 + source = "registry+https://github.com/rust-lang/crates.io-index" 4679 + checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" 4680 + dependencies = [ 4681 + "quote", 4682 + "wasm-bindgen-macro-support", 4683 + ] 4684 + 4685 + [[package]] 4686 + name = "wasm-bindgen-macro-support" 4687 + version = "0.2.108" 4688 + source = "registry+https://github.com/rust-lang/crates.io-index" 4689 + checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" 4690 + dependencies = [ 4691 + "bumpalo", 4692 + "proc-macro2", 4693 + "quote", 4694 + "syn", 4695 + "wasm-bindgen-shared", 4696 + ] 4697 + 4698 + [[package]] 4699 + name = "wasm-bindgen-shared" 4700 + version = "0.2.108" 4701 + source = "registry+https://github.com/rust-lang/crates.io-index" 4702 + checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" 4703 + dependencies = [ 4704 + "unicode-ident", 4705 + ] 4706 + 4707 + [[package]] 4708 + name = "wasm-streams" 4709 + version = "0.4.2" 4710 + source = "registry+https://github.com/rust-lang/crates.io-index" 4711 + checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" 4712 + dependencies = [ 4713 + "futures-util", 4714 + "js-sys", 4715 + "wasm-bindgen", 4716 + "wasm-bindgen-futures", 4717 + "web-sys", 4718 + ] 4719 + 4720 + [[package]] 4721 + name = "web-sys" 4722 + version = "0.3.85" 4723 + source = "registry+https://github.com/rust-lang/crates.io-index" 4724 + checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" 4725 + dependencies = [ 4726 + "js-sys", 4727 + "wasm-bindgen", 4728 + ] 4729 + 4730 + [[package]] 4731 + name = "web-time" 4732 + version = "1.1.0" 4733 + source = "registry+https://github.com/rust-lang/crates.io-index" 4734 + checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" 4735 + dependencies = [ 4736 + "js-sys", 4737 + "wasm-bindgen", 4738 + ] 4739 + 4740 + [[package]] 4741 + name = "webbrowser" 4742 + version = "1.0.6" 4743 + source = "registry+https://github.com/rust-lang/crates.io-index" 4744 + checksum = "00f1243ef785213e3a32fa0396093424a3a6ea566f9948497e5a2309261a4c97" 4745 + dependencies = [ 4746 + "core-foundation 0.10.1", 4747 + "jni", 4748 + "log", 4749 + "ndk-context", 4750 + "objc2", 4751 + "objc2-foundation", 4752 + "url", 4753 + "web-sys", 4754 + ] 4755 + 4756 + [[package]] 4757 + name = "webpage" 4758 + version = "2.0.1" 4759 + source = "registry+https://github.com/rust-lang/crates.io-index" 4760 + checksum = "70862efc041d46e6bbaa82bb9c34ae0596d090e86cbd14bd9e93b36ee6802eac" 4761 + dependencies = [ 4762 + "html5ever", 4763 + "markup5ever_rcdom", 4764 + "serde_json", 4765 + "url", 4766 + ] 4767 + 4768 + [[package]] 4769 + name = "webpki-roots" 4770 + version = "1.0.5" 4771 + source = "registry+https://github.com/rust-lang/crates.io-index" 4772 + checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c" 4773 + dependencies = [ 4774 + "rustls-pki-types", 4775 + ] 4776 + 4777 + [[package]] 4778 + name = "widestring" 4779 + version = "1.2.1" 4780 + source = "registry+https://github.com/rust-lang/crates.io-index" 4781 + checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" 4782 + 4783 + [[package]] 4784 + name = "winapi-util" 4785 + version = "0.1.11" 4786 + source = "registry+https://github.com/rust-lang/crates.io-index" 4787 + checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" 4788 + dependencies = [ 4789 + "windows-sys 0.61.2", 4790 + ] 4791 + 4792 + [[package]] 4793 + name = "windows-core" 4794 + version = "0.62.2" 4795 + source = "registry+https://github.com/rust-lang/crates.io-index" 4796 + checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" 4797 + dependencies = [ 4798 + "windows-implement", 4799 + "windows-interface", 4800 + "windows-link", 4801 + "windows-result", 4802 + "windows-strings", 4803 + ] 4804 + 4805 + [[package]] 4806 + name = "windows-implement" 4807 + version = "0.60.2" 4808 + source = "registry+https://github.com/rust-lang/crates.io-index" 4809 + checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" 4810 + dependencies = [ 4811 + "proc-macro2", 4812 + "quote", 4813 + "syn", 4814 + ] 4815 + 4816 + [[package]] 4817 + name = "windows-interface" 4818 + version = "0.59.3" 4819 + source = "registry+https://github.com/rust-lang/crates.io-index" 4820 + checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" 4821 + dependencies = [ 4822 + "proc-macro2", 4823 + "quote", 4824 + "syn", 4825 + ] 4826 + 4827 + [[package]] 4828 + name = "windows-link" 4829 + version = "0.2.1" 4830 + source = "registry+https://github.com/rust-lang/crates.io-index" 4831 + checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" 4832 + 4833 + [[package]] 4834 + name = "windows-registry" 4835 + version = "0.6.1" 4836 + source = "registry+https://github.com/rust-lang/crates.io-index" 4837 + checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" 4838 + dependencies = [ 4839 + "windows-link", 4840 + "windows-result", 4841 + "windows-strings", 4842 + ] 4843 + 4844 + [[package]] 4845 + name = "windows-result" 4846 + version = "0.4.1" 4847 + source = "registry+https://github.com/rust-lang/crates.io-index" 4848 + checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" 4849 + dependencies = [ 4850 + "windows-link", 4851 + ] 4852 + 4853 + [[package]] 4854 + name = "windows-strings" 4855 + version = "0.5.1" 4856 + source = "registry+https://github.com/rust-lang/crates.io-index" 4857 + checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" 4858 + dependencies = [ 4859 + "windows-link", 4860 + ] 4861 + 4862 + [[package]] 4863 + name = "windows-sys" 4864 + version = "0.45.0" 4865 + source = "registry+https://github.com/rust-lang/crates.io-index" 4866 + checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" 4867 + dependencies = [ 4868 + "windows-targets 0.42.2", 4869 + ] 4870 + 4871 + [[package]] 4872 + name = "windows-sys" 4873 + version = "0.48.0" 4874 + source = "registry+https://github.com/rust-lang/crates.io-index" 4875 + checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 4876 + dependencies = [ 4877 + "windows-targets 0.48.5", 4878 + ] 4879 + 4880 + [[package]] 4881 + name = "windows-sys" 4882 + version = "0.52.0" 4883 + source = "registry+https://github.com/rust-lang/crates.io-index" 4884 + checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 4885 + dependencies = [ 4886 + "windows-targets 0.52.6", 4887 + ] 4888 + 4889 + [[package]] 4890 + name = "windows-sys" 4891 + version = "0.60.2" 4892 + source = "registry+https://github.com/rust-lang/crates.io-index" 4893 + checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" 4894 + dependencies = [ 4895 + "windows-targets 0.53.5", 4896 + ] 4897 + 4898 + [[package]] 4899 + name = "windows-sys" 4900 + version = "0.61.2" 4901 + source = "registry+https://github.com/rust-lang/crates.io-index" 4902 + checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" 4903 + dependencies = [ 4904 + "windows-link", 4905 + ] 4906 + 4907 + [[package]] 4908 + name = "windows-targets" 4909 + version = "0.42.2" 4910 + source = "registry+https://github.com/rust-lang/crates.io-index" 4911 + checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" 4912 + dependencies = [ 4913 + "windows_aarch64_gnullvm 0.42.2", 4914 + "windows_aarch64_msvc 0.42.2", 4915 + "windows_i686_gnu 0.42.2", 4916 + "windows_i686_msvc 0.42.2", 4917 + "windows_x86_64_gnu 0.42.2", 4918 + "windows_x86_64_gnullvm 0.42.2", 4919 + "windows_x86_64_msvc 0.42.2", 4920 + ] 4921 + 4922 + [[package]] 4923 + name = "windows-targets" 4924 + version = "0.48.5" 4925 + source = "registry+https://github.com/rust-lang/crates.io-index" 4926 + checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 4927 + dependencies = [ 4928 + "windows_aarch64_gnullvm 0.48.5", 4929 + "windows_aarch64_msvc 0.48.5", 4930 + "windows_i686_gnu 0.48.5", 4931 + "windows_i686_msvc 0.48.5", 4932 + "windows_x86_64_gnu 0.48.5", 4933 + "windows_x86_64_gnullvm 0.48.5", 4934 + "windows_x86_64_msvc 0.48.5", 4935 + ] 4936 + 4937 + [[package]] 4938 + name = "windows-targets" 4939 + version = "0.52.6" 4940 + source = "registry+https://github.com/rust-lang/crates.io-index" 4941 + checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 4942 + dependencies = [ 4943 + "windows_aarch64_gnullvm 0.52.6", 4944 + "windows_aarch64_msvc 0.52.6", 4945 + "windows_i686_gnu 0.52.6", 4946 + "windows_i686_gnullvm 0.52.6", 4947 + "windows_i686_msvc 0.52.6", 4948 + "windows_x86_64_gnu 0.52.6", 4949 + "windows_x86_64_gnullvm 0.52.6", 4950 + "windows_x86_64_msvc 0.52.6", 4951 + ] 4952 + 4953 + [[package]] 4954 + name = "windows-targets" 4955 + version = "0.53.5" 4956 + source = "registry+https://github.com/rust-lang/crates.io-index" 4957 + checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" 4958 + dependencies = [ 4959 + "windows-link", 4960 + "windows_aarch64_gnullvm 0.53.1", 4961 + "windows_aarch64_msvc 0.53.1", 4962 + "windows_i686_gnu 0.53.1", 4963 + "windows_i686_gnullvm 0.53.1", 4964 + "windows_i686_msvc 0.53.1", 4965 + "windows_x86_64_gnu 0.53.1", 4966 + "windows_x86_64_gnullvm 0.53.1", 4967 + "windows_x86_64_msvc 0.53.1", 4968 + ] 4969 + 4970 + [[package]] 4971 + name = "windows_aarch64_gnullvm" 4972 + version = "0.42.2" 4973 + source = "registry+https://github.com/rust-lang/crates.io-index" 4974 + checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" 4975 + 4976 + [[package]] 4977 + name = "windows_aarch64_gnullvm" 4978 + version = "0.48.5" 4979 + source = "registry+https://github.com/rust-lang/crates.io-index" 4980 + checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 4981 + 4982 + [[package]] 4983 + name = "windows_aarch64_gnullvm" 4984 + version = "0.52.6" 4985 + source = "registry+https://github.com/rust-lang/crates.io-index" 4986 + checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 4987 + 4988 + [[package]] 4989 + name = "windows_aarch64_gnullvm" 4990 + version = "0.53.1" 4991 + source = "registry+https://github.com/rust-lang/crates.io-index" 4992 + checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" 4993 + 4994 + [[package]] 4995 + name = "windows_aarch64_msvc" 4996 + version = "0.42.2" 4997 + source = "registry+https://github.com/rust-lang/crates.io-index" 4998 + checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" 4999 + 5000 + [[package]] 5001 + name = "windows_aarch64_msvc" 5002 + version = "0.48.5" 5003 + source = "registry+https://github.com/rust-lang/crates.io-index" 5004 + checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 5005 + 5006 + [[package]] 5007 + name = "windows_aarch64_msvc" 5008 + version = "0.52.6" 5009 + source = "registry+https://github.com/rust-lang/crates.io-index" 5010 + checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 5011 + 5012 + [[package]] 5013 + name = "windows_aarch64_msvc" 5014 + version = "0.53.1" 5015 + source = "registry+https://github.com/rust-lang/crates.io-index" 5016 + checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" 5017 + 5018 + [[package]] 5019 + name = "windows_i686_gnu" 5020 + version = "0.42.2" 5021 + source = "registry+https://github.com/rust-lang/crates.io-index" 5022 + checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" 5023 + 5024 + [[package]] 5025 + name = "windows_i686_gnu" 5026 + version = "0.48.5" 5027 + source = "registry+https://github.com/rust-lang/crates.io-index" 5028 + checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 5029 + 5030 + [[package]] 5031 + name = "windows_i686_gnu" 5032 + version = "0.52.6" 5033 + source = "registry+https://github.com/rust-lang/crates.io-index" 5034 + checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 5035 + 5036 + [[package]] 5037 + name = "windows_i686_gnu" 5038 + version = "0.53.1" 5039 + source = "registry+https://github.com/rust-lang/crates.io-index" 5040 + checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" 5041 + 5042 + [[package]] 5043 + name = "windows_i686_gnullvm" 5044 + version = "0.52.6" 5045 + source = "registry+https://github.com/rust-lang/crates.io-index" 5046 + checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 5047 + 5048 + [[package]] 5049 + name = "windows_i686_gnullvm" 5050 + version = "0.53.1" 5051 + source = "registry+https://github.com/rust-lang/crates.io-index" 5052 + checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" 5053 + 5054 + [[package]] 5055 + name = "windows_i686_msvc" 5056 + version = "0.42.2" 5057 + source = "registry+https://github.com/rust-lang/crates.io-index" 5058 + checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" 5059 + 5060 + [[package]] 5061 + name = "windows_i686_msvc" 5062 + version = "0.48.5" 5063 + source = "registry+https://github.com/rust-lang/crates.io-index" 5064 + checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 5065 + 5066 + [[package]] 5067 + name = "windows_i686_msvc" 5068 + version = "0.52.6" 5069 + source = "registry+https://github.com/rust-lang/crates.io-index" 5070 + checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 5071 + 5072 + [[package]] 5073 + name = "windows_i686_msvc" 5074 + version = "0.53.1" 5075 + source = "registry+https://github.com/rust-lang/crates.io-index" 5076 + checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" 5077 + 5078 + [[package]] 5079 + name = "windows_x86_64_gnu" 5080 + version = "0.42.2" 5081 + source = "registry+https://github.com/rust-lang/crates.io-index" 5082 + checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" 5083 + 5084 + [[package]] 5085 + name = "windows_x86_64_gnu" 5086 + version = "0.48.5" 5087 + source = "registry+https://github.com/rust-lang/crates.io-index" 5088 + checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 5089 + 5090 + [[package]] 5091 + name = "windows_x86_64_gnu" 5092 + version = "0.52.6" 5093 + source = "registry+https://github.com/rust-lang/crates.io-index" 5094 + checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 5095 + 5096 + [[package]] 5097 + name = "windows_x86_64_gnu" 5098 + version = "0.53.1" 5099 + source = "registry+https://github.com/rust-lang/crates.io-index" 5100 + checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" 5101 + 5102 + [[package]] 5103 + name = "windows_x86_64_gnullvm" 5104 + version = "0.42.2" 5105 + source = "registry+https://github.com/rust-lang/crates.io-index" 5106 + checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" 5107 + 5108 + [[package]] 5109 + name = "windows_x86_64_gnullvm" 5110 + version = "0.48.5" 5111 + source = "registry+https://github.com/rust-lang/crates.io-index" 5112 + checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 5113 + 5114 + [[package]] 5115 + name = "windows_x86_64_gnullvm" 5116 + version = "0.52.6" 5117 + source = "registry+https://github.com/rust-lang/crates.io-index" 5118 + checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 5119 + 5120 + [[package]] 5121 + name = "windows_x86_64_gnullvm" 5122 + version = "0.53.1" 5123 + source = "registry+https://github.com/rust-lang/crates.io-index" 5124 + checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" 5125 + 5126 + [[package]] 5127 + name = "windows_x86_64_msvc" 5128 + version = "0.42.2" 5129 + source = "registry+https://github.com/rust-lang/crates.io-index" 5130 + checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" 5131 + 5132 + [[package]] 5133 + name = "windows_x86_64_msvc" 5134 + version = "0.48.5" 5135 + source = "registry+https://github.com/rust-lang/crates.io-index" 5136 + checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 5137 + 5138 + [[package]] 5139 + name = "windows_x86_64_msvc" 5140 + version = "0.52.6" 5141 + source = "registry+https://github.com/rust-lang/crates.io-index" 5142 + checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 5143 + 5144 + [[package]] 5145 + name = "windows_x86_64_msvc" 5146 + version = "0.53.1" 5147 + source = "registry+https://github.com/rust-lang/crates.io-index" 5148 + checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" 5149 + 5150 + [[package]] 5151 + name = "winreg" 5152 + version = "0.50.0" 5153 + source = "registry+https://github.com/rust-lang/crates.io-index" 5154 + checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" 5155 + dependencies = [ 5156 + "cfg-if", 5157 + "windows-sys 0.48.0", 5158 + ] 5159 + 5160 + [[package]] 5161 + name = "wit-bindgen" 5162 + version = "0.51.0" 5163 + source = "registry+https://github.com/rust-lang/crates.io-index" 5164 + checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" 5165 + 5166 + [[package]] 5167 + name = "writeable" 5168 + version = "0.6.2" 5169 + source = "registry+https://github.com/rust-lang/crates.io-index" 5170 + checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" 5171 + 5172 + [[package]] 5173 + name = "xml5ever" 5174 + version = "0.18.1" 5175 + source = "registry+https://github.com/rust-lang/crates.io-index" 5176 + checksum = "9bbb26405d8e919bc1547a5aa9abc95cbfa438f04844f5fdd9dc7596b748bf69" 5177 + dependencies = [ 5178 + "log", 5179 + "mac", 5180 + "markup5ever", 5181 + ] 5182 + 5183 + [[package]] 5184 + name = "xxhash-rust" 5185 + version = "0.8.15" 5186 + source = "registry+https://github.com/rust-lang/crates.io-index" 5187 + checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" 5188 + 5189 + [[package]] 5190 + name = "yansi" 5191 + version = "1.0.1" 5192 + source = "registry+https://github.com/rust-lang/crates.io-index" 5193 + checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" 5194 + 5195 + [[package]] 5196 + name = "yoke" 5197 + version = "0.8.1" 5198 + source = "registry+https://github.com/rust-lang/crates.io-index" 5199 + checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" 5200 + dependencies = [ 5201 + "stable_deref_trait", 5202 + "yoke-derive", 5203 + "zerofrom", 5204 + ] 5205 + 5206 + [[package]] 5207 + name = "yoke-derive" 5208 + version = "0.8.1" 5209 + source = "registry+https://github.com/rust-lang/crates.io-index" 5210 + checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" 5211 + dependencies = [ 5212 + "proc-macro2", 5213 + "quote", 5214 + "syn", 5215 + "synstructure", 5216 + ] 5217 + 5218 + [[package]] 5219 + name = "zerocopy" 5220 + version = "0.8.37" 5221 + source = "registry+https://github.com/rust-lang/crates.io-index" 5222 + checksum = "7456cf00f0685ad319c5b1693f291a650eaf345e941d082fc4e03df8a03996ac" 5223 + dependencies = [ 5224 + "zerocopy-derive", 5225 + ] 5226 + 5227 + [[package]] 5228 + name = "zerocopy-derive" 5229 + version = "0.8.37" 5230 + source = "registry+https://github.com/rust-lang/crates.io-index" 5231 + checksum = "1328722bbf2115db7e19d69ebcc15e795719e2d66b60827c6a69a117365e37a0" 5232 + dependencies = [ 5233 + "proc-macro2", 5234 + "quote", 5235 + "syn", 5236 + ] 5237 + 5238 + [[package]] 5239 + name = "zerofrom" 5240 + version = "0.1.6" 5241 + source = "registry+https://github.com/rust-lang/crates.io-index" 5242 + checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" 5243 + dependencies = [ 5244 + "zerofrom-derive", 5245 + ] 5246 + 5247 + [[package]] 5248 + name = "zerofrom-derive" 5249 + version = "0.1.6" 5250 + source = "registry+https://github.com/rust-lang/crates.io-index" 5251 + checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" 5252 + dependencies = [ 5253 + "proc-macro2", 5254 + "quote", 5255 + "syn", 5256 + "synstructure", 5257 + ] 5258 + 5259 + [[package]] 5260 + name = "zeroize" 5261 + version = "1.8.2" 5262 + source = "registry+https://github.com/rust-lang/crates.io-index" 5263 + checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" 5264 + dependencies = [ 5265 + "serde", 5266 + ] 5267 + 5268 + [[package]] 5269 + name = "zerotrie" 5270 + version = "0.2.3" 5271 + source = "registry+https://github.com/rust-lang/crates.io-index" 5272 + checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" 5273 + dependencies = [ 5274 + "displaydoc", 5275 + "yoke", 5276 + "zerofrom", 5277 + ] 5278 + 5279 + [[package]] 5280 + name = "zerovec" 5281 + version = "0.11.5" 5282 + source = "registry+https://github.com/rust-lang/crates.io-index" 5283 + checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" 5284 + dependencies = [ 5285 + "yoke", 5286 + "zerofrom", 5287 + "zerovec-derive", 5288 + ] 5289 + 5290 + [[package]] 5291 + name = "zerovec-derive" 5292 + version = "0.11.2" 5293 + source = "registry+https://github.com/rust-lang/crates.io-index" 5294 + checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" 5295 + dependencies = [ 5296 + "proc-macro2", 5297 + "quote", 5298 + "syn", 5299 + ] 5300 + 5301 + [[package]] 5302 + name = "zmij" 5303 + version = "1.0.18" 5304 + source = "registry+https://github.com/rust-lang/crates.io-index" 5305 + checksum = "1966f8ac2c1f76987d69a74d0e0f929241c10e78136434e3be70ff7f58f64214" 5306 + 5307 + [[package]] 5308 + name = "zstd" 5309 + version = "0.13.3" 5310 + source = "registry+https://github.com/rust-lang/crates.io-index" 5311 + checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" 5312 + dependencies = [ 5313 + "zstd-safe", 5314 + ] 5315 + 5316 + [[package]] 5317 + name = "zstd-safe" 5318 + version = "7.2.4" 5319 + source = "registry+https://github.com/rust-lang/crates.io-index" 5320 + checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" 5321 + dependencies = [ 5322 + "zstd-sys", 5323 + ] 5324 + 5325 + [[package]] 5326 + name = "zstd-sys" 5327 + version = "2.0.16+zstd.1.5.7" 5328 + source = "registry+https://github.com/rust-lang/crates.io-index" 5329 + checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" 5330 + dependencies = [ 5331 + "cc", 5332 + "pkg-config", 5333 + ]
+40
Cargo.toml
···
··· 1 + [package] 2 + name = "hydrant" 3 + version = "0.1.0" 4 + edition = "2021" 5 + 6 + [dependencies] 7 + tokio = { version = "1.0", features = ["full"] } 8 + n0-future = "0.3" 9 + 10 + tracing = "0.1" 11 + tracing-subscriber = { version = "0.3", features = ["env-filter"] } 12 + miette = { version = "7", features = ["fancy"] } 13 + 14 + serde = { version = "1.0", features = ["derive"] } 15 + serde_json = "1.0" 16 + rmp-serde = "1.3.1" 17 + 18 + fjall = "3.0" 19 + serde_ipld_dagcbor = "0.6" 20 + 21 + url = "2.5" 22 + smol_str = "0.3" 23 + futures = "0.3" 24 + reqwest = { version = "0.12", features = ["json", "rustls-tls", "stream", "gzip", "brotli", "zstd"], default-features = false } 25 + axum = { version = "0.8.8", features = ["ws", "macros"] } 26 + tower-http = { version = "0.6.6", features = ["cors", "trace"] } 27 + tokio-stream = "0.1" 28 + async-stream = "0.3" 29 + 30 + jacquard = { git = "https://tangled.org/nonbinary.computer/jacquard", features = ["streaming"] } 31 + jacquard-common = { git = "https://tangled.org/nonbinary.computer/jacquard" } 32 + jacquard-api = { git = "https://tangled.org/nonbinary.computer/jacquard" } 33 + jacquard-identity = { git = "https://tangled.org/nonbinary.computer/jacquard", features = ["dns", "cache"] } 34 + jacquard-repo = { git = "https://tangled.org/nonbinary.computer/jacquard" } 35 + jacquard-axum = { git = "https://tangled.org/nonbinary.computer/jacquard" } 36 + jacquard-derive = { git = "https://tangled.org/nonbinary.computer/jacquard" } 37 + chrono = { version = "0.4.43", features = ["serde"] } 38 + humantime = "2.3.0" 39 + 40 + mimalloc = { version = "0.1", features = ["v3"] }
+18
LICENSE
···
··· 1 + Copyright (c) 2026 dawn <90008@gaze.systems> 2 + 3 + Permission is hereby granted, free of charge, to any person obtaining a copy 4 + of this software and associated documentation files (the "Software"), to deal 5 + in the Software without restriction, including without limitation the rights to 6 + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 + the Software, and to permit persons to whom the Software is furnished to do so, 8 + subject to the following conditions: 9 + 10 + The above copyright notice and this permission notice shall be included in all 11 + copies or substantial portions of the Software. 12 + 13 + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 + FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 16 + OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 17 + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+10
README.md
···
··· 1 + # hydrant 2 + 3 + ## configuration 4 + 5 + environment variables: 6 + - `HYDRANT_DATABASE_PATH`: path to database folder (default: `./hydrant.db`) 7 + - `HYDRANT_RELAY_HOST`: relay WebSocket URL (default: `wss://relay.fire.hose.cam`) 8 + - `HYDRANT_PLC_URL`: base URL of the PLC directory (default: `https://plc.wtf`). 9 + - `HYDRANT_FULL_NETWORK`: if set to `true`, the indexer will discover and index all repos it sees. 10 + - `HYDRANT_CURSOR_SAVE_INTERVAL`: how often to save the Firehose cursor (default: `10s`).
+26
default.nix
···
··· 1 + { 2 + lib, 3 + rustPlatform, 4 + cmake, 5 + ... 6 + }: 7 + rustPlatform.buildRustPackage { 8 + pname = "hydrant"; 9 + version = "main"; 10 + 11 + src = lib.fileset.toSource { 12 + root = ./.; 13 + fileset = lib.fileset.unions [ 14 + ./src ./Cargo.toml ./Cargo.lock 15 + ]; 16 + }; 17 + 18 + nativeBuildInputs = [cmake]; 19 + 20 + cargoLock = { 21 + lockFile = ./Cargo.lock; 22 + outputHashes = { 23 + "jacquard-0.9.5" = "sha256-3bVpFW/qtP9cjkU2LflHjSI3mzw1iUPH+92n0zcOqDg="; 24 + }; 25 + }; 26 + }
+61
flake.lock
···
··· 1 + { 2 + "nodes": { 3 + "nixpkgs": { 4 + "locked": { 5 + "lastModified": 1769740369, 6 + "narHash": "sha256-xKPyJoMoXfXpDM5DFDZDsi9PHArf2k5BJjvReYXoFpM=", 7 + "owner": "nixos", 8 + "repo": "nixpkgs", 9 + "rev": "6308c3b21396534d8aaeac46179c14c439a89b8a", 10 + "type": "github" 11 + }, 12 + "original": { 13 + "owner": "nixos", 14 + "ref": "nixpkgs-unstable", 15 + "repo": "nixpkgs", 16 + "type": "github" 17 + } 18 + }, 19 + "nixpkgs-lib": { 20 + "locked": { 21 + "lastModified": 1765674936, 22 + "narHash": "sha256-k00uTP4JNfmejrCLJOwdObYC9jHRrr/5M/a/8L2EIdo=", 23 + "owner": "nix-community", 24 + "repo": "nixpkgs.lib", 25 + "rev": "2075416fcb47225d9b68ac469a5c4801a9c4dd85", 26 + "type": "github" 27 + }, 28 + "original": { 29 + "owner": "nix-community", 30 + "repo": "nixpkgs.lib", 31 + "type": "github" 32 + } 33 + }, 34 + "parts": { 35 + "inputs": { 36 + "nixpkgs-lib": "nixpkgs-lib" 37 + }, 38 + "locked": { 39 + "lastModified": 1768135262, 40 + "narHash": "sha256-PVvu7OqHBGWN16zSi6tEmPwwHQ4rLPU9Plvs8/1TUBY=", 41 + "owner": "hercules-ci", 42 + "repo": "flake-parts", 43 + "rev": "80daad04eddbbf5a4d883996a73f3f542fa437ac", 44 + "type": "github" 45 + }, 46 + "original": { 47 + "owner": "hercules-ci", 48 + "repo": "flake-parts", 49 + "type": "github" 50 + } 51 + }, 52 + "root": { 53 + "inputs": { 54 + "nixpkgs": "nixpkgs", 55 + "parts": "parts" 56 + } 57 + } 58 + }, 59 + "root": "root", 60 + "version": 7 61 + }
+35
flake.nix
···
··· 1 + { 2 + inputs.parts.url = "github:hercules-ci/flake-parts"; 3 + inputs.nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; 4 + 5 + outputs = 6 + inp: 7 + inp.parts.lib.mkFlake { inputs = inp; } { 8 + systems = [ "x86_64-linux" ]; 9 + perSystem = 10 + { 11 + pkgs, 12 + config, 13 + ... 14 + }: 15 + { 16 + packages.default = pkgs.callPackage ./default.nix {}; 17 + devShells = { 18 + default = pkgs.mkShell { 19 + packages = with pkgs; [ 20 + rustPlatform.rustLibSrc 21 + rust-analyzer 22 + cargo 23 + cargo-outdated 24 + rustc 25 + rustfmt 26 + gemini-cli 27 + go 28 + cmake 29 + websocat 30 + ]; 31 + }; 32 + }; 33 + }; 34 + }; 35 + }
+68
src/api/debug.rs
···
··· 1 + use crate::api::AppState; 2 + use crate::db::keys; 3 + use axum::{ 4 + extract::{ConnectInfo, Query, State}, 5 + http::StatusCode, 6 + Json, 7 + }; 8 + use jacquard::types::ident::AtIdentifier; 9 + use serde::{Deserialize, Serialize}; 10 + use std::net::SocketAddr; 11 + use std::sync::Arc; 12 + 13 + #[derive(Deserialize)] 14 + pub struct DebugCountRequest { 15 + pub did: String, 16 + pub collection: String, 17 + } 18 + 19 + #[derive(Serialize)] 20 + pub struct DebugCountResponse { 21 + pub count: usize, 22 + } 23 + 24 + pub async fn handle_debug_count( 25 + State(state): State<Arc<AppState>>, 26 + ConnectInfo(addr): ConnectInfo<SocketAddr>, 27 + Query(req): Query<DebugCountRequest>, 28 + ) -> Result<Json<DebugCountResponse>, StatusCode> { 29 + if !addr.ip().is_loopback() { 30 + return Err(StatusCode::FORBIDDEN); 31 + } 32 + 33 + let did = state 34 + .resolver 35 + .resolve_did(&AtIdentifier::new(req.did.as_str()).map_err(|_| StatusCode::BAD_REQUEST)?) 36 + .await 37 + .map_err(|_| StatusCode::BAD_REQUEST)?; 38 + 39 + let db = &state.db; 40 + let ks = db.records.clone(); 41 + 42 + // {did_prefix}\x00{collection}\x00 43 + let mut prefix = Vec::new(); 44 + prefix.extend_from_slice(keys::did_prefix(&did).as_bytes()); 45 + prefix.push(keys::SEP); 46 + prefix.extend_from_slice(req.collection.as_bytes()); 47 + prefix.push(keys::SEP); 48 + 49 + let count = tokio::task::spawn_blocking(move || { 50 + let mut count = 0; 51 + let start_key = prefix.clone(); 52 + let mut end_key = prefix.clone(); 53 + if let Some(msg) = end_key.last_mut() { 54 + *msg += 1; 55 + } 56 + 57 + for item in ks.range(start_key..end_key) { 58 + if item.into_inner().is_ok() { 59 + count += 1; 60 + } 61 + } 62 + count 63 + }) 64 + .await 65 + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; 66 + 67 + Ok(Json(DebugCountResponse { count })) 68 + }
+15
src/api/event.rs
···
··· 1 + use serde::{Deserialize, Serialize}; 2 + use smol_str::SmolStr; 3 + 4 + use crate::types::{IdentityEvt, RecordEvt}; 5 + 6 + #[derive(Debug, Serialize, Deserialize, Clone)] 7 + pub struct MarshallableEvt { 8 + pub id: u64, 9 + #[serde(rename = "type")] 10 + pub event_type: SmolStr, 11 + #[serde(skip_serializing_if = "Option::is_none")] 12 + pub record: Option<RecordEvt>, 13 + #[serde(skip_serializing_if = "Option::is_none")] 14 + pub identity: Option<IdentityEvt>, 15 + }
+40
src/api/mod.rs
···
··· 1 + use crate::state::AppState; 2 + use axum::{routing::get, Router}; 3 + use std::{net::SocketAddr, sync::Arc}; 4 + use tower_http::cors::CorsLayer; 5 + use tower_http::trace::TraceLayer; 6 + 7 + mod debug; 8 + pub mod event; 9 + pub mod repo; 10 + pub mod stats; 11 + mod stream; 12 + pub mod xrpc; 13 + 14 + pub async fn serve(state: Arc<AppState>, port: u16) -> miette::Result<()> { 15 + let app = Router::new() 16 + .route("/health", get(|| async { "OK" })) 17 + .route("/stats", get(stats::get_stats)) 18 + .route("/stream", get(stream::handle_stream)) 19 + .route("/debug/count", get(debug::handle_debug_count)) 20 + .merge(xrpc::router()) 21 + .merge(repo::router()) 22 + .with_state(state) 23 + .layer(TraceLayer::new_for_http()) 24 + .layer(CorsLayer::permissive()); 25 + 26 + let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{port}")) 27 + .await 28 + .map_err(|e| miette::miette!("failed to bind to port {port}: {e}"))?; 29 + 30 + tracing::info!("API server listening on {}", listener.local_addr().unwrap()); 31 + 32 + axum::serve( 33 + listener, 34 + app.into_make_service_with_connect_info::<SocketAddr>(), 35 + ) 36 + .await 37 + .map_err(|e| miette::miette!("axum server error: {e}"))?; 38 + 39 + Ok(()) 40 + }
+107
src/api/repo.rs
···
··· 1 + use crate::api::AppState; 2 + use crate::db::{keys, Db}; 3 + use crate::types::{RepoState, RepoStatus}; 4 + use axum::{extract::State, http::StatusCode, routing::post, Json, Router}; 5 + use jacquard::{types::did::Did, IntoStatic}; 6 + use serde::Deserialize; 7 + use std::sync::Arc; 8 + 9 + pub fn router() -> Router<Arc<AppState>> { 10 + Router::new() 11 + .route("/repo/add", post(handle_repo_add)) 12 + .route("/repo/remove", post(handle_repo_remove)) 13 + } 14 + 15 + #[derive(Deserialize)] 16 + pub struct RepoAddRemoveRequest { 17 + pub dids: Vec<String>, 18 + } 19 + 20 + pub async fn handle_repo_add( 21 + State(state): State<Arc<AppState>>, 22 + Json(req): Json<RepoAddRemoveRequest>, 23 + ) -> Result<StatusCode, (StatusCode, String)> { 24 + let db = &state.db; 25 + let mut batch = db.inner.batch(); 26 + let mut added_count = 0; 27 + let mut to_backfill = Vec::new(); 28 + 29 + for did_str in req.dids { 30 + let did = Did::new_owned(did_str.as_str()) 31 + .map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?; 32 + let did_key = keys::repo_key(&did); 33 + if !Db::contains_key(db.repos.clone(), did_key) 34 + .await 35 + .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? 36 + { 37 + let mut repo_state = RepoState::new(did.clone()); 38 + repo_state.status = RepoStatus::Backfilling; 39 + let bytes = rmp_serde::to_vec(&repo_state) 40 + .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; 41 + 42 + batch.insert(&db.repos, did_key, bytes); 43 + batch.insert(&db.pending, did_key, Vec::new()); 44 + 45 + added_count += 1; 46 + 47 + let jacquard_did = Did::new_owned(did.as_str()) 48 + .map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?; 49 + to_backfill.push(jacquard_did); 50 + } 51 + } 52 + 53 + if added_count > 0 { 54 + tokio::task::spawn_blocking(move || batch.commit().map_err(|e| e.to_string())) 55 + .await 56 + .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? 57 + .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e))?; 58 + 59 + // update counts 60 + tokio::task::spawn({ 61 + let state = state.clone(); 62 + async move { 63 + let _ = state 64 + .db 65 + .increment_count(keys::count_keyspace_key("repos"), added_count) 66 + .await; 67 + let _ = state 68 + .db 69 + .increment_count(keys::count_keyspace_key("pending"), added_count) 70 + .await; 71 + } 72 + }); 73 + 74 + // trigger backfill 75 + for did in to_backfill { 76 + let _ = state.backfill_tx.send(did.into_static()); 77 + } 78 + } 79 + Ok(StatusCode::OK) 80 + } 81 + 82 + pub async fn handle_repo_remove( 83 + State(state): State<Arc<AppState>>, 84 + Json(req): Json<RepoAddRemoveRequest>, 85 + ) -> Result<StatusCode, (StatusCode, String)> { 86 + let db = &state.db; 87 + for did_str in req.dids { 88 + let did = Did::new_owned(did_str.as_str()) 89 + .map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?; 90 + let did_key = keys::repo_key(&did); 91 + if Db::contains_key(db.repos.clone(), did_key) 92 + .await 93 + .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? 94 + { 95 + let mut batch = db.inner.batch(); 96 + batch.remove(&db.repos, did_key); 97 + batch.remove(&db.pending, did_key); 98 + batch.remove(&db.errors, did_key); 99 + 100 + tokio::task::spawn_blocking(move || batch.commit().map_err(|e| e.to_string())) 101 + .await 102 + .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? 103 + .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e))?; 104 + } 105 + } 106 + Ok(StatusCode::OK) 107 + }
+40
src/api/stats.rs
···
··· 1 + use crate::api::AppState; 2 + use crate::db::keys; 3 + use axum::{extract::State, response::Result, Json}; 4 + use serde::Serialize; 5 + use smol_str::SmolStr; 6 + use std::sync::Arc; 7 + 8 + #[derive(Serialize)] 9 + pub struct StatsResponse { 10 + pub keyspace_stats: Vec<KeyspaceStat>, 11 + } 12 + 13 + #[derive(Serialize)] 14 + pub struct KeyspaceStat { 15 + pub name: SmolStr, 16 + pub count: i64, 17 + } 18 + 19 + pub async fn get_stats(State(state): State<Arc<AppState>>) -> Result<Json<StatsResponse>> { 20 + let db = &state.db; 21 + 22 + let stats = futures::future::try_join_all( 23 + [ 24 + "repos", "records", "blocks", "events", "buffer", "pending", "errors", 25 + ] 26 + .into_iter() 27 + .map(|name| async move { 28 + Ok::<_, miette::Report>(KeyspaceStat { 29 + name: name.into(), 30 + count: db.get_count(keys::count_keyspace_key(name)).await?, 31 + }) 32 + }), 33 + ) 34 + .await 35 + .map_err(|e| e.to_string())?; 36 + 37 + Ok(Json(StatsResponse { 38 + keyspace_stats: stats, 39 + })) 40 + }
+157
src/api/stream.rs
···
··· 1 + use crate::api::event::MarshallableEvt; 2 + use crate::api::AppState; 3 + use crate::db::keys; 4 + use crate::types::{RecordEvt, StoredEvent}; 5 + use axum::{ 6 + extract::{ 7 + ws::{Message, WebSocket, WebSocketUpgrade}, 8 + Query, State, 9 + }, 10 + response::IntoResponse, 11 + }; 12 + use jacquard_common::types::value::RawData; 13 + use serde::Deserialize; 14 + use std::sync::Arc; 15 + use tokio::sync::{broadcast, mpsc}; 16 + 17 + #[derive(Deserialize)] 18 + pub struct StreamQuery { 19 + pub cursor: Option<u64>, 20 + } 21 + 22 + pub async fn handle_stream( 23 + State(state): State<Arc<AppState>>, 24 + Query(query): Query<StreamQuery>, 25 + ws: WebSocketUpgrade, 26 + ) -> impl IntoResponse { 27 + ws.on_upgrade(move |socket| handle_socket(socket, state, query)) 28 + } 29 + 30 + async fn handle_socket(mut socket: WebSocket, state: Arc<AppState>, query: StreamQuery) { 31 + let (tx, mut rx) = mpsc::channel(100); 32 + 33 + std::thread::Builder::new() 34 + .name(format!( 35 + "stream-handler-{}", 36 + std::time::SystemTime::UNIX_EPOCH 37 + .elapsed() 38 + .unwrap() 39 + .as_secs() 40 + )) 41 + .spawn(move || { 42 + let db = &state.db; 43 + let mut event_rx = db.event_tx.subscribe(); 44 + let ks = db.events.clone(); 45 + let mut current_id = match query.cursor { 46 + Some(cursor) => cursor.saturating_sub(1), 47 + None => { 48 + let max_id = db.next_event_id.load(std::sync::atomic::Ordering::SeqCst); 49 + max_id.saturating_sub(1) 50 + } 51 + }; 52 + 53 + loop { 54 + // 1. catch up from DB 55 + loop { 56 + let mut found = false; 57 + for item in ks.range(keys::event_key((current_id + 1) as i64)..) { 58 + if let Ok((k, v)) = item.into_inner() { 59 + let mut buf = [0u8; 8]; 60 + buf.copy_from_slice(&k); 61 + let id = u64::from_be_bytes(buf); 62 + current_id = id; 63 + 64 + let stored_evt: StoredEvent = match rmp_serde::from_slice(&v) { 65 + Ok(e) => e, 66 + Err(_) => continue, 67 + }; 68 + 69 + let marshallable = match stored_evt { 70 + StoredEvent::Record { 71 + live, 72 + did, 73 + rev, 74 + collection, 75 + rkey, 76 + action, 77 + cid, 78 + } => { 79 + let mut record_val = None; 80 + if let Some(cid_str) = &cid { 81 + if let Ok(Some(block_bytes)) = 82 + db.blocks.get(keys::block_key(cid_str)) 83 + { 84 + if let Ok(raw_data) = 85 + serde_ipld_dagcbor::from_slice::<RawData>( 86 + &block_bytes, 87 + ) 88 + { 89 + record_val = serde_json::to_value(raw_data).ok(); 90 + } 91 + } 92 + } 93 + 94 + MarshallableEvt { 95 + id, 96 + event_type: "record".into(), 97 + record: Some(RecordEvt { 98 + live, 99 + did, 100 + rev, 101 + collection, 102 + rkey, 103 + action, 104 + record: record_val, 105 + cid, 106 + }), 107 + identity: None, 108 + } 109 + } 110 + StoredEvent::Identity(identity) => MarshallableEvt { 111 + id, 112 + event_type: "identity".into(), 113 + record: None, 114 + identity: Some(identity), 115 + }, 116 + }; 117 + 118 + let json_str = match serde_json::to_string(&marshallable) { 119 + Ok(s) => s, 120 + Err(_) => continue, 121 + }; 122 + 123 + if tx.blocking_send(Message::Text(json_str.into())).is_err() { 124 + return; 125 + } 126 + found = true; 127 + } else { 128 + break; 129 + } 130 + } 131 + if !found { 132 + break; 133 + } 134 + } 135 + 136 + // 2. wait for live events 137 + match event_rx.blocking_recv() { 138 + Ok(_) => { 139 + // just wake up and run catch-up loop again 140 + } 141 + Err(broadcast::error::RecvError::Lagged(_)) => { 142 + // continue to catch up 143 + } 144 + Err(broadcast::error::RecvError::Closed) => { 145 + break; 146 + } 147 + } 148 + } 149 + }) 150 + .expect("failed to spawn stream handler thread"); 151 + 152 + while let Some(msg) = rx.recv().await { 153 + if socket.send(msg).await.is_err() { 154 + break; 155 + } 156 + } 157 + }
+307
src/api/xrpc.rs
···
··· 1 + use crate::api::AppState; 2 + use crate::db::{keys, Db}; 3 + use axum::{extract::State, http::StatusCode, Json, Router}; 4 + use jacquard::types::ident::AtIdentifier; 5 + use jacquard::{ 6 + api::com_atproto::repo::{ 7 + get_record::{GetRecordError, GetRecordOutput, GetRecordRequest}, 8 + list_records::{ListRecordsOutput, ListRecordsRequest, Record as RepoRecord}, 9 + }, 10 + xrpc::XrpcRequest, 11 + IntoStatic, 12 + }; 13 + use jacquard_api::com_atproto::repo::{get_record::GetRecord, list_records::ListRecords}; 14 + use jacquard_axum::{ExtractXrpc, IntoRouter, XrpcErrorResponse}; 15 + use jacquard_common::{ 16 + types::{ 17 + string::{AtUri, Cid}, 18 + value::Data, 19 + }, 20 + xrpc::{GenericXrpcError, XrpcError}, 21 + }; 22 + use serde::{Deserialize, Serialize}; 23 + use smol_str::ToSmolStr; 24 + use std::{fmt::Display, sync::Arc}; 25 + 26 + pub fn router() -> Router<Arc<AppState>> { 27 + Router::new() 28 + .merge(GetRecordRequest::into_router(handle_get_record)) 29 + .merge(ListRecordsRequest::into_router(handle_list_records)) 30 + .merge(CountRecords::into_router(handle_count_records)) 31 + } 32 + 33 + fn internal_error<E: std::error::Error + IntoStatic>( 34 + nsid: &'static str, 35 + message: impl Display, 36 + ) -> XrpcErrorResponse<E> { 37 + XrpcErrorResponse { 38 + status: StatusCode::INTERNAL_SERVER_ERROR, 39 + error: XrpcError::Generic(GenericXrpcError { 40 + error: "InternalError".into(), 41 + message: Some(message.to_smolstr()), 42 + nsid, 43 + method: "GET", 44 + http_status: StatusCode::INTERNAL_SERVER_ERROR, 45 + }), 46 + } 47 + } 48 + 49 + fn bad_request<E: std::error::Error + IntoStatic>( 50 + nsid: &'static str, 51 + message: impl Display, 52 + ) -> XrpcErrorResponse<E> { 53 + XrpcErrorResponse { 54 + status: StatusCode::BAD_REQUEST, 55 + error: XrpcError::Generic(GenericXrpcError { 56 + error: "InvalidRequest".into(), 57 + message: Some(message.to_smolstr()), 58 + nsid, 59 + method: "GET", 60 + http_status: StatusCode::BAD_REQUEST, 61 + }), 62 + } 63 + } 64 + 65 + pub async fn handle_get_record( 66 + State(state): State<Arc<AppState>>, 67 + ExtractXrpc(req): ExtractXrpc<GetRecordRequest>, 68 + ) -> Result<Json<GetRecordOutput<'static>>, XrpcErrorResponse<GetRecordError<'static>>> { 69 + let db = &state.db; 70 + let did = state 71 + .resolver 72 + .resolve_did(&req.repo) 73 + .await 74 + .map_err(|e| bad_request(GetRecord::NSID, e))?; 75 + 76 + let db_key = keys::record_key(&did, req.collection.as_str(), req.rkey.0.as_str()); 77 + 78 + let cid_bytes = Db::get(db.records.clone(), db_key) 79 + .await 80 + .map_err(|e| internal_error(GetRecord::NSID, e))?; 81 + 82 + if let Some(cid_bytes) = cid_bytes { 83 + let cid_str = 84 + std::str::from_utf8(&cid_bytes).map_err(|e| internal_error(GetRecord::NSID, e))?; 85 + 86 + let block_bytes = Db::get(db.blocks.clone(), keys::block_key(cid_str)) 87 + .await 88 + .map_err(|e| internal_error(GetRecord::NSID, e))? 89 + .ok_or_else(|| internal_error(GetRecord::NSID, "not found"))?; 90 + 91 + let value: Data = serde_ipld_dagcbor::from_slice(&block_bytes) 92 + .map_err(|e| internal_error(GetRecord::NSID, e))?; 93 + 94 + let cid = Cid::new(cid_str.as_bytes()).unwrap().into_static(); 95 + 96 + Ok(Json(GetRecordOutput { 97 + uri: AtUri::from_parts_owned( 98 + did.as_str(), 99 + req.collection.as_str(), 100 + req.rkey.0.as_str(), 101 + ) 102 + .unwrap(), 103 + cid: Some(cid), 104 + value: value.into_static(), 105 + extra_data: Default::default(), 106 + })) 107 + } else { 108 + Err(XrpcErrorResponse { 109 + status: StatusCode::NOT_FOUND, 110 + error: XrpcError::Xrpc(GetRecordError::RecordNotFound(None)), 111 + }) 112 + } 113 + } 114 + 115 + pub async fn handle_list_records( 116 + State(state): State<Arc<AppState>>, 117 + ExtractXrpc(req): ExtractXrpc<ListRecordsRequest>, 118 + ) -> Result<Json<ListRecordsOutput<'static>>, XrpcErrorResponse<GenericXrpcError>> { 119 + let db = &state.db; 120 + let did = state 121 + .resolver 122 + .resolve_did(&req.repo) 123 + .await 124 + .map_err(|e| bad_request(ListRecords::NSID, e))?; 125 + 126 + let prefix = format!( 127 + "{}{}{}{}", 128 + keys::did_prefix(&did), 129 + keys::SEP as char, 130 + req.collection.as_str(), 131 + keys::SEP as char 132 + ); 133 + 134 + let limit = req.limit.unwrap_or(50).min(100) as usize; 135 + let reverse = req.reverse.unwrap_or(false); 136 + let ks = db.records.clone(); 137 + let blocks_ks = db.blocks.clone(); 138 + 139 + let did_str = smol_str::SmolStr::from(did.as_str()); 140 + let collection_str = smol_str::SmolStr::from(req.collection.as_str()); 141 + 142 + let (results, cursor) = tokio::task::spawn_blocking(move || { 143 + let mut results = Vec::new(); 144 + let mut cursor = None; 145 + 146 + let mut end_prefix = prefix.clone().into_bytes(); 147 + if let Some(last) = end_prefix.last_mut() { 148 + *last += 1; 149 + } 150 + 151 + if !reverse { 152 + let end_key = if let Some(cursor) = &req.cursor { 153 + format!("{}{}", prefix, cursor).into_bytes() 154 + } else { 155 + end_prefix.clone() 156 + }; 157 + 158 + for item in ks.range(prefix.as_bytes()..end_key.as_slice()).rev() { 159 + let (key, cid_bytes) = item.into_inner().ok()?; 160 + 161 + if !key.starts_with(prefix.as_bytes()) { 162 + break; 163 + } 164 + if results.len() >= limit { 165 + let key_str = String::from_utf8_lossy(&key); 166 + if let Some(last_part) = key_str.split(keys::SEP as char).last() { 167 + cursor = Some(smol_str::SmolStr::from(last_part)); 168 + } 169 + break; 170 + } 171 + 172 + let key_str = String::from_utf8_lossy(&key); 173 + let parts: Vec<&str> = key_str.split(keys::SEP as char).collect(); 174 + if parts.len() == 3 { 175 + let rkey = parts[2]; 176 + let cid_str = std::str::from_utf8(&cid_bytes).ok()?; 177 + 178 + if let Ok(Some(block_bytes)) = blocks_ks.get(keys::block_key(cid_str)) { 179 + let val: Data = 180 + serde_ipld_dagcbor::from_slice(&block_bytes).unwrap_or(Data::Null); 181 + let cid = Cid::new(cid_str.as_bytes()).unwrap().into_static(); 182 + results.push(RepoRecord { 183 + uri: AtUri::from_parts_owned( 184 + did_str.as_str(), 185 + collection_str.as_str(), 186 + rkey, 187 + ) 188 + .unwrap(), 189 + cid, 190 + value: val.into_static(), 191 + extra_data: Default::default(), 192 + }); 193 + } 194 + } 195 + } 196 + } else { 197 + let start_key = if let Some(cursor) = &req.cursor { 198 + format!("{}{}\0", prefix, cursor).into_bytes() 199 + } else { 200 + prefix.clone().into_bytes() 201 + }; 202 + 203 + for item in ks.range(start_key.as_slice()..) { 204 + let (key, cid_bytes) = item.into_inner().ok()?; 205 + 206 + if !key.starts_with(prefix.as_bytes()) { 207 + break; 208 + } 209 + if results.len() >= limit { 210 + let key_str = String::from_utf8_lossy(&key); 211 + if let Some(last_part) = key_str.split(keys::SEP as char).last() { 212 + cursor = Some(smol_str::SmolStr::from(last_part)); 213 + } 214 + break; 215 + } 216 + 217 + let key_str = String::from_utf8_lossy(&key); 218 + let parts: Vec<&str> = key_str.split(keys::SEP as char).collect(); 219 + if parts.len() == 3 { 220 + let rkey = parts[2]; 221 + let cid_str = std::str::from_utf8(&cid_bytes).ok()?; 222 + 223 + if let Ok(Some(block_bytes)) = blocks_ks.get(keys::block_key(cid_str)) { 224 + let val: Data = 225 + serde_ipld_dagcbor::from_slice(&block_bytes).unwrap_or(Data::Null); 226 + let cid = Cid::new(cid_str.as_bytes()).unwrap().into_static(); 227 + results.push(RepoRecord { 228 + uri: AtUri::from_parts_owned( 229 + did_str.as_str(), 230 + collection_str.as_str(), 231 + rkey, 232 + ) 233 + .unwrap(), 234 + cid, 235 + value: val.into_static(), 236 + extra_data: Default::default(), 237 + }); 238 + } 239 + } 240 + } 241 + } 242 + Some((results, cursor)) 243 + }) 244 + .await 245 + .map_err(|e| internal_error(ListRecords::NSID, e))? 246 + .ok_or_else(|| internal_error(ListRecords::NSID, "not found"))?; 247 + 248 + Ok(Json(ListRecordsOutput { 249 + records: results, 250 + cursor: cursor.map(|c| c.into()), 251 + extra_data: Default::default(), 252 + })) 253 + } 254 + 255 + #[derive(Serialize, Deserialize, jacquard_derive::IntoStatic)] 256 + pub struct CountRecordsOutput { 257 + pub count: i64, 258 + } 259 + 260 + pub struct CountRecordsResponse; 261 + impl jacquard_common::xrpc::XrpcResp for CountRecordsResponse { 262 + const NSID: &'static str = "systems.gaze.hydrant.countRecords"; 263 + const ENCODING: &'static str = "application/json"; 264 + type Output<'de> = CountRecordsOutput; 265 + type Err<'de> = GenericXrpcError; 266 + } 267 + 268 + #[derive(Serialize, Deserialize, jacquard_derive::IntoStatic)] 269 + pub struct CountRecordsRequest<'i> { 270 + #[serde(borrow)] 271 + pub identifier: AtIdentifier<'i>, 272 + pub collection: String, 273 + } 274 + 275 + impl<'a> jacquard_common::xrpc::XrpcRequest for CountRecordsRequest<'a> { 276 + const NSID: &'static str = "systems.gaze.hydrant.countRecords"; 277 + const METHOD: jacquard_common::xrpc::XrpcMethod = jacquard_common::xrpc::XrpcMethod::Query; 278 + type Response = CountRecordsResponse; 279 + } 280 + 281 + pub struct CountRecords; 282 + impl jacquard_common::xrpc::XrpcEndpoint for CountRecords { 283 + const PATH: &'static str = "/xrpc/systems.gaze.hydrant.countRecords"; 284 + const METHOD: jacquard_common::xrpc::XrpcMethod = jacquard_common::xrpc::XrpcMethod::Query; 285 + type Request<'de> = CountRecordsRequest<'de>; 286 + type Response = CountRecordsResponse; 287 + } 288 + 289 + #[axum::debug_handler] 290 + pub async fn handle_count_records( 291 + State(state): State<Arc<AppState>>, 292 + ExtractXrpc(req): ExtractXrpc<CountRecords>, 293 + ) -> Result<Json<CountRecordsOutput>, XrpcErrorResponse<GenericXrpcError>> { 294 + let db = &state.db; 295 + let did = state 296 + .resolver 297 + .resolve_did(&req.identifier) 298 + .await 299 + .map_err(|e| bad_request(CountRecordsRequest::NSID, e))?; 300 + 301 + let count = db 302 + .get_count(keys::count_collection_key(&did, &req.collection)) 303 + .await 304 + .map_err(|e| internal_error(CountRecordsRequest::NSID, e))?; 305 + 306 + Ok(Json(CountRecordsOutput { count })) 307 + }
+106
src/backfill/manager.rs
···
··· 1 + use crate::db::keys::reconstruct_did; 2 + use crate::db::Db; 3 + use crate::state::AppState; 4 + use crate::types::ErrorState; 5 + use fjall::Slice; 6 + use miette::{IntoDiagnostic, Result}; 7 + use std::sync::Arc; 8 + use std::time::Duration; 9 + use tracing::{debug, error, info, warn}; 10 + 11 + pub async fn queue_pending_backfills(state: &AppState) -> Result<()> { 12 + info!("scanning for pending backfills..."); 13 + let mut count = 0; 14 + 15 + let ks = state.db.pending.clone(); 16 + let items = tokio::task::spawn_blocking(move || { 17 + let mut collected: Vec<Slice> = Vec::new(); 18 + for item in ks.iter() { 19 + let k = item.key().into_diagnostic()?; 20 + collected.push(k); 21 + } 22 + Ok::<Vec<Slice>, miette::Report>(collected) 23 + }) 24 + .await 25 + .into_diagnostic()??; 26 + 27 + for key in items { 28 + let did_str = String::from_utf8_lossy(&key); 29 + let Ok(did) = reconstruct_did(&did_str) else { 30 + error!("invalid did in db, skipping: did:{did_str}"); 31 + continue; 32 + }; 33 + 34 + debug!("queuing did {did}"); 35 + if let Err(e) = state.backfill_tx.send(did) { 36 + warn!("failed to queue pending backfill for did:{did_str}: {e}"); 37 + } else { 38 + count += 1; 39 + } 40 + } 41 + 42 + info!("queued {count} pending backfills"); 43 + Ok(()) 44 + } 45 + 46 + pub async fn retry_worker(state: Arc<AppState>) { 47 + let db = &state.db; 48 + info!("retry worker started"); 49 + loop { 50 + // sleep first (e.g., check every minute) 51 + tokio::time::sleep(Duration::from_secs(60)).await; 52 + 53 + let now = chrono::Utc::now().timestamp(); 54 + let mut count = 0; 55 + 56 + let ks = db.errors.clone(); 57 + let items = tokio::task::spawn_blocking(move || { 58 + let mut collected: Vec<(Slice, Slice)> = Vec::new(); 59 + for item in ks.iter() { 60 + let (k, v) = item.into_inner().into_diagnostic()?; 61 + collected.push((k, v)); 62 + } 63 + Ok::<_, miette::Report>(collected) 64 + }) 65 + .await 66 + .into_diagnostic() 67 + .unwrap_or_else(|e| { 68 + warn!("failed to scan errors: {e}"); 69 + Ok(Vec::new()) 70 + }) 71 + .unwrap_or_else(|e| { 72 + warn!("failed to scan errors: {e}"); 73 + Vec::new() 74 + }); 75 + 76 + for (key, value) in items { 77 + let did_str = String::from_utf8_lossy(&key); 78 + let Ok(did) = reconstruct_did(&did_str) else { 79 + error!("invalid did in db, skipping: {did_str}"); 80 + continue; 81 + }; 82 + if let Ok(err_state) = rmp_serde::from_slice::<ErrorState>(&value) { 83 + if err_state.next_retry <= now { 84 + debug!("retrying backfill for {did}"); 85 + 86 + // move back to pending 87 + if let Err(e) = Db::insert(db.pending.clone(), key, Vec::new()).await { 88 + warn!("failed to move {did} to pending: {e}"); 89 + continue; 90 + } 91 + 92 + // queue 93 + if let Err(e) = state.backfill_tx.send(did.to_owned()) { 94 + warn!("failed to queue retry for {did}: {e}"); 95 + } else { 96 + count += 1; 97 + } 98 + } 99 + } 100 + } 101 + 102 + if count > 0 { 103 + info!("queued {count} retries"); 104 + } 105 + } 106 + }
+430
src/backfill/mod.rs
···
··· 1 + use crate::db::{keys, Db}; 2 + use crate::ops; 3 + use crate::state::{AppState, BackfillRx}; 4 + use crate::types::{ErrorState, RepoState, RepoStatus, StoredEvent}; 5 + use futures::TryFutureExt; 6 + use jacquard::api::com_atproto::sync::get_repo::GetRepo; 7 + use jacquard::api::com_atproto::sync::subscribe_repos::Commit; 8 + use jacquard::prelude::*; 9 + use jacquard::types::did::Did; 10 + use jacquard_repo::mst::Mst; 11 + use jacquard_repo::MemoryBlockStore; 12 + use miette::{IntoDiagnostic, Result}; 13 + use smol_str::{SmolStr, ToSmolStr}; 14 + use std::collections::HashMap; 15 + use std::iter::once; 16 + use std::sync::atomic::Ordering; 17 + use std::sync::Arc; 18 + use std::time::{Duration, Instant}; 19 + use tokio::sync::Semaphore; 20 + use tracing::{debug, error, info, trace}; 21 + 22 + pub mod manager; 23 + 24 + pub struct Worker { 25 + state: Arc<AppState>, 26 + rx: BackfillRx, 27 + http: reqwest::Client, 28 + semaphore: Arc<Semaphore>, 29 + } 30 + 31 + impl Worker { 32 + pub fn new( 33 + state: Arc<AppState>, 34 + rx: BackfillRx, 35 + timeout: Duration, 36 + concurrency_limit: usize, 37 + ) -> Self { 38 + Self { 39 + state, 40 + rx, 41 + http: reqwest::Client::builder() 42 + .timeout(timeout) 43 + .zstd(true) 44 + .brotli(true) 45 + .gzip(true) 46 + .build() 47 + .expect("failed to build http client"), 48 + semaphore: Arc::new(Semaphore::new(concurrency_limit)), 49 + } 50 + } 51 + 52 + pub async fn run(mut self) { 53 + info!("backfill worker started"); 54 + while let Some(did) = self.rx.recv().await { 55 + let permit = self 56 + .semaphore 57 + .clone() 58 + .acquire_owned() 59 + .await 60 + .expect("semaphore closed"); 61 + 62 + tokio::spawn( 63 + Self::process_did_wrapper( 64 + self.state.clone(), 65 + self.http.clone(), 66 + did.clone(), 67 + permit, 68 + ) 69 + .inspect_err(move |e| error!("backfill process failed for {did}: {e}")), 70 + ); 71 + } 72 + } 73 + 74 + async fn process_did_wrapper( 75 + state: Arc<AppState>, 76 + http: reqwest::Client, 77 + did: Did<'static>, 78 + _permit: tokio::sync::OwnedSemaphorePermit, 79 + ) -> Result<()> { 80 + let db = &state.db; 81 + match Self::process_did(&state, &http, &did).await { 82 + Ok(previous_state) => { 83 + let did_key = keys::repo_key(&did); 84 + 85 + let is_pending = matches!( 86 + previous_state.status, 87 + RepoStatus::Backfilling | RepoStatus::New 88 + ); 89 + let is_error = matches!(previous_state.status, RepoStatus::Error(_)); 90 + 91 + let mut batch = db.inner.batch(); 92 + // remove from pending 93 + if is_pending { 94 + batch.remove(&db.pending, did_key); 95 + } 96 + // remove from errors (if it was a retry) 97 + if is_error { 98 + batch.remove(&db.errors, did_key); 99 + } 100 + 101 + tokio::task::spawn_blocking(move || batch.commit().into_diagnostic()) 102 + .await 103 + .into_diagnostic()??; 104 + 105 + tokio::spawn({ 106 + let state = state.clone(); 107 + async move { 108 + if is_pending { 109 + let _ = state 110 + .db 111 + .increment_count(keys::count_keyspace_key("pending"), -1) 112 + .await; 113 + } 114 + if is_error { 115 + let _ = state 116 + .db 117 + .increment_count(keys::count_keyspace_key("errors"), -1) 118 + .await; 119 + } 120 + } 121 + }); 122 + 123 + Ok(()) 124 + } 125 + Err(e) => { 126 + error!("backfill failed for {did}: {e}"); 127 + let did_key = keys::repo_key(&did); 128 + 129 + // 1. get current retry count 130 + let mut retry_count = 0; 131 + if let Ok(Some(bytes)) = Db::get(db.errors.clone(), did_key).await { 132 + if let Ok(old_err) = rmp_serde::from_slice::<ErrorState>(&bytes) { 133 + retry_count = old_err.retry_count + 1; 134 + } 135 + } 136 + 137 + // 2. calculate backoff 138 + let next_retry = ErrorState::next_backoff(retry_count); 139 + 140 + let err_state = ErrorState { 141 + error: e.to_string().into(), 142 + retry_count, 143 + next_retry, 144 + }; 145 + 146 + let mut batch = db.inner.batch(); 147 + 148 + // 3. save to errors 149 + let bytes = rmp_serde::to_vec(&err_state).into_diagnostic()?; 150 + batch.insert(&db.errors, did_key, bytes); 151 + 152 + // 4. update main repo state 153 + if let Some(state_bytes) = Db::get(db.repos.clone(), did_key).await? { 154 + let mut state: RepoState = 155 + rmp_serde::from_slice(&state_bytes).into_diagnostic()?; 156 + state.status = RepoStatus::Error(e.to_string().into()); 157 + let state_bytes = rmp_serde::to_vec(&state).into_diagnostic()?; 158 + batch.insert(&db.repos, did_key, state_bytes); 159 + } 160 + 161 + // 5. remove from pending (it's now in errors) 162 + batch.remove(&db.pending, did_key); 163 + 164 + tokio::task::spawn_blocking(move || batch.commit().into_diagnostic()) 165 + .await 166 + .into_diagnostic()??; 167 + 168 + Ok(()) 169 + } 170 + } 171 + } 172 + 173 + // returns previous repo state if successful 174 + async fn process_did( 175 + app_state: &Arc<AppState>, 176 + http: &reqwest::Client, 177 + did: &Did<'static>, 178 + ) -> Result<RepoState> { 179 + info!("backfilling {}", did); 180 + 181 + let db = &app_state.db; 182 + let did_key = keys::repo_key(did); 183 + let state_bytes = Db::get(db.repos.clone(), did_key) 184 + .await? 185 + .ok_or_else(|| miette::miette!("!!!THIS IS A BUG!!! repo state for {did} missing"))?; 186 + let mut state: RepoState = rmp_serde::from_slice(&state_bytes).into_diagnostic()?; 187 + let previous_state = state.clone(); 188 + 189 + // 1. resolve pds 190 + let start = Instant::now(); 191 + let pds_url = app_state.resolver.resolve_pds(did).await?; 192 + trace!( 193 + "resolved {} to pds {} in {:?}", 194 + did, 195 + pds_url, 196 + start.elapsed() 197 + ); 198 + 199 + // 2. fetch repo (car) 200 + let start = Instant::now(); 201 + let req = GetRepo::new().did(did.clone()).build(); 202 + let car_bytes = http 203 + .xrpc(pds_url) 204 + .send(&req) 205 + .await 206 + .into_diagnostic()? 207 + .into_output() 208 + .into_diagnostic()?; 209 + trace!( 210 + "fetched {} bytes for {} in {:?}", 211 + car_bytes.body.len(), 212 + did, 213 + start.elapsed() 214 + ); 215 + 216 + // 3. import repo 217 + let start = Instant::now(); 218 + let parsed = jacquard_repo::car::reader::parse_car_bytes(&car_bytes.body) 219 + .await 220 + .into_diagnostic()?; 221 + trace!("parsed car for {} in {:?}", did, start.elapsed()); 222 + 223 + let start = Instant::now(); 224 + let store = Arc::new(MemoryBlockStore::new()); 225 + for (_cid, bytes) in &parsed.blocks { 226 + jacquard_repo::BlockStore::put(store.as_ref(), bytes) 227 + .await 228 + .into_diagnostic()?; 229 + } 230 + trace!( 231 + "stored {} blocks in memory for {} in {:?}", 232 + parsed.blocks.len(), 233 + did, 234 + start.elapsed() 235 + ); 236 + 237 + // 4. parse root commit to get mst root 238 + let root_bytes = parsed 239 + .blocks 240 + .get(&parsed.root) 241 + .ok_or_else(|| miette::miette!("root block missing from CAR"))?; 242 + 243 + let commit = jacquard_repo::commit::Commit::from_cbor(root_bytes).into_diagnostic()?; 244 + info!("backfilling repo at revision {}", commit.rev); 245 + 246 + // 5. walk mst 247 + let start = Instant::now(); 248 + let mst: Mst<MemoryBlockStore> = Mst::load(store, commit.data, None); 249 + let leaves = mst.leaves().await.into_diagnostic()?; 250 + trace!("walked mst for {} in {:?}", did, start.elapsed()); 251 + 252 + // 6. insert records into db 253 + let start = Instant::now(); 254 + let (added_records, added_blocks, collection_counts, count) = { 255 + let app_state = app_state.clone(); 256 + let loop_did = did.clone(); 257 + let loop_rev = commit.rev; 258 + let storage = mst.storage().clone(); 259 + tokio::task::spawn_blocking(move || { 260 + let mut count = 0; 261 + let mut added_records = 0; 262 + let mut added_blocks = 0; 263 + let mut collection_counts: HashMap<SmolStr, i64> = HashMap::new(); 264 + let mut batch = app_state.db.inner.batch(); 265 + 266 + for (key, cid) in leaves { 267 + let val_bytes = tokio::runtime::Handle::current() 268 + .block_on(jacquard_repo::BlockStore::get(storage.as_ref(), &cid)) 269 + .into_diagnostic()?; 270 + 271 + if let Some(val) = val_bytes { 272 + let parts: Vec<&str> = key.splitn(2, '/').collect(); 273 + if parts.len() == 2 { 274 + let collection = parts[0]; 275 + let rkey = parts[1]; 276 + 277 + let db_key = keys::record_key(&loop_did, collection, rkey); 278 + let cid_str = cid.to_smolstr(); 279 + 280 + let val_vec: Vec<u8> = val.to_vec(); 281 + batch.insert( 282 + &app_state.db.blocks, 283 + keys::block_key(&cid_str), 284 + val_vec.clone(), 285 + ); 286 + 287 + batch.insert( 288 + &app_state.db.records, 289 + db_key, 290 + cid_str.as_bytes().to_vec(), 291 + ); 292 + 293 + added_records += 1; 294 + added_blocks += 1; 295 + *collection_counts 296 + .entry(collection.to_smolstr()) 297 + .or_default() += 1; 298 + 299 + let event_id = 300 + app_state.db.next_event_id.fetch_add(1, Ordering::SeqCst); 301 + let evt = StoredEvent::Record { 302 + live: false, 303 + did: loop_did.as_str().into(), 304 + rev: loop_rev.as_str().into(), 305 + collection: collection.into(), 306 + rkey: rkey.into(), 307 + action: "create".into(), 308 + cid: Some(cid_str), 309 + }; 310 + 311 + let bytes = rmp_serde::to_vec(&evt).into_diagnostic()?; 312 + batch.insert( 313 + &app_state.db.events, 314 + keys::event_key(event_id as i64), 315 + bytes, 316 + ); 317 + 318 + count += 1; 319 + } 320 + } 321 + } 322 + 323 + // 6. update status to synced (inside batch) 324 + state.status = RepoStatus::Synced; 325 + state.rev = loop_rev.as_str().into(); 326 + state.last_updated_at = chrono::Utc::now().timestamp(); 327 + 328 + let did_key = keys::repo_key(&loop_did); 329 + let bytes = rmp_serde::to_vec(&state).into_diagnostic()?; 330 + batch.insert(&app_state.db.repos, did_key, bytes); 331 + 332 + batch.commit().into_diagnostic()?; 333 + 334 + Ok::<_, miette::Report>((added_records, added_blocks, collection_counts, count)) 335 + }) 336 + .await 337 + .into_diagnostic()?? 338 + }; 339 + 340 + trace!( 341 + "inserted {} records into db for {} in {:?}", 342 + count, 343 + did, 344 + start.elapsed() 345 + ); 346 + 347 + // do the counts 348 + if added_records > 0 { 349 + tokio::spawn({ 350 + let state = app_state.clone(); 351 + let did = did.clone(); 352 + let records_fut = state 353 + .db 354 + .increment_count(keys::count_keyspace_key("records"), added_records); 355 + let blocks_fut = state 356 + .db 357 + .increment_count(keys::count_keyspace_key("blocks"), added_blocks); 358 + let events_fut = state 359 + .db 360 + .increment_count(keys::count_keyspace_key("events"), added_records); 361 + let collections_futs = collection_counts.into_iter().map(|(col, cnt)| { 362 + state 363 + .db 364 + .increment_count(keys::count_collection_key(&did, &col), cnt) 365 + }); 366 + futures::future::join_all( 367 + once(records_fut) 368 + .chain(once(blocks_fut)) 369 + .chain(once(events_fut)) 370 + .chain(collections_futs), 371 + ) 372 + }); 373 + } 374 + trace!( 375 + "committed backfill batch for {} in {:?}", 376 + did, 377 + start.elapsed() 378 + ); 379 + 380 + let _ = db 381 + .event_tx 382 + .send(db.next_event_id.load(Ordering::SeqCst) - 1); 383 + 384 + info!("marked {did} as synced, draining buffer..."); 385 + 386 + // 7. drain buffer 387 + let start = Instant::now(); 388 + let prefix = keys::buffer_prefix(did).to_vec(); 389 + 390 + let num_buffered = tokio::task::spawn_blocking({ 391 + let state = app_state.clone(); 392 + let did = did.clone(); 393 + move || -> Result<i64> { 394 + let mut batch = state.db.inner.batch(); 395 + 396 + for res in state 397 + .db 398 + .buffer 399 + .prefix(&prefix) 400 + .map(|item| item.into_inner().into_diagnostic()) 401 + { 402 + let (key, value) = res?; 403 + let commit: Commit = rmp_serde::from_slice(&value).into_diagnostic()?; 404 + debug!("applying buffered commit seq: {}", commit.seq); 405 + 406 + if let Err(e) = ops::apply_commit(&state.db, &commit, true) { 407 + error!("failed to apply buffered commit for {did}: {e}"); 408 + } 409 + 410 + // delete from buffer 411 + batch.remove(&state.db.buffer, key); 412 + } 413 + 414 + batch.commit().into_diagnostic()?; 415 + 416 + Ok(count) 417 + } 418 + }) 419 + .await 420 + .into_diagnostic()??; 421 + 422 + trace!( 423 + "drained {num_buffered} buffered commits for {did} in {:?}", 424 + start.elapsed() 425 + ); 426 + 427 + info!("backfill complete for {did}"); 428 + Ok(previous_state) 429 + } 430 + }
+89
src/config.rs
···
··· 1 + use miette::{IntoDiagnostic, Result}; 2 + use smol_str::SmolStr; 3 + use std::env; 4 + use std::path::PathBuf; 5 + use std::time::Duration; 6 + use url::Url; 7 + 8 + #[derive(Debug, Clone)] 9 + pub struct Config { 10 + pub database_path: PathBuf, 11 + pub relay_host: SmolStr, 12 + pub plc_url: Url, 13 + pub full_network: bool, 14 + pub cursor_save_interval: Duration, 15 + pub repo_fetch_timeout: Duration, 16 + pub log_level: SmolStr, 17 + pub api_port: u16, 18 + pub cache_size: u64, 19 + pub backfill_concurrency_limit: usize, 20 + pub disable_lz4_compression: bool, 21 + } 22 + 23 + impl Config { 24 + pub fn from_env() -> Result<Self> { 25 + let database_path = env::var("HYDRANT_DATABASE_PATH") 26 + .unwrap_or_else(|_| "./hydrant.db".to_string()) 27 + .into(); 28 + 29 + let relay_host = env::var("HYDRANT_RELAY_HOST") 30 + .unwrap_or_else(|_| "wss://relay.fire.hose.cam".to_string()) 31 + .into(); 32 + 33 + let plc_url = env::var("HYDRANT_PLC_URL") 34 + .unwrap_or_else(|_| "https://plc.wtf".to_string()) 35 + .parse() 36 + .into_diagnostic()?; 37 + 38 + let full_network = env::var("HYDRANT_FULL_NETWORK") 39 + .map(|v| v == "true") 40 + .unwrap_or(false); 41 + 42 + let cursor_save_interval = env::var("HYDRANT_CURSOR_SAVE_INTERVAL") 43 + .ok() 44 + .and_then(|s| humantime::parse_duration(&s).ok()) 45 + .unwrap_or(Duration::from_secs(10)); 46 + 47 + let repo_fetch_timeout = env::var("HYDRANT_REPO_FETCH_TIMEOUT") 48 + .ok() 49 + .and_then(|s| humantime::parse_duration(&s).ok()) 50 + .unwrap_or(Duration::from_secs(300)); 51 + 52 + let log_level = env::var("HYDRANT_LOG_LEVEL") 53 + .unwrap_or_else(|_| "info".to_string()) 54 + .into(); 55 + 56 + let api_port = env::var("HYDRANT_API_PORT") 57 + .ok() 58 + .and_then(|s| s.parse().ok()) 59 + .unwrap_or(3000); 60 + 61 + let cache_size = env::var("HYDRANT_CACHE_SIZE") 62 + .ok() 63 + .and_then(|s| s.parse().ok()) 64 + .unwrap_or(256); 65 + 66 + let backfill_concurrency_limit = env::var("HYDRANT_BACKFILL_CONCURRENCY_LIMIT") 67 + .ok() 68 + .and_then(|s| s.parse().ok()) 69 + .unwrap_or(32); 70 + 71 + let disable_lz4_compression = env::var("HYDRANT_NO_LZ4_COMPRESSION") 72 + .map(|v| v == "true") 73 + .unwrap_or(false); 74 + 75 + Ok(Self { 76 + database_path, 77 + relay_host, 78 + plc_url, 79 + full_network, 80 + cursor_save_interval, 81 + repo_fetch_timeout, 82 + log_level, 83 + api_port, 84 + cache_size, 85 + backfill_concurrency_limit, 86 + disable_lz4_compression, 87 + }) 88 + } 89 + }
+155
src/crawler/mod.rs
···
··· 1 + use crate::db::{keys, Db}; 2 + use crate::state::AppState; 3 + use crate::types::{RepoState, RepoStatus}; 4 + use jacquard::api::com_atproto::sync::list_repos::{ListRepos, ListReposOutput}; 5 + use jacquard::prelude::*; 6 + use jacquard::types::did::Did; 7 + use jacquard_common::CowStr; 8 + use miette::{IntoDiagnostic, Result}; 9 + use smol_str::SmolStr; 10 + use std::sync::Arc; 11 + use std::time::Duration; 12 + use tracing::{debug, error, info}; 13 + use url::Url; 14 + 15 + pub struct Crawler { 16 + state: Arc<AppState>, 17 + relay_host: SmolStr, 18 + http: reqwest::Client, 19 + } 20 + 21 + impl Crawler { 22 + pub fn new(state: Arc<AppState>, relay_host: SmolStr) -> Self { 23 + Self { 24 + state, 25 + relay_host, 26 + http: reqwest::Client::new(), 27 + } 28 + } 29 + 30 + pub async fn run(self) -> Result<()> { 31 + info!("crawler started"); 32 + 33 + let db = &self.state.db; 34 + 35 + let relay_url = Url::parse(&self.relay_host).into_diagnostic()?; 36 + 37 + // 1. load cursor 38 + let cursor_key = b"crawler_cursor"; 39 + let mut cursor: Option<SmolStr> = 40 + if let Ok(Some(bytes)) = Db::get(db.cursors.clone(), cursor_key.to_vec()).await { 41 + let s = String::from_utf8_lossy(&bytes); 42 + info!("resuming crawler from cursor: {}", s); 43 + Some(s.into()) 44 + } else { 45 + None 46 + }; 47 + 48 + loop { 49 + // 2. fetch listrepos 50 + let req = ListRepos::new() 51 + .limit(1000) 52 + .maybe_cursor(cursor.clone().map(|c| CowStr::from(c.to_string()))) 53 + .build(); 54 + 55 + let res_result = self.http.xrpc(relay_url.clone()).send(&req).await; 56 + 57 + let output: ListReposOutput = match res_result { 58 + Ok(res) => res.into_output().into_diagnostic()?, 59 + Err(e) => { 60 + error!("crawler failed to list repos: {}. retrying in 30s...", e); 61 + tokio::time::sleep(Duration::from_secs(30)).await; 62 + continue; 63 + } 64 + }; 65 + 66 + if output.repos.is_empty() { 67 + info!("crawler finished enumeration (or empty page). sleeping for 1 hour."); 68 + tokio::time::sleep(Duration::from_secs(3600)).await; 69 + // we might want to reset cursor to start over? tap seems to loop. 70 + // for now, just wait. 71 + continue; 72 + } 73 + 74 + info!("crawler fetched {} repos...", output.repos.len()); 75 + 76 + let mut batch = db.inner.batch(); 77 + let mut to_queue = Vec::new(); 78 + 79 + // 3. process repos 80 + for repo in output.repos { 81 + let did_str = smol_str::SmolStr::from(repo.did.as_str()); 82 + let did_key = keys::repo_key(&repo.did); 83 + 84 + // check if known 85 + if !Db::contains_key(db.repos.clone(), did_key).await? { 86 + debug!("crawler found new repo: {}", did_str); 87 + 88 + // create state (backfilling) 89 + let mut state = RepoState::new(repo.did.to_owned()); 90 + state.status = RepoStatus::Backfilling; 91 + let bytes = rmp_serde::to_vec(&state).into_diagnostic()?; 92 + 93 + batch.insert(&db.repos, did_key, bytes); 94 + batch.insert(&db.pending, did_key, Vec::new()); 95 + to_queue.push(did_str); 96 + } 97 + } 98 + 99 + // update counts if we found new repos 100 + if !to_queue.is_empty() { 101 + let count = to_queue.len() as i64; 102 + tokio::spawn({ 103 + let state = self.state.clone(); 104 + async move { 105 + let _ = state 106 + .db 107 + .increment_count(keys::count_keyspace_key("repos"), count) 108 + .await; 109 + let _ = state 110 + .db 111 + .increment_count(keys::count_keyspace_key("pending"), count) 112 + .await; 113 + } 114 + }); 115 + } 116 + 117 + // 4. update cursor 118 + if let Some(new_cursor) = output.cursor { 119 + cursor = Some(new_cursor.as_str().into()); 120 + 121 + batch.insert( 122 + &db.cursors, 123 + cursor_key.to_vec(), 124 + new_cursor.as_bytes().to_vec(), 125 + ); 126 + } else { 127 + // end of pagination 128 + info!("crawler reached end of list."); 129 + cursor = None; 130 + } 131 + 132 + tokio::task::spawn_blocking(move || batch.commit().into_diagnostic()) 133 + .await 134 + .into_diagnostic()??; 135 + 136 + // 5. queue for backfill 137 + for did_str in to_queue { 138 + let did = match Did::new_owned(did_str.as_str()) { 139 + Ok(d) => d, 140 + Err(e) => { 141 + error!("got invalid DID ({did_str}) from relay, skipping this: {e}"); 142 + continue; 143 + } 144 + }; 145 + if let Err(e) = self.state.backfill_tx.send(did) { 146 + error!("crawler failed to queue {did_str}: {e}"); 147 + } 148 + } 149 + 150 + if cursor.is_none() { 151 + tokio::time::sleep(Duration::from_secs(3600)).await; 152 + } 153 + } 154 + } 155 + }
+69
src/db/keys.rs
···
··· 1 + use jacquard_common::types::string::Did; 2 + use miette::{Context, IntoDiagnostic}; 3 + 4 + /// separator used for composite keys 5 + pub const SEP: u8 = 0x00; 6 + 7 + pub fn did_prefix<'a>(did: &'a Did<'a>) -> &'a str { 8 + did.as_str().trim_start_matches("did:") 9 + } 10 + 11 + pub fn reconstruct_did<'a>(trimmed_did: &'a str) -> Result<Did<'static>, miette::Error> { 12 + Did::new_owned(format!("did:{trimmed_did}")) 13 + .into_diagnostic() 14 + .wrap_err("expected did to be trimmed") 15 + } 16 + 17 + // Key format: {DID} (trimmed) 18 + pub fn repo_key<'a>(did: &'a Did) -> &'a [u8] { 19 + did_prefix(did).as_bytes() 20 + } 21 + 22 + // key format: {DID}\x00{Collection}\x00{RKey} (DID trimmed) 23 + pub fn record_key(did: &Did, collection: &str, rkey: &str) -> Vec<u8> { 24 + let prefix = did_prefix(did); 25 + let mut key = Vec::with_capacity(prefix.len() + collection.len() + rkey.len() + 2); 26 + key.extend_from_slice(prefix.as_bytes()); 27 + key.push(SEP); 28 + key.extend_from_slice(collection.as_bytes()); 29 + key.push(SEP); 30 + key.extend_from_slice(rkey.as_bytes()); 31 + key 32 + } 33 + 34 + // key format: {DID} 35 + pub fn buffer_prefix<'a>(did: &'a Did) -> &'a [u8] { 36 + repo_key(did) 37 + } 38 + 39 + // key format: {SEQ} 40 + pub fn event_key(seq: i64) -> [u8; 8] { 41 + seq.to_be_bytes() 42 + } 43 + 44 + // key format: {CID} 45 + pub fn block_key(cid: &str) -> &[u8] { 46 + cid.as_bytes() 47 + } 48 + 49 + // count keys for the counts keyspace 50 + // key format: k\x00{keyspace_name} 51 + pub fn count_keyspace_key(name: &str) -> Vec<u8> { 52 + let mut key = Vec::with_capacity(2 + name.len()); 53 + key.push(b'k'); 54 + key.push(SEP); 55 + key.extend_from_slice(name.as_bytes()); 56 + key 57 + } 58 + 59 + // key format: r\x00{DID}\x00{collection} (DID trimmed) 60 + pub fn count_collection_key(did: &Did, collection: &str) -> Vec<u8> { 61 + let prefix = did_prefix(did); 62 + let mut key = Vec::with_capacity(2 + prefix.len() + 1 + collection.len()); 63 + key.push(b'r'); 64 + key.push(SEP); 65 + key.extend_from_slice(prefix.as_bytes()); 66 + key.push(SEP); 67 + key.extend_from_slice(collection.as_bytes()); 68 + key 69 + }
+193
src/db/mod.rs
···
··· 1 + use fjall::{Database, Keyspace, KeyspaceCreateOptions, PersistMode, Slice}; 2 + use futures::FutureExt; 3 + use miette::{IntoDiagnostic, Result}; 4 + use std::future::Future; 5 + use std::path::Path; 6 + use std::sync::Arc; 7 + 8 + pub mod keys; 9 + 10 + use std::sync::atomic::AtomicU64; 11 + use tokio::sync::broadcast; 12 + 13 + #[derive(Clone)] 14 + pub struct Db { 15 + pub inner: Arc<Database>, 16 + pub repos: Keyspace, 17 + pub records: Keyspace, 18 + pub blocks: Keyspace, 19 + pub cursors: Keyspace, 20 + pub buffer: Keyspace, 21 + pub pending: Keyspace, 22 + pub errors: Keyspace, 23 + pub events: Keyspace, 24 + pub counts: Keyspace, 25 + pub event_tx: broadcast::Sender<u64>, 26 + pub next_event_id: Arc<AtomicU64>, 27 + } 28 + 29 + impl Db { 30 + pub fn open( 31 + path: impl AsRef<Path>, 32 + cache_size: u64, 33 + disable_lz4_compression: bool, 34 + ) -> Result<Self> { 35 + let db = Database::builder(path.as_ref()) 36 + .cache_size(cache_size * 2_u64.pow(20) / 2) 37 + .manual_journal_persist(true) 38 + .journal_compression( 39 + disable_lz4_compression 40 + .then_some(fjall::CompressionType::None) 41 + .unwrap_or(fjall::CompressionType::Lz4), 42 + ) 43 + .open() 44 + .into_diagnostic()?; 45 + let db = Arc::new(db); 46 + 47 + let opts = || KeyspaceCreateOptions::default(); 48 + 49 + let repos = db.keyspace("repos", opts).into_diagnostic()?; 50 + let records = db 51 + .keyspace("records", || opts().max_memtable_size(32 * 2_u64.pow(20))) 52 + .into_diagnostic()?; 53 + 54 + let block_opts = || { 55 + opts() 56 + // point reads are used a lot by stream 57 + .expect_point_read_hits(true) 58 + .max_memtable_size(32 * 2_u64.pow(20)) 59 + }; 60 + let blocks = db.keyspace("blocks", block_opts).into_diagnostic()?; 61 + let cursors = db.keyspace("cursors", opts).into_diagnostic()?; 62 + let buffer = db.keyspace("buffer", opts).into_diagnostic()?; 63 + let pending = db.keyspace("pending", opts).into_diagnostic()?; 64 + let errors = db.keyspace("errors", opts).into_diagnostic()?; 65 + let events = db.keyspace("events", opts).into_diagnostic()?; 66 + let counts = db.keyspace("counts", opts).into_diagnostic()?; 67 + 68 + let mut last_id = 0; 69 + let mut iter = events.iter(); 70 + if let Some(guard) = iter.next_back() { 71 + let res: Result<fjall::KvPair, _> = guard.into_inner().map_err(|e| miette::miette!(e)); 72 + if let Ok(kv) = res { 73 + let k = &kv.0; 74 + let mut buf = [0u8; 8]; 75 + if k.len() == 8 { 76 + buf.copy_from_slice(k); 77 + last_id = u64::from_be_bytes(buf); 78 + } 79 + } 80 + } 81 + 82 + let (event_tx, _) = broadcast::channel(10000); 83 + 84 + Ok(Self { 85 + inner: db, 86 + repos, 87 + records, 88 + blocks, 89 + cursors, 90 + buffer, 91 + pending, 92 + errors, 93 + events, 94 + counts, 95 + event_tx, 96 + next_event_id: Arc::new(AtomicU64::new(last_id + 1)), 97 + }) 98 + } 99 + 100 + pub fn persist(&self) -> Result<()> { 101 + self.inner 102 + .persist(PersistMode::SyncData) 103 + .into_diagnostic()?; 104 + Ok(()) 105 + } 106 + 107 + pub async fn get(ks: Keyspace, key: impl AsRef<[u8]>) -> Result<Option<Slice>> { 108 + let key = key.as_ref().to_vec(); 109 + tokio::task::spawn_blocking(move || ks.get(key).into_diagnostic()) 110 + .await 111 + .into_diagnostic()? 112 + } 113 + 114 + pub async fn insert( 115 + ks: Keyspace, 116 + key: impl AsRef<[u8]>, 117 + value: impl AsRef<[u8]>, 118 + ) -> Result<()> { 119 + let key = key.as_ref().to_vec(); 120 + let value = value.as_ref().to_vec(); 121 + tokio::task::spawn_blocking(move || ks.insert(key, value).into_diagnostic()) 122 + .await 123 + .into_diagnostic()? 124 + } 125 + 126 + #[allow(dead_code)] 127 + pub async fn remove(ks: Keyspace, key: impl AsRef<[u8]>) -> Result<()> { 128 + let key = key.as_ref().to_vec(); 129 + tokio::task::spawn_blocking(move || ks.remove(key).into_diagnostic()) 130 + .await 131 + .into_diagnostic()? 132 + } 133 + 134 + pub async fn contains_key(ks: Keyspace, key: impl AsRef<[u8]>) -> Result<bool> { 135 + let key = key.as_ref().to_vec(); 136 + tokio::task::spawn_blocking(move || ks.contains_key(key).into_diagnostic()) 137 + .await 138 + .into_diagnostic()? 139 + } 140 + 141 + pub fn increment_count( 142 + &self, 143 + key: impl AsRef<[u8]>, 144 + delta: i64, 145 + ) -> impl Future<Output = Result<i64>> + Send + 'static { 146 + let key = key.as_ref().to_vec(); 147 + let counts = self.counts.clone(); 148 + tokio::task::spawn_blocking(move || { 149 + let current = counts 150 + .get(&key) 151 + .into_diagnostic()? 152 + .map(|v| { 153 + let mut buf = [0u8; 8]; 154 + if v.len() == 8 { 155 + buf.copy_from_slice(&v); 156 + i64::from_be_bytes(buf) 157 + } else { 158 + 0 159 + } 160 + }) 161 + .unwrap_or(0); 162 + let new_val = current.saturating_add(delta); 163 + counts 164 + .insert(key, new_val.to_be_bytes()) 165 + .into_diagnostic()?; 166 + Ok(new_val) 167 + }) 168 + .map(|res| res.into_diagnostic().flatten()) 169 + } 170 + 171 + pub async fn get_count(&self, key: impl AsRef<[u8]>) -> Result<i64> { 172 + let key = key.as_ref().to_vec(); 173 + let counts = self.counts.clone(); 174 + tokio::task::spawn_blocking(move || { 175 + Ok(counts 176 + .get(&key) 177 + .into_diagnostic()? 178 + .map(|v| { 179 + let mut buf = [0u8; 8]; 180 + if v.len() == 8 { 181 + buf.copy_from_slice(&v); 182 + i64::from_be_bytes(buf) 183 + } else { 184 + 0 185 + } 186 + }) 187 + .unwrap_or(0)) 188 + }) 189 + .await 190 + .into_diagnostic() 191 + .flatten() 192 + } 193 + }
+246
src/ingest/mod.rs
···
··· 1 + use crate::db::{keys, Db}; 2 + use crate::ops; 3 + use crate::state::AppState; 4 + use crate::types::{RepoState, RepoStatus}; 5 + use jacquard::api::com_atproto::sync::subscribe_repos::{SubscribeRepos, SubscribeReposMessage}; 6 + use jacquard_common::xrpc::{SubscriptionClient, TungsteniteSubscriptionClient}; 7 + use jacquard_common::IntoStatic; 8 + use miette::{IntoDiagnostic, Result}; 9 + use n0_future::StreamExt; 10 + use smol_str::SmolStr; 11 + use std::sync::atomic::Ordering; 12 + use std::sync::Arc; 13 + use tokio::sync::mpsc; 14 + use tracing::{debug, error, info}; 15 + use url::Url; 16 + 17 + pub struct Ingestor { 18 + state: Arc<AppState>, 19 + relay_host: SmolStr, 20 + buffer_tx: mpsc::Sender<(Vec<u8>, Vec<u8>)>, 21 + full_network: bool, 22 + } 23 + 24 + impl Ingestor { 25 + pub fn new(state: Arc<AppState>, relay_host: SmolStr, full_network: bool) -> Self { 26 + let (buffer_tx, mut buffer_rx) = mpsc::channel::<(Vec<u8>, Vec<u8>)>(1000); 27 + 28 + let state_clone = state.clone(); 29 + tokio::spawn(async move { 30 + let mut batch_items = Vec::with_capacity(100); 31 + const MAX_BATCH_SIZE: usize = 100; 32 + const BATCH_TIMEOUT: std::time::Duration = std::time::Duration::from_millis(10); 33 + 34 + loop { 35 + // wait for at least one item 36 + match buffer_rx.recv().await { 37 + Some(item) => batch_items.push(item), 38 + None => break, 39 + } 40 + 41 + // collect more items until batch is full or timeout 42 + let deadline = tokio::time::Instant::now() + BATCH_TIMEOUT; 43 + while batch_items.len() < MAX_BATCH_SIZE { 44 + match tokio::time::timeout_at(deadline, buffer_rx.recv()).await { 45 + Ok(Some(item)) => batch_items.push(item), 46 + Ok(None) => break, // channel closed 47 + Err(_) => break, // timeout reached 48 + } 49 + } 50 + 51 + if !batch_items.is_empty() { 52 + let mut batch = state_clone.db.inner.batch(); 53 + for (k, v) in batch_items.drain(..) { 54 + batch.insert(&state_clone.db.buffer, k, v); 55 + } 56 + 57 + let res = tokio::task::spawn_blocking(move || batch.commit()).await; 58 + match res { 59 + Ok(Ok(_)) => {} 60 + Ok(Err(e)) => error!("failed to persist buffer batch: {}", e), 61 + Err(e) => error!("buffer worker join error: {}", e), 62 + } 63 + } 64 + } 65 + }); 66 + 67 + Self { 68 + state, 69 + relay_host, 70 + buffer_tx, 71 + full_network, 72 + } 73 + } 74 + 75 + pub async fn run(mut self) -> Result<()> { 76 + let base_url = Url::parse(&self.relay_host).into_diagnostic()?; 77 + 78 + // 1. load cursor 79 + let cursor_key = b"firehose_cursor"; 80 + let start_cursor = if let Ok(Some(bytes)) = 81 + Db::get(self.state.db.cursors.clone(), cursor_key.to_vec()).await 82 + { 83 + let s = String::from_utf8_lossy(&bytes); 84 + debug!("resuming from cursor: {}", s); 85 + Some(s.parse::<i64>().unwrap_or(0)) 86 + } else { 87 + info!("no cursor found, live tailing"); 88 + None 89 + }; 90 + 91 + if let Some(c) = start_cursor { 92 + self.state.cur_firehose.store(c, Ordering::SeqCst); 93 + } 94 + 95 + // 2. connect 96 + let client = TungsteniteSubscriptionClient::from_base_uri(base_url); 97 + let params = if let Some(c) = start_cursor { 98 + SubscribeRepos::new().cursor(c).build() 99 + } else { 100 + SubscribeRepos::new().build() 101 + }; 102 + 103 + let stream = client.subscribe(&params).await.into_diagnostic()?; 104 + let (_sink, mut messages) = stream.into_stream(); 105 + 106 + info!("firehose connected"); 107 + 108 + // 3. process loop 109 + while let Some(msg_res) = messages.next().await { 110 + match msg_res { 111 + Ok(msg) => { 112 + self.handle_message(msg).await?; 113 + } 114 + Err(e) => { 115 + error!("firehose stream error: {}", e); 116 + break; 117 + } 118 + } 119 + } 120 + 121 + Ok(()) 122 + } 123 + 124 + async fn handle_message(&mut self, msg: SubscribeReposMessage<'_>) -> Result<()> { 125 + let db = self.state.db.clone(); 126 + match msg { 127 + SubscribeReposMessage::Commit(commit) => { 128 + self.state.cur_firehose.store(commit.seq, Ordering::SeqCst); 129 + 130 + let did = &commit.repo; 131 + 132 + let mut should_process = self.full_network; 133 + let did_key = keys::repo_key(&did); 134 + 135 + if !should_process { 136 + if Db::contains_key(db.repos.clone(), did_key).await? { 137 + should_process = true; 138 + } 139 + } 140 + 141 + if !should_process { 142 + return Ok(()); 143 + } 144 + 145 + // check repo state 146 + let state_bytes = Db::get(db.repos.clone(), did_key).await?; 147 + 148 + let repo_state = if let Some(bytes) = state_bytes { 149 + rmp_serde::from_slice::<RepoState>(&bytes).ok() 150 + } else { 151 + None 152 + }; 153 + 154 + let status = repo_state 155 + .as_ref() 156 + .map(|s| s.status.clone()) 157 + .unwrap_or(RepoStatus::New); 158 + 159 + match status { 160 + RepoStatus::New => { 161 + info!("new repo detected: {}", did); 162 + // 1. save state as backfilling 163 + let mut new_state = RepoState::new(commit.repo.clone().into_static()); 164 + new_state.status = RepoStatus::Backfilling; 165 + let bytes = rmp_serde::to_vec(&new_state).into_diagnostic()?; 166 + 167 + let mut batch = db.inner.batch(); 168 + batch.insert(&db.repos, did_key, bytes); 169 + batch.insert(&db.pending, did_key, Vec::new()); 170 + 171 + tokio::task::spawn_blocking(move || batch.commit().into_diagnostic()) 172 + .await 173 + .into_diagnostic()??; 174 + 175 + // 2. queue for backfill 176 + if let Err(e) = self.state.backfill_tx.send(did.clone().into_static()) { 177 + error!("failed to queue backfill for {}: {}", did, e); 178 + } 179 + 180 + // 3. buffer this event 181 + self.buffer_event(&commit).await?; 182 + } 183 + RepoStatus::Backfilling => { 184 + debug!("buffering event for backfilling repo: {}", did); 185 + self.buffer_event(&commit).await?; 186 + } 187 + RepoStatus::Synced => { 188 + // check revision 189 + if let Some(state) = repo_state { 190 + if !state.rev.is_empty() && commit.rev.as_str() <= state.rev.as_str() { 191 + debug!( 192 + "skipping replayed event for {}: {} <= {}", 193 + did, 194 + commit.rev, 195 + state.rev 196 + ); 197 + return Ok(()); 198 + } 199 + } 200 + 201 + // apply immediately 202 + let db = db.clone(); 203 + let commit = commit.clone().into_static(); 204 + let did = did.clone().into_static(); 205 + 206 + tokio::task::spawn_blocking(move || { 207 + if let Err(e) = ops::apply_commit(&db, &commit, true) { 208 + error!("failed to apply live commit for {}: {}", did, e); 209 + } else { 210 + debug!("synced event for {}, {} ops", did, commit.ops.len()); 211 + } 212 + }) 213 + .await 214 + .into_diagnostic()?; 215 + } 216 + RepoStatus::Error(_) => { 217 + // maybe retry? for now ignore. 218 + } 219 + } 220 + } 221 + _ => {} // ignore identity/account/etc for now 222 + } 223 + Ok(()) 224 + } 225 + 226 + async fn buffer_event( 227 + &mut self, 228 + commit: &jacquard::api::com_atproto::sync::subscribe_repos::Commit<'_>, 229 + ) -> Result<()> { 230 + // we need to store the event to replay it later. 231 + // key: {DID}\x00{SEQ} -> guarantees ordering 232 + let mut key = Vec::new(); 233 + key.extend_from_slice(keys::buffer_prefix(&commit.repo)); 234 + key.push(0x00); 235 + key.extend_from_slice(&commit.seq.to_be_bytes()); 236 + 237 + // value: serialized commit 238 + let val = rmp_serde::to_vec(commit).into_diagnostic()?; 239 + 240 + if let Err(e) = self.buffer_tx.send((key, val)).await { 241 + error!("failed to buffer event (channel closed): {}", e); 242 + } 243 + 244 + Ok(()) 245 + } 246 + }
+156
src/main.rs
···
··· 1 + mod api; 2 + mod backfill; 3 + mod config; 4 + mod crawler; 5 + mod db; 6 + mod ingest; 7 + mod ops; 8 + mod resolver; 9 + mod state; 10 + mod types; 11 + 12 + use crate::backfill::Worker; 13 + use crate::config::Config; 14 + use crate::crawler::Crawler; 15 + use crate::db::Db; 16 + use crate::ingest::Ingestor; 17 + use crate::state::AppState; 18 + use mimalloc::MiMalloc; 19 + use std::sync::atomic::Ordering; 20 + use std::sync::Arc; 21 + use tracing::{error, info}; 22 + 23 + #[global_allocator] 24 + static GLOBAL: MiMalloc = MiMalloc; 25 + 26 + #[tokio::main] 27 + async fn main() -> miette::Result<()> { 28 + let cfg = Config::from_env()?; 29 + 30 + let env_filter = tracing_subscriber::EnvFilter::new(&cfg.log_level); 31 + tracing_subscriber::fmt().with_env_filter(env_filter).init(); 32 + 33 + info!("starting hydrant with config: {cfg:?}"); 34 + 35 + let (state, backfill_rx) = AppState::new(&cfg)?; 36 + let state = Arc::new(state); 37 + 38 + tokio::spawn({ 39 + let port = cfg.api_port; 40 + let state = state.clone(); 41 + async move { 42 + if let Err(e) = api::serve(state, port).await { 43 + error!("API server failed: {e}"); 44 + } 45 + } 46 + }); 47 + 48 + tokio::spawn({ 49 + let state = state.clone(); 50 + let timeout = cfg.repo_fetch_timeout; 51 + async move { 52 + let worker = Worker::new(state, backfill_rx, timeout, cfg.backfill_concurrency_limit); 53 + worker.run().await; 54 + } 55 + }); 56 + 57 + if let Err(e) = crate::backfill::manager::queue_pending_backfills(&state).await { 58 + error!("failed to queue pending backfills: {e}"); 59 + } 60 + 61 + tokio::spawn({ 62 + let state = state.clone(); 63 + async move { 64 + crate::backfill::manager::retry_worker(state).await; 65 + } 66 + }); 67 + 68 + tokio::spawn({ 69 + let state = state.clone(); 70 + async move { 71 + let mut last_id = state.db.next_event_id.load(Ordering::Relaxed); 72 + let mut last_time = std::time::Instant::now(); 73 + let mut interval = tokio::time::interval(std::time::Duration::from_secs(60)); 74 + 75 + loop { 76 + interval.tick().await; 77 + 78 + let current_id = state.db.next_event_id.load(Ordering::Relaxed); 79 + let current_time = std::time::Instant::now(); 80 + 81 + let delta = current_id.saturating_sub(last_id); 82 + let elapsed = current_time.duration_since(last_time).as_secs_f64(); 83 + let rate = if elapsed > 0.0 { 84 + delta as f64 / elapsed 85 + } else { 86 + 0.0 87 + }; 88 + 89 + info!("{rate:.2} events/s ({delta} events in {elapsed:.1}s)"); 90 + 91 + last_id = current_id; 92 + last_time = current_time; 93 + } 94 + } 95 + }); 96 + 97 + tokio::spawn({ 98 + let state = state.clone(); 99 + let persist_interval = cfg.cursor_save_interval; 100 + 101 + async move { 102 + info!("persistence worker started"); 103 + loop { 104 + tokio::time::sleep(persist_interval).await; 105 + 106 + let seq = state.cur_firehose.load(Ordering::SeqCst); 107 + const CURSOR_KEY: &[u8] = b"firehose_cursor"; 108 + if let Err(e) = Db::insert( 109 + state.db.cursors.clone(), 110 + CURSOR_KEY, 111 + seq.to_string().into_bytes(), 112 + ) 113 + .await 114 + { 115 + error!("failed to save cursor: {e}"); 116 + } 117 + 118 + let state = state.clone(); 119 + let res = tokio::task::spawn_blocking(move || state.db.persist()).await; 120 + 121 + match res { 122 + Ok(Err(e)) => { 123 + error!("db persist failed: {e}"); 124 + } 125 + Err(e) => { 126 + error!("persistence task join failed: {e}"); 127 + } 128 + _ => {} 129 + } 130 + } 131 + } 132 + }); 133 + 134 + if cfg.full_network { 135 + tokio::spawn({ 136 + let state = state.clone(); 137 + let crawler_host = cfg.relay_host.clone(); 138 + async move { 139 + let crawler = Crawler::new(state, crawler_host); 140 + if let Err(e) = crawler.run().await { 141 + error!("crawler died: {e}"); 142 + } 143 + } 144 + }); 145 + } 146 + 147 + let ingestor = Ingestor::new(state.clone(), cfg.relay_host.clone(), cfg.full_network); 148 + 149 + if let Err(e) = ingestor.run().await { 150 + error!("ingestor died: {e}"); 151 + } 152 + 153 + state.db.persist()?; 154 + 155 + Ok(()) 156 + }
+141
src/ops.rs
···
··· 1 + use crate::db::{keys, Db}; 2 + use crate::types::{RepoState, StoredEvent}; 3 + use jacquard::api::com_atproto::sync::subscribe_repos::Commit; 4 + use jacquard::cowstr::ToCowStr; 5 + use jacquard_repo::car::reader::parse_car_bytes; 6 + use miette::{IntoDiagnostic, Result}; 7 + use smol_str::{SmolStr, ToSmolStr}; 8 + use std::collections::HashMap; 9 + use std::sync::atomic::Ordering; 10 + use std::time::Instant; 11 + use tracing::{debug, trace}; 12 + 13 + pub fn apply_commit(db: &Db, commit: &Commit<'_>, live: bool) -> Result<()> { 14 + let did = &commit.repo; 15 + debug!("applying commit {} for {did}", &commit.commit); 16 + 17 + // 1. parse CAR blocks and store them in CAS 18 + let start = Instant::now(); 19 + let parsed = tokio::task::block_in_place(|| { 20 + tokio::runtime::Handle::current() 21 + .block_on(parse_car_bytes(commit.blocks.as_ref())) 22 + .into_diagnostic() 23 + })?; 24 + 25 + trace!("parsed car for {did} in {:?}", start.elapsed()); 26 + 27 + let mut batch = db.inner.batch(); 28 + 29 + let did_key = keys::repo_key(did); 30 + if let Some(state_bytes) = db.repos.get(did_key).into_diagnostic()? { 31 + let mut state: RepoState = rmp_serde::from_slice(&state_bytes).into_diagnostic()?; 32 + state.rev = commit.rev.as_str().into(); 33 + state.last_updated_at = chrono::Utc::now().timestamp(); 34 + let bytes = rmp_serde::to_vec(&state).into_diagnostic()?; 35 + batch.insert(&db.repos, did_key, bytes); 36 + } 37 + 38 + // store all blocks in the CAS 39 + for (cid, bytes) in &parsed.blocks { 40 + batch.insert( 41 + &db.blocks, 42 + keys::block_key(&cid.to_cowstr()), 43 + bytes.to_vec(), 44 + ); 45 + } 46 + 47 + // 2. iterate ops and update records index 48 + let mut records_delta = 0; 49 + let mut events_count = 0; 50 + let mut collection_deltas: HashMap<SmolStr, i64> = HashMap::new(); 51 + 52 + for op in &commit.ops { 53 + let parts: Vec<&str> = op.path.splitn(2, '/').collect(); 54 + if parts.len() != 2 { 55 + continue; 56 + } 57 + let collection = parts[0]; 58 + let rkey = parts[1]; 59 + 60 + let db_key = keys::record_key(did, collection, rkey); 61 + 62 + let event_id = db.next_event_id.fetch_add(1, Ordering::SeqCst); 63 + 64 + let mut cid_str = None; 65 + 66 + match op.action.as_str() { 67 + "create" | "update" => { 68 + let Some(cid) = &op.cid else { 69 + continue; 70 + }; 71 + let s = smol_str::SmolStr::from(cid.as_str()); 72 + batch.insert(&db.records, db_key, s.as_bytes().to_vec()); 73 + cid_str = Some(s); 74 + 75 + // accumulate counts 76 + if op.action.as_str() == "create" { 77 + records_delta += 1; 78 + *collection_deltas 79 + .entry(collection.to_smolstr()) 80 + .or_default() += 1; 81 + } 82 + } 83 + "delete" => { 84 + batch.remove(&db.records, db_key); 85 + 86 + // accumulate counts 87 + records_delta -= 1; 88 + *collection_deltas 89 + .entry(collection.to_smolstr()) 90 + .or_default() -= 1; 91 + } 92 + _ => {} 93 + } 94 + 95 + let evt = StoredEvent::Record { 96 + live, 97 + did: did.as_str().into(), 98 + rev: commit.rev.as_str().into(), 99 + collection: collection.into(), 100 + rkey: rkey.into(), 101 + action: op.action.as_str().into(), 102 + cid: cid_str, 103 + }; 104 + 105 + let bytes = rmp_serde::to_vec(&evt).into_diagnostic()?; 106 + batch.insert(&db.events, keys::event_key(event_id as i64), bytes); 107 + events_count += 1; 108 + } 109 + 110 + let start = Instant::now(); 111 + 112 + batch.commit().into_diagnostic()?; 113 + trace!("committed sync batch for {did} in {:?}", start.elapsed()); 114 + 115 + let blocks_count = parsed.blocks.len() as i64; 116 + tokio::spawn({ 117 + let blocks_fut = (blocks_count > 0) 118 + .then(|| db.increment_count(keys::count_keyspace_key("blocks"), blocks_count)); 119 + let records_fut = (records_delta != 0) 120 + .then(|| db.increment_count(keys::count_keyspace_key("records"), records_delta)); 121 + let events_fut = (events_count > 0) 122 + .then(|| db.increment_count(keys::count_keyspace_key("events"), events_count)); 123 + let collections_fut = collection_deltas 124 + .into_iter() 125 + .map(|(col, delta)| db.increment_count(keys::count_collection_key(&did, &col), delta)) 126 + .collect::<Vec<_>>(); 127 + futures::future::join_all( 128 + blocks_fut 129 + .into_iter() 130 + .chain(records_fut) 131 + .chain(events_fut) 132 + .chain(collections_fut), 133 + ) 134 + }); 135 + 136 + let _ = db 137 + .event_tx 138 + .send(db.next_event_id.load(Ordering::SeqCst) - 1); 139 + 140 + Ok(()) 141 + }
+44
src/resolver.rs
···
··· 1 + use jacquard::IntoStatic; 2 + use jacquard_common::types::ident::AtIdentifier; 3 + use jacquard_common::types::string::Did; 4 + use jacquard_identity::resolver::{IdentityResolver, PlcSource, ResolverOptions}; 5 + use jacquard_identity::JacquardResolver; 6 + use miette::{IntoDiagnostic, Result}; 7 + use url::Url; 8 + 9 + pub struct Resolver { 10 + inner: JacquardResolver, 11 + } 12 + 13 + impl Resolver { 14 + pub fn new(plc_url: Url) -> Self { 15 + let http = reqwest::Client::new(); 16 + let mut opts = ResolverOptions::default(); 17 + opts.plc_source = PlcSource::PlcDirectory { base: plc_url }; 18 + 19 + let inner = JacquardResolver::new(http, opts); 20 + 21 + Self { inner } 22 + } 23 + 24 + pub async fn resolve_did(&self, identifier: &AtIdentifier<'_>) -> Result<Did<'static>> { 25 + match identifier { 26 + AtIdentifier::Did(did) => Ok(did.clone().into_static()), 27 + AtIdentifier::Handle(handle) => { 28 + let did = self.inner.resolve_handle(handle).await.into_diagnostic()?; 29 + Ok(did.into_static()) 30 + } 31 + } 32 + } 33 + 34 + pub async fn resolve_pds(&self, did: &Did<'_>) -> Result<Url> { 35 + let doc_resp = self.inner.resolve_did_doc(did).await.into_diagnostic()?; 36 + let doc = doc_resp.parse().into_diagnostic()?; 37 + 38 + if let Some(url) = doc.pds_endpoint() { 39 + return Ok(url); 40 + } 41 + 42 + Err(miette::miette!("no PDS service found in DID Doc for {did}")) 43 + } 44 + }
+40
src/state.rs
···
··· 1 + use std::sync::atomic::AtomicI64; 2 + 3 + use jacquard_common::types::string::Did; 4 + use tokio::sync::mpsc; 5 + 6 + use miette::Result; 7 + 8 + use crate::{config::Config, db::Db, resolver::Resolver}; 9 + 10 + pub type BackfillTx = mpsc::UnboundedSender<Did<'static>>; 11 + pub type BackfillRx = mpsc::UnboundedReceiver<Did<'static>>; 12 + 13 + pub struct AppState { 14 + pub db: Db, 15 + pub backfill_tx: BackfillTx, 16 + pub resolver: Resolver, 17 + pub cur_firehose: AtomicI64, 18 + } 19 + 20 + impl AppState { 21 + pub fn new(config: &Config) -> Result<(Self, BackfillRx)> { 22 + let db = Db::open( 23 + &config.database_path, 24 + config.cache_size, 25 + config.disable_lz4_compression, 26 + )?; 27 + let resolver = Resolver::new(config.plc_url.clone()); 28 + let (backfill_tx, backfill_rx) = mpsc::unbounded_channel(); 29 + 30 + Ok(( 31 + Self { 32 + db, 33 + backfill_tx, 34 + resolver, 35 + cur_firehose: AtomicI64::new(0), 36 + }, 37 + backfill_rx, 38 + )) 39 + } 40 + }
+93
src/types.rs
···
··· 1 + use jacquard_common::types::string::Did; 2 + use serde::{Deserialize, Serialize}; 3 + use serde_json::Value; 4 + use smol_str::SmolStr; 5 + 6 + // From src/state.rs 7 + 8 + #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] 9 + pub enum RepoStatus { 10 + New, 11 + Backfilling, 12 + Synced, 13 + Error(SmolStr), 14 + } 15 + 16 + #[derive(Debug, Clone, Serialize, Deserialize)] 17 + pub struct RepoState { 18 + pub did: SmolStr, 19 + pub status: RepoStatus, 20 + pub rev: SmolStr, 21 + pub last_seq: Option<i64>, 22 + pub last_updated_at: i64, // Unix timestamp 23 + } 24 + 25 + impl RepoState { 26 + pub fn new(did: Did) -> Self { 27 + Self { 28 + did: did.as_str().into(), 29 + status: RepoStatus::New, 30 + rev: "".into(), 31 + last_seq: None, 32 + last_updated_at: chrono::Utc::now().timestamp(), 33 + } 34 + } 35 + } 36 + 37 + // From src/backfill/error_state.rs 38 + 39 + #[derive(Debug, Clone, Serialize, Deserialize)] 40 + pub struct ErrorState { 41 + pub error: SmolStr, 42 + pub retry_count: u32, 43 + pub next_retry: i64, // unix timestamp 44 + } 45 + 46 + impl ErrorState { 47 + pub fn next_backoff(retry_count: u32) -> i64 { 48 + // exponential backoff: 1m, 2m, 4m, 8m... up to 1h 49 + let base = 60; 50 + let cap = 3600; 51 + let mult = 2u64.pow(retry_count.min(10)) as i64; 52 + let delay = (base * mult).min(cap); 53 + chrono::Utc::now().timestamp() + delay 54 + } 55 + } 56 + 57 + // From src/api/event.rs 58 + 59 + #[derive(Debug, Serialize, Deserialize, Clone)] 60 + pub struct RecordEvt { 61 + pub live: bool, 62 + pub did: SmolStr, 63 + pub rev: SmolStr, 64 + pub collection: SmolStr, 65 + pub rkey: SmolStr, 66 + pub action: SmolStr, 67 + #[serde(skip_serializing_if = "Option::is_none")] 68 + pub record: Option<Value>, 69 + #[serde(skip_serializing_if = "Option::is_none")] 70 + pub cid: Option<SmolStr>, 71 + } 72 + 73 + #[derive(Debug, Serialize, Deserialize, Clone)] 74 + pub struct IdentityEvt { 75 + pub did: SmolStr, 76 + pub handle: SmolStr, 77 + pub is_active: bool, 78 + pub status: SmolStr, 79 + } 80 + 81 + #[derive(Debug, Serialize, Deserialize, Clone)] 82 + pub enum StoredEvent { 83 + Record { 84 + live: bool, 85 + did: SmolStr, 86 + rev: SmolStr, 87 + collection: SmolStr, 88 + rkey: SmolStr, 89 + action: SmolStr, 90 + cid: Option<SmolStr>, 91 + }, 92 + Identity(IdentityEvt), 93 + }
+200
tests/authenticated_stream_test.nu
···
··· 1 + #!/usr/bin/env nu 2 + use common.nu * 3 + 4 + # simplistic dotenv parser 5 + def load-env-file [] { 6 + if (".env" | path exists) { 7 + let content = (open .env) 8 + $content | lines 9 + | where { |x| ($x | str trim | is-empty) == false and ($x | str trim | str starts-with "#") == false } 10 + | each { |x| 11 + let parts = ($x | split row "=" -n 2) 12 + { key: ($parts.0 | str trim), value: ($parts.1 | str trim | str trim -c '"' | str trim -c "'") } 13 + } 14 + | reduce -f {} { |it, acc| $acc | insert $it.key $it.value } 15 + } else { 16 + {} 17 + } 18 + } 19 + 20 + def authenticate [pds_url: string, identifier: string, password: string] { 21 + print $"authenticating with ($pds_url) for ($identifier)..." 22 + let resp = (http post -t application/json $"($pds_url)/xrpc/com.atproto.server.createSession" { 23 + identifier: $identifier, 24 + password: $password 25 + }) 26 + return $resp 27 + } 28 + 29 + def create-record [pds_url: string, jwt: string, repo: string, collection: string, record: any] { 30 + http post -t application/json -H ["Authorization" $"Bearer ($jwt)"] $"($pds_url)/xrpc/com.atproto.repo.createRecord" { 31 + repo: $repo, 32 + collection: $collection, 33 + record: $record 34 + } 35 + } 36 + 37 + def delete-record [pds_url: string, jwt: string, repo: string, collection: string, rkey: string] { 38 + http post -t application/json -H ["Authorization" $"Bearer ($jwt)"] $"($pds_url)/xrpc/com.atproto.repo.deleteRecord" { 39 + repo: $repo, 40 + collection: $collection, 41 + rkey: $rkey 42 + } 43 + } 44 + 45 + def resolve-pds [did: string] { 46 + print $"resolving pds for ($did)..." 47 + let doc = (http get $"https://plc.wtf/($did)" | from json) 48 + let pds = ($doc.service | where type == "AtprotoPersonalDataServer" | first).serviceEndpoint 49 + print $"resolved pds: ($pds)" 50 + return $pds 51 + } 52 + 53 + def main [] { 54 + let env_vars = load-env-file 55 + let did = ($env_vars | get --optional TEST_REPO) 56 + let password = ($env_vars | get --optional TEST_PASSWORD) 57 + 58 + if ($did | is-empty) or ($password | is-empty) { 59 + print "error: TEST_REPO and TEST_PASSWORD must be set in .env" 60 + exit 1 61 + } 62 + 63 + let pds_url = resolve-pds $did 64 + 65 + let port = 3003 66 + let url = $"http://localhost:($port)" 67 + let ws_url = $"ws://localhost:($port)/stream" 68 + let db_path = (mktemp -d -t hydrant_auth_test.XXXXXX) 69 + 70 + # 1. authenticate 71 + print $"authenticating with ($pds_url)..." 72 + let session = authenticate $pds_url $did $password 73 + let jwt = $session.accessJwt 74 + print "authentication successful" 75 + 76 + # 2. start hydrant 77 + print $"starting hydrant on port ($port)..." 78 + let binary = build-hydrant 79 + let instance = start-hydrant $binary $db_path $port 80 + 81 + mut test_passed = false 82 + 83 + if (wait-for-api $url) { 84 + # 3. start listener (live stream) 85 + let output_file = $"($db_path)/stream_output.txt" 86 + print $"starting stream listener -> ($output_file)" 87 + # use websocat to capture output. 88 + let stream_pid = (bash -c $"websocat '($ws_url)' > '($output_file)' 2>&1 & echo $!" | str trim | into int) 89 + print $"listener pid: ($stream_pid)" 90 + 91 + # 4. add repo to hydrant (backfill trigger) 92 + print $"adding repo ($did) to tracking..." 93 + try { 94 + http post -t application/json $"($url)/repo/add" { dids: [($did)] } 95 + } catch { 96 + print "warning: failed to add repo (might already be tracked), continuing..." 97 + } 98 + 99 + # wait for connection stability and potential backfill start 100 + sleep 2sec 101 + 102 + # 5. perform actions 103 + let collection = "app.bsky.feed.post" 104 + let timestamp = (date now | format date "%Y-%m-%dT%H:%M:%SZ") 105 + let record_data = { 106 + "$type": "app.bsky.feed.post", 107 + text: $"hydrant integration test ($timestamp)", 108 + createdAt: $timestamp 109 + } 110 + 111 + print "--- action: create ---" 112 + let create_res = create-record $pds_url $jwt $did $collection $record_data 113 + print $"created uri: ($create_res.uri)" 114 + print $"created cid: ($create_res.cid)" 115 + let rkey = ($create_res.uri | split row "/" | last) 116 + 117 + print "--- action: update ---" 118 + let update_data = ($record_data | update text $"updated text ($timestamp)") 119 + 120 + try { 121 + http post -t application/json -H ["Authorization" $"Bearer ($jwt)"] $"($pds_url)/xrpc/com.atproto.repo.putRecord" { 122 + repo: $did, 123 + collection: $collection, 124 + rkey: $rkey, 125 + record: $update_data, 126 + } 127 + print "updated record" 128 + } catch { |err| 129 + print $"update failed: ($err)" 130 + # try to continue to delete 131 + } 132 + 133 + print "--- action: delete ---" 134 + delete-record $pds_url $jwt $did $collection $rkey 135 + print "deleted record" 136 + 137 + # 6. verify 138 + sleep 2sec 139 + print "stopping listener..." 140 + try { kill $stream_pid } 141 + 142 + if ($output_file | path exists) { 143 + let content = (open $output_file | str trim) 144 + if ($content | is-empty) { 145 + print "failed: no events captured" 146 + } else { 147 + # parse json lines 148 + let events = ($content | lines | each { |it| $it | from json }) 149 + print $"captured ($events | length) events" 150 + 151 + # hydrant stream events seem to be type: "record" with payload in "record" field 152 + # structure: { id: ..., type: "record", record: { action: ..., collection: ..., rkey: ... } } 153 + 154 + let relevant_events = ($events | where type == "record" and record.collection == $collection and record.rkey == $rkey) 155 + 156 + let creates = ($relevant_events | where record.action == "create") 157 + let updates = ($relevant_events | where record.action == "update") 158 + let deletes = ($relevant_events | where record.action == "delete") 159 + 160 + print $"found creates: ($creates | length)" 161 + print $"found updates: ($updates | length)" 162 + print $"found deletes: ($deletes | length)" 163 + 164 + if ($relevant_events | length) != 3 { 165 + print "test failed: expected exactly 3 events" 166 + print "captured events:" 167 + print ($events | table -e) 168 + } else { 169 + let first = ($relevant_events | get 0) 170 + let second = ($relevant_events | get 1) 171 + let third = ($relevant_events | get 2) 172 + 173 + if ($first.record.action == "create") and ($second.record.action == "update") and ($third.record.action == "delete") { 174 + print "test passed: all operations (create, update, delete) captured in correct order" 175 + $test_passed = true 176 + } else { 177 + print "test failed: events out of order or incorrect" 178 + print "captured events:" 179 + print ($events | table -e) 180 + } 181 + } 182 + } 183 + } else { 184 + print "failed: output file missing" 185 + } 186 + 187 + } else { 188 + print "hydrant failed to start" 189 + } 190 + 191 + # cleanup 192 + print "cleaning up..." 193 + try { kill $instance.pid } 194 + 195 + if $test_passed { 196 + exit 0 197 + } else { 198 + exit 1 199 + } 200 + }
+68
tests/common.nu
···
··· 1 + # build the hydrant binary 2 + export def build-hydrant [] { 3 + print "building hydrant..." 4 + cargo build --release --quiet 5 + "target/release/hydrant" 6 + } 7 + 8 + # start hydrant in the background 9 + export def start-hydrant [binary: string, db_path: string, port: int] { 10 + let log_file = $"($db_path)/hydrant.log" 11 + print $"starting hydrant - logs at ($log_file)..." 12 + 13 + let pid = ( 14 + with-env { 15 + HYDRANT_DATABASE_PATH: ($db_path), 16 + HYDRANT_FULL_NETWORK: "false", 17 + HYDRANT_API_PORT: ($port | into string), 18 + HYDRANT_LOG_LEVEL: "debug" 19 + } { 20 + sh -c $"($binary) >($log_file) 2>&1 & echo $!" | str trim | into int 21 + } 22 + ) 23 + 24 + print $"hydrant started with pid: ($pid)" 25 + { pid: $pid, log: $log_file } 26 + } 27 + 28 + # wait for the api to become responsive 29 + export def wait-for-api [url: string] { 30 + print "waiting for api to be ready..." 31 + for i in 1..30 { 32 + try { 33 + http get $"($url)/stats" 34 + return true 35 + } catch { 36 + sleep 1sec 37 + } 38 + } 39 + false 40 + } 41 + 42 + # poll stats until backfill is complete or fails 43 + export def wait-for-backfill [url: string] { 44 + print "waiting for backfill to complete..." 45 + for i in 1..120 { 46 + let stats = (http get $"($url)/stats?accurate=true").keyspace_stats 47 + let pending = ($stats | where name == "pending" | first).count 48 + let records = ($stats | where name == "records" | first).count 49 + let repos = ($stats | where name == "repos" | first).count 50 + let errors = ($stats | where name == "errors" | first).count 51 + 52 + print $"[($i)/120] pending: ($pending), records: ($records), repos: ($repos), errors: ($errors)" 53 + 54 + if $errors > 0 { 55 + print "error detected during backfill!" 56 + print ($stats | table) 57 + return false 58 + } 59 + 60 + if ($pending == 0) and ($repos > 0) and ($records > 0) { 61 + print "backfill complete." 62 + return true 63 + } 64 + 65 + sleep 2sec 66 + } 67 + false 68 + }
+139
tests/repo_sync_integrity.nu
···
··· 1 + #!/usr/bin/env nu 2 + use common.nu * 3 + 4 + # compare records between hydrant and upstream pds 5 + export def check-consistency [hydrant_url: string, pds_url: string, did: string] { 6 + print "comparing records with pds..." 7 + let collections = [ 8 + "app.bsky.feed.post" 9 + "app.bsky.actor.profile" 10 + ] 11 + 12 + mut success = true 13 + 14 + for coll in $collections { 15 + for is_rev in [false, true] { 16 + print $"checking collection: ($coll) reverse:($is_rev)" 17 + 18 + let hydrant_records = (http get $"($hydrant_url)/xrpc/com.atproto.repo.listRecords?repo=($did)&collection=($coll)&reverse=($is_rev)").records 19 + let pds_records = (http get $"($pds_url)/xrpc/com.atproto.repo.listRecords?repo=($did)&collection=($coll)&reverse=($is_rev)").records 20 + 21 + let hydrant_count = ($hydrant_records | length) 22 + let pds_count = ($pds_records | length) 23 + 24 + print $" hydrant count: ($hydrant_count), pds count: ($pds_count)" 25 + 26 + if $hydrant_count != $pds_count { 27 + print $" mismatch in count for ($coll) rev:($is_rev)!" 28 + $success = false 29 + continue 30 + } 31 + 32 + if $hydrant_count > 0 { 33 + let h_first = ($hydrant_records | first).uri | split row "/" | last 34 + let p_first = ($pds_records | first).uri | split row "/" | last 35 + print $" first rkey - hydrant: ($h_first), pds: ($p_first)" 36 + } 37 + 38 + # compare cids and rkeys 39 + for i in 0..($hydrant_count - 1) { 40 + let h_record = ($hydrant_records | get $i) 41 + let p_record = ($pds_records | get $i) 42 + 43 + if $h_record.cid != $p_record.cid { 44 + let h_rkey = ($h_record.uri | split row "/" | last) 45 + let p_rkey = ($p_record.uri | split row "/" | last) 46 + print $" mismatch at index ($i) for ($coll) rev:($is_rev):" 47 + print $" hydrant: ($h_rkey) -> ($h_record.cid)" 48 + print $" pds: ($p_rkey) -> ($p_record.cid)" 49 + $success = false 50 + } 51 + } 52 + } 53 + } 54 + $success 55 + } 56 + 57 + # verify countRecords API against debug endpoint 58 + def check-count [hydrant_url: string, did: string] { 59 + print "verifying countRecords API..." 60 + let collections = [ 61 + "app.bsky.feed.post" 62 + "app.bsky.actor.profile" 63 + ] 64 + 65 + mut success = true 66 + 67 + for coll in $collections { 68 + print $" checking count for ($coll)..." 69 + 70 + # 1. get cached count from API 71 + let api_count = try { 72 + (http get $"($hydrant_url)/xrpc/systems.gaze.hydrant.countRecords?identifier=($did)&collection=($coll)").count 73 + } catch { 74 + print $" error calling countRecords API for ($coll)" 75 + return false 76 + } 77 + 78 + # 2. get actual scan count from debug endpoint 79 + let debug_count = try { 80 + (http get $"($hydrant_url)/debug/count?did=($did)&collection=($coll)").count 81 + } catch { 82 + print $" error calling debug count for ($coll)" 83 + return false 84 + } 85 + 86 + print $" api: ($api_count), debug scan: ($debug_count)" 87 + 88 + if $api_count != $debug_count { 89 + print $" COUNT MISMATCH for ($coll)! api: ($api_count) vs scan: ($debug_count)" 90 + $success = false 91 + } 92 + } 93 + $success 94 + } 95 + 96 + def main [] { 97 + let did = "did:plc:dfl62fgb7wtjj3fcbb72naae" 98 + let pds = "https://zwsp.xyz" 99 + let port = 3001 100 + let url = $"http://localhost:($port)" 101 + let db_path = (mktemp -d -t hydrant_test.XXXXXX) 102 + 103 + print $"testing backfill integrity for ($did)..." 104 + print $"database path: ($db_path)" 105 + 106 + let binary = build-hydrant 107 + let instance = start-hydrant $binary $db_path $port 108 + 109 + mut success = false 110 + 111 + if (wait-for-api $url) { 112 + # track the repo via API 113 + print $"adding repo ($did) to tracking..." 114 + http post -t application/json $"($url)/repo/add" { dids: [($did)] } 115 + 116 + if (wait-for-backfill $url) { 117 + # Run both consistency checks 118 + let integrity_passed = (check-consistency $url $pds $did) 119 + let count_passed = (check-count $url $did) 120 + 121 + if $integrity_passed and $count_passed { 122 + print "all integrity checks passed!" 123 + $success = true 124 + } else { 125 + print $"integrity checks failed. consistency: ($integrity_passed), count: ($count_passed)" 126 + } 127 + } else { 128 + print "backfill failed or timed out." 129 + } 130 + } else { 131 + print "api failed to start." 132 + } 133 + 134 + let hydrant_pid = $instance.pid 135 + print $"stopping hydrant - pid: ($hydrant_pid)..." 136 + try { kill $hydrant_pid } 137 + 138 + if not $success { exit 1 } 139 + }
+138
tests/stream_test.nu
···
··· 1 + #!/usr/bin/env nu 2 + use common.nu * 3 + 4 + def main [] { 5 + let did = "did:web:guestbook.gaze.systems" 6 + let port = 3002 7 + let url = $"http://localhost:($port)" 8 + let ws_url = $"ws://localhost:($port)/stream" 9 + let db_path = (mktemp -d -t hydrant_stream_test.XXXXXX) 10 + 11 + print $"testing streaming for ($did)..." 12 + print $"database path: ($db_path)" 13 + 14 + let binary = build-hydrant 15 + let instance = start-hydrant $binary $db_path $port 16 + 17 + mut test1_passed = false 18 + mut test2_passed = false 19 + 20 + if (wait-for-api $url) { 21 + # test 1: connect to stream BEFORE backfill to catch live events 22 + print "=== test 1: live streaming during backfill ===" 23 + 24 + let live_output = $"($db_path)/stream_live.txt" 25 + print $"starting stream listener -> ($live_output)" 26 + 27 + # start websocat in background to capture live events (no cursor = live only) 28 + let stream_pid = (bash -c $"websocat '($ws_url)' > '($live_output)' 2>&1 & echo $!" | str trim | into int) 29 + print $"stream listener pid: ($stream_pid)" 30 + sleep 1sec 31 + 32 + # trigger backfill 33 + print $"adding repo ($did) to tracking..." 34 + http post -t application/json $"($url)/repo/add" { dids: [($did)] } 35 + 36 + if (wait-for-backfill $url) { 37 + sleep 2sec 38 + 39 + # stop the stream listener 40 + try { kill $stream_pid } 41 + sleep 1sec 42 + 43 + if ($live_output | path exists) { 44 + let live_content = (open $live_output | str trim) 45 + 46 + if ($live_content | is-empty) { 47 + print "test 1 FAILED: no live events received during backfill" 48 + } else { 49 + let live_messages = ($live_content | lines) 50 + let live_count = ($live_messages | length) 51 + print $"test 1: received ($live_count) live events during backfill" 52 + 53 + if $live_count > 0 { 54 + let first = ($live_messages | first | from json) 55 + print $" first event: id=($first.id), type=($first.type)" 56 + print "test 1 PASSED: live streaming works" 57 + $test1_passed = true 58 + } 59 + } 60 + } else { 61 + print "test 1 FAILED: output file not created" 62 + } 63 + 64 + # test 2: connect AFTER backfill with cursor=1 to replay all events 65 + print "" 66 + print "=== test 2: historical replay with cursor=0 ===" 67 + 68 + sleep 2sec 69 + 70 + let stats = (http get $"($url)/stats?accurate=true").keyspace_stats 71 + let events_count = ($stats | where name == "events" | first).count 72 + print $"total events in db: ($events_count)" 73 + 74 + if $events_count > 0 { 75 + let history_output = $"($db_path)/stream_history.txt" 76 + 77 + # use same approach as test 1: background process with file output 78 + # cursor=0 replays from the beginning (no cursor = live-tail only) 79 + print "starting historical stream listener..." 80 + let history_pid = (bash -c $"websocat '($ws_url)?cursor=0' > '($history_output)' 2>&1 & echo $!" | str trim | into int) 81 + print $"history listener pid: ($history_pid)" 82 + 83 + # wait for events to be streamed (should be fast for historical replay) 84 + sleep 5sec 85 + 86 + # kill the listener 87 + try { kill $history_pid } 88 + sleep 500ms 89 + 90 + if ($history_output | path exists) { 91 + let history_content = (open $history_output | str trim) 92 + 93 + if ($history_content | is-empty) { 94 + print "test 2 FAILED: no historical events received" 95 + } else { 96 + let history_messages = ($history_content | lines) 97 + let history_count = ($history_messages | length) 98 + print $"test 2: received ($history_count) historical events" 99 + 100 + if $history_count > 0 { 101 + let first = ($history_messages | first | from json) 102 + let last = ($history_messages | last | from json) 103 + print $" first event: id=($first.id), type=($first.type)" 104 + print $" last event: id=($last.id), type=($last.type)" 105 + 106 + if $history_count >= ($events_count | into int) { 107 + print $"test 2 PASSED: replayed all ($history_count) events" 108 + $test2_passed = true 109 + } else { 110 + print $"test 2 PARTIAL: got ($history_count)/($events_count) events" 111 + $test2_passed = true 112 + } 113 + } 114 + } 115 + } else { 116 + print "test 2 FAILED: output file not created" 117 + } 118 + } 119 + } else { 120 + print "backfill failed or timed out." 121 + try { kill $stream_pid } 122 + } 123 + } else { 124 + print "api failed to start." 125 + } 126 + 127 + let hydrant_pid = $instance.pid 128 + print $"stopping hydrant - pid: ($hydrant_pid)..." 129 + try { kill $hydrant_pid } 130 + 131 + print "" 132 + if $test1_passed and $test2_passed { 133 + print "=== ALL TESTS PASSED ===" 134 + } else { 135 + print $"=== TESTS FAILED === test1: ($test1_passed), test2: ($test2_passed)" 136 + exit 1 137 + } 138 + }