json vs cbor clickhouse benchmark, indexer skeleton

Orual 0a223e0f c0d3a5da

+1683 -1751
+212 -415
Cargo.lock
··· 772 ] 773 774 [[package]] 775 name = "bon" 776 version = "3.8.1" 777 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 795 "rustversion", 796 "syn 2.0.111", 797 ] 798 - 799 - [[package]] 800 - name = "borrow-or-share" 801 - version = "0.2.4" 802 - source = "registry+https://github.com/rust-lang/crates.io-index" 803 - checksum = "dc0b364ead1874514c8c2855ab558056ebfeb775653e7ae45ff72f28f8f3166c" 804 805 [[package]] 806 name = "borsh" ··· 1096 "multihash", 1097 "serde", 1098 "serde_bytes", 1099 - "unsigned-varint", 1100 ] 1101 1102 [[package]] ··· 1110 "inout", 1111 "zeroize", 1112 ] 1113 1114 [[package]] 1115 name = "clap" ··· 1154 checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" 1155 1156 [[package]] 1157 name = "cobs" 1158 version = "0.3.0" 1159 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1618 "crypto_secretbox", 1619 "curve25519-dalek 5.0.0-pre.1", 1620 "salsa20", 1621 - "serdect 0.4.1", 1622 "subtle", 1623 "zeroize", 1624 ] ··· 1841 checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376" 1842 1843 [[package]] 1844 - name = "deadpool" 1845 - version = "0.12.3" 1846 - source = "registry+https://github.com/rust-lang/crates.io-index" 1847 - checksum = "0be2b1d1d6ec8d846f05e137292d0b89133caf95ef33695424c09568bdd39b1b" 1848 - dependencies = [ 1849 - "deadpool-runtime", 1850 - "lazy_static", 1851 - "num_cpus", 1852 - "tokio", 1853 - ] 1854 - 1855 - [[package]] 1856 - name = "deadpool-runtime" 1857 - version = "0.1.4" 1858 - source = "registry+https://github.com/rust-lang/crates.io-index" 1859 - checksum = "092966b41edc516079bdf31ec78a2e0588d1d0c08f78b91d8307215928642b2b" 1860 - 1861 - [[package]] 1862 name = "deflate" 1863 version = "1.0.0" 1864 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1973 version = "0.2.3" 1974 source = "registry+https://github.com/rust-lang/crates.io-index" 1975 checksum = "ab03c107fafeb3ee9f5925686dbb7a73bc76e3932abb0d2b365cb64b169cf04c" 1976 - 1977 - [[package]] 1978 - name = "diesel" 1979 - version = "2.3.4" 1980 - source = "registry+https://github.com/rust-lang/crates.io-index" 1981 - checksum = "0c415189028b232660655e4893e8bc25ca7aee8e96888db66d9edb400535456a" 1982 - dependencies = [ 1983 - "chrono", 1984 - "diesel_derives", 1985 - "downcast-rs 2.0.2", 1986 - "libsqlite3-sys", 1987 - "serde_json", 1988 - "sqlite-wasm-rs", 1989 - "time", 1990 - ] 1991 - 1992 - [[package]] 1993 - name = "diesel-async" 1994 - version = "0.7.4" 1995 - source = "registry+https://github.com/rust-lang/crates.io-index" 1996 - checksum = "13096fb8dae53f2d411c4b523bec85f45552ed3044a2ab4d85fb2092d9cb4f34" 1997 - dependencies = [ 1998 - "deadpool", 1999 - "diesel", 2000 - "futures-core", 2001 - "futures-util", 2002 - "scoped-futures", 2003 - "tokio", 2004 - ] 2005 - 2006 - [[package]] 2007 - name = "diesel_derives" 2008 - version = "2.3.5" 2009 - source = "registry+https://github.com/rust-lang/crates.io-index" 2010 - checksum = "8587cbca3c929fb198e7950d761d31ca72b80aa6e07c1b7bec5879d187720436" 2011 - dependencies = [ 2012 - "diesel_table_macro_syntax", 2013 - "dsl_auto_type", 2014 - "proc-macro2", 2015 - "quote", 2016 - "syn 2.0.111", 2017 - ] 2018 - 2019 - [[package]] 2020 - name = "diesel_migrations" 2021 - version = "2.3.1" 2022 - source = "registry+https://github.com/rust-lang/crates.io-index" 2023 - checksum = "745fd255645f0f1135f9ec55c7b00e0882192af9683ab4731e4bba3da82b8f9c" 2024 - dependencies = [ 2025 - "diesel", 2026 - "migrations_internals", 2027 - "migrations_macros", 2028 - ] 2029 - 2030 - [[package]] 2031 - name = "diesel_table_macro_syntax" 2032 - version = "0.3.0" 2033 - source = "registry+https://github.com/rust-lang/crates.io-index" 2034 - checksum = "fe2444076b48641147115697648dc743c2c00b61adade0f01ce67133c7babe8c" 2035 - dependencies = [ 2036 - "syn 2.0.111", 2037 - ] 2038 2039 [[package]] 2040 name = "diff" ··· 3233 checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" 3234 3235 [[package]] 3236 - name = "downcast-rs" 3237 - version = "2.0.2" 3238 - source = "registry+https://github.com/rust-lang/crates.io-index" 3239 - checksum = "117240f60069e65410b3ae1bb213295bd828f707b5bec6596a1afc8793ce0cbc" 3240 - 3241 - [[package]] 3242 name = "dpi" 3243 version = "0.1.2" 3244 source = "registry+https://github.com/rust-lang/crates.io-index" 3245 checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" 3246 3247 [[package]] 3248 - name = "dsl_auto_type" 3249 - version = "0.2.0" 3250 - source = "registry+https://github.com/rust-lang/crates.io-index" 3251 - checksum = "dd122633e4bef06db27737f21d3738fb89c8f6d5360d6d9d7635dda142a7757e" 3252 - dependencies = [ 3253 - "darling 0.21.3", 3254 - "either", 3255 - "heck 0.5.0", 3256 - "proc-macro2", 3257 - "quote", 3258 - "syn 2.0.111", 3259 - ] 3260 - 3261 - [[package]] 3262 name = "dtoa" 3263 version = "1.0.10" 3264 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3305 source = "registry+https://github.com/rust-lang/crates.io-index" 3306 checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" 3307 dependencies = [ 3308 "signature 2.2.0", 3309 ] 3310 ··· 3328 "curve25519-dalek 4.1.3", 3329 "ed25519 2.2.3", 3330 "rand_core 0.6.4", 3331 "sha2 0.10.9", 3332 "subtle", 3333 "zeroize", ··· 3362 checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" 3363 dependencies = [ 3364 "base16ct 0.2.0", 3365 - "base64ct", 3366 "crypto-bigint", 3367 "digest 0.10.7", 3368 "ff", ··· 3372 "pkcs8 0.10.2", 3373 "rand_core 0.6.4", 3374 "sec1", 3375 - "serde_json", 3376 - "serdect 0.2.0", 3377 "subtle", 3378 "zeroize", 3379 ] ··· 3641 checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" 3642 3643 [[package]] 3644 - name = "fluent-uri" 3645 - version = "0.3.2" 3646 - source = "registry+https://github.com/rust-lang/crates.io-index" 3647 - checksum = "1918b65d96df47d3591bed19c5cca17e3fa5d0707318e4b5ef2eae01764df7e5" 3648 - dependencies = [ 3649 - "borrow-or-share", 3650 - "ref-cast", 3651 - ] 3652 - 3653 - [[package]] 3654 name = "fnv" 3655 version = "1.0.7" 3656 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 4026 source = "registry+https://github.com/rust-lang/crates.io-index" 4027 checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" 4028 dependencies = [ 4029 - "serde", 4030 "typenum", 4031 "version_check", 4032 "zeroize", ··· 4473 source = "registry+https://github.com/rust-lang/crates.io-index" 4474 checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" 4475 dependencies = [ 4476 - "allocator-api2", 4477 - "equivalent", 4478 "foldhash 0.1.5", 4479 - "serde", 4480 ] 4481 4482 [[package]] ··· 4686 ] 4687 4688 [[package]] 4689 name = "hmac" 4690 version = "0.12.1" 4691 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 5266 ] 5267 5268 [[package]] 5269 name = "iroh-gossip" 5270 version = "0.95.0" 5271 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 5592 "chrono", 5593 "ciborium", 5594 "cid", 5595 "futures", 5596 "getrandom 0.2.16", 5597 "getrandom 0.3.4", ··· 5725 ] 5726 5727 [[package]] 5728 name = "javascriptcore-rs" 5729 version = "1.1.2" 5730 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 5780 ] 5781 5782 [[package]] 5783 - name = "jose" 5784 - version = "0.0.2" 5785 - source = "registry+https://github.com/rust-lang/crates.io-index" 5786 - checksum = "de791f8b277ef903d49b0c6f3f3b866569ae8cb1d0829825eea28374cc4c803c" 5787 - dependencies = [ 5788 - "base64ct", 5789 - "cfg-if", 5790 - "digest 0.10.7", 5791 - "ecdsa", 5792 - "ed25519-dalek 2.2.0", 5793 - "elliptic-curve", 5794 - "fluent-uri", 5795 - "generic-array", 5796 - "hashbrown 0.15.5", 5797 - "hmac", 5798 - "k256", 5799 - "mediatype", 5800 - "p256", 5801 - "p384", 5802 - "rand_core 0.6.4", 5803 - "rsa", 5804 - "secrecy", 5805 - "serde", 5806 - "serde-value", 5807 - "serde_json", 5808 - "sha2 0.10.9", 5809 - "signature 2.2.0", 5810 - "subtle", 5811 - "thiserror 2.0.17", 5812 - "zeroize", 5813 - ] 5814 - 5815 - [[package]] 5816 name = "jose-b64" 5817 version = "0.1.2" 5818 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 5867 "cfg-if", 5868 "ecdsa", 5869 "elliptic-curve", 5870 "sha2 0.10.9", 5871 ] 5872 5873 [[package]] ··· 6018 "bitflags 2.10.0", 6019 "libc", 6020 "redox_syscall", 6021 - ] 6022 - 6023 - [[package]] 6024 - name = "libsqlite3-sys" 6025 - version = "0.35.0" 6026 - source = "registry+https://github.com/rust-lang/crates.io-index" 6027 - checksum = "133c182a6a2c87864fe97778797e46c7e999672690dc9fa3ee8e241aa4a9c13f" 6028 - dependencies = [ 6029 - "cc", 6030 - "pkg-config", 6031 - "vcpkg", 6032 ] 6033 6034 [[package]] ··· 6314 ] 6315 6316 [[package]] 6317 name = "malloc_buf" 6318 version = "0.0.6" 6319 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 6501 checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" 6502 6503 [[package]] 6504 - name = "mediatype" 6505 - version = "0.19.20" 6506 - source = "registry+https://github.com/rust-lang/crates.io-index" 6507 - checksum = "33746aadcb41349ec291e7f2f0a3aa6834d1d7c58066fb4b01f68efc4c4b7631" 6508 - dependencies = [ 6509 - "serde", 6510 - ] 6511 - 6512 - [[package]] 6513 name = "memchr" 6514 version = "2.7.6" 6515 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 6568 "supports-color", 6569 "supports-hyperlinks", 6570 "supports-unicode", 6571 - "syntect", 6572 "terminal_size", 6573 "textwrap", 6574 "unicode-width 0.1.14", ··· 6597 ] 6598 6599 [[package]] 6600 - name = "migrations_internals" 6601 - version = "2.3.0" 6602 - source = "registry+https://github.com/rust-lang/crates.io-index" 6603 - checksum = "36c791ecdf977c99f45f23280405d7723727470f6689a5e6dbf513ac547ae10d" 6604 - dependencies = [ 6605 - "serde", 6606 - "toml 0.9.8", 6607 - ] 6608 - 6609 - [[package]] 6610 - name = "migrations_macros" 6611 - version = "2.3.0" 6612 - source = "registry+https://github.com/rust-lang/crates.io-index" 6613 - checksum = "36fc5ac76be324cfd2d3f2cf0fdf5d5d3c4f14ed8aaebadb09e304ba42282703" 6614 - dependencies = [ 6615 - "migrations_internals", 6616 - "proc-macro2", 6617 - "quote", 6618 - ] 6619 - 6620 - [[package]] 6621 name = "mime" 6622 version = "0.3.17" 6623 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 6671 ] 6672 6673 [[package]] 6674 - name = "minijinja" 6675 - version = "2.13.0" 6676 - source = "registry+https://github.com/rust-lang/crates.io-index" 6677 - checksum = "0adbe6e92a6ce0fd6c4aac593fdfd3e3950b0f61b1a63aa9731eb6fd85776fa3" 6678 - dependencies = [ 6679 - "serde", 6680 - ] 6681 - 6682 - [[package]] 6683 - name = "minijinja-contrib" 6684 - version = "2.13.0" 6685 - source = "registry+https://github.com/rust-lang/crates.io-index" 6686 - checksum = "a915f5cc17b954d5252b6373cc5fd97eb86d75e29b30ac2ee7932126451eef24" 6687 - dependencies = [ 6688 - "minijinja", 6689 - "serde", 6690 - ] 6691 - 6692 - [[package]] 6693 name = "minimal-lexical" 6694 version = "0.2.1" 6695 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 6792 dependencies = [ 6793 "core2", 6794 "serde", 6795 - "unsigned-varint", 6796 ] 6797 6798 [[package]] ··· 7035 ] 7036 7037 [[package]] 7038 name = "new_debug_unreachable" 7039 version = "1.0.6" 7040 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 7387 checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" 7388 7389 [[package]] 7390 - name = "onig" 7391 - version = "6.5.1" 7392 - source = "registry+https://github.com/rust-lang/crates.io-index" 7393 - checksum = "336b9c63443aceef14bea841b899035ae3abe89b7c486aaf4c5bd8aafedac3f0" 7394 - dependencies = [ 7395 - "bitflags 2.10.0", 7396 - "libc", 7397 - "once_cell", 7398 - "onig_sys", 7399 - ] 7400 - 7401 - [[package]] 7402 - name = "onig_sys" 7403 - version = "69.9.1" 7404 - source = "registry+https://github.com/rust-lang/crates.io-index" 7405 - checksum = "c7f86c6eef3d6df15f23bcfb6af487cbd2fed4e5581d58d5bf1f5f8b7f6727dc" 7406 - dependencies = [ 7407 - "cc", 7408 - "pkg-config", 7409 - ] 7410 - 7411 - [[package]] 7412 name = "openssl" 7413 version = "0.10.75" 7414 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 7459 checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" 7460 7461 [[package]] 7462 - name = "ordered-float" 7463 - version = "2.10.1" 7464 - source = "registry+https://github.com/rust-lang/crates.io-index" 7465 - checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" 7466 - dependencies = [ 7467 - "num-traits", 7468 - ] 7469 - 7470 - [[package]] 7471 name = "ordered-stream" 7472 version = "0.2.0" 7473 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 7525 source = "registry+https://github.com/rust-lang/crates.io-index" 7526 checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" 7527 dependencies = [ 7528 - "ecdsa", 7529 "elliptic-curve", 7530 "primeorder", 7531 - "sha2 0.10.9", 7532 ] 7533 7534 [[package]] ··· 7952 checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" 7953 7954 [[package]] 7955 name = "poly1305" 7956 version = "0.9.0-rc.2" 7957 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 8197 ] 8198 8199 [[package]] 8200 name = "quick-error" 8201 version = "1.2.3" 8202 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 8432 version = "0.3.2" 8433 source = "registry+https://github.com/rust-lang/crates.io-index" 8434 checksum = "d20581732dd76fa913c7dff1a2412b714afe3573e94d41c34719de73337cc8ab" 8435 8436 [[package]] 8437 name = "raw-window-handle" ··· 8902 ] 8903 8904 [[package]] 8905 - name = "scoped-futures" 8906 - version = "0.1.4" 8907 - source = "registry+https://github.com/rust-lang/crates.io-index" 8908 - checksum = "1b24aae2d0636530f359e9d5ef0c04669d11c5e756699b27a6a6d845d8329091" 8909 - dependencies = [ 8910 - "pin-project-lite", 8911 - ] 8912 - 8913 - [[package]] 8914 name = "scoped-tls" 8915 version = "1.0.1" 8916 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 8932 "der 0.7.10", 8933 "generic-array", 8934 "pkcs8 0.10.2", 8935 - "serdect 0.2.0", 8936 "subtle", 8937 - "zeroize", 8938 - ] 8939 - 8940 - [[package]] 8941 - name = "secrecy" 8942 - version = "0.10.3" 8943 - source = "registry+https://github.com/rust-lang/crates.io-index" 8944 - checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a" 8945 - dependencies = [ 8946 - "serde", 8947 "zeroize", 8948 ] 8949 ··· 9039 ] 9040 9041 [[package]] 9042 - name = "serde-value" 9043 - version = "0.7.0" 9044 - source = "registry+https://github.com/rust-lang/crates.io-index" 9045 - checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" 9046 - dependencies = [ 9047 - "ordered-float", 9048 - "serde", 9049 - ] 9050 - 9051 - [[package]] 9052 name = "serde-wasm-bindgen" 9053 version = "0.4.5" 9054 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 9137 ] 9138 9139 [[package]] 9140 name = "serde_html_form" 9141 version = "0.2.8" 9142 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 9229 ] 9230 9231 [[package]] 9232 - name = "serde_spanned" 9233 - version = "1.0.3" 9234 - source = "registry+https://github.com/rust-lang/crates.io-index" 9235 - checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" 9236 - dependencies = [ 9237 - "serde_core", 9238 - ] 9239 - 9240 - [[package]] 9241 name = "serde_urlencoded" 9242 version = "0.7.1" 9243 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 9282 9283 [[package]] 9284 name = "serdect" 9285 - version = "0.2.0" 9286 - source = "registry+https://github.com/rust-lang/crates.io-index" 9287 - checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177" 9288 - dependencies = [ 9289 - "base16ct 0.2.0", 9290 - "serde", 9291 - ] 9292 - 9293 - [[package]] 9294 - name = "serdect" 9295 version = "0.4.1" 9296 source = "registry+https://github.com/rust-lang/crates.io-index" 9297 checksum = "d3ef0e35b322ddfaecbc60f34ab448e157e48531288ee49fafbb053696b8ffe2" ··· 9694 ] 9695 9696 [[package]] 9697 - name = "sqlite-wasm-rs" 9698 - version = "0.4.8" 9699 - source = "registry+https://github.com/rust-lang/crates.io-index" 9700 - checksum = "60bdd87fcb4c9764b024805fb2df5f1d659bea6e629fdbdcdcfc4042b9a640d0" 9701 - dependencies = [ 9702 - "js-sys", 9703 - "once_cell", 9704 - "thiserror 2.0.17", 9705 - "tokio", 9706 - "wasm-bindgen", 9707 - "wasm-bindgen-futures", 9708 - "web-sys", 9709 - ] 9710 - 9711 - [[package]] 9712 name = "stable_deref_trait" 9713 version = "1.2.1" 9714 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 10113 "flate2", 10114 "fnv", 10115 "once_cell", 10116 - "onig", 10117 "plist", 10118 "regex-syntax", 10119 "serde", ··· 10154 "cfg-expr", 10155 "heck 0.5.0", 10156 "pkg-config", 10157 - "toml 0.8.23", 10158 "version-compare", 10159 ] 10160 ··· 10575 source = "registry+https://github.com/rust-lang/crates.io-index" 10576 checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" 10577 dependencies = [ 10578 - "indexmap 2.12.1", 10579 "serde", 10580 - "serde_spanned 0.6.9", 10581 "toml_datetime 0.6.11", 10582 "toml_edit 0.22.27", 10583 - ] 10584 - 10585 - [[package]] 10586 - name = "toml" 10587 - version = "0.9.8" 10588 - source = "registry+https://github.com/rust-lang/crates.io-index" 10589 - checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" 10590 - dependencies = [ 10591 - "serde_core", 10592 - "serde_spanned 1.0.3", 10593 - "toml_datetime 0.7.3", 10594 - "toml_parser", 10595 - "winnow 0.7.14", 10596 ] 10597 10598 [[package]] ··· 10643 dependencies = [ 10644 "indexmap 2.12.1", 10645 "serde", 10646 - "serde_spanned 0.6.9", 10647 "toml_datetime 0.6.11", 10648 - "toml_write", 10649 "winnow 0.7.14", 10650 ] 10651 ··· 10671 ] 10672 10673 [[package]] 10674 - name = "toml_write" 10675 - version = "0.1.2" 10676 - source = "registry+https://github.com/rust-lang/crates.io-index" 10677 - checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" 10678 - 10679 - [[package]] 10680 name = "tower" 10681 version = "0.5.2" 10682 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 10693 ] 10694 10695 [[package]] 10696 - name = "tower-cookies" 10697 - version = "0.11.0" 10698 - source = "registry+https://github.com/rust-lang/crates.io-index" 10699 - checksum = "151b5a3e3c45df17466454bb74e9ecedecc955269bdedbf4d150dfa393b55a36" 10700 - dependencies = [ 10701 - "axum-core", 10702 - "cookie", 10703 - "futures-util", 10704 - "http", 10705 - "parking_lot", 10706 - "pin-project-lite", 10707 - "tower-layer", 10708 - "tower-service", 10709 - ] 10710 - 10711 - [[package]] 10712 name = "tower-http" 10713 version = "0.6.8" 10714 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 10734 "tower-layer", 10735 "tower-service", 10736 "tracing", 10737 - "uuid", 10738 ] 10739 10740 [[package]] ··· 10762 ] 10763 10764 [[package]] 10765 - name = "tracing-appender" 10766 - version = "0.2.4" 10767 - source = "registry+https://github.com/rust-lang/crates.io-index" 10768 - checksum = "786d480bce6247ab75f005b14ae1624ad978d3029d9113f0a22fa1ac773faeaf" 10769 - dependencies = [ 10770 - "crossbeam-channel", 10771 - "parking_lot", 10772 - "thiserror 2.0.17", 10773 - "time", 10774 - "tracing-subscriber", 10775 - ] 10776 - 10777 - [[package]] 10778 name = "tracing-attributes" 10779 version = "0.1.31" 10780 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 10817 ] 10818 10819 [[package]] 10820 - name = "tracing-serde" 10821 - version = "0.2.0" 10822 - source = "registry+https://github.com/rust-lang/crates.io-index" 10823 - checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" 10824 - dependencies = [ 10825 - "serde", 10826 - "tracing-core", 10827 - ] 10828 - 10829 - [[package]] 10830 name = "tracing-subscriber" 10831 version = "0.3.22" 10832 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 10836 "nu-ansi-term", 10837 "once_cell", 10838 "regex-automata", 10839 - "serde", 10840 - "serde_json", 10841 "sharded-slab", 10842 "smallvec", 10843 "thread_local", 10844 "tracing", 10845 "tracing-core", 10846 "tracing-log", 10847 - "tracing-serde", 10848 ] 10849 10850 [[package]] ··· 11138 11139 [[package]] 11140 name = "unsigned-varint" 11141 version = "0.8.0" 11142 source = "registry+https://github.com/rust-lang/crates.io-index" 11143 checksum = "eb066959b24b5196ae73cb057f45598450d2c5f71460e98c49b738086eff9c06" ··· 11441 checksum = "673a33c33048a5ade91a6b139580fa174e19fb0d23f396dca9fa15f2e1e49b35" 11442 dependencies = [ 11443 "cc", 11444 - "downcast-rs 1.2.1", 11445 "rustix", 11446 "scoped-tls", 11447 "smallvec", ··· 11643 name = "weaver-index" 11644 version = "0.1.0" 11645 dependencies = [ 11646 - "axum", 11647 "chrono", 11648 "clap", 11649 - "dashmap 6.1.0", 11650 - "diesel", 11651 - "diesel-async", 11652 - "diesel_migrations", 11653 "dotenvy", 11654 - "futures-util", 11655 - "hyper", 11656 "jacquard", 11657 - "jacquard-axum", 11658 - "jose", 11659 - "jose-jwk", 11660 - "js-sys", 11661 - "libsqlite3-sys", 11662 "miette 7.6.0", 11663 - "minijinja", 11664 - "minijinja-contrib", 11665 - "pin-project", 11666 - "reqwest", 11667 - "send_wrapper", 11668 "serde", 11669 "serde_json", 11670 "thiserror 2.0.17", 11671 "tokio", 11672 - "toml 0.8.23", 11673 - "tower", 11674 - "tower-cookies", 11675 - "tower-http", 11676 "tracing", 11677 - "tracing-appender", 11678 "tracing-subscriber", 11679 "url", 11680 - "uuid", 11681 - "wasm-bindgen", 11682 - "wasm-bindgen-futures", 11683 - "wasmworker", 11684 - "wasmworker-proc-macro", 11685 - "weaver-common", 11686 - "web-time", 11687 ] 11688 11689 [[package]]
··· 772 ] 773 774 [[package]] 775 + name = "bnum" 776 + version = "0.13.0" 777 + source = "registry+https://github.com/rust-lang/crates.io-index" 778 + checksum = "119771309b95163ec7aaf79810da82f7cd0599c19722d48b9c03894dca833966" 779 + 780 + [[package]] 781 name = "bon" 782 version = "3.8.1" 783 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 801 "rustversion", 802 "syn 2.0.111", 803 ] 804 805 [[package]] 806 name = "borsh" ··· 1096 "multihash", 1097 "serde", 1098 "serde_bytes", 1099 + "unsigned-varint 0.8.0", 1100 ] 1101 1102 [[package]] ··· 1110 "inout", 1111 "zeroize", 1112 ] 1113 + 1114 + [[package]] 1115 + name = "cityhash-rs" 1116 + version = "1.0.1" 1117 + source = "registry+https://github.com/rust-lang/crates.io-index" 1118 + checksum = "93a719913643003b84bd13022b4b7e703c09342cd03b679c4641c7d2e50dc34d" 1119 1120 [[package]] 1121 name = "clap" ··· 1160 checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" 1161 1162 [[package]] 1163 + name = "clickhouse" 1164 + version = "0.14.1" 1165 + source = "registry+https://github.com/rust-lang/crates.io-index" 1166 + checksum = "a4916807143285e80eefcaf741a29a98bbd687e4581352631a74ab223e66558b" 1167 + dependencies = [ 1168 + "bnum", 1169 + "bstr", 1170 + "bytes", 1171 + "chrono", 1172 + "cityhash-rs", 1173 + "clickhouse-macros", 1174 + "clickhouse-types", 1175 + "futures-channel", 1176 + "futures-util", 1177 + "http-body-util", 1178 + "hyper", 1179 + "hyper-rustls", 1180 + "hyper-util", 1181 + "lz4_flex", 1182 + "polonius-the-crab", 1183 + "quanta", 1184 + "rustls", 1185 + "serde", 1186 + "thiserror 2.0.17", 1187 + "tokio", 1188 + "url", 1189 + ] 1190 + 1191 + [[package]] 1192 + name = "clickhouse-macros" 1193 + version = "0.3.0" 1194 + source = "registry+https://github.com/rust-lang/crates.io-index" 1195 + checksum = "ff6669899e23cb87b43daf7996f0ea3b9c07d0fb933d745bb7b815b052515ae3" 1196 + dependencies = [ 1197 + "proc-macro2", 1198 + "quote", 1199 + "serde_derive_internals", 1200 + "syn 2.0.111", 1201 + ] 1202 + 1203 + [[package]] 1204 + name = "clickhouse-types" 1205 + version = "0.1.1" 1206 + source = "registry+https://github.com/rust-lang/crates.io-index" 1207 + checksum = "358fbfd439fb0bed02a3e2ecc5131f6a9d039ba5639aed650cf0e845f6ebfc16" 1208 + dependencies = [ 1209 + "bytes", 1210 + "thiserror 2.0.17", 1211 + ] 1212 + 1213 + [[package]] 1214 name = "cobs" 1215 version = "0.3.0" 1216 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1675 "crypto_secretbox", 1676 "curve25519-dalek 5.0.0-pre.1", 1677 "salsa20", 1678 + "serdect", 1679 "subtle", 1680 "zeroize", 1681 ] ··· 1898 checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376" 1899 1900 [[package]] 1901 name = "deflate" 1902 version = "1.0.0" 1903 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2012 version = "0.2.3" 2013 source = "registry+https://github.com/rust-lang/crates.io-index" 2014 checksum = "ab03c107fafeb3ee9f5925686dbb7a73bc76e3932abb0d2b365cb64b169cf04c" 2015 2016 [[package]] 2017 name = "diff" ··· 3210 checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" 3211 3212 [[package]] 3213 name = "dpi" 3214 version = "0.1.2" 3215 source = "registry+https://github.com/rust-lang/crates.io-index" 3216 checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" 3217 3218 [[package]] 3219 name = "dtoa" 3220 version = "1.0.10" 3221 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3262 source = "registry+https://github.com/rust-lang/crates.io-index" 3263 checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" 3264 dependencies = [ 3265 + "pkcs8 0.10.2", 3266 "signature 2.2.0", 3267 ] 3268 ··· 3286 "curve25519-dalek 4.1.3", 3287 "ed25519 2.2.3", 3288 "rand_core 0.6.4", 3289 + "serde", 3290 "sha2 0.10.9", 3291 "subtle", 3292 "zeroize", ··· 3321 checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" 3322 dependencies = [ 3323 "base16ct 0.2.0", 3324 "crypto-bigint", 3325 "digest 0.10.7", 3326 "ff", ··· 3330 "pkcs8 0.10.2", 3331 "rand_core 0.6.4", 3332 "sec1", 3333 "subtle", 3334 "zeroize", 3335 ] ··· 3597 checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" 3598 3599 [[package]] 3600 name = "fnv" 3601 version = "1.0.7" 3602 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3972 source = "registry+https://github.com/rust-lang/crates.io-index" 3973 checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" 3974 dependencies = [ 3975 "typenum", 3976 "version_check", 3977 "zeroize", ··· 4418 source = "registry+https://github.com/rust-lang/crates.io-index" 4419 checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" 4420 dependencies = [ 4421 "foldhash 0.1.5", 4422 ] 4423 4424 [[package]] ··· 4628 ] 4629 4630 [[package]] 4631 + name = "higher-kinded-types" 4632 + version = "0.2.1" 4633 + source = "registry+https://github.com/rust-lang/crates.io-index" 4634 + checksum = "e690f8474c6c5d8ff99656fcbc195a215acc3949481a8b0b3351c838972dc776" 4635 + dependencies = [ 4636 + "macro_rules_attribute", 4637 + "never-say-never", 4638 + "paste", 4639 + ] 4640 + 4641 + [[package]] 4642 name = "hmac" 4643 version = "0.12.1" 4644 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 5219 ] 5220 5221 [[package]] 5222 + name = "iroh-car" 5223 + version = "0.5.1" 5224 + source = "registry+https://github.com/rust-lang/crates.io-index" 5225 + checksum = "cb7f8cd4cb9aa083fba8b52e921764252d0b4dcb1cd6d120b809dbfe1106e81a" 5226 + dependencies = [ 5227 + "anyhow", 5228 + "cid", 5229 + "futures", 5230 + "serde", 5231 + "serde_ipld_dagcbor", 5232 + "thiserror 1.0.69", 5233 + "tokio", 5234 + "unsigned-varint 0.7.2", 5235 + ] 5236 + 5237 + [[package]] 5238 name = "iroh-gossip" 5239 version = "0.95.0" 5240 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 5561 "chrono", 5562 "ciborium", 5563 "cid", 5564 + "ed25519-dalek 2.2.0", 5565 "futures", 5566 "getrandom 0.2.16", 5567 "getrandom 0.3.4", ··· 5695 ] 5696 5697 [[package]] 5698 + name = "jacquard-repo" 5699 + version = "0.9.4" 5700 + dependencies = [ 5701 + "bytes", 5702 + "cid", 5703 + "ed25519-dalek 2.2.0", 5704 + "iroh-car", 5705 + "jacquard-common", 5706 + "jacquard-derive", 5707 + "k256", 5708 + "miette 7.6.0", 5709 + "multihash", 5710 + "n0-future 0.1.3", 5711 + "p256", 5712 + "serde", 5713 + "serde_bytes", 5714 + "serde_ipld_dagcbor", 5715 + "sha2 0.10.9", 5716 + "smol_str", 5717 + "thiserror 2.0.17", 5718 + "tokio", 5719 + "trait-variant", 5720 + ] 5721 + 5722 + [[package]] 5723 name = "javascriptcore-rs" 5724 version = "1.1.2" 5725 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 5775 ] 5776 5777 [[package]] 5778 name = "jose-b64" 5779 version = "0.1.2" 5780 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 5829 "cfg-if", 5830 "ecdsa", 5831 "elliptic-curve", 5832 + "once_cell", 5833 "sha2 0.10.9", 5834 + "signature 2.2.0", 5835 ] 5836 5837 [[package]] ··· 5982 "bitflags 2.10.0", 5983 "libc", 5984 "redox_syscall", 5985 ] 5986 5987 [[package]] ··· 6267 ] 6268 6269 [[package]] 6270 + name = "macro_rules_attribute" 6271 + version = "0.2.2" 6272 + source = "registry+https://github.com/rust-lang/crates.io-index" 6273 + checksum = "65049d7923698040cd0b1ddcced9b0eb14dd22c5f86ae59c3740eab64a676520" 6274 + dependencies = [ 6275 + "macro_rules_attribute-proc_macro", 6276 + "paste", 6277 + ] 6278 + 6279 + [[package]] 6280 + name = "macro_rules_attribute-proc_macro" 6281 + version = "0.2.2" 6282 + source = "registry+https://github.com/rust-lang/crates.io-index" 6283 + checksum = "670fdfda89751bc4a84ac13eaa63e205cf0fd22b4c9a5fbfa085b63c1f1d3a30" 6284 + 6285 + [[package]] 6286 name = "malloc_buf" 6287 version = "0.0.6" 6288 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 6470 checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" 6471 6472 [[package]] 6473 name = "memchr" 6474 version = "2.7.6" 6475 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 6528 "supports-color", 6529 "supports-hyperlinks", 6530 "supports-unicode", 6531 "terminal_size", 6532 "textwrap", 6533 "unicode-width 0.1.14", ··· 6556 ] 6557 6558 [[package]] 6559 name = "mime" 6560 version = "0.3.17" 6561 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 6609 ] 6610 6611 [[package]] 6612 name = "minimal-lexical" 6613 version = "0.2.1" 6614 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 6711 dependencies = [ 6712 "core2", 6713 "serde", 6714 + "unsigned-varint 0.8.0", 6715 ] 6716 6717 [[package]] ··· 6954 ] 6955 6956 [[package]] 6957 + name = "never-say-never" 6958 + version = "6.6.666" 6959 + source = "registry+https://github.com/rust-lang/crates.io-index" 6960 + checksum = "cf5a574dadd7941adeaa71823ecba5e28331b8313fb2e1c6a5c7e5981ea53ad6" 6961 + 6962 + [[package]] 6963 name = "new_debug_unreachable" 6964 version = "1.0.6" 6965 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 7312 checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" 7313 7314 [[package]] 7315 name = "openssl" 7316 version = "0.10.75" 7317 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 7362 checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" 7363 7364 [[package]] 7365 name = "ordered-stream" 7366 version = "0.2.0" 7367 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 7419 source = "registry+https://github.com/rust-lang/crates.io-index" 7420 checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" 7421 dependencies = [ 7422 "elliptic-curve", 7423 "primeorder", 7424 ] 7425 7426 [[package]] ··· 7844 checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" 7845 7846 [[package]] 7847 + name = "polonius-the-crab" 7848 + version = "0.5.0" 7849 + source = "registry+https://github.com/rust-lang/crates.io-index" 7850 + checksum = "ec242d7eccbb2fd8b3b5b6e3cf89f94a91a800f469005b44d154359609f8af72" 7851 + dependencies = [ 7852 + "higher-kinded-types", 7853 + "never-say-never", 7854 + ] 7855 + 7856 + [[package]] 7857 name = "poly1305" 7858 version = "0.9.0-rc.2" 7859 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 8099 ] 8100 8101 [[package]] 8102 + name = "quanta" 8103 + version = "0.12.6" 8104 + source = "registry+https://github.com/rust-lang/crates.io-index" 8105 + checksum = "f3ab5a9d756f0d97bdc89019bd2e4ea098cf9cde50ee7564dde6b81ccc8f06c7" 8106 + dependencies = [ 8107 + "crossbeam-utils", 8108 + "libc", 8109 + "once_cell", 8110 + "raw-cpuid", 8111 + "wasi 0.11.1+wasi-snapshot-preview1", 8112 + "web-sys", 8113 + "winapi", 8114 + ] 8115 + 8116 + [[package]] 8117 name = "quick-error" 8118 version = "1.2.3" 8119 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 8349 version = "0.3.2" 8350 source = "registry+https://github.com/rust-lang/crates.io-index" 8351 checksum = "d20581732dd76fa913c7dff1a2412b714afe3573e94d41c34719de73337cc8ab" 8352 + 8353 + [[package]] 8354 + name = "raw-cpuid" 8355 + version = "11.6.0" 8356 + source = "registry+https://github.com/rust-lang/crates.io-index" 8357 + checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" 8358 + dependencies = [ 8359 + "bitflags 2.10.0", 8360 + ] 8361 8362 [[package]] 8363 name = "raw-window-handle" ··· 8828 ] 8829 8830 [[package]] 8831 name = "scoped-tls" 8832 version = "1.0.1" 8833 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 8849 "der 0.7.10", 8850 "generic-array", 8851 "pkcs8 0.10.2", 8852 "subtle", 8853 "zeroize", 8854 ] 8855 ··· 8945 ] 8946 8947 [[package]] 8948 name = "serde-wasm-bindgen" 8949 version = "0.4.5" 8950 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 9033 ] 9034 9035 [[package]] 9036 + name = "serde_derive_internals" 9037 + version = "0.29.1" 9038 + source = "registry+https://github.com/rust-lang/crates.io-index" 9039 + checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" 9040 + dependencies = [ 9041 + "proc-macro2", 9042 + "quote", 9043 + "syn 2.0.111", 9044 + ] 9045 + 9046 + [[package]] 9047 name = "serde_html_form" 9048 version = "0.2.8" 9049 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 9136 ] 9137 9138 [[package]] 9139 name = "serde_urlencoded" 9140 version = "0.7.1" 9141 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 9180 9181 [[package]] 9182 name = "serdect" 9183 version = "0.4.1" 9184 source = "registry+https://github.com/rust-lang/crates.io-index" 9185 checksum = "d3ef0e35b322ddfaecbc60f34ab448e157e48531288ee49fafbb053696b8ffe2" ··· 9582 ] 9583 9584 [[package]] 9585 name = "stable_deref_trait" 9586 version = "1.2.1" 9587 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 9986 "flate2", 9987 "fnv", 9988 "once_cell", 9989 "plist", 9990 "regex-syntax", 9991 "serde", ··· 10026 "cfg-expr", 10027 "heck 0.5.0", 10028 "pkg-config", 10029 + "toml", 10030 "version-compare", 10031 ] 10032 ··· 10447 source = "registry+https://github.com/rust-lang/crates.io-index" 10448 checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" 10449 dependencies = [ 10450 "serde", 10451 + "serde_spanned", 10452 "toml_datetime 0.6.11", 10453 "toml_edit 0.22.27", 10454 ] 10455 10456 [[package]] ··· 10501 dependencies = [ 10502 "indexmap 2.12.1", 10503 "serde", 10504 + "serde_spanned", 10505 "toml_datetime 0.6.11", 10506 "winnow 0.7.14", 10507 ] 10508 ··· 10528 ] 10529 10530 [[package]] 10531 name = "tower" 10532 version = "0.5.2" 10533 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 10544 ] 10545 10546 [[package]] 10547 name = "tower-http" 10548 version = "0.6.8" 10549 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 10569 "tower-layer", 10570 "tower-service", 10571 "tracing", 10572 ] 10573 10574 [[package]] ··· 10596 ] 10597 10598 [[package]] 10599 name = "tracing-attributes" 10600 version = "0.1.31" 10601 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 10638 ] 10639 10640 [[package]] 10641 name = "tracing-subscriber" 10642 version = "0.3.22" 10643 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 10647 "nu-ansi-term", 10648 "once_cell", 10649 "regex-automata", 10650 "sharded-slab", 10651 "smallvec", 10652 "thread_local", 10653 "tracing", 10654 "tracing-core", 10655 "tracing-log", 10656 ] 10657 10658 [[package]] ··· 10946 10947 [[package]] 10948 name = "unsigned-varint" 10949 + version = "0.7.2" 10950 + source = "registry+https://github.com/rust-lang/crates.io-index" 10951 + checksum = "6889a77d49f1f013504cec6bf97a2c730394adedaeb1deb5ea08949a50541105" 10952 + 10953 + [[package]] 10954 + name = "unsigned-varint" 10955 version = "0.8.0" 10956 source = "registry+https://github.com/rust-lang/crates.io-index" 10957 checksum = "eb066959b24b5196ae73cb057f45598450d2c5f71460e98c49b738086eff9c06" ··· 11255 checksum = "673a33c33048a5ade91a6b139580fa174e19fb0d23f396dca9fa15f2e1e49b35" 11256 dependencies = [ 11257 "cc", 11258 + "downcast-rs", 11259 "rustix", 11260 "scoped-tls", 11261 "smallvec", ··· 11457 name = "weaver-index" 11458 version = "0.1.0" 11459 dependencies = [ 11460 + "base64 0.22.1", 11461 + "bytes", 11462 "chrono", 11463 + "ciborium", 11464 + "cid", 11465 "clap", 11466 + "clickhouse", 11467 "dotenvy", 11468 + "humansize", 11469 "jacquard", 11470 + "jacquard-common", 11471 + "jacquard-repo", 11472 "miette 7.6.0", 11473 + "n0-future 0.1.3", 11474 "serde", 11475 + "serde_ipld_dagcbor", 11476 "serde_json", 11477 + "smol_str", 11478 "thiserror 2.0.17", 11479 "tokio", 11480 "tracing", 11481 "tracing-subscriber", 11482 "url", 11483 + "weaver-api", 11484 ] 11485 11486 [[package]]
+1
Cargo.toml
··· 47 jacquard-axum = {path = "../jacquard/crates/jacquard-axum" } 48 jacquard-derive = { path = "../jacquard/crates/jacquard-derive" } 49 jacquard-lexicon = { path = "../jacquard/crates/jacquard-lexicon", default-features = false } 50 51 # jacquard = { path = "../jacquard-facet/crates/jacquard", default-features = false, features = ["derive", "api_bluesky", "tracing", "serde"] } 52 # jacquard-identity = { path = "../jacquard-facet/crates/jacquard-identity", features = ["cache"] }
··· 47 jacquard-axum = {path = "../jacquard/crates/jacquard-axum" } 48 jacquard-derive = { path = "../jacquard/crates/jacquard-derive" } 49 jacquard-lexicon = { path = "../jacquard/crates/jacquard-lexicon", default-features = false } 50 + jacquard-repo = { path = "../jacquard/crates/jacquard-repo" } 51 52 # jacquard = { path = "../jacquard-facet/crates/jacquard", default-features = false, features = ["derive", "api_bluesky", "tracing", "serde"] } 53 # jacquard-identity = { path = "../jacquard-facet/crates/jacquard-identity", features = ["cache"] }
+40 -52
crates/weaver-index/Cargo.toml
··· 4 edition.workspace = true 5 license.workspace = true 6 publish = false 7 8 [features] 9 default = [] 10 - embedded-db = ["diesel/sqlite", "diesel-async/sync-connection-wrapper"] 11 12 - [dependencies] 13 - weaver-common = { path = "../weaver-common", default-features = false } 14 - clap = { version = "4.5", features = ["derive", "env", "cargo", "unicode"] } 15 - dotenvy = "0.15" 16 17 - tracing = "0.1.40" 18 - tracing-subscriber = { version = "0.3.18", features = ["json", "env-filter"] } 19 - tracing-appender = { version = "0.2.3", features = ["parking_lot"] } 20 21 - chrono = "0.4.41" 22 - serde = { version = "1.0.210", features = ["derive"] } 23 - serde_json = "1.0.128" 24 - uuid = { version = "1.8.0", features = ["v7", "serde"] } 25 26 jacquard = { workspace = true, features = ["websocket", "zstd"] } 27 - jacquard-axum = { workspace = true } 28 29 - axum = "0.8" 30 - hyper = "1.4.1" 31 - tower = { version = "0.5", features = [] } 32 - tower-cookies = "0.11" 33 - tower-http = { version = "0.6", features = [ 34 - "fs", 35 - "trace", 36 - "timeout", 37 - "request-id", 38 - "cors", 39 - "normalize-path", 40 - ] } 41 42 - reqwest = { version = "0.12.7", default-features = false, features = [ 43 - "json", 44 - "rustls-tls", 45 - ] } 46 47 tokio = { version = "1.44", features = ["full"] } 48 49 - miette = { workspace = true, features = ["fancy", "syntect-highlighter"] } 50 thiserror = { workspace = true } 51 52 - diesel = { version = "2.3.0", features = ["sqlite", "serde_json", "chrono", "returning_clauses_for_sqlite_3_35"] } 53 - diesel-async = { version = "0.7", features = ["sqlite", "deadpool"] } 54 - diesel_migrations = "2.3.0" 55 56 - libsqlite3-sys = {version = ">=0.30.1,<0.36.0", features = ["bundled"] } 57 58 - toml = { version = "0.8.22", features = ["preserve_order"] } 59 - jose = { version = "0.0.2", features = ["crypto-rustcrypto"] } 60 - jose-jwk = "0.1.2" 61 url = "2" 62 - 63 - minijinja = { version = "2.9.0" } 64 - minijinja-contrib = { version = "2.9.0" } 65 - dashmap = "6.1.0" 66 67 68 - # wasm-in-browser dependencies 69 - [target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dependencies] 70 - futures-util = "0.3" 71 - js-sys = "0.3" 72 - pin-project = "1" 73 - wasm-bindgen = "0.2" 74 - wasm-bindgen-futures = "0.4" 75 - web-time = "1" 76 - send_wrapper = "0.6" 77 - wasmworker = "0.1" 78 - wasmworker-proc-macro = "0.1"
··· 4 edition.workspace = true 5 license.workspace = true 6 publish = false 7 + default-run = "indexer" 8 9 [features] 10 default = [] 11 12 + [[bin]] 13 + name = "storage-benchmark" 14 + path = "src/bin/storage_benchmark.rs" 15 + 16 + [[bin]] 17 + name = "indexer" 18 + path = "src/bin/weaver_indexer.rs" 19 20 21 + [dependencies] 22 + # Internal 23 + weaver-api = { path = "../weaver-api", features = ["streaming"] } 24 25 + # AT Protocol / Jacquard 26 jacquard = { workspace = true, features = ["websocket", "zstd"] } 27 + jacquard-common = { workspace = true } 28 + jacquard-repo = { workspace = true } 29 30 + # ClickHouse 31 + clickhouse = { version = "0.14", features = ["inserter", "chrono", "rustls-tls-ring", "rustls-tls-webpki-roots"] } 32 33 + # Serialization 34 + serde = { workspace = true } 35 + serde_json = "1.0" 36 + serde_ipld_dagcbor = "0.6" 37 + ciborium = "0.2" 38 39 + # Async runtime 40 tokio = { version = "1.44", features = ["full"] } 41 + n0-future = { workspace = true } 42 43 + # Error handling 44 + miette = { workspace = true, features = ["fancy"] } 45 thiserror = { workspace = true } 46 47 + # Config / CLI 48 + clap = { version = "4.5", features = ["derive", "env"] } 49 + dotenvy = "0.15" 50 51 + # Tracing 52 + tracing = { workspace = true } 53 + tracing-subscriber = { version = "0.3", features = ["env-filter"] } 54 55 + # Data types 56 + bytes = { workspace = true } 57 url = "2" 58 + chrono = { version = "0.4", features = ["serde"] } 59 + smol_str = "0.3" 60 61 + # CID handling (for CAR block lookups) 62 + cid = "0.11" 63 64 + # Utilities 65 + humansize = "2.0" 66 + base64 = "0.22"
crates/weaver-index/appview-config.toml

