A better Rust ATProto crate

version bump and changelog updates

Changed files
+71 -18
crates
jacquard
src
jacquard-api
jacquard-axum
jacquard-common
jacquard-oauth
+37 -1
CHANGELOG.md
··· 1 1 # Changelog 2 2 3 - ## [0.9.1] - 2025-11-04 (`jacquard-identity`) 3 + ## [0.9.2] - 2025-11-17 4 + 5 + ### Added 6 + 7 + **WASM compatibility improvements** (`jacquard-common`, `jacquard-identity`) 8 + - Vendored mini-moka implementation with WASM support for caching 9 + - regex-lite usage on WASM targets for reduced binary size 10 + - Schema resolver now works on WASM targets 11 + 12 + **Data query improvements** (`jacquard-common`) 13 + - Mutable path query access and setting for `Data` values 14 + 15 + ### Changed 16 + 17 + **URL handling** (`jacquard-common`) 18 + - Rework of some internal URL handling for better compatibility 19 + - Includes a minor change to the return type of the endpoint() method of XrpcClient and equivalents. 20 + 21 + **OAuth improvements** (`jacquard-oauth`) 22 + - Fixed OAuth scope handling in loopback flow 23 + - OAuth metadata resolution improvements 24 + - Various OAuth flow enhancements and bug fixes 25 + 26 + **Identity resolution** (`jacquard-identity`) 27 + - Fixed non-DNS lexicon and did:web resolution using Cloudflare DoH 28 + - Reduced noisy logging in identity resolution 29 + 30 + **Lexicons** (`jacquard-api`) 31 + - Updated to latest AT Protocol lexicons 32 + 33 + ### Fixed 34 + 35 + **Data deserialization** (`jacquard-common`) 36 + - Fixed CID deserialization edge cases for better spec compliance 37 + - More permissive JSON shape handling for better interoperability with varied implementations 38 + 39 + ## [0.9.1] - 2025-11-04 (`jacquard-identity`, `jacquard-lexicon`) 4 40 5 41 ### Fixed 6 42
+8 -8
Cargo.lock
··· 2262 2262 2263 2263 [[package]] 2264 2264 name = "jacquard" 2265 - version = "0.9.0" 2265 + version = "0.9.2" 2266 2266 dependencies = [ 2267 2267 "bytes", 2268 2268 "clap", ··· 2297 2297 2298 2298 [[package]] 2299 2299 name = "jacquard-api" 2300 - version = "0.9.0" 2300 + version = "0.9.2" 2301 2301 dependencies = [ 2302 2302 "bon", 2303 2303 "bytes", ··· 2314 2314 2315 2315 [[package]] 2316 2316 name = "jacquard-axum" 2317 - version = "0.9.0" 2317 + version = "0.9.2" 2318 2318 dependencies = [ 2319 2319 "axum", 2320 2320 "axum-macros", ··· 2344 2344 2345 2345 [[package]] 2346 2346 name = "jacquard-common" 2347 - version = "0.9.0" 2347 + version = "0.9.2" 2348 2348 dependencies = [ 2349 2349 "base64 0.22.1", 2350 2350 "bon", ··· 2389 2389 2390 2390 [[package]] 2391 2391 name = "jacquard-derive" 2392 - version = "0.9.0" 2392 + version = "0.9.2" 2393 2393 dependencies = [ 2394 2394 "heck 0.5.0", 2395 2395 "inventory", ··· 2432 2432 2433 2433 [[package]] 2434 2434 name = "jacquard-lexgen" 2435 - version = "0.9.0" 2435 + version = "0.9.2" 2436 2436 dependencies = [ 2437 2437 "clap", 2438 2438 "clap_complete", ··· 2487 2487 2488 2488 [[package]] 2489 2489 name = "jacquard-oauth" 2490 - version = "0.9.0" 2490 + version = "0.9.2" 2491 2491 dependencies = [ 2492 2492 "base64 0.22.1", 2493 2493 "bytes", ··· 2519 2519 2520 2520 [[package]] 2521 2521 name = "jacquard-repo" 2522 - version = "0.9.0" 2522 + version = "0.9.2" 2523 2523 dependencies = [ 2524 2524 "anyhow", 2525 2525 "bytes",
+1 -1
Cargo.toml
··· 5 5 6 6 [workspace.package] 7 7 edition = "2024" 8 - version = "0.9.0" 8 + version = "0.9.2" 9 9 authors = ["Orual <orual@nonbinary.computer>"] 10 10 #repository = "https://github.com/rsform/jacquard" 11 11 repository = "https://tangled.org/@nonbinary.computer/jacquard"
+5 -1
crates/jacquard-api/Cargo.toml
··· 2 2 name = "jacquard-api" 3 3 description = "Generated AT Protocol API bindings for Jacquard" 4 4 edition.workspace = true 5 - version = "0.9.0" 5 + version = "0.9.2" 6 6 authors.workspace = true 7 7 repository.workspace = true 8 8 keywords.workspace = true ··· 61 61 club_stellz = [] 62 62 com_atproto = [] 63 63 com_bad_example = [] 64 + com_crabdance = [] 64 65 com_shinolabs = [] 65 66 com_whtwnd = [] 66 67 community_lexicon = [] ··· 73 74 fyi_frontpage = [] 74 75 fyi_unravel = [] 75 76 garden_lexicon = [] 77 + lol_jbc = [] 76 78 moe_karashiiro = [] 77 79 my_skylights = [] 78 80 net_aftertheinter = [] ··· 80 82 net_anisota = ["app_bsky"] 81 83 net_bnewbold = [] 82 84 net_mmatt = [] 85 + net_wafrn = [] 83 86 network_slices = [] 84 87 org_devcon = [] 85 88 org_farmapps = [] ··· 95 98 social_grain = [] 96 99 social_pmsky = [] 97 100 social_psky = [] 101 + tech_manos = [] 98 102 tools_ozone = ["chat_bsky", "com_atproto"] 99 103 tools_smokesignal = [] 100 104 uk_ewancroft = []
+1 -1
crates/jacquard-axum/Cargo.toml
··· 1 1 [package] 2 2 name = "jacquard-axum" 3 3 edition.workspace = true 4 - version = "0.9.0" 4 + version = "0.9.2" 5 5 authors.workspace = true 6 6 repository.workspace = true 7 7 keywords.workspace = true
+1 -1
crates/jacquard-common/Cargo.toml
··· 2 2 name = "jacquard-common" 3 3 description = "Core AT Protocol types and utilities for Jacquard" 4 4 edition.workspace = true 5 - version = "0.9.0" 5 + version = "0.9.2" 6 6 authors.workspace = true 7 7 repository.workspace = true 8 8 keywords.workspace = true
+12
crates/jacquard-common/src/types/value.rs
··· 180 180 } 181 181 } 182 182 183 + /// Get a mutable reference to the boolean if this is a Boolean variant 183 184 pub fn as_boolean_mut(&mut self) -> Option<&mut bool> { 184 185 if let Data::Boolean(b) = self { 185 186 Some(b) ··· 246 247 parse_and_traverse_path(self, path) 247 248 } 248 249 250 + /// Get a mutable reference to a field at the given path 251 + /// 252 + /// Uses the same path syntax as [`get_at_path`](Self::get_at_path). 249 253 pub fn get_at_path_mut(&mut self, path: &str) -> Option<&mut Data<'s>> { 250 254 parse_and_traverse_path_mut(self, path) 251 255 } 252 256 257 + /// Set the value at the given path, returning true if successful 258 + /// 259 + /// Uses the same path syntax as [`get_at_path`](Self::get_at_path). 253 260 pub fn set_at_path(&mut self, path: &str, new_data: Data<'_>) -> bool { 254 261 if let Some(data) = parse_and_traverse_path_mut(self, path) { 255 262 *data = new_data.into_static(); ··· 344 351 self.0.get(index) 345 352 } 346 353 354 + /// Get a mutable reference to an element by index 347 355 pub fn get_mut(&mut self, index: usize) -> Option<&mut Data<'s>> { 348 356 self.0.get_mut(index) 349 357 } ··· 396 404 self.0.get(key) 397 405 } 398 406 407 + /// Get a mutable reference to a value by key 399 408 pub fn get_mut(&mut self, key: &str) -> Option<&mut Data<'s>> { 400 409 self.0.get_mut(key) 401 410 } ··· 723 732 parse_and_traverse_raw_path(self, path) 724 733 } 725 734 735 + /// Get a mutable reference to a field at the given path 736 + /// 737 + /// Uses the same path syntax as [`get_at_path`](Self::get_at_path). 726 738 pub fn get_at_path_mut<'a>(&'a mut self, path: &str) -> Option<&'a mut RawData<'d>> { 727 739 parse_and_traverse_raw_path_mut(self, path) 728 740 }
-1
crates/jacquard-common/src/xrpc.rs
··· 28 28 use crate::http_client::HttpClient; 29 29 #[cfg(feature = "streaming")] 30 30 use crate::http_client::HttpClientExt; 31 - use crate::types::nsid::Nsid; 32 31 use crate::types::value::Data; 33 32 use crate::{AuthorizationToken, error::AuthError}; 34 33 use crate::{CowStr, error::XrpcResult};
+1 -1
crates/jacquard-oauth/Cargo.toml
··· 1 1 [package] 2 2 name = "jacquard-oauth" 3 - version = "0.9.0" 3 + version = "0.9.2" 4 4 edition.workspace = true 5 5 description = "AT Protocol OAuth 2.1 core types and helpers for Jacquard" 6 6 authors.workspace = true
-1
crates/jacquard-oauth/src/resolver.rs
··· 5 5 use http::{Request, StatusCode}; 6 6 use jacquard_common::CowStr; 7 7 use jacquard_common::IntoStatic; 8 - use jacquard_common::cowstr::ToCowStr; 9 8 use jacquard_common::types::did_doc::DidDocument; 10 9 use jacquard_common::types::ident::AtIdentifier; 11 10 use jacquard_common::{http_client::HttpClient, types::did::Did};
-1
crates/jacquard-oauth/src/types/client_metadata.rs
··· 2 2 use jose_jwk::JwkSet; 3 3 use serde::{Deserialize, Serialize}; 4 4 use smol_str::SmolStr; 5 - use url::Url; 6 5 7 6 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] 8 7 pub struct OAuthClientMetadata<'c> {
-1
crates/jacquard-oauth/src/types/metadata.rs
··· 1 1 use jacquard_common::{CowStr, IntoStatic, types::string::Language}; 2 2 use serde::{Deserialize, Serialize}; 3 - use url::Url; 4 3 5 4 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)] 6 5 pub struct OAuthAuthorizationServerMetadata<'s> {
+5
crates/jacquard/src/client.rs
··· 153 153 Self::unauthenticated() 154 154 } 155 155 } 156 + 157 + /// Unauthenticated XRPC client session with identity resolution 156 158 pub struct UnauthenticatedSession<T> { 157 159 resolver: Arc<T>, 158 160 endpoint: Arc<RwLock<Option<CowStr<'static>>>>, ··· 166 168 } 167 169 168 170 impl UnauthenticatedSession<JacquardResolver> { 171 + /// Create a new unauthenticated session using public resolvers 169 172 pub fn new_public() -> Self { 170 173 let resolver = Arc::new(JacquardResolver::default()); 171 174 let endpoint = Arc::new(RwLock::new(None)); ··· 177 180 } 178 181 } 179 182 183 + /// Create a new unauthenticated session using the Slingshot relay resolver 180 184 pub fn new_slingshot() -> Self { 181 185 let resolver = Arc::new(slingshot_resolver_default()); 182 186 let endpoint = Arc::new(RwLock::new(None)); ··· 527 531 Self { inner } 528 532 } 529 533 534 + /// Get a reference to the underlying session 530 535 pub fn inner(&self) -> &A { 531 536 &self.inner 532 537 }