A better Rust ATProto crate
1[](https://crates.io/crates/jacquard) [](https://docs.rs/jacquard)
2
3# Jacquard
4
5A suite of Rust crates intended to make it much easier to get started with atproto development, without sacrificing flexibility or performance.
6
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
9It is also designed around zero-copy/borrowed deserialization: types like [`Post<'_>`](https://tangled.org/nonbinary.computer/jacquard/blob/main/crates/jacquard-api/src/app_bsky/feed/post.rs) can borrow data (via the [`CowStr<'_>`](https://docs.rs/jacquard/latest/jacquard/cowstr/enum.CowStr.html) type and a host of other types built on top of it) directly from the response buffer instead of allocating owned copies. Owned versions are themselves mostly inlined or reference-counted pointers and are therefore still quite efficient. The `IntoStatic` trait (which is derivable) makes it easy to get an owned version and avoid worrying about lifetimes.
10
11## Features
12
13- Validated, spec-compliant, easy to work with, and performant baseline types
14- Designed such that you can just work with generated API bindings easily
15- Straightforward OAuth
16- Server-side convenience features
17- Lexicon Data value type for working with unknown atproto data (dag-cbor or json)
18- An order of magnitude less boilerplate than some existing crates
19- Batteries-included, but easily replaceable batteries.
20 - Easy to extend with custom lexicons using code generation or handwritten api types
21 - Stateless options (or options where you handle the state) for rolling your own
22 - All the building blocks of the convenient abstractions are available
23 - Use as much or as little from the crates as you need
24
25
26## Example
27
28Dead simple API client. Logs in with OAuth and prints the latest 5 posts from your timeline.
29
30```rust
31// Note: this requires the `loopback` feature enabled (it is currently by default)
32use clap::Parser;
33use jacquard::CowStr;
34use jacquard::api::app_bsky::feed::get_timeline::GetTimeline;
35use jacquard::client::{Agent, FileAuthStore};
36use jacquard::oauth::client::OAuthClient;
37use jacquard::oauth::loopback::LoopbackConfig;
38use jacquard::types::xrpc::XrpcClient;
39use miette::IntoDiagnostic;
40
41#[derive(Parser, Debug)]
42#[command(author, version, about = "Jacquard - OAuth (DPoP) loopback demo")]
43struct Args {
44 /// Handle (e.g., alice.bsky.social), DID, or PDS URL
45 input: CowStr<'static>,
46
47 /// Path to auth store file (will be created if missing)
48 #[arg(long, default_value = "/tmp/jacquard-oauth-session.json")]
49 store: String,
50}
51
52#[tokio::main]
53async fn main() -> miette::Result<()> {
54 let args = Args::parse();
55
56 // Build an OAuth client with file-backed auth store and default localhost config
57 let oauth = OAuthClient::with_default_config(FileAuthStore::new(&args.store));
58 // Authenticate with a PDS, using a loopback server to handle the callback flow
59 let session = oauth
60 .login_with_local_server(
61 args.input.clone(),
62 Default::default(),
63 LoopbackConfig::default(),
64 )
65 .await?;
66 // Wrap in Agent and fetch the timeline
67 let agent: Agent<_> = Agent::from(session);
68 let timeline = agent
69 .send(&GetTimeline::new().limit(5).build())
70 .await?
71 .into_output()?;
72 for (i, post) in timeline.feed.iter().enumerate() {
73 println!("\n{}. by {}", i + 1, post.post.author.handle);
74 println!(
75 " {}",
76 serde_json::to_string_pretty(&post.post.record).into_diagnostic()?
77 );
78 }
79
80 Ok(())
81}
82
83```
84
85If 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.
86
87> [!WARNING]
88> 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.
89
90### Changelog
91
92[CHANGELOG.md](./CHANGELOG.md)
93
94#### 0.10 Release Highlights:
95
96**URL type migration**
97- Migrated from `url` crate to `fluent_uri` for validated URL/URI types
98- All `Url` types are now `Uri` from `fluent_uri`
99- Affects any code that constructs, passes, or pattern-matches on endpoint URLs
100
101**Re-exported crate paths**
102- Re-exported crates (including non-proc-macro dependencies of the generated API crate) are now centralized into a distinct module
103- Import paths for re-exported types have changed
104
105**`no_std` groundwork**
106- Initial work toward allowing jacquard to function on platforms without access to the standard library.
107- `std` usage is now feature-gated. the library currently *does not compile* without `std` due to some remaining dependencies.
108
109### Projects using Jacquard
110
111- [Tranquil PDS](https://tangled.org/tranquil.farm/tranquil-pds)
112- [skywatch-phash-rs](https://tangled.org/skywatch.blue/skywatch-phash-rs)
113- [Weaver](https://weaver.sh/) - [tangled repository](https://tangled.org/nonbinary.computer/weaver)
114- [wisp.place CLI tool](https://docs.wisp.place/cli/) - formerly
115- [PDS MOOver](https://pdsmoover.com/) - [tangled repository](https://tangled.org/baileytownsend.dev/pds-moover)
116
117## Component crates
118
119Jacquard is broken up into several crates for modularity. The correct one to use is generally `jacquard` itself, as it re-exports most of the others.
120
121| | | |
122| --- | --- | --- |
123| `jacquard` | Main crate | [](https://crates.io/crates/jacquard) [](https://docs.rs/jacquard) |
124|`jacquard-common` | Foundation crate | [](https://crates.io/crates/jacquard-common) [](https://docs.rs/jacquard-common)|
125| `jacquard-axum` | Axum extractor and other helpers | [](https://crates.io/crates/jacquard-axum) [](https://docs.rs/jacquard-axum) |
126| `jacquard-api` | Autogenerated API bindings | [](https://crates.io/crates/jacquard-api) [](https://docs.rs/jacquard-api) |
127| `jacquard-oauth` | atproto OAuth implementation | [](https://crates.io/crates/jacquard-oauth) [](https://docs.rs/jacquard-oauth) |
128| `jacquard-identity` | Identity resolution | [](https://crates.io/crates/jacquard-identity) [](https://docs.rs/jacquard-identity) |
129| `jacquard-repo` | Repository primitives (MST, commits, CAR I/O) | [](https://crates.io/crates/jacquard-repo) [](https://docs.rs/jacquard-repo) |
130| `jacquard-lexicon` | Lexicon parsing and code generation | [](https://crates.io/crates/jacquard-lexicon) [](https://docs.rs/jacquard-lexicon) |
131| `jacquard-lexgen` | Code generation binaries | [](https://crates.io/crates/jacquard-lexgen) [](https://docs.rs/jacquard-lexgen) |
132| `jacquard-derive` | Macros for lexicon types | [](https://crates.io/crates/jacquard-derive) [](https://docs.rs/jacquard-derive) |
133
134### Testimonials
135
136- ["the most straightforward interface to atproto I've encountered so far."](https://bsky.app/profile/offline.mountainherder.xyz/post/3m3xwewzs3k2v) - @offline.mountainherder.xyz
137- "It has saved me a lot of time already! Well worth a few beers and or microcontrollers" - [@baileytownsend.dev](https://bsky.app/profile/baileytownsend.dev)
138- ["This is what your library allowed me to do in an hour!!! Thank you!!!"](https://bsky.app/profile/desertthunder.dev/post/3mhhbcula6224) - @desertthunder.dev
139
140
141## Development
142
143This repo uses [Flakes](https://nixos.asia/en/flakes)
144
145```bash
146# Dev shell
147nix develop
148
149# or run via cargo
150nix develop -c cargo run
151
152# build
153nix build
154```
155
156There's also a [`justfile`](https://just.systems/) for Makefile-esque commands to be run inside of the devShell, and you can generally `cargo ...` or `just ...` whatever just fine if you don't want to use Nix and have the prerequisites installed.
157
158
159
160[](./LICENSE)