This is a binary file and will not be displayed.

-3
crates/weaver-index/build.rs
··· 1 - fn main() { 2 - println!("cargo:rerun-if-changed=migrations"); 3 - }
···
-11
crates/weaver-index/diesel.toml
··· 1 - # For documentation on how to configure this file, 2 - # see https://diesel.rs/guides/configuring-diesel-cli 3 - 4 - [print_schema] 5 - file = "src/schema.rs" 6 - custom_type_derives = ["diesel::query_builder::QueryId", "Clone", "std::fmt::Debug", "jacquard::IntoStatic"] 7 - patch_file = "src/schema.patch" 8 - 9 - 10 - [migrations_directory] 11 - dir = "/home/orual/Projects/weaver.sh/crates/weaver-index/migrations"
···
crates/weaver-index/migrations/.keep

This is a binary file and will not be displayed.

-28
crates/weaver-index/migrations/2025-05-15-230036_create_appviewdb/down.sql
··· 1 - -- This file should undo anything in `up.sql` 2 - drop index if exists idx_oauth_auth_requests_did; 3 - 4 - drop index if exists idx_oauth_auth_requests_expires; 5 - 6 - drop index if exists idx_oauth_sessions_did; 7 - 8 - drop index if exists idx_oauth_sessions_did_session; 9 - 10 - drop table if exists oauth_auth_requests; 11 - 12 - drop table if exists oauth_sessions; 13 - 14 - drop table if exists profile_pronouns; 15 - 16 - drop table if exists profile_links; 17 - 18 - drop table if exists profile; 19 - 20 - drop table if exists emails; 21 - 22 - drop table if exists _jetstream; 23 - 24 - drop table if exists follows; 25 - 26 - drop table if exists public_keys; 27 - 28 - drop table if exists registrations;
···
-118
crates/weaver-index/migrations/2025-05-15-230036_create_appviewdb/up.sql
··· 1 - create table if not exists registrations ( 2 - id integer not null primary key autoincrement, 3 - domain text not null unique, 4 - did text not null, 5 - secret text not null, 6 - created timestamp not null default (datetime('now')), 7 - registered text 8 - ); 9 - 10 - create table if not exists public_keys ( 11 - id integer not null primary key autoincrement, 12 - did text not null, 13 - name text not null, 14 - key_contents text not null, 15 - rkey text not null, 16 - created timestamp not null default (datetime('now')), 17 - unique (did, name, key_contents) 18 - ); 19 - 20 - create table if not exists follows ( 21 - user_did text not null, 22 - subject_did text not null, 23 - rkey text not null, 24 - followed_at timestamp not null default (datetime('now')), 25 - primary key (user_did, subject_did), 26 - check (user_did <> subject_did) 27 - ); 28 - 29 - create table if not exists _jetstream ( 30 - id integer not null primary key autoincrement, 31 - last_time_us integer not null 32 - ); 33 - 34 - create table if not exists emails ( 35 - id integer not null primary key autoincrement, 36 - did text not null, 37 - email text not null, 38 - verified boolean not null default false, 39 - verification_code text not null, 40 - last_sent timestamp not null default (datetime('now')), 41 - is_primary boolean not null default false, 42 - created timestamp not null default (datetime('now')), 43 - unique (did, email) 44 - ); 45 - 46 - create table if not exists profile ( 47 - -- id 48 - id integer not null primary key autoincrement, 49 - did text not null, 50 - -- data 51 - avatar text, 52 - description text not null, 53 - include_bluesky boolean not null default false, 54 - include_tangled boolean not null default false, 55 - location text, 56 - pinned_post text, 57 - created_at timestamp default (datetime('now')), 58 - -- constraints 59 - unique (did) 60 - ); 61 - 62 - create table if not exists profile_links ( 63 - -- id 64 - id integer not null primary key autoincrement, 65 - did text not null, 66 - -- data 67 - link text not null, 68 - -- constraints 69 - foreign key (did) references profile (did) on delete cascade 70 - ); 71 - 72 - create table if not exists profile_pronouns ( 73 - -- id 74 - id integer not null primary key autoincrement, 75 - did text not null, 76 - -- data 77 - pronoun text not null, 78 - -- constraints 79 - foreign key (did) references profile (did) on delete cascade 80 - ); 81 - 82 - -- OAuth sessions table for jacquard ClientSessionData 83 - create table if not exists oauth_sessions ( 84 - id integer not null primary key autoincrement, 85 - -- Extracted from ClientSessionData for indexing 86 - did text not null, 87 - session_id text not null, 88 - -- Full ClientSessionData as JSON 89 - session_data blob not null, 90 - created_at timestamp not null default (datetime('now')), 91 - updated_at timestamp not null default (datetime('now')), 92 - unique (did, session_id) 93 - ); 94 - 95 - -- OAuth authorization requests table for jacquard AuthRequestData 96 - create table if not exists oauth_auth_requests ( 97 - id integer not null primary key autoincrement, 98 - -- Extracted from AuthRequestData for indexing 99 - state text not null unique, 100 - -- Optional DID if known at auth request time 101 - account_did text, 102 - -- Full AuthRequestData as JSON 103 - auth_req_data blob not null, 104 - created_at timestamp not null default (datetime('now')), 105 - expires_at timestamp not null default (datetime('now', '+10 minutes')) 106 - ); 107 - 108 - -- Index for quick session lookups 109 - create index if not exists idx_oauth_sessions_did_session on oauth_sessions(did, session_id); 110 - 111 - -- Index for DID lookups 112 - create index if not exists idx_oauth_sessions_did on oauth_sessions(did); 113 - 114 - -- Index for auth request cleanup 115 - create index if not exists idx_oauth_auth_requests_expires on oauth_auth_requests(expires_at); 116 - 117 - -- Index for DID lookups in auth requests 118 - create index if not exists idx_oauth_auth_requests_did on oauth_auth_requests(account_did) where account_did is not null;
···
+12
crates/weaver-index/migrations/clickhouse/000_migrations.sql
···
··· 1 + -- Migration tracking table 2 + -- Tracks which migrations have been applied 3 + 4 + CREATE TABLE IF NOT EXISTS _migrations ( 5 + -- Migration filename (e.g., '001_raw_records.sql') 6 + name String, 7 + 8 + -- When this migration was applied 9 + applied_at DateTime64(3) DEFAULT now64(3) 10 + ) 11 + ENGINE = MergeTree() 12 + ORDER BY (name);
+35
crates/weaver-index/migrations/clickhouse/001_raw_records.sql
···
··· 1 + -- Raw records from firehose/jetstream 2 + -- Core table for all AT Protocol records before denormalization 3 + -- 4 + -- Uses ReplacingMergeTree to deduplicate on (collection, did, rkey) keeping latest indexed_at 5 + -- JSON column stores full record, extract fields only when needed for ORDER BY/WHERE/JOINs 6 + 7 + CREATE TABLE IF NOT EXISTS raw_records ( 8 + -- Decomposed AT URI components (at://did/collection/rkey) 9 + did String, 10 + collection LowCardinality(String), 11 + rkey String, 12 + 13 + -- Content identifier from the record 14 + cid String, 15 + 16 + -- Full record as native JSON (schema-flexible, queryable with record.field.subfield) 17 + record JSON, 18 + 19 + -- Operation: 'create', 'update', 'delete' 20 + operation LowCardinality(String), 21 + 22 + -- Firehose sequence number (metadata only, not for ordering - can jump on relay restart) 23 + seq UInt64, 24 + 25 + -- Event timestamp from firehose 26 + event_time DateTime64(3), 27 + 28 + -- When we indexed this record 29 + indexed_at DateTime64(3) DEFAULT now64(3), 30 + 31 + -- Materialized AT URI for convenience 32 + uri String MATERIALIZED concat('at://', did, '/', collection, '/', rkey) 33 + ) 34 + ENGINE = ReplacingMergeTree(indexed_at) 35 + ORDER BY (collection, did, rkey, indexed_at);
+21
crates/weaver-index/migrations/clickhouse/002_identity_events.sql
···
··· 1 + -- Identity events from firehose (#identity messages) 2 + -- Tracks handle changes, key rotation, etc. 3 + 4 + CREATE TABLE IF NOT EXISTS raw_identity_events ( 5 + -- The DID this identity event is about 6 + did String, 7 + 8 + -- Handle (may be empty if cleared) 9 + handle String, 10 + 11 + -- Sequence number from firehose 12 + seq UInt64, 13 + 14 + -- Event timestamp from firehose 15 + event_time DateTime64(3), 16 + 17 + -- When we indexed this event 18 + indexed_at DateTime64(3) DEFAULT now64(3) 19 + ) 20 + ENGINE = MergeTree() 21 + ORDER BY (did, indexed_at);
+24
crates/weaver-index/migrations/clickhouse/003_account_events.sql
···
··· 1 + -- Account events from firehose (#account messages) 2 + -- Tracks account status changes: active, deactivated, deleted, suspended, takendown 3 + 4 + CREATE TABLE IF NOT EXISTS raw_account_events ( 5 + -- The DID this account event is about 6 + did String, 7 + 8 + -- Whether the account is active 9 + active UInt8, 10 + 11 + -- Account status: 'active', 'deactivated', 'deleted', 'suspended', 'takendown' 12 + status LowCardinality(String), 13 + 14 + -- Sequence number from firehose 15 + seq UInt64, 16 + 17 + -- Event timestamp from firehose 18 + event_time DateTime64(3), 19 + 20 + -- When we indexed this event 21 + indexed_at DateTime64(3) DEFAULT now64(3) 22 + ) 23 + ENGINE = MergeTree() 24 + ORDER BY (did, indexed_at);
+21
crates/weaver-index/migrations/clickhouse/004_events_dlq.sql
···
··· 1 + -- Dead-letter queue for malformed events 2 + -- Events that couldn't be parsed or processed land here for debugging 3 + 4 + CREATE TABLE IF NOT EXISTS raw_events_dlq ( 5 + -- Event type we attempted to parse (if known) 6 + event_type LowCardinality(String), 7 + 8 + -- Raw event data (JSON string of whatever we received) 9 + raw_data String, 10 + 11 + -- Error message describing why parsing failed 12 + error_message String, 13 + 14 + -- Sequence number from firehose (if available) 15 + seq UInt64, 16 + 17 + -- When we received this event 18 + received_at DateTime64(3) DEFAULT now64(3) 19 + ) 20 + ENGINE = MergeTree() 21 + ORDER BY (received_at);
+18
crates/weaver-index/migrations/clickhouse/005_firehose_cursor.sql
···
··· 1 + -- Firehose cursor persistence 2 + -- Tracks our position in the firehose stream for resumption after restart 3 + 4 + CREATE TABLE IF NOT EXISTS firehose_cursor ( 5 + -- Consumer identifier (allows multiple consumers with different cursors) 6 + consumer_id String, 7 + 8 + -- Last successfully processed sequence number 9 + seq UInt64, 10 + 11 + -- Timestamp of the last processed event 12 + event_time DateTime64(3), 13 + 14 + -- When we saved this cursor 15 + updated_at DateTime64(3) DEFAULT now64(3) 16 + ) 17 + ENGINE = ReplacingMergeTree(updated_at) 18 + ORDER BY (consumer_id);
-90
crates/weaver-index/src/api_error.rs
··· 1 - use axum::{ 2 - Json, 3 - extract::rejection::JsonRejection, 4 - response::{IntoResponse, Response}, 5 - }; 6 - use hyper::StatusCode; 7 - use miette::Diagnostic; 8 - use serde::{Deserialize, Serialize}; 9 - use thiserror::Error; 10 - use tracing::error; 11 - 12 - /// Custom error type for the API. 13 - /// The `#[from]` attribute allows for easy conversion from other error types. 14 - #[derive(Error, Debug, Diagnostic)] 15 - pub enum ApiError { 16 - /// Converts from an Axum built-in extractor error. 17 - #[diagnostic_source] 18 - #[error("Invalid payload.")] 19 - InvalidJsonBody(#[from] JsonRejection), 20 - 21 - /// For errors that occur during manual validation. 22 - #[error("Invalid request: {0}")] 23 - #[diagnostic()] 24 - InvalidRequest(String), 25 - 26 - /// Converts from `sqlx::Error`. 27 - #[error("A database error has occurred.")] 28 - #[diagnostic_source] 29 - DatabaseError(#[from] diesel::result::Error), 30 - 31 - #[error("A Weaver error has occurred.")] 32 - #[diagnostic(transparent)] 33 - WeaverError(#[from] weaver_common::error::WeaverError), 34 - /// Converts from any `anyhow::Error`. 35 - #[error("An internal server error has occurred.")] 36 - #[diagnostic(transparent)] 37 - InternalError(miette::Report), 38 - } 39 - 40 - impl From<miette::Report> for ApiError { 41 - fn from(err: miette::Report) -> Self { 42 - ApiError::InternalError(err) 43 - } 44 - } 45 - 46 - #[derive(Serialize, Deserialize)] 47 - pub struct ApiErrorResp { 48 - pub message: String, 49 - } 50 - 51 - // The IntoResponse implementation for ApiError logs the error message. 52 - // 53 - // To avoid exposing implementation details to API consumers, we separate 54 - // the message that we log from the API response message. 55 - impl IntoResponse for ApiError { 56 - fn into_response(self) -> Response { 57 - // Log detailed error for telemetry. 58 - let error_to_log = match &self { 59 - ApiError::InvalidJsonBody(err) => match err { 60 - JsonRejection::JsonDataError(e) => e.body_text(), 61 - JsonRejection::JsonSyntaxError(e) => e.body_text(), 62 - JsonRejection::MissingJsonContentType(_) => { 63 - "Missing `Content-Type: application/json` header".to_string() 64 - } 65 - JsonRejection::BytesRejection(_) => "Failed to buffer request body".to_string(), 66 - _ => "Unknown error".to_string(), 67 - }, 68 - ApiError::InvalidRequest(_) => format!("{}", self), 69 - ApiError::WeaverError(err) => format!("{}", err), 70 - ApiError::DatabaseError(err) => format!("{}", err), 71 - ApiError::InternalError(err) => format!("{}", err), 72 - }; 73 - error!("{}", error_to_log); 74 - 75 - // Create a generic response to hide specific implementation details. 76 - let resp = ApiErrorResp { 77 - message: self.to_string(), 78 - }; 79 - 80 - // Determine the appropriate status code. 81 - let status = match self { 82 - ApiError::InvalidJsonBody(_) | ApiError::InvalidRequest(_) => StatusCode::BAD_REQUEST, 83 - ApiError::WeaverError(_) | ApiError::DatabaseError(_) | ApiError::InternalError(_) => { 84 - StatusCode::INTERNAL_SERVER_ERROR 85 - } 86 - }; 87 - 88 - (status, Json(resp)).into_response() 89 - } 90 - }
···
+483
crates/weaver-index/src/bin/storage_benchmark.rs
···
··· 1 + use bytes::Bytes; 2 + use chrono::{DateTime, Utc}; 3 + use clap::Parser; 4 + use clickhouse::Row; 5 + use n0_future::StreamExt; 6 + use smol_str::SmolStr; 7 + use std::time::{Duration, Instant}; 8 + use tracing::{info, warn}; 9 + use weaver_index::clickhouse::Client; 10 + use weaver_index::config::{ClickHouseConfig, FirehoseConfig}; 11 + use weaver_index::firehose::{FirehoseConsumer, SubscribeReposMessage, extract_records}; 12 + 13 + // ============================================================================= 14 + // Benchmark-specific schema (not part of production) 15 + // ============================================================================= 16 + 17 + const TABLE_JSON: &str = "raw_records_json"; 18 + const TABLE_CBOR: &str = "raw_records_cbor"; 19 + 20 + /// Row type for JSON benchmark records 21 + #[derive(Debug, Clone, Row, serde::Serialize, serde::Deserialize)] 22 + struct RawRecordJson { 23 + did: SmolStr, 24 + collection: SmolStr, 25 + rkey: SmolStr, 26 + cid: String, 27 + record: String, 28 + operation: SmolStr, 29 + seq: u64, 30 + #[serde(with = "clickhouse::serde::chrono::datetime64::millis")] 31 + event_time: DateTime<Utc>, 32 + } 33 + 34 + /// Row type for CBOR benchmark records 35 + #[derive(Debug, Clone, Row, serde::Serialize, serde::Deserialize)] 36 + struct RawRecordCbor { 37 + did: SmolStr, 38 + collection: SmolStr, 39 + rkey: SmolStr, 40 + cid: String, 41 + #[serde(with = "jacquard::serde_bytes_helper")] 42 + record: Bytes, 43 + operation: SmolStr, 44 + seq: u64, 45 + #[serde(with = "clickhouse::serde::chrono::datetime64::millis")] 46 + event_time: DateTime<Utc>, 47 + } 48 + 49 + async fn create_benchmark_tables(client: &Client) -> miette::Result<()> { 50 + client 51 + .execute(&format!( 52 + r#" 53 + CREATE TABLE IF NOT EXISTS {} ( 54 + did String, 55 + collection LowCardinality(String), 56 + rkey String, 57 + cid String, 58 + record JSON, 59 + operation LowCardinality(String), 60 + seq UInt64, 61 + event_time DateTime64(3), 62 + indexed_at DateTime64(3) DEFAULT now64(3) 63 + ) 64 + ENGINE = MergeTree() 65 + ORDER BY (collection, did, rkey, indexed_at) 66 + "#, 67 + TABLE_JSON 68 + )) 69 + .await?; 70 + 71 + client 72 + .execute(&format!( 73 + r#" 74 + CREATE TABLE IF NOT EXISTS {} ( 75 + did String, 76 + collection LowCardinality(String), 77 + rkey String, 78 + cid String, 79 + record String, 80 + operation LowCardinality(String), 81 + seq UInt64, 82 + event_time DateTime64(3), 83 + indexed_at DateTime64(3) DEFAULT now64(3) 84 + ) 85 + ENGINE = MergeTree() 86 + ORDER BY (collection, did, rkey, indexed_at) 87 + "#, 88 + TABLE_CBOR 89 + )) 90 + .await?; 91 + 92 + Ok(()) 93 + } 94 + 95 + async fn drop_benchmark_tables(client: &Client) -> miette::Result<()> { 96 + client 97 + .execute(&format!("DROP TABLE IF EXISTS {}", TABLE_JSON)) 98 + .await?; 99 + client 100 + .execute(&format!("DROP TABLE IF EXISTS {}", TABLE_CBOR)) 101 + .await?; 102 + Ok(()) 103 + } 104 + 105 + // ============================================================================= 106 + // Benchmark logic 107 + // ============================================================================= 108 + 109 + /// Tracks firehose lag to detect if we're falling behind 110 + #[derive(Default)] 111 + struct LagStats { 112 + min_ms: Option<i64>, 113 + max_ms: Option<i64>, 114 + current_ms: i64, 115 + sample_count: u64, 116 + } 117 + 118 + impl LagStats { 119 + fn update(&mut self, event_time_ms: i64) { 120 + let now_ms = Utc::now().timestamp_millis(); 121 + let lag = now_ms - event_time_ms; 122 + 123 + self.current_ms = lag; 124 + self.sample_count += 1; 125 + 126 + self.min_ms = Some(self.min_ms.map_or(lag, |m| m.min(lag))); 127 + self.max_ms = Some(self.max_ms.map_or(lag, |m| m.max(lag))); 128 + } 129 + 130 + fn reset_window(&mut self) { 131 + // Keep current but reset min/max for next reporting window 132 + self.min_ms = Some(self.current_ms); 133 + self.max_ms = Some(self.current_ms); 134 + } 135 + } 136 + 137 + #[derive(Parser)] 138 + #[command(name = "storage-benchmark")] 139 + #[command(about = "Benchmark CBOR vs JSON storage in ClickHouse")] 140 + struct Args { 141 + /// Duration to run the benchmark in minutes 142 + #[arg(short, long, default_value = "60")] 143 + duration_minutes: u64, 144 + 145 + /// Batch size for ClickHouse inserts 146 + #[arg(short, long, default_value = "1000")] 147 + batch_size: usize, 148 + 149 + /// Report interval in seconds 150 + #[arg(short, long, default_value = "30")] 151 + report_interval_secs: u64, 152 + 153 + /// Drop and recreate tables before starting 154 + #[arg(long)] 155 + reset_tables: bool, 156 + } 157 + 158 + #[tokio::main] 159 + async fn main() -> miette::Result<()> { 160 + dotenvy::dotenv().ok(); 161 + 162 + tracing_subscriber::fmt() 163 + .with_env_filter( 164 + tracing_subscriber::EnvFilter::from_default_env() 165 + .add_directive("weaver_index=info".parse().unwrap()) 166 + .add_directive("storage_benchmark=info".parse().unwrap()), 167 + ) 168 + .init(); 169 + 170 + let args = Args::parse(); 171 + 172 + info!("Storage Benchmark: CBOR vs JSON in ClickHouse"); 173 + info!("Duration: {} minutes", args.duration_minutes); 174 + info!("Batch size: {}", args.batch_size); 175 + 176 + // Load configs 177 + let ch_config = ClickHouseConfig::from_env()?; 178 + let firehose_config = FirehoseConfig::from_env()?; 179 + 180 + info!( 181 + "Connecting to ClickHouse at {} (database: {})", 182 + ch_config.url, ch_config.database 183 + ); 184 + let client = Client::new(&ch_config)?; 185 + 186 + // Reset tables if requested 187 + if args.reset_tables { 188 + info!("Dropping existing benchmark tables..."); 189 + drop_benchmark_tables(&client).await?; 190 + } 191 + 192 + // Create tables 193 + info!("Creating benchmark tables..."); 194 + create_benchmark_tables(&client).await?; 195 + 196 + // Create inserters 197 + let mut json_inserter = client.inserter::<RawRecordJson>(TABLE_JSON); 198 + let mut cbor_inserter = client.inserter::<RawRecordCbor>(TABLE_CBOR); 199 + 200 + // Connect to firehose 201 + info!("Connecting to firehose at {}", firehose_config.relay_url); 202 + let consumer = FirehoseConsumer::new(firehose_config); 203 + let mut stream = consumer.connect().await?; 204 + 205 + // Tracking 206 + let start = Instant::now(); 207 + let duration = Duration::from_secs(args.duration_minutes * 60); 208 + let report_interval = Duration::from_secs(args.report_interval_secs); 209 + let mut last_report = Instant::now(); 210 + let mut total_records = 0u64; 211 + let mut total_commits = 0u64; 212 + let mut errors = 0u64; 213 + let mut lag_stats = LagStats::default(); 214 + 215 + info!("Starting benchmark..."); 216 + 217 + while start.elapsed() < duration { 218 + // Check for report interval 219 + if last_report.elapsed() >= report_interval { 220 + // Flush inserters so size measurements are accurate 221 + match json_inserter.commit().await { 222 + Ok(stats) => info!( 223 + " JSON flush: {} rows, {} transactions", 224 + stats.rows, stats.transactions 225 + ), 226 + Err(e) => warn!("Failed to flush JSON inserter: {}", e), 227 + } 228 + match cbor_inserter.commit().await { 229 + Ok(stats) => info!( 230 + " CBOR flush: {} rows, {} transactions", 231 + stats.rows, stats.transactions 232 + ), 233 + Err(e) => warn!("Failed to flush CBOR inserter: {}", e), 234 + } 235 + 236 + report_progress( 237 + &client, 238 + total_records, 239 + total_commits, 240 + errors, 241 + start.elapsed(), 242 + &lag_stats, 243 + ) 244 + .await; 245 + lag_stats.reset_window(); 246 + last_report = Instant::now(); 247 + } 248 + 249 + // Get next message with timeout 250 + let msg = tokio::time::timeout(Duration::from_secs(30), stream.next()).await; 251 + 252 + let msg = match msg { 253 + Ok(Some(Ok(msg))) => msg, 254 + Ok(Some(Err(e))) => { 255 + warn!("Stream error: {}", e); 256 + errors += 1; 257 + continue; 258 + } 259 + Ok(None) => { 260 + warn!("Stream ended unexpectedly"); 261 + break; 262 + } 263 + Err(_) => { 264 + warn!("Timeout waiting for message"); 265 + continue; 266 + } 267 + }; 268 + 269 + // Only process commits 270 + let commit = match msg { 271 + SubscribeReposMessage::Commit(c) => c, 272 + _ => continue, 273 + }; 274 + 275 + total_commits += 1; 276 + 277 + // Track lag 278 + lag_stats.update(commit.time.as_ref().timestamp_millis()); 279 + 280 + // Extract records from the commit 281 + let records = match extract_records(&commit).await { 282 + Ok(r) => r, 283 + Err(e) => { 284 + warn!("Record extraction error: {}", e); 285 + errors += 1; 286 + continue; 287 + } 288 + }; 289 + 290 + // Insert to both tables 291 + for record in records { 292 + // Skip deletes (no record data) 293 + let Some(cbor_bytes) = &record.cbor_bytes else { 294 + continue; 295 + }; 296 + 297 + // JSON table: decode CBOR to JSON 298 + let json_str = match record.to_json() { 299 + Ok(Some(j)) => j, 300 + Ok(None) => continue, 301 + Err(e) => { 302 + warn!("JSON encode error: {}", e); 303 + errors += 1; 304 + continue; 305 + } 306 + }; 307 + 308 + let event_time = DateTime::from_timestamp_millis(record.event_time_ms).unwrap(); 309 + // Insert JSON record 310 + json_inserter 311 + .write(&RawRecordJson { 312 + did: record.did.clone(), 313 + collection: record.collection.clone(), 314 + rkey: record.rkey.clone(), 315 + cid: record.cid.clone(), 316 + record: json_str, 317 + operation: record.operation.clone(), 318 + seq: record.seq as u64, 319 + event_time: event_time.clone(), 320 + }) 321 + .await 322 + .map_err(|e| weaver_index::error::ClickHouseError::Insert { 323 + message: "json insert failed".into(), 324 + source: e, 325 + })?; 326 + 327 + // Insert CBOR record (raw bytes, no base64) 328 + cbor_inserter 329 + .write(&RawRecordCbor { 330 + did: record.did, 331 + collection: record.collection, 332 + rkey: record.rkey, 333 + cid: record.cid, 334 + record: cbor_bytes.clone(), 335 + operation: record.operation, 336 + seq: record.seq as u64, 337 + event_time, 338 + }) 339 + .await 340 + .map_err(|e| weaver_index::error::ClickHouseError::Insert { 341 + message: "cbor insert failed".into(), 342 + source: e, 343 + })?; 344 + 345 + match json_inserter.commit().await { 346 + Ok(_) => {} 347 + Err(e) => warn!("Failed to flush JSON inserter: {}", e), 348 + } 349 + match cbor_inserter.commit().await { 350 + Ok(_) => {} 351 + Err(e) => warn!("Failed to flush CBOR inserter: {}", e), 352 + } 353 + total_records += 1; 354 + } 355 + } 356 + 357 + // Final flush 358 + info!("Flushing remaining records..."); 359 + json_inserter 360 + .end() 361 + .await 362 + .map_err(|e| weaver_index::error::ClickHouseError::Insert { 363 + message: "json flush failed".into(), 364 + source: e, 365 + })?; 366 + cbor_inserter 367 + .end() 368 + .await 369 + .map_err(|e| weaver_index::error::ClickHouseError::Insert { 370 + message: "cbor flush failed".into(), 371 + source: e, 372 + })?; 373 + 374 + // Final report 375 + info!("\n========== FINAL RESULTS =========="); 376 + report_progress( 377 + &client, 378 + total_records, 379 + total_commits, 380 + errors, 381 + start.elapsed(), 382 + &lag_stats, 383 + ) 384 + .await; 385 + 386 + // Detailed size comparison 387 + info!("\nStorage Comparison:"); 388 + let sizes = client.table_sizes(&[TABLE_JSON, TABLE_CBOR]).await?; 389 + 390 + for size in &sizes { 391 + info!( 392 + " {}: {} compressed, {} uncompressed, {:.2}x ratio, {} rows", 393 + size.table, 394 + size.compressed_human(), 395 + size.uncompressed_human(), 396 + size.compression_ratio(), 397 + size.row_count 398 + ); 399 + } 400 + 401 + if sizes.len() == 2 { 402 + let json_size = sizes.iter().find(|s| s.table == TABLE_JSON); 403 + let cbor_size = sizes.iter().find(|s| s.table == TABLE_CBOR); 404 + 405 + if let (Some(json), Some(cbor)) = (json_size, cbor_size) { 406 + let compressed_diff = json.compressed_bytes as f64 / cbor.compressed_bytes as f64; 407 + let uncompressed_diff = json.uncompressed_bytes as f64 / cbor.uncompressed_bytes as f64; 408 + 409 + info!("\nJSON vs CBOR:"); 410 + info!( 411 + " Compressed: JSON is {:.2}x the size of CBOR", 412 + compressed_diff 413 + ); 414 + info!( 415 + " Uncompressed: JSON is {:.2}x the size of CBOR", 416 + uncompressed_diff 417 + ); 418 + 419 + if compressed_diff < 1.0 { 420 + info!( 421 + " Winner (compressed): JSON ({:.1}% smaller)", 422 + (1.0 - compressed_diff) * 100.0 423 + ); 424 + } else { 425 + info!( 426 + " Winner (compressed): CBOR ({:.1}% smaller)", 427 + (1.0 - 1.0 / compressed_diff) * 100.0 428 + ); 429 + } 430 + } 431 + } 432 + 433 + info!("\nBenchmark complete!"); 434 + 435 + Ok(()) 436 + } 437 + 438 + async fn report_progress( 439 + client: &Client, 440 + total_records: u64, 441 + total_commits: u64, 442 + errors: u64, 443 + elapsed: Duration, 444 + lag: &LagStats, 445 + ) { 446 + let records_per_sec = total_records as f64 / elapsed.as_secs_f64(); 447 + 448 + info!( 449 + "Progress: {} records from {} commits in {:.1}s ({:.1}/s), {} errors", 450 + total_records, 451 + total_commits, 452 + elapsed.as_secs_f64(), 453 + records_per_sec, 454 + errors 455 + ); 456 + 457 + // Lag info - critical for detecting if we're falling behind 458 + if lag.sample_count > 0 { 459 + info!( 460 + " Lag: current={:.1}s, min={:.1}s, max={:.1}s (window)", 461 + lag.current_ms as f64 / 1000.0, 462 + lag.min_ms.unwrap_or(0) as f64 / 1000.0, 463 + lag.max_ms.unwrap_or(0) as f64 / 1000.0, 464 + ); 465 + } 466 + 467 + // Try to get current sizes 468 + match client.table_sizes(&[TABLE_JSON, TABLE_CBOR]).await { 469 + Ok(sizes) => { 470 + for size in sizes { 471 + info!( 472 + " {}: {} compressed ({} rows)", 473 + size.table, 474 + size.compressed_human(), 475 + size.row_count 476 + ); 477 + } 478 + } 479 + Err(e) => { 480 + warn!("Failed to query table sizes: {}", e); 481 + } 482 + } 483 + }
+98
crates/weaver-index/src/bin/weaver_indexer.rs
···
··· 1 + use clap::{Parser, Subcommand}; 2 + use tracing::info; 3 + use weaver_index::clickhouse::{Client, Migrator}; 4 + use weaver_index::config::ClickHouseConfig; 5 + 6 + #[derive(Parser)] 7 + #[command(name = "weaver-indexer")] 8 + #[command(about = "Weaver index service - firehose ingestion and query serving")] 9 + struct Args { 10 + #[command(subcommand)] 11 + command: Command, 12 + } 13 + 14 + #[derive(Subcommand)] 15 + enum Command { 16 + /// Run database migrations 17 + Migrate { 18 + /// Show what would be run without executing 19 + #[arg(long)] 20 + dry_run: bool, 21 + }, 22 + 23 + /// Check database connectivity 24 + Health, 25 + 26 + /// Start the indexer service (not yet implemented) 27 + Run, 28 + } 29 + 30 + #[tokio::main] 31 + async fn main() -> miette::Result<()> { 32 + dotenvy::dotenv().ok(); 33 + 34 + tracing_subscriber::fmt() 35 + .with_env_filter( 36 + tracing_subscriber::EnvFilter::from_default_env() 37 + .add_directive("weaver_index=info".parse().unwrap()) 38 + .add_directive("weaver_indexer=info".parse().unwrap()), 39 + ) 40 + .init(); 41 + 42 + let args = Args::parse(); 43 + 44 + match args.command { 45 + Command::Migrate { dry_run } => run_migrate(dry_run).await, 46 + Command::Health => run_health().await, 47 + Command::Run => run_indexer().await, 48 + } 49 + } 50 + 51 + async fn run_migrate(dry_run: bool) -> miette::Result<()> { 52 + let config = ClickHouseConfig::from_env()?; 53 + info!( 54 + "Connecting to ClickHouse at {} (database: {})", 55 + config.url, config.database 56 + ); 57 + 58 + let client = Client::new(&config)?; 59 + let migrator = Migrator::new(&client); 60 + 61 + if dry_run { 62 + let pending = migrator.pending().await?; 63 + if pending.is_empty() { 64 + info!("No pending migrations"); 65 + } else { 66 + info!("Pending migrations:"); 67 + for name in pending { 68 + info!(" - {}", name); 69 + } 70 + } 71 + } else { 72 + let result = migrator.run().await?; 73 + info!("{}", result); 74 + } 75 + 76 + Ok(()) 77 + } 78 + 79 + async fn run_health() -> miette::Result<()> { 80 + let config = ClickHouseConfig::from_env()?; 81 + info!( 82 + "Connecting to ClickHouse at {} (database: {})", 83 + config.url, config.database 84 + ); 85 + 86 + let client = Client::new(&config)?; 87 + 88 + // Simple connectivity check 89 + client.execute("SELECT 1").await?; 90 + info!("ClickHouse connection OK"); 91 + 92 + Ok(()) 93 + } 94 + 95 + async fn run_indexer() -> miette::Result<()> { 96 + info!("Indexer not yet implemented"); 97 + Ok(()) 98 + }
+9
crates/weaver-index/src/clickhouse.rs
···
··· 1 + mod client; 2 + mod migrations; 3 + mod schema; 4 + 5 + pub use client::{Client, TableSize}; 6 + pub use migrations::{MigrationResult, Migrator}; 7 + pub use schema::{ 8 + FirehoseCursor, RawAccountEvent, RawEventDlq, RawIdentityEvent, RawRecord, Tables, 9 + };
+119
crates/weaver-index/src/clickhouse/client.rs
···
··· 1 + use crate::config::ClickHouseConfig; 2 + use crate::error::{ClickHouseError, IndexError}; 3 + use clickhouse::Row; 4 + use clickhouse::inserter::Inserter; 5 + 6 + /// ClickHouse client wrapper with connection pooling and batched inserts 7 + pub struct Client { 8 + inner: clickhouse::Client, 9 + } 10 + 11 + impl Client { 12 + /// Create a new client from configuration 13 + pub fn new(config: &ClickHouseConfig) -> Result<Self, IndexError> { 14 + let inner = clickhouse::Client::default() 15 + .with_url(config.url.as_str()) 16 + .with_database(&config.database) 17 + .with_user(&config.user) 18 + .with_password(&config.password); 19 + 20 + Ok(Self { inner }) 21 + } 22 + 23 + /// Execute a DDL query (CREATE TABLE, etc.) 24 + pub async fn execute(&self, query: &str) -> Result<(), IndexError> { 25 + self.inner 26 + .query(query) 27 + .execute() 28 + .await 29 + .map_err(|e| ClickHouseError::Query { 30 + message: "DDL execution failed".into(), 31 + source: e, 32 + })?; 33 + Ok(()) 34 + } 35 + 36 + /// Create a batched inserter for a table 37 + /// 38 + /// The inserter accumulates rows and flushes them in batches for efficiency. 39 + pub fn inserter<T: Row>(&self, table: &str) -> Inserter<T> { 40 + self.inner 41 + .inserter(table) 42 + .with_max_rows(1000) 43 + .with_period_bias(0.1) 44 + .with_max_bytes(1_048_576) 45 + } 46 + 47 + /// Query table sizes from system.parts 48 + /// 49 + /// Returns (table_name, compressed_bytes, uncompressed_bytes, row_count) 50 + pub async fn table_sizes(&self, tables: &[&str]) -> Result<Vec<TableSize>, IndexError> { 51 + let table_list = tables 52 + .iter() 53 + .map(|t| format!("'{}'", t)) 54 + .collect::<Vec<_>>() 55 + .join(", "); 56 + 57 + let query = format!( 58 + r#" 59 + SELECT 60 + table, 61 + sum(bytes_on_disk) as compressed_bytes, 62 + sum(data_uncompressed_bytes) as uncompressed_bytes, 63 + sum(rows) as row_count 64 + FROM system.parts 65 + WHERE table IN ({}) 66 + AND active 67 + GROUP BY table 68 + "#, 69 + table_list 70 + ); 71 + 72 + let rows = self 73 + .inner 74 + .query(&query) 75 + .fetch_all::<TableSize>() 76 + .await 77 + .map_err(|e| ClickHouseError::Query { 78 + message: "failed to query table sizes".into(), 79 + source: e, 80 + })?; 81 + 82 + Ok(rows) 83 + } 84 + 85 + /// Get reference to inner client for advanced operations 86 + pub fn inner(&self) -> &clickhouse::Client { 87 + &self.inner 88 + } 89 + } 90 + 91 + /// Table size statistics from system.parts 92 + #[derive(Debug, Clone, Row, serde::Deserialize)] 93 + pub struct TableSize { 94 + pub table: String, 95 + pub compressed_bytes: u64, 96 + pub uncompressed_bytes: u64, 97 + pub row_count: u64, 98 + } 99 + 100 + impl TableSize { 101 + /// Format compressed size as human-readable string 102 + pub fn compressed_human(&self) -> String { 103 + humansize::format_size(self.compressed_bytes, humansize::BINARY) 104 + } 105 + 106 + /// Format uncompressed size as human-readable string 107 + pub fn uncompressed_human(&self) -> String { 108 + humansize::format_size(self.uncompressed_bytes, humansize::BINARY) 109 + } 110 + 111 + /// Compression ratio (uncompressed / compressed) 112 + pub fn compression_ratio(&self) -> f64 { 113 + if self.compressed_bytes == 0 { 114 + 0.0 115 + } else { 116 + self.uncompressed_bytes as f64 / self.compressed_bytes as f64 117 + } 118 + } 119 + }
+151
crates/weaver-index/src/clickhouse/migrations.rs
···
··· 1 + use crate::error::{ClickHouseError, IndexError}; 2 + use tracing::info; 3 + 4 + use super::Client; 5 + 6 + /// Embedded migrations - compiled into the binary 7 + const MIGRATIONS: &[(&str, &str)] = &[ 8 + ( 9 + "000_migrations.sql", 10 + include_str!("../../migrations/clickhouse/000_migrations.sql"), 11 + ), 12 + ( 13 + "001_raw_records.sql", 14 + include_str!("../../migrations/clickhouse/001_raw_records.sql"), 15 + ), 16 + ( 17 + "002_identity_events.sql", 18 + include_str!("../../migrations/clickhouse/002_identity_events.sql"), 19 + ), 20 + ( 21 + "003_account_events.sql", 22 + include_str!("../../migrations/clickhouse/003_account_events.sql"), 23 + ), 24 + ( 25 + "004_events_dlq.sql", 26 + include_str!("../../migrations/clickhouse/004_events_dlq.sql"), 27 + ), 28 + ( 29 + "005_firehose_cursor.sql", 30 + include_str!("../../migrations/clickhouse/005_firehose_cursor.sql"), 31 + ), 32 + ]; 33 + 34 + /// Migration runner for ClickHouse 35 + pub struct Migrator<'a> { 36 + client: &'a Client, 37 + } 38 + 39 + impl<'a> Migrator<'a> { 40 + pub fn new(client: &'a Client) -> Self { 41 + Self { client } 42 + } 43 + 44 + /// Run all pending migrations 45 + pub async fn run(&self) -> Result<MigrationResult, IndexError> { 46 + // First, ensure the migrations table exists (bootstrap) 47 + self.ensure_migrations_table().await?; 48 + 49 + // Get list of already applied migrations 50 + let applied = self.get_applied_migrations().await?; 51 + 52 + let mut applied_count = 0; 53 + let mut skipped_count = 0; 54 + 55 + for (name, sql) in MIGRATIONS { 56 + // Skip the bootstrap migration after first run 57 + if *name == "000_migrations.sql" && applied.contains(&"000_migrations.sql".to_string()) 58 + { 59 + skipped_count += 1; 60 + continue; 61 + } 62 + 63 + if applied.contains(&name.to_string()) { 64 + info!(migration = %name, "already applied, skipping"); 65 + skipped_count += 1; 66 + continue; 67 + } 68 + 69 + info!(migration = %name, "applying migration"); 70 + self.client.execute(sql).await?; 71 + self.record_migration(name).await?; 72 + applied_count += 1; 73 + } 74 + 75 + Ok(MigrationResult { 76 + applied: applied_count, 77 + skipped: skipped_count, 78 + }) 79 + } 80 + 81 + /// Check which migrations would be applied without running them 82 + pub async fn pending(&self) -> Result<Vec<String>, IndexError> { 83 + // Try to get applied migrations, but if table doesn't exist, all are pending 84 + let applied = match self.get_applied_migrations().await { 85 + Ok(list) => list, 86 + Err(_) => vec![], 87 + }; 88 + 89 + let pending: Vec<String> = MIGRATIONS 90 + .iter() 91 + .filter(|(name, _)| !applied.contains(&name.to_string())) 92 + .map(|(name, _)| name.to_string()) 93 + .collect(); 94 + 95 + Ok(pending) 96 + } 97 + 98 + async fn ensure_migrations_table(&self) -> Result<(), IndexError> { 99 + // Run the bootstrap migration directly 100 + let (_, sql) = MIGRATIONS 101 + .iter() 102 + .find(|(name, _)| *name == "000_migrations.sql") 103 + .expect("bootstrap migration must exist"); 104 + 105 + self.client.execute(sql).await 106 + } 107 + 108 + async fn get_applied_migrations(&self) -> Result<Vec<String>, IndexError> { 109 + let rows: Vec<MigrationRow> = self 110 + .client 111 + .inner() 112 + .query("SELECT name FROM _migrations ORDER BY name") 113 + .fetch_all() 114 + .await 115 + .map_err(|e| ClickHouseError::Query { 116 + message: "failed to fetch applied migrations".into(), 117 + source: e, 118 + })?; 119 + 120 + Ok(rows.into_iter().map(|r| r.name).collect()) 121 + } 122 + 123 + async fn record_migration(&self, name: &str) -> Result<(), IndexError> { 124 + let query = format!("INSERT INTO _migrations (name) VALUES ('{}')", name); 125 + self.client.execute(&query).await 126 + } 127 + } 128 + 129 + #[derive(Debug, Clone, clickhouse::Row, serde::Deserialize)] 130 + struct MigrationRow { 131 + name: String, 132 + } 133 + 134 + /// Result of running migrations 135 + #[derive(Debug, Clone)] 136 + pub struct MigrationResult { 137 + /// Number of migrations applied 138 + pub applied: usize, 139 + /// Number of migrations skipped (already applied) 140 + pub skipped: usize, 141 + } 142 + 143 + impl std::fmt::Display for MigrationResult { 144 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 145 + write!( 146 + f, 147 + "{} migrations applied, {} skipped", 148 + self.applied, self.skipped 149 + ) 150 + } 151 + }
+67
crates/weaver-index/src/clickhouse/schema.rs
···
··· 1 + use chrono::{DateTime, Utc}; 2 + use clickhouse::Row; 3 + 4 + /// Table names for production schema 5 + pub struct Tables; 6 + 7 + impl Tables { 8 + pub const RAW_RECORDS: &'static str = "raw_records"; 9 + pub const RAW_IDENTITY_EVENTS: &'static str = "raw_identity_events"; 10 + pub const RAW_ACCOUNT_EVENTS: &'static str = "raw_account_events"; 11 + pub const RAW_EVENTS_DLQ: &'static str = "raw_events_dlq"; 12 + pub const FIREHOSE_CURSOR: &'static str = "firehose_cursor"; 13 + } 14 + 15 + /// Row type for raw_records table 16 + /// Schema defined in migrations/clickhouse/001_raw_records.sql 17 + #[derive(Debug, Clone, Row, serde::Serialize, serde::Deserialize)] 18 + pub struct RawRecord { 19 + pub did: String, 20 + pub collection: String, 21 + pub rkey: String, 22 + pub cid: String, 23 + pub record: String, // JSON string - ClickHouse JSON type accepts string 24 + pub operation: String, 25 + pub seq: u64, 26 + #[serde(with = "clickhouse::serde::chrono::datetime64::millis")] 27 + pub event_time: DateTime<Utc>, 28 + } 29 + 30 + /// Row type for raw_identity_events table 31 + #[derive(Debug, Clone, Row, serde::Serialize, serde::Deserialize)] 32 + pub struct RawIdentityEvent { 33 + pub did: String, 34 + pub handle: String, 35 + pub seq: u64, 36 + #[serde(with = "clickhouse::serde::chrono::datetime64::millis")] 37 + pub event_time: DateTime<Utc>, 38 + } 39 + 40 + /// Row type for raw_account_events table 41 + #[derive(Debug, Clone, Row, serde::Serialize, serde::Deserialize)] 42 + pub struct RawAccountEvent { 43 + pub did: String, 44 + pub active: u8, 45 + pub status: String, 46 + pub seq: u64, 47 + #[serde(with = "clickhouse::serde::chrono::datetime64::millis")] 48 + pub event_time: DateTime<Utc>, 49 + } 50 + 51 + /// Row type for raw_events_dlq table 52 + #[derive(Debug, Clone, Row, serde::Serialize, serde::Deserialize)] 53 + pub struct RawEventDlq { 54 + pub event_type: String, 55 + pub raw_data: String, // JSON string 56 + pub error_message: String, 57 + pub seq: u64, 58 + } 59 + 60 + /// Row type for firehose_cursor table 61 + #[derive(Debug, Clone, Row, serde::Serialize, serde::Deserialize)] 62 + pub struct FirehoseCursor { 63 + pub consumer_id: String, 64 + pub seq: u64, 65 + #[serde(with = "clickhouse::serde::chrono::datetime64::millis")] 66 + pub event_time: DateTime<Utc>, 67 + }
+84 -43
crates/weaver-index/src/config.rs
··· 1 - use miette::miette; 2 - use serde::{Deserialize, Serialize}; 3 - use std::{env, fs}; 4 5 - #[derive(Deserialize, Serialize, Clone)] 6 - pub struct OauthConfig { 7 - pub jwks: Vec<jose_jwk::Jwk>, 8 } 9 10 - #[derive(Deserialize, Serialize, Clone)] 11 - pub struct JetstreamConfig { 12 - pub endpoint: String, 13 - } 14 15 - impl Default for JetstreamConfig { 16 - fn default() -> Self { 17 - Self { 18 - endpoint: "wss://jetstream1.us-east.bsky.network/subscribe".into(), 19 - } 20 } 21 } 22 23 - #[derive(Deserialize, Serialize, Clone)] 24 - pub struct CoreConfig { 25 - pub db_path: String, 26 - pub listen_addr: String, 27 - pub appview_host: String, 28 - pub cookie_secret: String, 29 } 30 31 - impl Default for CoreConfig { 32 - fn default() -> Self { 33 - Self { 34 - db_path: "postgres://postgres:@localhost/weaver_appview".into(), 35 - listen_addr: "0.0.0.0:4000".into(), 36 - appview_host: "https://appview.weaver.sh".into(), 37 - cookie_secret: "00000000000000000000000000000000".into(), 38 - } 39 } 40 } 41 42 - #[derive(Deserialize, Serialize, Clone)] 43 pub struct Config { 44 - pub oauth: OauthConfig, 45 - pub jetstream: JetstreamConfig, 46 - pub core: CoreConfig, 47 } 48 49 impl Config { 50 - pub fn load(config_file: &str) -> miette::Result<Config> { 51 - let mut config_string = fs::read_to_string(config_file) 52 - .map_err(|e| miette!("error reading config file {}", e))?; 53 - // substitute environment variables in config file 54 - for (k, v) in env::vars() { 55 - config_string = config_string.replace(&format!("${}", k), &v); 56 - } 57 - 58 - Ok(toml::from_str(&config_string) 59 - .map_err(|e| miette!("error parsing config file {}", e))?) 60 } 61 }
··· 1 + use crate::error::{ConfigError, IndexError}; 2 + use url::Url; 3 4 + /// ClickHouse connection configuration 5 + #[derive(Debug, Clone)] 6 + pub struct ClickHouseConfig { 7 + pub url: Url, 8 + pub database: String, 9 + pub user: String, 10 + pub password: String, 11 } 12 13 + impl ClickHouseConfig { 14 + /// Load configuration from environment variables. 15 + /// 16 + /// Required env vars: 17 + /// - `CLICKHOUSE_URL`: Full URL including protocol (e.g., `https://xyz.clickhouse.cloud:8443`) 18 + /// - `CLICKHOUSE_DATABASE`: Database name 19 + /// - `CLICKHOUSE_USER`: Username 20 + /// - `CLICKHOUSE_PASSWORD`: Password 21 + pub fn from_env() -> Result<Self, IndexError> { 22 + let url_str = std::env::var("CLICKHOUSE_URL").map_err(|_| ConfigError::MissingEnv { 23 + var: "CLICKHOUSE_URL", 24 + })?; 25 26 + let url = Url::parse(&url_str).map_err(|e| ConfigError::UrlParse { 27 + url: url_str, 28 + message: e.to_string(), 29 + })?; 30 + 31 + let database = 32 + std::env::var("CLICKHOUSE_DATABASE").map_err(|_| ConfigError::MissingEnv { 33 + var: "CLICKHOUSE_DATABASE", 34 + })?; 35 + 36 + let user = std::env::var("CLICKHOUSE_USER").map_err(|_| ConfigError::MissingEnv { 37 + var: "CLICKHOUSE_USER", 38 + })?; 39 + 40 + let password = 41 + std::env::var("CLICKHOUSE_PASSWORD").map_err(|_| ConfigError::MissingEnv { 42 + var: "CLICKHOUSE_PASSWORD", 43 + })?; 44 + 45 + Ok(Self { 46 + url, 47 + database, 48 + user, 49 + password, 50 + }) 51 } 52 } 53 54 + /// Firehose relay configuration 55 + #[derive(Debug, Clone)] 56 + pub struct FirehoseConfig { 57 + pub relay_url: Url, 58 + pub cursor: Option<i64>, 59 } 60 61 + impl FirehoseConfig { 62 + /// Default relay URL (Bluesky network) 63 + pub const DEFAULT_RELAY: &'static str = "wss://bsky.network"; 64 + 65 + /// Load configuration from environment variables. 66 + /// 67 + /// Optional env vars: 68 + /// - `FIREHOSE_RELAY_URL`: Relay WebSocket URL (default: wss://bsky.network) 69 + /// - `FIREHOSE_CURSOR`: Starting cursor position (default: none, starts from live) 70 + pub fn from_env() -> Result<Self, IndexError> { 71 + let relay_str = 72 + std::env::var("FIREHOSE_RELAY_URL").unwrap_or_else(|_| Self::DEFAULT_RELAY.to_string()); 73 + 74 + let relay_url = Url::parse(&relay_str).map_err(|e| ConfigError::UrlParse { 75 + url: relay_str, 76 + message: e.to_string(), 77 + })?; 78 + 79 + let cursor = std::env::var("FIREHOSE_CURSOR") 80 + .ok() 81 + .and_then(|s| s.parse().ok()); 82 + 83 + Ok(Self { relay_url, cursor }) 84 } 85 } 86 87 + /// Combined configuration for the indexer 88 + #[derive(Debug, Clone)] 89 pub struct Config { 90 + pub clickhouse: ClickHouseConfig, 91 + pub firehose: FirehoseConfig, 92 } 93 94 impl Config { 95 + /// Load all configuration from environment variables. 96 + pub fn from_env() -> Result<Self, IndexError> { 97 + Ok(Self { 98 + clickhouse: ClickHouseConfig::from_env()?, 99 + firehose: FirehoseConfig::from_env()?, 100 + }) 101 } 102 }
-63
crates/weaver-index/src/db.rs
··· 1 - use diesel::prelude::*; 2 - use diesel_migrations::{EmbeddedMigrations, MigrationHarness, embed_migrations}; 3 - pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!(); 4 - use diesel_async::RunQueryDsl; 5 - use diesel_async::pooled_connection::AsyncDieselConnectionManager; 6 - use diesel_async::pooled_connection::deadpool::Pool; 7 - use diesel_async::sync_connection_wrapper::SyncConnectionWrapper; 8 - 9 - #[derive(Clone)] 10 - pub struct Db { 11 - pub pool: Pool<SyncConnectionWrapper<SqliteConnection>>, 12 - } 13 - 14 - impl Db { 15 - /// Yes, this fuction can and WILL panic if it can't create the connection pool 16 - /// for some reason. We just want to bail because the appview 17 - /// does not work without a database. 18 - pub async fn new(db_path: Option<String>) -> Self { 19 - let database_url = if let Some(db_path) = db_path { 20 - db_path 21 - } else { 22 - std::env::var("DATABASE_URL").expect("DATABASE_URL must be set") 23 - }; 24 - let config = AsyncDieselConnectionManager::<SyncConnectionWrapper<SqliteConnection>>::new( 25 - database_url, 26 - ); 27 - let pool = Pool::builder(config) 28 - .build() 29 - .expect("Failed to create pool"); 30 - Self { pool } 31 - } 32 - } 33 - 34 - pub fn run_migrations( 35 - db_path: Option<String>, 36 - ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { 37 - let database_url = if let Some(db_path) = db_path { 38 - db_path 39 - } else { 40 - std::env::var("DATABASE_URL").expect("DATABASE_URL must be set") 41 - }; 42 - let mut connection = SqliteConnection::establish(&database_url) 43 - .unwrap_or_else(|_| panic!("Error connecting to {}", database_url)); 44 - // This will run the necessary migrations. 45 - // 46 - // See the documentation for `MigrationHarness` for 47 - // all available methods. 48 - println!("Attempting migrations..."); 49 - let result = connection.run_pending_migrations(MIGRATIONS); 50 - println!("{:?}", result); 51 - if result.is_err() { 52 - println!("Failed to run migrations"); 53 - return result.map(|_| ()); 54 - } 55 - println!("Migrations Applied:"); 56 - let applied_migrations = connection.applied_migrations()?; 57 - for migration in applied_migrations { 58 - println!(" * {}", migration); 59 - } 60 - Ok(()) 61 - } 62 - 63 - pub struct Runtime;
···
+118
crates/weaver-index/src/error.rs
···
··· 1 + use miette::Diagnostic; 2 + use thiserror::Error; 3 + 4 + /// Top-level error type for weaver-index operations 5 + #[derive(Debug, Error, Diagnostic)] 6 + pub enum IndexError { 7 + #[error(transparent)] 8 + #[diagnostic(transparent)] 9 + ClickHouse(#[from] ClickHouseError), 10 + 11 + #[error(transparent)] 12 + #[diagnostic(transparent)] 13 + Firehose(#[from] FirehoseError), 14 + 15 + #[error(transparent)] 16 + #[diagnostic(transparent)] 17 + Car(#[from] CarError), 18 + 19 + #[error(transparent)] 20 + #[diagnostic(transparent)] 21 + Config(#[from] ConfigError), 22 + } 23 + 24 + /// ClickHouse database errors 25 + #[derive(Debug, Error, Diagnostic)] 26 + pub enum ClickHouseError { 27 + #[error("failed to connect to ClickHouse: {message}")] 28 + #[diagnostic(code(clickhouse::connection))] 29 + Connection { 30 + message: String, 31 + #[source] 32 + source: clickhouse::error::Error, 33 + }, 34 + 35 + #[error("ClickHouse query failed: {message}")] 36 + #[diagnostic(code(clickhouse::query))] 37 + Query { 38 + message: String, 39 + #[source] 40 + source: clickhouse::error::Error, 41 + }, 42 + 43 + #[error("failed to insert batch: {message}")] 44 + #[diagnostic(code(clickhouse::insert))] 45 + Insert { 46 + message: String, 47 + #[source] 48 + source: clickhouse::error::Error, 49 + }, 50 + 51 + #[error("schema migration failed: {message}")] 52 + #[diagnostic(code(clickhouse::schema))] 53 + Schema { message: String }, 54 + } 55 + 56 + /// Firehose/subscription stream errors 57 + #[derive(Debug, Error, Diagnostic)] 58 + pub enum FirehoseError { 59 + #[error("failed to connect to relay at {url}")] 60 + #[diagnostic(code(firehose::connection))] 61 + Connection { url: String, message: String }, 62 + 63 + #[error("websocket stream error: {message}")] 64 + #[diagnostic(code(firehose::stream))] 65 + Stream { message: String }, 66 + 67 + #[error("failed to parse event header")] 68 + #[diagnostic(code(firehose::header))] 69 + HeaderParse { 70 + #[source] 71 + source: ciborium::de::Error<std::io::Error>, 72 + }, 73 + 74 + #[error("failed to decode event body: {event_type}")] 75 + #[diagnostic(code(firehose::decode))] 76 + BodyDecode { event_type: String, message: String }, 77 + 78 + #[error("unknown event type: {event_type}")] 79 + #[diagnostic(code(firehose::unknown_event))] 80 + UnknownEvent { event_type: String }, 81 + } 82 + 83 + /// CAR file parsing errors 84 + #[derive(Debug, Error, Diagnostic)] 85 + pub enum CarError { 86 + #[error("failed to parse CAR data")] 87 + #[diagnostic(code(car::parse))] 88 + Parse { message: String }, 89 + 90 + #[error("block not found for CID: {cid}")] 91 + #[diagnostic(code(car::block_not_found))] 92 + BlockNotFound { cid: String }, 93 + 94 + #[error("failed to decode record from block: {message}")] 95 + #[diagnostic(code(car::record_decode))] 96 + RecordDecode { message: String }, 97 + } 98 + 99 + /// Configuration errors 100 + #[derive(Debug, Error, Diagnostic)] 101 + pub enum ConfigError { 102 + #[error("missing required environment variable: {var}")] 103 + #[diagnostic( 104 + code(config::missing_env), 105 + help("Set the {var} environment variable or add it to your .env file") 106 + )] 107 + MissingEnv { var: &'static str }, 108 + 109 + #[error("invalid configuration value for {field}: {message}")] 110 + #[diagnostic(code(config::invalid))] 111 + Invalid { field: &'static str, message: String }, 112 + 113 + #[error("failed to parse URL: {url}")] 114 + #[diagnostic(code(config::url_parse))] 115 + UrlParse { url: String, message: String }, 116 + } 117 + 118 + pub type Result<T> = std::result::Result<T, IndexError>;
+7
crates/weaver-index/src/firehose.rs
···
··· 1 + mod consumer; 2 + mod records; 3 + 4 + pub use consumer::{ 5 + FirehoseConsumer, MessageStream, SubscribeReposMessage, Commit, Identity, Account, Sync, 6 + }; 7 + pub use records::{extract_records, ExtractedRecord};
+48
crates/weaver-index/src/firehose/consumer.rs
···
··· 1 + use crate::config::FirehoseConfig; 2 + use crate::error::{FirehoseError, IndexError}; 3 + use jacquard_common::stream::StreamError; 4 + use jacquard_common::xrpc::subscription::{SubscriptionClient, TungsteniteSubscriptionClient}; 5 + use n0_future::stream::Boxed; 6 + 7 + // Re-export the message types from weaver_api for convenience 8 + pub use weaver_api::com_atproto::sync::subscribe_repos::{ 9 + Account, Commit, Identity, SubscribeRepos, SubscribeReposMessage, Sync, 10 + }; 11 + 12 + /// Typed firehose message stream 13 + pub type MessageStream = Boxed<Result<SubscribeReposMessage<'static>, StreamError>>; 14 + 15 + /// Firehose consumer that connects to a relay and yields typed events 16 + pub struct FirehoseConsumer { 17 + config: FirehoseConfig, 18 + } 19 + 20 + impl FirehoseConsumer { 21 + pub fn new(config: FirehoseConfig) -> Self { 22 + Self { config } 23 + } 24 + 25 + /// Connect to the firehose and return a typed message stream 26 + /// 27 + /// Messages are automatically decoded and converted to owned ('static) types. 28 + pub async fn connect(&self) -> Result<MessageStream, IndexError> { 29 + let client = TungsteniteSubscriptionClient::from_base_uri(self.config.relay_url.clone()); 30 + 31 + let mut params = SubscribeRepos::new(); 32 + if let Some(cursor) = self.config.cursor { 33 + params = params.cursor(cursor); 34 + } 35 + let params = params.build(); 36 + 37 + let stream = client 38 + .subscribe(&params) 39 + .await 40 + .map_err(|e| FirehoseError::Connection { 41 + url: self.config.relay_url.to_string(), 42 + message: e.to_string(), 43 + })?; 44 + 45 + let (_sink, messages) = stream.into_stream(); 46 + Ok(messages) 47 + } 48 + }
+108
crates/weaver-index/src/firehose/records.rs
···
··· 1 + use crate::error::{CarError, IndexError}; 2 + use bytes::Bytes; 3 + use jacquard_repo::car::reader::parse_car_bytes; 4 + use smol_str::{SmolStr, ToSmolStr}; 5 + 6 + use super::consumer::Commit; 7 + 8 + /// An extracted record from a firehose commit 9 + #[derive(Debug, Clone)] 10 + pub struct ExtractedRecord { 11 + /// DID of the repo owner 12 + pub did: SmolStr, 13 + /// Collection NSID (e.g., "app.bsky.feed.post") 14 + pub collection: SmolStr, 15 + /// Record key within the collection 16 + pub rkey: SmolStr, 17 + /// Content identifier 18 + pub cid: String, 19 + /// Operation type: "create", "update", or "delete" 20 + pub operation: SmolStr, 21 + /// Raw DAG-CBOR bytes of the record (None for deletes) 22 + pub cbor_bytes: Option<Bytes>, 23 + /// Sequence number from the firehose event 24 + pub seq: i64, 25 + /// Event timestamp (milliseconds since epoch) 26 + pub event_time_ms: i64, 27 + } 28 + 29 + impl ExtractedRecord { 30 + /// Decode the CBOR bytes to JSON string 31 + /// 32 + /// Uses jacquard's RawData type which properly handles CID links 33 + /// and other AT Protocol specific types. 34 + pub fn to_json(&self) -> Result<Option<String>, IndexError> { 35 + use jacquard_common::types::value::{RawData, from_cbor}; 36 + 37 + match &self.cbor_bytes { 38 + Some(bytes) => { 39 + // RawData handles CID links and other IPLD types correctly 40 + let value: RawData<'static> = 41 + from_cbor::<RawData>(bytes).map_err(|e| CarError::RecordDecode { 42 + message: format!("failed to decode DAG-CBOR: {}", e), 43 + })?; 44 + let json = serde_json::to_string(&value).map_err(|e| CarError::RecordDecode { 45 + message: format!("failed to encode JSON: {}", e), 46 + })?; 47 + Ok(Some(json)) 48 + } 49 + None => Ok(None), 50 + } 51 + } 52 + } 53 + 54 + /// Extract records from a firehose commit 55 + /// 56 + /// Parses the CAR data and extracts each record referenced by the operations. 57 + pub async fn extract_records(commit: &Commit<'_>) -> Result<Vec<ExtractedRecord>, IndexError> { 58 + let parsed_car = parse_car_bytes(&commit.blocks) 59 + .await 60 + .map_err(|e| CarError::Parse { 61 + message: e.to_string(), 62 + })?; 63 + 64 + let event_time_ms = commit.time.as_ref().timestamp_millis(); 65 + let mut records = Vec::with_capacity(commit.ops.len()); 66 + 67 + for op in &commit.ops { 68 + let path: &str = op.path.as_ref(); 69 + 70 + // Path format: "collection/rkey" 71 + let (collection, rkey) = match path.split_once('/') { 72 + Some((c, r)) => (c.to_smolstr(), r.to_smolstr()), 73 + None => { 74 + tracing::warn!(path = %path, "invalid op path format, skipping"); 75 + continue; 76 + } 77 + }; 78 + 79 + let operation = op.action.to_smolstr(); 80 + let cid_str = op.cid.as_ref().map(|c| c.to_string()).unwrap_or_default(); 81 + 82 + // For creates/updates, look up the record in the CAR blocks 83 + let cbor_bytes = if let Some(cid_link) = &op.cid { 84 + match cid_link.0.to_ipld() { 85 + Ok(ipld_cid) => parsed_car.blocks.get(&ipld_cid).cloned(), 86 + Err(_) => { 87 + tracing::warn!(cid = %cid_str, "failed to convert CID to IPLD format"); 88 + None 89 + } 90 + } 91 + } else { 92 + None 93 + }; 94 + 95 + records.push(ExtractedRecord { 96 + did: commit.repo.to_smolstr(), 97 + collection, 98 + rkey, 99 + cid: cid_str, 100 + operation, 101 + cbor_bytes, 102 + seq: commit.seq, 103 + event_time_ms, 104 + }); 105 + } 106 + 107 + Ok(records) 108 + }
+7
crates/weaver-index/src/lib.rs
···
··· 1 + pub mod clickhouse; 2 + pub mod config; 3 + pub mod error; 4 + pub mod firehose; 5 + 6 + pub use config::Config; 7 + pub use error::{IndexError, Result};
-134
crates/weaver-index/src/main.rs
··· 1 - pub mod api_error; 2 - 3 - pub mod config; 4 - pub mod db; 5 - pub mod middleware; 6 - pub mod models; 7 - pub mod oauth; 8 - pub mod routes; 9 - pub mod schema; 10 - pub mod state; 11 - pub mod telemetry; 12 - 13 - use axum::Router; 14 - use clap::Parser; 15 - use config::*; 16 - use db::*; 17 - use dotenvy::dotenv; 18 - use miette::IntoDiagnostic; 19 - use miette::miette; 20 - use state::*; 21 - use std::env; 22 - 23 - use tokio::net::TcpListener; 24 - use tracing::{debug, error, info}; 25 - 26 - #[derive(Parser)] 27 - #[command(author, version, about, long_about = None)] 28 - struct Cli { 29 - #[arg( 30 - short, 31 - long, 32 - value_name = "FILE", 33 - default_value = "appview-config.toml" 34 - )] 35 - config: String, 36 - } 37 - 38 - #[tokio::main] 39 - async fn main() -> miette::Result<()> { 40 - let config = initialize()?; 41 - // Run any migrations before we do anything else. 42 - let db_path = config.core.db_path.clone(); 43 - let _ = tokio::task::spawn_blocking(|| db::run_migrations(Some(db_path))) 44 - .await 45 - .into_diagnostic()?; 46 - let db = Db::new(Some(config.core.db_path.clone())).await; 47 - debug!("Connected to database"); 48 - // Spin up our server. 49 - info!("Starting server on {}", config.core.listen_addr); 50 - let listener = TcpListener::bind(&config.core.listen_addr) 51 - .await 52 - .expect("Failed to bind address"); 53 - let router = router(config, db); 54 - axum::serve(listener, router) 55 - .await 56 - .expect("Failed to start server"); 57 - Ok(()) 58 - } 59 - 60 - pub fn router(cfg: Config, db: Db) -> Router { 61 - let app_state = AppState::new(cfg, db); 62 - 63 - // Middleware that adds high level tracing to a Service. 64 - // Trace comes with good defaults but also supports customizing many aspects of the output: 65 - // https://docs.rs/tower-http/latest/tower_http/trace/index.html 66 - let trace_layer = telemetry::trace_layer(); 67 - 68 - // Sets 'x-request-id' header with randomly generated uuid v7. 69 - let request_id_layer = middleware::request_id_layer(); 70 - 71 - // Propagates 'x-request-id' header from the request to the response. 72 - let propagate_request_id_layer = middleware::propagate_request_id_layer(); 73 - 74 - // Layer that applies the Cors middleware which adds headers for CORS. 75 - let cors_layer = middleware::cors_layer(); 76 - 77 - // Layer that applies the Timeout middleware, which sets a timeout for requests. 78 - // The default value is 15 seconds. 79 - let timeout_layer = middleware::timeout_layer(); 80 - 81 - // Any trailing slashes from request paths will be removed. For example, a request with `/foo/` 82 - // will be changed to `/foo` before reaching the internal service. 83 - let normalize_path_layer = middleware::normalize_path_layer(); 84 - 85 - // Create the router with the routes. 86 - let router = routes::router(); 87 - 88 - // Combine all the routes and apply the middleware layers. 89 - // The order of the layers is important. The first layer is the outermost layer. 90 - Router::new() 91 - .merge(router) 92 - .layer(normalize_path_layer) 93 - .layer(cors_layer) 94 - .layer(timeout_layer) 95 - .layer(propagate_request_id_layer) 96 - .layer(trace_layer) 97 - .layer(request_id_layer) 98 - .with_state(app_state) 99 - } 100 - 101 - pub fn initialize() -> miette::Result<Config> { 102 - miette::set_hook(Box::new(|_| { 103 - Box::new( 104 - miette::MietteHandlerOpts::new() 105 - .terminal_links(true) 106 - //.rgb_colors(miette::RgbColors::) 107 - .with_cause_chain() 108 - .with_syntax_highlighting(miette::highlighters::SyntectHighlighter::default()) 109 - .color(true) 110 - .context_lines(5) 111 - .tab_width(2) 112 - .break_words(true) 113 - .build(), 114 - ) 115 - })) 116 - .map_err(|e| miette!("Failed to set miette hook: {}", e))?; 117 - miette::set_panic_hook(); 118 - dotenv().ok(); 119 - let cli = Cli::parse(); 120 - let config = config::Config::load(&cli.config); 121 - let config = if let Err(e) = config { 122 - error!("{}", e); 123 - config::Config::load( 124 - &env::var("APPVIEW_CONFIG").expect("Either set APPVIEW_CONFIG to the path to your config file, pass --config FILE to specify the path, or create a file called appview-config.toml in the directory where you are running the binary from."), 125 - ) 126 - .map_err(|e| miette!(e)) 127 - } else { 128 - config 129 - }?; 130 - let log_dir = env::var("LOG_DIR").unwrap_or_else(|_| "/tmp/appview".to_string()); 131 - std::fs::create_dir_all(&log_dir).unwrap(); 132 - let _guard = telemetry::setup_tracing(&log_dir); 133 - Ok(config) 134 - }
···
-61
crates/weaver-index/src/middleware.rs
··· 1 - use std::time::Duration; 2 - 3 - use axum::http::HeaderName; 4 - use hyper::Request; 5 - use tower_http::{ 6 - cors::{AllowHeaders, Any, CorsLayer}, 7 - normalize_path::NormalizePathLayer, 8 - request_id::{MakeRequestId, PropagateRequestIdLayer, RequestId, SetRequestIdLayer}, 9 - timeout::TimeoutLayer, 10 - }; 11 - 12 - #[derive(Clone, Default)] 13 - pub struct Id; 14 - 15 - impl MakeRequestId for Id { 16 - fn make_request_id<B>(&mut self, _: &Request<B>) -> Option<RequestId> { 17 - let id = uuid::Uuid::now_v7().to_string().parse().unwrap(); 18 - Some(RequestId::new(id)) 19 - } 20 - } 21 - 22 - /// Sets the 'x-request-id' header with a randomly generated UUID v7. 23 - /// 24 - /// SetRequestId will not override request IDs if they are already present 25 - /// on requests or responses. 26 - pub fn request_id_layer() -> SetRequestIdLayer<Id> { 27 - let x_request_id = HeaderName::from_static("x-request-id"); 28 - SetRequestIdLayer::new(x_request_id.clone(), Id) 29 - } 30 - 31 - // Propagates 'x-request-id' header from the request to the response. 32 - /// 33 - /// PropagateRequestId wont override request ids if its already 34 - /// present on requests or responses. 35 - pub fn propagate_request_id_layer() -> PropagateRequestIdLayer { 36 - let x_request_id = HeaderName::from_static("x-request-id"); 37 - PropagateRequestIdLayer::new(x_request_id) 38 - } 39 - 40 - /// Layer that applies the Cors middleware which adds headers for CORS. 41 - pub fn cors_layer() -> CorsLayer { 42 - CorsLayer::new() 43 - .allow_origin(Any) 44 - .allow_methods(Any) 45 - .allow_headers(AllowHeaders::mirror_request()) 46 - .max_age(Duration::from_secs(600)) 47 - } 48 - 49 - /// Layer that applies the Timeout middleware which apply a timeout to requests. 50 - /// The default timeout value is set to 15 seconds. 51 - pub fn timeout_layer() -> TimeoutLayer { 52 - TimeoutLayer::new(Duration::from_secs(15)) 53 - } 54 - 55 - /// Middleware that normalizes paths. 56 - /// 57 - /// Any trailing slashes from request paths will be removed. For example, a request with `/foo/` 58 - /// will be changed to `/foo` before reaching the inner service. 59 - pub fn normalize_path_layer() -> NormalizePathLayer { 60 - NormalizePathLayer::trim_trailing_slash() 61 - }
···
-124
crates/weaver-index/src/models.rs
··· 1 - use chrono::NaiveDateTime; 2 - use diesel::prelude::*; 3 - use jacquard::CowStr; 4 - 5 - #[derive(Queryable, Selectable)] 6 - #[diesel(table_name = crate::schema::profile)] 7 - pub struct Profile { 8 - pub id: i32, 9 - pub did: String, 10 - pub avatar: Option<String>, 11 - pub description: String, 12 - pub include_bluesky: bool, 13 - pub include_tangled: bool, 14 - pub location: Option<String>, 15 - pub pinned_post: Option<serde_json::Value>, 16 - pub created_at: Option<NaiveDateTime>, 17 - } 18 - 19 - #[derive(Queryable, Selectable)] 20 - #[diesel(table_name = crate::schema::registrations)] 21 - pub struct Registration { 22 - pub id: i32, 23 - pub domain: String, 24 - pub did: String, 25 - pub secret: String, 26 - pub created: NaiveDateTime, 27 - pub registered: Option<String>, 28 - } 29 - 30 - #[derive(Queryable, Selectable)] 31 - #[diesel(table_name = crate::schema::public_keys)] 32 - pub struct PublicKey<'a> { 33 - pub id: i32, 34 - pub did: CowStr<'a>, 35 - pub name: String, 36 - pub key_contents: String, 37 - pub rkey: String, 38 - pub created: NaiveDateTime, 39 - } 40 - 41 - #[derive(Queryable, Selectable)] 42 - #[diesel(table_name = crate::schema::follows)] 43 - pub struct Follow { 44 - pub user_did: String, 45 - pub subject_did: String, 46 - pub rkey: String, 47 - pub followed_at: NaiveDateTime, 48 - } 49 - 50 - #[derive(Queryable, Selectable)] 51 - #[diesel(table_name = crate::schema::_jetstream)] 52 - pub struct Jetstream { 53 - pub id: i32, 54 - pub last_time_us: i32, 55 - } 56 - 57 - #[derive(Queryable, Selectable)] 58 - #[diesel(table_name = crate::schema::emails)] 59 - pub struct Email { 60 - pub id: i32, 61 - pub did: String, 62 - pub email: String, 63 - pub verified: bool, 64 - pub verification_code: String, 65 - pub last_sent: NaiveDateTime, 66 - pub is_primary: bool, 67 - pub created: NaiveDateTime, 68 - } 69 - 70 - #[derive(Queryable, Selectable)] 71 - #[diesel(table_name = crate::schema::profile_links)] 72 - pub struct ProfileLink { 73 - pub id: i32, 74 - pub did: String, 75 - pub link: String, 76 - } 77 - 78 - #[derive(Queryable, Selectable)] 79 - #[diesel(table_name = crate::schema::profile_pronouns)] 80 - pub struct ProfilePronoun { 81 - pub id: i32, 82 - pub did: String, 83 - pub pronoun: String, 84 - } 85 - 86 - // Jacquard OAuth models 87 - 88 - #[derive(Queryable, Selectable, QueryableByName)] 89 - #[diesel(table_name = crate::schema::oauth_sessions, check_for_backend(diesel::sqlite::Sqlite))] 90 - pub struct OauthSession { 91 - pub id: i32, 92 - pub did: String, 93 - pub session_id: String, 94 - pub session_data: serde_json::Value, 95 - pub created_at: NaiveDateTime, 96 - pub updated_at: NaiveDateTime, 97 - } 98 - 99 - #[derive(Insertable)] 100 - #[diesel(table_name = crate::schema::oauth_sessions)] 101 - pub struct NewOauthSession { 102 - pub did: String, 103 - pub session_id: String, 104 - pub session_data: serde_json::Value, 105 - } 106 - 107 - #[derive(Queryable, Selectable, QueryableByName)] 108 - #[diesel(table_name = crate::schema::oauth_auth_requests)] 109 - pub struct OauthAuthRequest { 110 - pub id: i32, 111 - pub state: String, 112 - pub account_did: Option<String>, 113 - pub auth_req_data: serde_json::Value, 114 - pub created_at: NaiveDateTime, 115 - pub expires_at: NaiveDateTime, 116 - } 117 - 118 - #[derive(Insertable)] 119 - #[diesel(table_name = crate::schema::oauth_auth_requests)] 120 - pub struct NewOauthAuthRequest { 121 - pub state: String, 122 - pub account_did: Option<String>, 123 - pub auth_req_data: serde_json::Value, 124 - }
···
-200
crates/weaver-index/src/oauth.rs
··· 1 - use diesel::prelude::*; 2 - use diesel_async::RunQueryDsl; 3 - use jacquard::oauth::authstore::ClientAuthStore; 4 - use jacquard::oauth::session::{AuthRequestData, ClientSessionData}; 5 - use jacquard::session::SessionStoreError; 6 - use jacquard::types::string::Did; 7 - use miette::IntoDiagnostic; 8 - 9 - use crate::models::{NewOauthAuthRequest, NewOauthSession, OauthAuthRequest, OauthSession}; 10 - 11 - /// Database-backed implementation of ClientAuthStore for jacquard OAuth 12 - pub struct DBAuthStore { 13 - pub db: crate::db::Db, 14 - } 15 - 16 - impl DBAuthStore { 17 - pub fn new(db: &crate::db::Db) -> Self { 18 - Self { db: db.clone() } 19 - } 20 - } 21 - 22 - impl ClientAuthStore for DBAuthStore { 23 - async fn get_session( 24 - &self, 25 - did_param: &Did<'_>, 26 - session_id_param: &str, 27 - ) -> Result<Option<ClientSessionData<'_>>, SessionStoreError> { 28 - use crate::schema::oauth_sessions::dsl::*; 29 - 30 - let mut conn = self 31 - .db 32 - .pool 33 - .get() 34 - .await 35 - .map_err(|e| SessionStoreError::Other(Box::new(e)))?; 36 - 37 - let result: Vec<OauthSession> = oauth_sessions 38 - .filter(did.eq(did_param.as_str())) 39 - .filter(session_id.eq(session_id_param)) 40 - .limit(1) 41 - .select(OauthSession::as_select()) 42 - .load(&mut conn) 43 - .await 44 - .map_err(|e| SessionStoreError::Other(Box::new(e)))?; 45 - 46 - if let Some(sess) = result.get(0) { 47 - let data = jacquard::from_json_value::<ClientSessionData>(sess.session_data.clone()) 48 - .map_err(|e| SessionStoreError::Other(Box::new(e)))?; 49 - Ok(Some(data)) 50 - } else { 51 - Ok(None) 52 - } 53 - } 54 - 55 - async fn upsert_session( 56 - &self, 57 - session: ClientSessionData<'_>, 58 - ) -> Result<(), SessionStoreError> { 59 - use crate::schema::oauth_sessions::dsl::*; 60 - 61 - let mut conn = self 62 - .db 63 - .pool 64 - .get() 65 - .await 66 - .map_err(|e| SessionStoreError::Other(Box::new(e)))?; 67 - 68 - let session_json = 69 - serde_json::to_value(&session).map_err(|e| SessionStoreError::Other(Box::new(e)))?; 70 - 71 - let new_session = NewOauthSession { 72 - did: session.account_did.as_str().to_string(), 73 - session_id: session.session_id.as_ref().to_string(), 74 - session_data: session_json, 75 - }; 76 - 77 - // Try insert, on conflict update 78 - diesel::insert_into(oauth_sessions) 79 - .values(&new_session) 80 - .on_conflict((did, session_id)) 81 - .do_update() 82 - .set(( 83 - session_data.eq(&new_session.session_data), 84 - updated_at.eq(diesel::dsl::now), 85 - )) 86 - .execute(&mut conn) 87 - .await 88 - .map_err(|e| SessionStoreError::Other(Box::new(e)))?; 89 - 90 - Ok(()) 91 - } 92 - 93 - async fn delete_session( 94 - &self, 95 - did_param: &Did<'_>, 96 - session_id_param: &str, 97 - ) -> Result<(), SessionStoreError> { 98 - use crate::schema::oauth_sessions::dsl::*; 99 - 100 - let mut conn = self 101 - .db 102 - .pool 103 - .get() 104 - .await 105 - .map_err(|e| SessionStoreError::Other(Box::new(e)))?; 106 - 107 - diesel::delete( 108 - oauth_sessions 109 - .filter(did.eq(did_param.as_str())) 110 - .filter(session_id.eq(session_id_param)), 111 - ) 112 - .execute(&mut conn) 113 - .await 114 - .map_err(|e| SessionStoreError::Other(Box::new(e)))?; 115 - 116 - Ok(()) 117 - } 118 - 119 - async fn get_auth_req_info( 120 - &self, 121 - state_param: &str, 122 - ) -> Result<Option<AuthRequestData<'_>>, SessionStoreError> { 123 - use crate::schema::oauth_auth_requests::dsl::*; 124 - 125 - let mut conn = self 126 - .db 127 - .pool 128 - .get() 129 - .await 130 - .map_err(|e| SessionStoreError::Other(Box::new(e)))?; 131 - 132 - let result: Vec<OauthAuthRequest> = oauth_auth_requests 133 - .filter(state.eq(state_param)) 134 - .limit(1) 135 - .select(OauthAuthRequest::as_select()) 136 - .load(&mut conn) 137 - .await 138 - .map_err(|e| SessionStoreError::Other(Box::new(e)))?; 139 - 140 - if let Some(req) = result.get(0) { 141 - let auth_data = jacquard::from_json_value::<AuthRequestData>(req.auth_req_data.clone()) 142 - .map_err(|e| SessionStoreError::Other(Box::new(e)))?; 143 - Ok(Some(auth_data)) 144 - } else { 145 - Ok(None) 146 - } 147 - } 148 - 149 - async fn save_auth_req_info( 150 - &self, 151 - auth_req_info: &AuthRequestData<'_>, 152 - ) -> Result<(), SessionStoreError> { 153 - use crate::schema::oauth_auth_requests::dsl::*; 154 - 155 - let mut conn = self 156 - .db 157 - .pool 158 - .get() 159 - .await 160 - .map_err(|e| SessionStoreError::Other(Box::new(e)))?; 161 - 162 - let auth_json = serde_json::to_value(auth_req_info) 163 - .map_err(|e| SessionStoreError::Other(Box::new(e)))?; 164 - 165 - let new_auth_req = NewOauthAuthRequest { 166 - state: auth_req_info.state.as_ref().to_string(), 167 - account_did: auth_req_info 168 - .account_did 169 - .as_ref() 170 - .map(|d| d.as_str().to_string()), 171 - auth_req_data: auth_json, 172 - }; 173 - 174 - diesel::insert_into(oauth_auth_requests) 175 - .values(&new_auth_req) 176 - .execute(&mut conn) 177 - .await 178 - .map_err(|e| SessionStoreError::Other(Box::new(e)))?; 179 - 180 - Ok(()) 181 - } 182 - 183 - async fn delete_auth_req_info(&self, state_param: &str) -> Result<(), SessionStoreError> { 184 - use crate::schema::oauth_auth_requests::dsl::*; 185 - 186 - let mut conn = self 187 - .db 188 - .pool 189 - .get() 190 - .await 191 - .map_err(|e| SessionStoreError::Other(Box::new(e)))?; 192 - 193 - diesel::delete(oauth_auth_requests.filter(state.eq(state_param))) 194 - .execute(&mut conn) 195 - .await 196 - .map_err(|e| SessionStoreError::Other(Box::new(e)))?; 197 - 198 - Ok(()) 199 - } 200 - }
···
-8
crates/weaver-index/src/routes/health_check.rs
··· 1 - use axum::Json; 2 - use serde_json::{Value, json}; 3 - 4 - use crate::api_error::ApiError; 5 - 6 - pub async fn health_check() -> Result<Json<Value>, ApiError> { 7 - Ok(Json(json!({ "status": "ok" }))) 8 - }
···
-4
crates/weaver-index/src/routes/login.rs
··· 1 - use axum::Json; 2 - use serde_json::{Value, json}; 3 - 4 - use crate::api_error::ApiError;
···
-4
crates/weaver-index/src/routes/logout.rs
··· 1 - use axum::Json; 2 - use serde_json::{Value, json}; 3 - 4 - use crate::api_error::ApiError;
···
-10
crates/weaver-index/src/routes/mod.rs
··· 1 - use axum::{Router, routing::get}; 2 - 3 - pub mod health_check; 4 - pub mod oauth; 5 - 6 - use crate::AppState; 7 - 8 - pub fn router() -> Router<AppState> { 9 - Router::new().route("/health_check", get(health_check::health_check)) 10 - }
···
-104
crates/weaver-index/src/routes/oauth.rs
··· 1 - use axum::{ 2 - Form, Json, 3 - extract::{Query, State}, 4 - }; 5 - use jacquard::{ 6 - IntoStatic, 7 - oauth::{ 8 - atproto::atproto_client_metadata, 9 - types::{AuthorizeOptions, CallbackParams}, 10 - }, 11 - }; 12 - use miette::{IntoDiagnostic, Result, miette}; 13 - use serde::{Deserialize, Serialize}; 14 - use serde_json::{Value, json}; 15 - 16 - use crate::{api_error::ApiError, state::AppState}; 17 - 18 - /// Passthrough callback for native endpoint 19 - pub async fn callback_native( 20 - Query(params): Query<CallbackParams<'_>>, 21 - ) -> Result<Json<Value>, ApiError> { 22 - Ok(Json(json!({ 23 - "code": params.code, 24 - "iss": params.iss, 25 - "state": params.state, 26 - }))) 27 - } 28 - 29 - /// OAuth callback handler 30 - pub async fn callback( 31 - State(state): State<AppState>, 32 - Query(params): Query<CallbackParams<'_>>, 33 - ) -> Result<Json<Value>, ApiError> { 34 - let oauth_client = state.oauth_client(); 35 - let active_sessions = state.active_sessions(); 36 - 37 - let session = oauth_client 38 - .callback(params) 39 - .await 40 - .map_err(|e| ApiError::InternalError(miette!("oauth callback failed: {e}")))?; 41 - 42 - let (did, session_id) = session.session_info().await; 43 - let did = did.to_string(); 44 - let session_id = session_id.clone().into_static(); 45 - 46 - active_sessions.insert(session_id.to_string(), session); 47 - 48 - Ok(Json(json!({ 49 - "status": "authenticated", 50 - "did": did, 51 - "session_id": session_id, 52 - }))) 53 - } 54 - 55 - /// Get OAuth client metadata 56 - pub async fn get_client_metadata(State(state): State<AppState>) -> Result<Json<Value>, ApiError> { 57 - let metadata = atproto_client_metadata( 58 - state.oauth_client().registry.client_data.config.clone(), 59 - &state.oauth_client().registry.client_data.keyset, 60 - ) 61 - .map_err(|e| ApiError::InternalError(miette!("couldn't get oauth metadata: {e}")))?; 62 - 63 - Ok(Json(serde_json::to_value(metadata).map_err(|e| { 64 - ApiError::InternalError(miette!("json serialization error: {e}")) 65 - })?)) 66 - } 67 - 68 - /// Get JWKS (public keys) 69 - pub async fn get_jwks(State(state): State<AppState>) -> Result<Json<Value>, ApiError> { 70 - let jwks = state.oauth_client().jwks(); 71 - 72 - Ok(Json(serde_json::to_value(jwks).map_err(|e| { 73 - ApiError::InternalError(miette!("json serialization error: {e}")) 74 - })?)) 75 - } 76 - 77 - /// Login stub 78 - pub async fn login(State(_state): State<AppState>) -> Result<Json<Value>, ApiError> { 79 - Ok(Json(json!({ "status": "ok" }))) 80 - } 81 - 82 - #[derive(Deserialize)] 83 - pub struct AuthorizeParams { 84 - pub handle: String, 85 - } 86 - 87 - /// Start OAuth authorization flow 88 - pub async fn authorize( 89 - State(state): State<AppState>, 90 - Form(params): Form<AuthorizeParams>, 91 - ) -> Result<Json<Value>, ApiError> { 92 - let url = state 93 - .oauth_client() 94 - .start_auth(params.handle, AuthorizeOptions::default()) 95 - .await 96 - .map_err(|e| ApiError::InternalError(miette!("oauth authorize error: {e}")))?; 97 - 98 - Ok(Json(json!({ "url": url }))) 99 - } 100 - 101 - /// Logout stub 102 - pub async fn logout(State(_state): State<AppState>) -> Result<Json<Value>, ApiError> { 103 - Ok(Json(json!({ "status": "ok" }))) 104 - }
···
-29
crates/weaver-index/src/schema.patch
··· 1 - --- src/schema.rs 2025-11-07 17:26:09.742146790 -0500 2 - +++ src/schema.rs 2025-11-07 17:25:42.871390425 -0500 3 - @@ -31,24 +31,24 @@ 4 - 5 - diesel::table! { 6 - oauth_auth_requests (id) { 7 - id -> Integer, 8 - state -> Text, 9 - account_did -> Nullable<Text>, 10 - - auth_req_data -> Binary, 11 - + auth_req_data -> Jsonb, 12 - created_at -> Timestamp, 13 - expires_at -> Timestamp, 14 - } 15 - } 16 - 17 - diesel::table! { 18 - oauth_sessions (id) { 19 - id -> Integer, 20 - did -> Text, 21 - session_id -> Text, 22 - - session_data -> Binary, 23 - + session_data -> Jsonb, 24 - created_at -> Timestamp, 25 - updated_at -> Timestamp, 26 - } 27 - } 28 - 29 - diesel::table! {
···
-117
crates/weaver-index/src/schema.rs
··· 1 - // @generated automatically by Diesel CLI. 2 - 3 - diesel::table! { 4 - _jetstream (id) { 5 - id -> Integer, 6 - last_time_us -> Integer, 7 - } 8 - } 9 - 10 - diesel::table! { 11 - emails (id) { 12 - id -> Integer, 13 - did -> Text, 14 - email -> Text, 15 - verified -> Bool, 16 - verification_code -> Text, 17 - last_sent -> Timestamp, 18 - is_primary -> Bool, 19 - created -> Timestamp, 20 - } 21 - } 22 - 23 - diesel::table! { 24 - follows (user_did, subject_did) { 25 - user_did -> Text, 26 - subject_did -> Text, 27 - rkey -> Text, 28 - followed_at -> Timestamp, 29 - } 30 - } 31 - 32 - diesel::table! { 33 - oauth_auth_requests (id) { 34 - id -> Integer, 35 - state -> Text, 36 - account_did -> Nullable<Text>, 37 - auth_req_data -> Jsonb, 38 - created_at -> Timestamp, 39 - expires_at -> Timestamp, 40 - } 41 - } 42 - 43 - diesel::table! { 44 - oauth_sessions (id) { 45 - id -> Integer, 46 - did -> Text, 47 - session_id -> Text, 48 - session_data -> Jsonb, 49 - created_at -> Timestamp, 50 - updated_at -> Timestamp, 51 - } 52 - } 53 - 54 - diesel::table! { 55 - profile (id) { 56 - id -> Integer, 57 - did -> Text, 58 - avatar -> Nullable<Text>, 59 - description -> Text, 60 - include_bluesky -> Bool, 61 - include_tangled -> Bool, 62 - location -> Nullable<Text>, 63 - pinned_post -> Nullable<Text>, 64 - created_at -> Nullable<Timestamp>, 65 - } 66 - } 67 - 68 - diesel::table! { 69 - profile_links (id) { 70 - id -> Integer, 71 - did -> Text, 72 - link -> Text, 73 - } 74 - } 75 - 76 - diesel::table! { 77 - profile_pronouns (id) { 78 - id -> Integer, 79 - did -> Text, 80 - pronoun -> Text, 81 - } 82 - } 83 - 84 - diesel::table! { 85 - public_keys (id) { 86 - id -> Integer, 87 - did -> Text, 88 - name -> Text, 89 - key_contents -> Text, 90 - rkey -> Text, 91 - created -> Timestamp, 92 - } 93 - } 94 - 95 - diesel::table! { 96 - registrations (id) { 97 - id -> Integer, 98 - domain -> Text, 99 - did -> Text, 100 - secret -> Text, 101 - created -> Timestamp, 102 - registered -> Nullable<Text>, 103 - } 104 - } 105 - 106 - diesel::allow_tables_to_appear_in_same_query!( 107 - _jetstream, 108 - emails, 109 - follows, 110 - oauth_auth_requests, 111 - oauth_sessions, 112 - profile, 113 - profile_links, 114 - profile_pronouns, 115 - public_keys, 116 - registrations, 117 - );
···
-85
crates/weaver-index/src/state.rs
··· 1 - use dashmap::DashMap; 2 - use jacquard::identity::JacquardResolver; 3 - use jacquard::oauth::atproto::{AtprotoClientMetadata, GrantType}; 4 - use jacquard::oauth::client::OAuthClient; 5 - use jacquard::oauth::keyset::Keyset; 6 - use jacquard::oauth::scopes::Scope; 7 - use jacquard::oauth::session::ClientData; 8 - use std::sync::Arc; 9 - use url::Url; 10 - 11 - use crate::config::Config; 12 - use crate::db::Db; 13 - use crate::oauth::DBAuthStore; 14 - 15 - pub type AppviewOAuthClient = OAuthClient<JacquardResolver, DBAuthStore>; 16 - pub type AppviewOAuthSession = jacquard::oauth::client::OAuthSession<JacquardResolver, DBAuthStore>; 17 - 18 - pub struct AppStateInner { 19 - pub cfg: Config, 20 - pub oauth_client: AppviewOAuthClient, 21 - pub active_sessions: DashMap<String, AppviewOAuthSession>, 22 - } 23 - 24 - #[derive(Clone)] 25 - pub struct AppState { 26 - pub db: Db, 27 - inner: Arc<AppStateInner>, 28 - } 29 - 30 - impl AppState { 31 - pub fn new(cfg: Config, db: Db) -> Self { 32 - let store = DBAuthStore::new(&db); 33 - 34 - // Build keyset from config JWKs 35 - let keyset = Some( 36 - Keyset::try_from(cfg.oauth.jwks.clone()).expect("failed to create keyset from JWKs"), 37 - ); 38 - 39 - // Build AT Protocol client metadata 40 - let client_id = 41 - Url::parse(&cfg.core.appview_host).expect("appview_host must be a valid URL"); 42 - 43 - let redirect_uris = vec![ 44 - Url::parse(&format!("{}/oauth/callback", cfg.core.appview_host)) 45 - .expect("failed to build redirect URI"), 46 - ]; 47 - 48 - let scopes = 49 - Scope::parse_multiple("atproto transition:generic").expect("failed to parse scopes"); 50 - 51 - let config = AtprotoClientMetadata::new( 52 - client_id.clone(), 53 - Some(client_id), 54 - redirect_uris, 55 - vec![GrantType::AuthorizationCode, GrantType::RefreshToken], 56 - scopes, 57 - None, // jwks_uri - None means jwks will be embedded 58 - ); 59 - 60 - let client_data = ClientData { keyset, config }; 61 - 62 - let oauth_client = OAuthClient::new(store, client_data); 63 - 64 - Self { 65 - db, 66 - inner: Arc::new(AppStateInner { 67 - cfg, 68 - oauth_client, 69 - active_sessions: DashMap::new(), 70 - }), 71 - } 72 - } 73 - 74 - pub fn cfg(&self) -> &Config { 75 - &self.inner.as_ref().cfg 76 - } 77 - 78 - pub fn oauth_client(&self) -> &AppviewOAuthClient { 79 - &self.inner.as_ref().oauth_client 80 - } 81 - 82 - pub fn active_sessions(&self) -> &DashMap<String, AppviewOAuthSession> { 83 - &self.inner.as_ref().active_sessions 84 - } 85 - }
···
-48
crates/weaver-index/src/telemetry.rs
··· 1 - use tower_http::{ 2 - classify::{ServerErrorsAsFailures, SharedClassifier}, 3 - trace::{DefaultMakeSpan, DefaultOnRequest, DefaultOnResponse, TraceLayer}, 4 - }; 5 - use tracing::Level; 6 - use tracing_appender::{self, non_blocking, non_blocking::WorkerGuard, rolling::daily}; 7 - use tracing_subscriber::{ 8 - EnvFilter, 9 - fmt::{self, layer, writer::MakeWriterExt}, 10 - layer::SubscriberExt, 11 - registry, 12 - util::SubscriberInitExt, 13 - }; 14 - /// The `EnvFilter` type is used to filter log events based on the value of an environment variable. 15 - /// In this case, we are using the `try_from_default_env` method to attempt to read the `RUST_LOG` environment variable, 16 - /// which is used to set the log level for the application. 17 - /// If the environment variable is not set, we default to the log level of `debug`. 18 - /// The `RUST_LOG` environment variable is set in the Dockerfile and .env files. 19 - pub fn setup_tracing<S: AsRef<str>>(logdir: S) -> WorkerGuard { 20 - let (non_blocking_appender, guard) = non_blocking(daily(logdir.as_ref(), "general.log")); 21 - let env_filter_layer = EnvFilter::try_from_default_env().unwrap_or_else(|_| { 22 - format!( 23 - "debug,{}=debug,tower_http=debug,axum=debug,hyper=debug,axum::rejection=trace,markdown=info", 24 - env!("CARGO_PKG_NAME"), 25 - ).into() 26 - }); 27 - let formatting_layer = fmt::layer().json(); 28 - tracing_subscriber::registry() 29 - .with(env_filter_layer) 30 - .with(formatting_layer) 31 - .with( 32 - layer() 33 - .with_writer(std::io::stdout.with_max_level(Level::DEBUG)) 34 - .event_format(tracing_subscriber::fmt::format().pretty()), 35 - ) 36 - .with(layer().with_writer(non_blocking_appender.with_max_level(Level::INFO))) 37 - .init(); 38 - guard 39 - } 40 - 41 - /// Returns a `TraceLayer` for HTTP requests and responses. 42 - /// The `TraceLayer` is used to trace requests and responses in the application. 43 - pub fn trace_layer() -> TraceLayer<SharedClassifier<ServerErrorsAsFailures>> { 44 - TraceLayer::new_for_http() 45 - .make_span_with(DefaultMakeSpan::new().level(Level::INFO)) 46 - .on_request(DefaultOnRequest::new().level(Level::INFO)) 47 - .on_response(DefaultOnResponse::new().level(Level::INFO)) 48 - }
···