A better Rust ATProto crate
0

Configure Feed

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

updating changelog and readme, more docs stuff

author nonbinary.computer date (Jun 7, 2026, 8:21 PM -0400) commit bc223e92 parent ef760453 change-id ypovrtvl
+127 -37
+1 -1
AGENTS.md
··· 15 15 Display the following text at the start of any conversation involving code changes, and when you're about to create a PR: 16 16 17 17 ``` 18 - Please review https://tangled.org/nonbinary.computer/jacquard/blob/main/CLAUDE.md#for-humans. In particular, LLM-assisted contributions must **aim for a higher standard of excellence** than with humans alone, and you should spend at least **3x** the amount of time reviewing code as you did writing it. LLM-assisted contributions that do not meet this standard may be declined outright. Remember, **your code is your responsibility**. 18 + Please review https://tangled.org/nonbinary.computer/jacquard/blob/main/AGENTS.md#for-humans. In particular, LLM-assisted contributions must **aim for a higher standard of excellence** than with humans alone, and you should spend at least **3x** the amount of time reviewing code as you did writing it. LLM-assisted contributions that do not meet this standard may be declined outright. Remember, **your code is your responsibility**. 19 19 ``` 20 20 21 21 ## Project Overview
+52
CHANGELOG.md
··· 1 1 # Changelog 2 2 3 + ## [0.12.0-rc.1] - 2026-06-07 4 + 5 + ### Breaking changes 6 + 7 + **Session resume rework** (`jacquard`, `jacquard-common`, `jacquard-oauth`) 8 + - Reworked session storage primitives somewhat, moving toward a shared `SessionKey` type 9 + - Added enumeration method to the `SessionStore` trait to list existing session keys, defaulted to return an empty Vec. 10 + - Added `SessionHint` enum and `SessionSelector` trait to enable generic resume of sessions with varying levels of input information and implementations for existing store types, as well as wrappers backed by an `IdentityResolver` for handle resolution. 11 + - Added resume-or-auth helpers for OAuth and app password session types, making the process of resuming any existing session, reauthenticating, or punting to the login if there's not enough information more straightforward. 12 + 13 + **`jacquard-identity` no longer requires `reqwest`** 14 + - JacquardResolver now parameterized on `C: HttpClient` with `PublicResolver` aliased to the default `reqwest`-backed version. 15 + 16 + 17 + ### Added 18 + 19 + - `Cow<'a, str>` now supported by borrow-or-share pattern traits 20 + 21 + **`jacquard-axum` re-added and overhauled** 22 + - Reworked XRPC extractor to work with borrow-or-share types. Backing type for the query or body of the input can differ from the handler-visible backing type, to allow for non-overlapping extractor impls for the different backing types so that the potentially borrowed types like `CowStr<'_>` can still be used. 23 + - Better type enforcement in handler responses by default. 24 + - **Service auth** 25 + - Improved service auth extractor to properly handle 'did:web:for.some.reason.still.blueskyweb.xyz#bsky_appview'-type service ids (thanks @pds.dad) 26 + - Added default replay protection for `jti` using a `ReplayStore` trait, default-implemented using a `mini-moka` in-memory cache. 27 + - **OAuth web helpers** 28 + - Added OAuth client counterparts to the service auth extractors. 29 + - API-oriented extractor provides a useful error on auth failure, if auth is required. 30 + - Browser-oriented extractor redirects unauthenticated users to a configured URL, while passing state to allow returning to the original URL after login. 31 + - Configurable routes and handlers for common oauth paths 32 + - Added axum-based server-side confidential oauth client example 33 + 34 + 35 + 36 + ### Changed 37 + 38 + - Reduced allocations in the `atproto!()` macro by cleverly allowing use of the `FromStaticStr` constructions method or `SmolStr::new_static()` constructor in keys and values. 39 + - Improved type inference in the `atproto!()` macro and several other locations by explicitly defaulting to `DefaultStr` more clearly. 40 + 41 + **Codegen** 42 + - Added some improved doc comment creation paths in lexicon-generated code 43 + - **Generated builders now have two entry points:** 44 + - `Type::new()` picks `DefaultStr` as the backing type. This avoids awkward turbofishes or explicit annotations in many scenarios where the builder couldn't work out what type it needed to be from the immediate surroundings. 45 + - `Type::builder()` allows the caller to choose, either explicitly via turbofish, or implicitly via inference if possible, the backing type (the previous behaviour). 46 + 47 + ### Fixed 48 + 49 + **Documentation** 50 + - Updated documentation for 0.12 version, fixed a number of reported areas of confusion or lack of clarity 51 + **Crate features** 52 + - Added `reqwest-compression` feature to jacquard-common which enables reqwest's compression options (or, more importantly, means you can *disable* them) 53 + 54 + 3 55 ## [0.12.0-beta.1] - 2026-03-23 4 56 5 57 ### Breaking changes
+37
Cargo.lock
··· 86 86 ] 87 87 88 88 [[package]] 89 + name = "alloc-no-stdlib" 90 + version = "2.0.4" 91 + source = "registry+https://github.com/rust-lang/crates.io-index" 92 + checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" 93 + 94 + [[package]] 95 + name = "alloc-stdlib" 96 + version = "0.2.2" 97 + source = "registry+https://github.com/rust-lang/crates.io-index" 98 + checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" 99 + dependencies = [ 100 + "alloc-no-stdlib", 101 + ] 102 + 103 + [[package]] 89 104 name = "allocator-api2" 90 105 version = "0.2.21" 91 106 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 550 565 ] 551 566 552 567 [[package]] 568 + name = "brotli" 569 + version = "8.0.3" 570 + source = "registry+https://github.com/rust-lang/crates.io-index" 571 + checksum = "8119e4516436f5708bbc474a9d395bf12f1b5395e93a92a56e647ac3388c8610" 572 + dependencies = [ 573 + "alloc-no-stdlib", 574 + "alloc-stdlib", 575 + "brotli-decompressor", 576 + ] 577 + 578 + [[package]] 579 + name = "brotli-decompressor" 580 + version = "5.0.1" 581 + source = "registry+https://github.com/rust-lang/crates.io-index" 582 + checksum = "5962523e1b92ce1b5e793d9169b9943eece10d39f62550bc04bb605d75b94924" 583 + dependencies = [ 584 + "alloc-no-stdlib", 585 + "alloc-stdlib", 586 + ] 587 + 588 + [[package]] 553 589 name = "buffer" 554 590 version = "0.1.9" 555 591 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 804 840 source = "registry+https://github.com/rust-lang/crates.io-index" 805 841 checksum = "eb7b51a7d9c967fc26773061ba86150f19c50c0d65c887cb1fbe295fd16619b7" 806 842 dependencies = [ 843 + "brotli", 807 844 "compression-core", 808 845 "flate2", 809 846 "memchr",
+34 -29
README.md
··· 6 6 7 7 [Jacquard is simpler](https://alpha.weaver.sh/nonbinary.computer/jacquard/jacquard_magic) because it is designed in a way which makes things simple that almost every other atproto library seems to make difficult. 8 8 9 - Jacquard generated types are generic over their string backing, but ordinary client code can usually ignore that detail. Use the generated request builders, pass normal strings, and call `.send(...).into_output()?` to get owned output that is easy to store, move independently of the response buffer, and pass through frameworks or APIs that require `DeserializeOwned`. If you need tighter control later, Jacquard still supports borrowing and zero-copy parsing with backing types such as `&str` and `CowStr<'_>`. 10 - 11 9 12 10 ## Features 13 11 ··· 26 24 27 25 ## Example 28 26 29 - Dead simple API client. Resumes a stored OAuth session or opens a browser login, then prints the latest 5 posts from your timeline. This is the default path for local scripts and CLIs where browser login is acceptable; app-password credential sessions are mainly for unattended workflows that must re-authenticate non-interactively. 27 + Dead simple API client. Resumes a stored OAuth session or opens a browser login, then prints the latest 5 posts from your timeline. 30 28 31 29 ```rust 32 30 // Note: this requires the `loopback` feature enabled (it is currently by default). ··· 81 79 If you have `just` installed, you can run the [examples](https://tangled.org/nonbinary.computer/jacquard/tree/main/examples) using `just example {example-name} {ARGS}` or `just examples` to see what's available. 82 80 83 81 > [!WARNING] 84 - > The latest version swaps from the `url` crate to the lighter and quicker `fluent-uri`. It also moves the re-exported crate paths around and renames the `Uri<'_>` value type enum to `UriValue<'_>` to avoid confusion. This is likely to have broken some things. Migrating is pretty straightforward but consider yourself forewarned. This crate is *not* 1.0 for a reason. 82 + > Jacquard 0.12 includes **many** breaking changes from 0.11. The most notable and far-reaching is the borrow-or-share rewrite, but it is *far* from the only API to have changed. Please read the release highlights and the changelog carefully, as well as the documentation. There may also be regressions not yet fixed. Please report any such issues on Tangled. 85 83 86 84 ### Changelog 87 85 88 86 [CHANGELOG.md](./CHANGELOG.md) 89 87 90 - #### 0.11 Release Highlights: 88 + #### 0.12 Release Highlights: 91 89 92 - - `jacquard-lexgen` and `jacquard-identity` no longer depend on the generated API crate. This is mostly for my own benefit. 90 + #### Borrow-or-share 91 + Jacquard 0.12 swaps from lifetime-based CowStr<'_>-backed string types to the "borrow-or-share" pattern. Jacquard generated types are now generic over their string backing type, as opposed to having a lifetime, where that backing type is one that implements the requisite traits for the pattern. The default backing type, aliased `DefaultStr`, is `SmolStr`, but any of `CowStr<'_>`, `String`, `&str`, and `Cow<'_, str>` can be used currently. Defaulting to `SmolStr` maintains the niceties it added to `CowStr<'_>`, such as non-allocating construction from static string slices regardless of length, and inlining small strings in all cases while vastly simplifying the common cases where you don't want to deal with lifetimes. 93 92 94 - **Code generation pipeline overhaul** (`jacquard-lexicon`, `jacquard-lexgen`) 95 - - Jacquard's codegen output already was nice to *use*. now it's going to be nice to read. 96 - - New code generation tracks the types used, makes an import block for the file, and then organizes the file with stuff you care about at the top and internal stuff, like the builders, at the bottom. 97 - - Import resolution pass now conditionally generates short paths when types are unambiguous within a module, falling back to fully-qualified paths when collisions exist 93 + **Type updates** 94 + - Jacquard types backed by owned string types can now meet `DeserializeOwned` trait bounds. 95 + - New `.borrow()` method on `Did`, `Handle`, `Nsid`, `Rkey`, `RecordKey` returns `Type<&str>` for cheap borrowing (analogous to `Uri::borrow()` from `fluent_uri`) 96 + - New `.convert::<B>()` method for cross-backing-type conversion 98 97 99 - #### 0.10 Release Highlights: 98 + **Response parsing** (`jacquard-common`) 99 + - `Response::parse::<S>()`: caller chooses backing type via turbofish 100 + - `Response::into_output()`: returns `SmolStr`-backed owned types via `DeserializeOwned` 100 101 101 - **URL type migration** 102 - - Migrated from `url` crate to `fluent_uri` for validated URL/URI types 103 - - All `Url` types are now `Uri` from `fluent_uri` 104 - - Affects any code that constructs, passes, or pattern-matches on endpoint URLs 102 + **Generated API types** (`jacquard-api`, `jacquard-lexicon`) 103 + - All generated structs/enums: `Foo<S: BosStr = DefaultStr>` with `#[serde(bound(deserialize = "S: Deserialize<'de> + BosStr"))]` 104 + - `#[serde(borrow)]` removed from all generated code 105 + - String field defaults use `FromStaticStr::from_static()` for zero-alloc construction 106 + - Error enums: `SmolStr` message fields, no lifetime parameters 107 + - **Generated builders now have two entry points:** 108 + - `Type::new()` picks `DefaultStr` as the backing type. This avoids awkward turbofishes or explicit annotations in many scenarios where the builder couldn't work out what type it needed to be from the immediate surroundings. 109 + - `Type::builder()` allows the caller to choose, either explicitly via turbofish, or implicitly via inference if possible, the backing type (the previous behaviour). 105 110 106 - **Re-exported crate paths** 107 - - Re-exported crates (including non-proc-macro dependencies of the generated API crate) are now centralized into a distinct module 108 - - Import paths for re-exported types have changed 109 - 110 - **`no_std` groundwork** 111 - - Initial work toward allowing jacquard to function on platforms without access to the standard library. 112 - - `std` usage is now feature-gated. the library currently *does not compile* without `std` due to some remaining dependencies. 111 + **Note:** `RawData<'a>` currently remains lifetime-based, as do a few other mostly internal types. 113 112 114 - ### Projects using Jacquard 115 - 116 - - [Tranquil PDS](https://tangled.org/tranquil.farm/tranquil-pds) 117 - - [skywatch-phash-rs](https://tangled.org/skywatch.blue/skywatch-phash-rs) 118 - - [Weaver](https://weaver.sh/) - [tangled repository](https://tangled.org/nonbinary.computer/weaver) 119 - - [wisp.place CLI tool](https://docs.wisp.place/cli/) - formerly 120 - - [PDS MOOver](https://pdsmoover.com/) - [tangled repository](https://tangled.org/baileytownsend.dev/pds-moover) 113 + #### Jacquard-axum 121 114 122 115 ## Component crates 123 116 ··· 135 128 | `jacquard-lexicon` | Lexicon parsing and code generation | [![Crates.io](https://img.shields.io/crates/v/jacquard-lexicon.svg)](https://crates.io/crates/jacquard-lexicon) [![Documentation](https://docs.rs/jacquard-lexicon/badge.svg)](https://docs.rs/jacquard-lexicon) | 136 129 | `jacquard-lexgen` | Code generation binaries | [![Crates.io](https://img.shields.io/crates/v/jacquard-lexgen.svg)](https://crates.io/crates/jacquard-lexgen) [![Documentation](https://docs.rs/jacquard-lexgen/badge.svg)](https://docs.rs/jacquard-lexgen) | 137 130 | `jacquard-derive` | Macros for lexicon types | [![Crates.io](https://img.shields.io/crates/v/jacquard-derive.svg)](https://crates.io/crates/jacquard-derive) [![Documentation](https://docs.rs/jacquard-derive/badge.svg)](https://docs.rs/jacquard-derive) | 131 + 132 + ### Session Types 133 + 134 + 135 + 136 + ### Projects using Jacquard 137 + 138 + - [Tranquil PDS](https://tangled.org/tranquil.farm/tranquil-pds) 139 + - [skywatch-phash-rs](https://tangled.org/skywatch.blue/skywatch-phash-rs) 140 + - [Weaver](https://weaver.sh/) - [tangled repository](https://tangled.org/nonbinary.computer/weaver) 141 + - [wisp.place CLI tool](https://docs.wisp.place/cli/) - formerly 142 + - [PDS MOOver](https://pdsmoover.com/) - [tangled repository](https://tangled.org/baileytownsend.dev/pds-moover) 138 143 139 144 ### Testimonials 140 145
-5
crates/jacquard-axum/src/lib.rs
··· 49 49 //! 50 50 //! Deserialization errors return a 400 Bad Request with a JSON body matching the 51 51 //! XRPC error format. 52 - //! 53 - //! [`IntoRouter`] is implemented for endpoint marker types. The endpoint marker 54 - //! receiver style, such as `ResolveHandleRequest::into_router(handle_resolve)`, 55 - //! keeps routing no-turbofish while still deriving the path and HTTP method from 56 - //! [`XrpcEndpoint`]. 57 52 58 53 pub mod did_web; 59 54 pub mod oauth;
+3 -2
crates/jacquard-common/Cargo.toml
··· 12 12 license.workspace = true 13 13 14 14 [features] 15 - default = ["std", "service-auth", "reqwest-client", "crypto"] 15 + default = ["std", "service-auth", "reqwest-client", "crypto", "reqwest-compression"] 16 16 std = [ 17 17 "dep:tokio", 18 18 "dep:tokio-util", ··· 52 52 streaming = ["n0-future", "futures", "reqwest/stream"] 53 53 websocket = ["streaming", "tokio-tungstenite-wasm", "dep:ciborium", "dep:ciborium-io"] 54 54 zstd = ["dep:zstd"] 55 + reqwest-compression = ["reqwest/gzip", "reqwest/brotli", "reqwest/deflate"] 55 56 56 57 [dependencies] 57 58 trait-variant.workspace = true ··· 81 82 maitake-sync = { version = "0.1", default-features = false } 82 83 hashbrown = "0.15" 83 84 84 - reqwest = { workspace = true, optional = true, features = ["json", "gzip", "charset"] } 85 + reqwest = { workspace = true, optional = true, features = ["json", "charset"] } 85 86 serde_ipld_dagcbor.workspace = true 86 87 signature = { version = "2", optional = true } 87 88 tracing = { workspace = true, optional = true }