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 30, 2026, 7:45 PM +0300) commit 0c55f113 parent 2a7de787 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", ··· 822 824 "futures", 823 825 "getrandom 0.4.3", 824 826 "http", 827 + "jacquard-common", 825 828 "reqwest 0.13.1", 826 829 "thiserror 2.0.18", 827 830 "tokio", ··· 923 926 "futures", 924 927 "http", 925 928 "jacquard-common", 929 + "jacquard-identity", 930 + "reqwest 0.13.1", 926 931 "serde", 927 932 "serde_json", 928 933 "thiserror 2.0.18", ··· 4176 4181 ] 4177 4182 4178 4183 [[package]] 4184 + name = "jacquard-identity" 4185 + version = "0.12.1" 4186 + source = "registry+https://github.com/rust-lang/crates.io-index" 4187 + checksum = "2c0a89c960d6c2245f26642830ecf85155eaf7395e5da8794173a78854f0e22b" 4188 + dependencies = [ 4189 + "bon", 4190 + "bytes", 4191 + "http", 4192 + "jacquard-common", 4193 + "jacquard-lexicon", 4194 + "miette", 4195 + "mini-moka-wasm", 4196 + "n0-future", 4197 + "reqwest 0.12.28", 4198 + "serde", 4199 + "serde_html_form", 4200 + "serde_json", 4201 + "thiserror 2.0.18", 4202 + "tokio", 4203 + "trait-variant", 4204 + ] 4205 + 4206 + [[package]] 4179 4207 name = "jacquard-lexicon" 4180 4208 version = "0.12.1" 4181 4209 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 5231 5259 dependencies = [ 5232 5260 "mime", 5233 5261 "unicase", 5262 + ] 5263 + 5264 + [[package]] 5265 + name = "mini-moka-wasm" 5266 + version = "0.10.99" 5267 + source = "registry+https://github.com/rust-lang/crates.io-index" 5268 + checksum = "0102b9a2ad50fa47ca89eead2316c8222285ecfbd3f69ce99564fbe4253866e8" 5269 + dependencies = [ 5270 + "crossbeam-channel", 5271 + "crossbeam-utils", 5272 + "dashmap", 5273 + "smallvec", 5274 + "tagptr", 5275 + "triomphe", 5234 5276 ] 5235 5277 5236 5278 [[package]] ··· 8372 8414 "quote", 8373 8415 "syn 2.0.118", 8374 8416 ] 8417 + 8418 + [[package]] 8419 + name = "triomphe" 8420 + version = "0.1.16" 8421 + source = "registry+https://github.com/rust-lang/crates.io-index" 8422 + checksum = "b40688ea6389c8171614b25491f71d4a27946e0c7ce2da1c6de27e25abf1a0ae" 8375 8423 8376 8424 [[package]] 8377 8425 name = "try-lock"
+1
Cargo.toml
··· 74 74 jacquard-common = "0.12.1" 75 75 jacquard-derive = "0.12.1" 76 76 jacquard-lexicon = { version = "0.12.1", default-features = false } 77 + jacquard-identity = { version = "0.12.1", features = ["cache"] } 77 78 jacquard-repo = "0.12.1" 78 79 79 80 gix = { version = "0.84", features = ["parallel", "revision", "blob-diff", "worktree-archive", "tree-editor", "sha1", "sha256"] }
+2
bobbin/crates/bobbin/Cargo.toml
··· 20 20 bobbin-slingshot-client = { workspace = true } 21 21 bobbin-xrpc = { workspace = true } 22 22 rustls = { workspace = true } 23 + jacquard-common = { workspace = true } 24 + jacquard-identity = { workspace = true } 23 25 24 26 axum = { workspace = true } 25 27 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(), ··· 317 337 knots, 318 338 search as Arc<dyn SearchReader>, 319 339 resolver, 340 + directory, 320 341 ) 321 342 .with_limiter(limiter); 322 343 let app = router(state);
+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 } ··· 27 28 tower-http = { workspace = true, features = ["trace"] } 28 29 tracing = { workspace = true } 29 30 url = { workspace = true } 31 + reqwest = { workspace = true } 30 32 31 33 [dev-dependencies] 32 34 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; ··· 109 111 110 112 const DEFAULT_LIMIT: u32 = 50; 111 113 const FETCH_CONCURRENCY: usize = 8; 114 + 115 + pub type Directory = JacquardResolver<ReqwestHttp>; 116 + 117 + pub fn default_directory() -> Directory { 118 + JacquardResolver::new(ReqwestHttp::new(reqwest::Client::new()), Default::default()) 119 + } 120 + 112 121 #[derive(Clone)] 113 122 pub struct AppState { 114 123 pub records: Arc<dyn RecordStore>, ··· 120 129 pub knots: Arc<KnotProxy>, 121 130 pub search: Arc<dyn SearchReader>, 122 131 pub resolver: Arc<RepoIdResolver>, 132 + pub directory: Arc<Directory>, 123 133 pub limiter: Option<Arc<HeavyLimiter>>, 124 134 enrich_router: Arc<std::sync::OnceLock<Router>>, 125 135 } ··· 136 146 knots: Arc<KnotProxy>, 137 147 search: Arc<dyn SearchReader>, 138 148 resolver: Arc<RepoIdResolver>, 149 + directory: Arc<Directory>, 139 150 ) -> Self { 140 151 Self { 141 152 records, ··· 147 158 knots, 148 159 search, 149 160 resolver, 161 + directory, 150 162 limiter: None, 151 163 enrich_router: Arc::new(std::sync::OnceLock::new()), 152 164 }
+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
··· 82 82 SearchIndex::new(DEFAULT_WRITER_HEAP_BYTES, Arc::new(SystemClock::new())).unwrap(), 83 83 ) as Arc<dyn SearchReader>, 84 84 Arc::new(RepoIdResolver::detached(RuntimeHasher::default())), 85 + Arc::new(bobbin_xrpc::default_directory()), 85 86 ); 86 87 Self { 87 88 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,