1# Jacquard
2
3A suite of Rust crates for the AT Protocol.
4
5[](https://crates.io/crates/jacquard) [](https://docs.rs/jacquard) [](./LICENSE)
6
7## Goals
8
9- Validated, spec-compliant, easy to work with, and performant baseline types (including typed at:// uris)
10- Batteries-included, but easily replaceable batteries.
11 - Easy to extend with custom lexicons
12- lexicon Value type for working with unknown atproto data (dag-cbor or json)
13- order of magnitude less boilerplate than some existing crates
14 - either the codegen produces code that's easy to work with, or there are good handwritten wrappers
15- didDoc type with helper methods for getting handles, multikey, and PDS endpoint
16- use as much or as little from the crates as you need
17
18
19## Example
20
21Dead simple API client. Logs in with an app password and prints the latest 5 posts from your timeline.
22
23```rust
24use std::sync::Arc;
25use clap::Parser;
26use jacquard::CowStr;
27use jacquard::api::app_bsky::feed::get_timeline::GetTimeline;
28use jacquard::client::credential_session::{CredentialSession, SessionKey};
29use jacquard::client::{AtpSession, FileAuthStore, MemorySessionStore};
30use jacquard::identity::PublicResolver as JacquardResolver;
31use miette::IntoDiagnostic;
32
33#[derive(Parser, Debug)]
34#[command(author, version, about = "Jacquard - AT Protocol client demo")]
35struct Args {
36 /// Username/handle (e.g., alice.bsky.social) or DID
37 #[arg(short, long)]
38 username: CowStr<'static>,
39 /// App password
40 #[arg(short, long)]
41 password: CowStr<'static>,
42}
43
44#[tokio::main]
45async fn main() -> miette::Result<()> {
46 let args = Args::parse();
47
48 // Resolver + storage
49 let resolver = Arc::new(JacquardResolver::default());
50 let store: Arc<MemorySessionStore<SessionKey, AtpSession>> = Arc::new(Default::default());
51 let client = Arc::new(resolver.clone());
52 let session = CredentialSession::new(store, client);
53
54 // Login (resolves PDS automatically) and persist as (did, "session")
55 session
56 .login(args.username.clone(), args.password.clone(), None, None, None)
57 .await
58 .into_diagnostic()?;
59
60 // Fetch timeline
61 let timeline = session
62 .clone()
63 .send(GetTimeline::new().limit(5).build())
64 .await
65 .into_diagnostic()?
66 .into_output()
67 .into_diagnostic()?;
68
69 println!("\ntimeline ({} posts):", timeline.feed.len());
70 for (i, post) in timeline.feed.iter().enumerate() {
71 println!("\n{}. by {}", i + 1, post.post.author.handle);
72 println!(
73 " {}",
74 serde_json::to_string_pretty(&post.post.record).into_diagnostic()?
75 );
76 }
77
78 Ok(())
79}
80```
81
82## Development
83
84This repo uses [Flakes](https://nixos.asia/en/flakes) from the get-go.
85
86```bash
87# Dev shell
88nix develop
89
90# or run via cargo
91nix develop -c cargo run
92
93# build
94nix build
95```
96
97There'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.