This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

bobbin: add jacquard identity resolver to AppState

Signed-off-by: Seongmin Lee <git@boltless.me>
Signed-off-by: dawn <dawn@tangled.org>

author
Seongmin Lee
committer
dawn
date (Jul 31, 2026, 10:57 PM +0300) commit 38d86887 parent cf4395a9 change-id spktmqmu
+125 -2
+48
Cargo.lock
··· 688 688 "confique", 689 689 "futures", 690 690 "rustls", 691 + "jacquard-common", 692 + "jacquard-identity", 691 693 "serde", 692 694 "socket2", 693 695 "thiserror 2.0.18", ··· 823 825 "futures", 824 826 "getrandom 0.4.3", 825 827 "http", 828 + "jacquard-common", 826 829 "reqwest 0.13.1", 827 830 "thiserror 2.0.18", 828 831 "tokio", ··· 924 927 "futures", 925 928 "http", 926 929 "jacquard-common", 930 + "jacquard-identity", 931 + "reqwest 0.13.1", 927 932 "serde", 928 933 "serde_json", 929 934 "thiserror 2.0.18", ··· 4178 4183 ] 4179 4184 4180 4185 [[package]] 4186 + name = "jacquard-identity" 4187 + version = "0.12.1" 4188 + source = "registry+https://github.com/rust-lang/crates.io-index" 4189 + checksum = "2c0a89c960d6c2245f26642830ecf85155eaf7395e5da8794173a78854f0e22b" 4190 + dependencies = [ 4191 + "bon", 4192 + "bytes", 4193 + "http", 4194 + "jacquard-common", 4195 + "jacquard-lexicon", 4196 + "miette", 4197 + "mini-moka-wasm", 4198 + "n0-future", 4199 + "reqwest 0.12.28", 4200 + "serde", 4201 + "serde_html_form", 4202 + "serde_json", 4203 + "thiserror 2.0.18", 4204 + "tokio", 4205 + "trait-variant", 4206 + ] 4207 + 4208 + [[package]] 4181 4209 name = "jacquard-lexicon" 4182 4210 version = "0.12.1" 4183 4211 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 5235 5263 dependencies = [ 5236 5264 "mime", 5237 5265 "unicase", 5266 + ] 5267 + 5268 + [[package]] 5269 + name = "mini-moka-wasm" 5270 + version = "0.10.99" 5271 + source = "registry+https://github.com/rust-lang/crates.io-index" 5272 + checksum = "0102b9a2ad50fa47ca89eead2316c8222285ecfbd3f69ce99564fbe4253866e8" 5273 + dependencies = [ 5274 + "crossbeam-channel", 5275 + "crossbeam-utils", 5276 + "dashmap", 5277 + "smallvec", 5278 + "tagptr", 5279 + "triomphe", 5238 5280 ] 5239 5281 5240 5282 [[package]] ··· 8384 8426 "ipnet", 8385 8427 "thiserror 2.0.18", 8386 8428 ] 8429 + 8430 + [[package]] 8431 + name = "triomphe" 8432 + version = "0.1.16" 8433 + source = "registry+https://github.com/rust-lang/crates.io-index" 8434 + checksum = "b40688ea6389c8171614b25491f71d4a27946e0c7ce2da1c6de27e25abf1a0ae" 8387 8435 8388 8436 [[package]] 8389 8437 name = "try-lock"
+1
Cargo.toml
··· 76 76 jacquard-common = "0.12.1" 77 77 jacquard-derive = "0.12.1" 78 78 jacquard-lexicon = { version = "0.12.1", default-features = false } 79 + jacquard-identity = { version = "0.12.1", features = ["cache"] } 79 80 jacquard-repo = "0.12.1" 80 81 81 82 gix = { version = "0.84", features = ["parallel", "revision", "blob-diff", "worktree-archive", "tree-editor", "sha1", "sha256"] }
+2
bobbin/crates/bobbin/Cargo.toml
··· 21 21 bobbin-xrpc = { workspace = true } 22 22 rustls = { workspace = true } 23 23 trusted-proxies = { workspace = true } 24 + jacquard-common = { workspace = true } 25 + jacquard-identity = { workspace = true } 24 26 25 27 axum = { workspace = true } 26 28 serde = { workspace = true, features = ["derive"] }
+23 -2
bobbin/crates/bobbin/src/main.rs
··· 14 14 use bobbin_knot_proxy::{KnotHttpConfig, KnotProxy, KnotProxyConfig, classify_ip}; 15 15 use bobbin_record_lru::{CacheCapacity, LruRecordStore, RecordStore}; 16 16 use bobbin_runtime::{ 17 - Clock, GuardedWs, MemoryBudget, NetworkError, OsEntropy, RuntimeHasher, SystemClock, 18 - TungsteniteWs, WsTransport, 17 + Clock, GuardedWs, MemoryBudget, NetworkError, OsEntropy, ReqwestHttp, RuntimeHasher, 18 + SystemClock, TungsteniteWs, WsTransport, 19 19 }; 20 20 use bobbin_search::{SearchIndex, SearchReader}; 21 21 use bobbin_slingshot_client::SlingshotClient; 22 + use bobbin_slingshot_client::default_http_client; 22 23 use bobbin_xrpc::{ 23 24 AppState, HeavyLimiter, MaxInFlight, PerRequestAnonBytes, ReservedFloor, router, 24 25 }; 25 26 use clap::{Parser, Subcommand}; 27 + use jacquard_common::deps::fluent_uri::Uri; 28 + use jacquard_identity::JacquardResolver; 29 + use jacquard_identity::resolver::{DidStep, PlcSource, ResolverOptions}; 26 30 use tokio::signal::unix::{SignalKind, signal}; 27 31 use tokio::task::JoinHandle; 28 32 use tokio_util::sync::CancellationToken; ··· 182 186 let records: Arc<dyn RecordStore> = 183 187 Arc::new(LruRecordStore::new(CacheCapacity::from_bytes(lru_cap))); 184 188 let slingshot = SlingshotClient::with_default_http(cfg.slingshot.url.clone())?; 189 + let mut resolver_opts = ResolverOptions::default(); 190 + // NOTE: see https://tangled.org/nonbinary.computer/jacquard/issues/39. 191 + resolver_opts.did_order = vec![ 192 + DidStep::DidWebHttps, 193 + // DidStep::PlcHttp, 194 + DidStep::PdsResolveDid, 195 + ]; 196 + let directory = Arc::new( 197 + JacquardResolver::new(ReqwestHttp::new(default_http_client()?), resolver_opts) 198 + .with_plc_source(PlcSource::Slingshot { 199 + base: Uri::parse(cfg.slingshot.url.as_str()) 200 + .context("slingshot url is not a valid URI")? 201 + .to_owned(), 202 + }) 203 + .with_cache(), 204 + ); 185 205 let resolver = Arc::new(RepoIdResolver::with_slingshot( 186 206 slingshot.clone(), 187 207 clock.clone(), ··· 321 341 knots, 322 342 search as Arc<dyn SearchReader>, 323 343 resolver, 344 + directory, 324 345 ) 325 346 .with_limiter(limiter) 326 347 .with_proxies(trusted_proxies);
+1
bobbin/crates/runtime/Cargo.toml
··· 11 11 futures = { workspace = true } 12 12 getrandom = { workspace = true } 13 13 http = { workspace = true } 14 + jacquard-common = { workspace = true } 14 15 reqwest = { workspace = true } 15 16 thiserror = { workspace = true } 16 17 tokio = { workspace = true }
+28
bobbin/crates/runtime/src/network.rs
··· 92 92 } 93 93 } 94 94 95 + /// Lets jacquard resolve identities over the workspace reqwest (0.13); jacquard's own 96 + /// `HttpClient` impl is against reqwest 0.12, which is built here without TLS. 97 + impl jacquard_common::http_client::HttpClient for ReqwestHttp { 98 + type Error = reqwest::Error; 99 + 100 + async fn send_http( 101 + &self, 102 + request: http::Request<Vec<u8>>, 103 + ) -> Result<http::Response<Vec<u8>>, reqwest::Error> { 104 + let (parts, body) = request.into_parts(); 105 + let mut req = self 106 + .client 107 + .request(parts.method, parts.uri.to_string()) 108 + .body(body); 109 + for (name, value) in parts.headers.iter() { 110 + req = req.header(name, value); 111 + } 112 + 113 + let resp = req.send().await?; 114 + let mut builder = http::Response::builder().status(resp.status()); 115 + for (name, value) in resp.headers().iter() { 116 + builder = builder.header(name, value); 117 + } 118 + let body = resp.bytes().await?.to_vec(); 119 + Ok(builder.body(body).expect("response parts came from reqwest")) 120 + } 121 + } 122 + 95 123 fn map_reqwest(err: reqwest::Error) -> NetworkError { 96 124 let msg = err.to_string(); 97 125 if err.is_timeout() {
+2
bobbin/crates/xrpc/Cargo.toml
··· 15 15 bobbin-slingshot-client = { workspace = true } 16 16 bobbin-knot-proxy = { workspace = true } 17 17 jacquard-common = { workspace = true } 18 + jacquard-identity = { workspace = true } 18 19 19 20 axum = { workspace = true } 20 21 chrono = { workspace = true } ··· 28 29 tracing = { workspace = true } 29 30 trusted-proxies = { workspace = true } 30 31 url = { workspace = true } 32 + reqwest = { workspace = true } 31 33 32 34 [dev-dependencies] 33 35 bobbin-runtime = { workspace = true }
+12
bobbin/crates/xrpc/src/lib.rs
··· 29 29 use bobbin_knot_proxy::{KnotHost, KnotProxy, KnotProxyError, ProxyResponse, RepoSlug}; 30 30 use bobbin_record_lru::RecordStore; 31 31 use bobbin_resolver::RepoIdResolver; 32 + use bobbin_runtime::ReqwestHttp; 32 33 use bobbin_search::{ 33 34 SearchCursor, SearchError, SearchFilters, SearchHit, SearchOffset, SearchReader, 34 35 }; ··· 87 88 use jacquard_common::types::string::{AtUri, Cid}; 88 89 use jacquard_common::xrpc::XrpcResp; 89 90 use jacquard_common::{DefaultStr, IntoStatic}; 91 + use jacquard_identity::JacquardResolver; 90 92 use serde::{Deserialize, Serialize}; 91 93 use std::convert::Infallible; 92 94 use std::time::Duration; ··· 113 115 114 116 const DEFAULT_LIMIT: u32 = 50; 115 117 const FETCH_CONCURRENCY: usize = 8; 118 + 119 + pub type Directory = JacquardResolver<ReqwestHttp>; 120 + 121 + pub fn default_directory() -> Directory { 122 + JacquardResolver::new(ReqwestHttp::new(reqwest::Client::new()), Default::default()) 123 + } 124 + 116 125 #[derive(Clone)] 117 126 pub struct AppState { 118 127 pub records: Arc<dyn RecordStore>, ··· 124 133 pub knots: Arc<KnotProxy>, 125 134 pub search: Arc<dyn SearchReader>, 126 135 pub resolver: Arc<RepoIdResolver>, 136 + pub directory: Arc<Directory>, 127 137 pub limiter: Option<Arc<HeavyLimiter>>, 128 138 pub client_address: Arc<ClientAddress>, 129 139 enrich_router: Arc<std::sync::OnceLock<Router>>, ··· 141 151 knots: Arc<KnotProxy>, 142 152 search: Arc<dyn SearchReader>, 143 153 resolver: Arc<RepoIdResolver>, 154 + directory: Arc<Directory>, 144 155 ) -> Self { 145 156 Self { 146 157 records, ··· 152 163 knots, 153 164 search, 154 165 resolver, 166 + directory, 155 167 limiter: None, 156 168 client_address: Arc::new(ClientAddress::default()), 157 169 enrich_router: Arc::new(std::sync::OnceLock::new()),
+1
bobbin/crates/xrpc/tests/aggregation.rs
··· 92 92 SearchIndex::new(DEFAULT_WRITER_HEAP_BYTES, Arc::new(SystemClock::new())).unwrap(), 93 93 ) as Arc<dyn SearchReader>, 94 94 Arc::new(RepoIdResolver::detached(RuntimeHasher::default())), 95 + Arc::new(bobbin_xrpc::default_directory()), 95 96 ); 96 97 Self { 97 98 server,
+1
bobbin/crates/xrpc/tests/bulk.rs
··· 69 69 SearchIndex::new(DEFAULT_WRITER_HEAP_BYTES, Arc::new(SystemClock::new())).unwrap(), 70 70 ) as Arc<dyn SearchReader>, 71 71 Arc::new(RepoIdResolver::detached(RuntimeHasher::default())), 72 + Arc::new(bobbin_xrpc::default_directory()), 72 73 ); 73 74 Self { server, state } 74 75 }
+1
bobbin/crates/xrpc/tests/cold_start.rs
··· 56 56 Arc::new(SearchIndex::new(DEFAULT_WRITER_HEAP_BYTES, Arc::new(SystemClock::new())).unwrap()) 57 57 as Arc<dyn SearchReader>, 58 58 Arc::new(RepoIdResolver::detached(RuntimeHasher::default())), 59 + Arc::new(bobbin_xrpc::default_directory()), 59 60 ) 60 61 } 61 62
+1
bobbin/crates/xrpc/tests/coverage.rs
··· 44 44 SearchIndex::new(DEFAULT_WRITER_HEAP_BYTES, Arc::new(SystemClock::new())).unwrap(), 45 45 ) as Arc<dyn SearchReader>, 46 46 Arc::new(RepoIdResolver::detached(RuntimeHasher::default())), 47 + Arc::new(bobbin_xrpc::default_directory()), 47 48 ); 48 49 Self { coverage, state } 49 50 }
+1
bobbin/crates/xrpc/tests/enrich.rs
··· 73 73 SearchIndex::new(DEFAULT_WRITER_HEAP_BYTES, Arc::new(SystemClock::new())).unwrap(), 74 74 ) as Arc<dyn SearchReader>, 75 75 Arc::new(RepoIdResolver::detached(RuntimeHasher::default())), 76 + Arc::new(bobbin_xrpc::default_directory()), 76 77 ); 77 78 Self { 78 79 server,
+1
bobbin/crates/xrpc/tests/extended.rs
··· 81 81 SearchIndex::new(DEFAULT_WRITER_HEAP_BYTES, Arc::new(SystemClock::new())).unwrap(), 82 82 ) as Arc<dyn SearchReader>, 83 83 Arc::new(RepoIdResolver::detached(RuntimeHasher::default())), 84 + Arc::new(bobbin_xrpc::default_directory()), 84 85 ); 85 86 Self { 86 87 server,
+1
bobbin/crates/xrpc/tests/knot_proxy.rs
··· 105 105 SearchIndex::new(DEFAULT_WRITER_HEAP_BYTES, Arc::new(SystemClock::new())).unwrap(), 106 106 ) as Arc<dyn SearchReader>, 107 107 Arc::new(RepoIdResolver::detached(RuntimeHasher::default())), 108 + Arc::new(bobbin_xrpc::default_directory()), 108 109 ); 109 110 Self { 110 111 slingshot: slingshot_server,
+1
bobbin/crates/xrpc/tests/search.rs
··· 76 76 ), 77 77 search.clone() as Arc<dyn SearchReader>, 78 78 Arc::new(RepoIdResolver::detached(RuntimeHasher::default())), 79 + Arc::new(bobbin_xrpc::default_directory()), 79 80 ); 80 81 Self { 81 82 server,