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://whtwnd.com/nonbinary.computer/3m33efvsylz2s) 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## Goals and Features
12
13- Validated, spec-compliant, easy to work with, and performant baseline types
14- Batteries-included, but easily replaceable batteries.
15 - Easy to extend with custom lexicons using code generation or handwritten api types
16 - Straightforward OAuth
17 - Stateless options (or options where you handle the state) for rolling your own
18 - All the building blocks of the convenient abstractions are available
19 - Server-side convenience features
20- Lexicon Data value type for working with unknown atproto data (dag-cbor or json)
21- An order of magnitude less boilerplate than some existing crates
22- Use as much or as little from the crates as you need
23
24
25## Example
26
27Dead simple API client. Logs in with OAuth and prints the latest 5 posts from your timeline.
28
29```rust
30// Note: this requires the `loopback` feature enabled (it is currently by default)
31use clap::Parser;
32use jacquard::CowStr;
33use jacquard::api::app_bsky::feed::get_timeline::GetTimeline;
34use jacquard::client::{Agent, FileAuthStore};
35use jacquard::oauth::client::OAuthClient;
36use jacquard::oauth::loopback::LoopbackConfig;
37use jacquard::types::xrpc::XrpcClient;
38use miette::IntoDiagnostic;
39
40#[derive(Parser, Debug)]
41#[command(author, version, about = "Jacquard - OAuth (DPoP) loopback demo")]
42struct Args {
43 /// Handle (e.g., alice.bsky.social), DID, or PDS URL
44 input: CowStr<'static>,
45
46 /// Path to auth store file (will be created if missing)
47 #[arg(long, default_value = "/tmp/jacquard-oauth-session.json")]
48 store: String,
49}
50
51#[tokio::main]
52async fn main() -> miette::Result<()> {
53 let args = Args::parse();
54
55 // Build an OAuth client with file-backed auth store and default localhost config
56 let oauth = OAuthClient::with_default_config(FileAuthStore::new(&args.store));
57 // Authenticate with a PDS, using a loopback server to handle the callback flow
58 let session = oauth
59 .login_with_local_server(
60 args.input.clone(),
61 Default::default(),
62 LoopbackConfig::default(),
63 )
64 .await?;
65 // Wrap in Agent and fetch the timeline
66 let agent: Agent<_> = Agent::from(session);
67 let timeline = agent
68 .send(&GetTimeline::new().limit(5).build())
69 .await?
70 .into_output()?;
71 for (i, post) in timeline.feed.iter().enumerate() {
72 println!("\n{}. by {}", i + 1, post.post.author.handle);
73 println!(
74 " {}",
75 serde_json::to_string_pretty(&post.post.record).into_diagnostic()?
76 );
77 }
78
79 Ok(())
80}
81
82```
83
84If 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.
85
86## Component crates
87
88Jacquard 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.
89- `jacquard`: Main crate [](https://crates.io/crates/jacquard) [](https://docs.rs/jacquard)
90- `jacquard-common`: Foundation crate [](https://crates.io/crates/jacquard-common) [](https://docs.rs/jacquard-common)
91- `jacquard-axum`: Axum extractor and other helpers [](https://crates.io/crates/jacquard-axum) [](https://docs.rs/jacquard-axum)
92- `jacquard-api`: Autogenerated API bindings [](https://crates.io/crates/jacquard-api) [](https://docs.rs/jacquard-api)
93- `jacquard-oauth`: atproto OAuth implementation [](https://crates.io/crates/jacquard-oauth) [](https://docs.rs/jacquard-oauth)
94- `jacquard-identity`: Identity resolution [](https://crates.io/crates/jacquard-identity) [](https://docs.rs/jacquard-identity)
95- `jacquard-lexicon`: Lexicon parsing and code generation [](https://crates.io/crates/jacquard-lexicon) [](https://docs.rs/jacquard-lexicon)
96- `jacquard-derive`: Macros for lexicon types [](https://crates.io/crates/jacquard-derive) [](https://docs.rs/jacquard-derive)
97
98## Changelog
99
100[CHANGELOG.md](./CHANGELOG.md)
101
102Highlights:
103
104- better value type deserialization helpers
105- service auth implementation
106- XrpcRequest derive Macros
107- more builders in generated api to make constructing things easier (lmk if compile time is awful)
108- `AgentSessionExt` trait with a host of convenience methods for working with records and preferences
109- Improvements to the `Collection` trait, code generation, and addition of the `VecUpdate` trait to enable that
110- A bunch of examples, both in the docs and in the repository
111- More lexicons in the generated API bindings.
112
113## Development
114
115This repo uses [Flakes](https://nixos.asia/en/flakes) from the get-go.
116
117```bash
118# Dev shell
119nix develop
120
121# or run via cargo
122nix develop -c cargo run
123
124# build
125nix build
126```
127
128There'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.
129
130[](./LICENSE)