Jacquard#
A suite of Rust crates intended to make it much easier to get started with atproto development, without sacrificing flexibility or performance.
Jacquard is simpler because it is designed in a way which makes things simple that almost every other atproto library seems to make difficult.
It is also designed around zero-copy/borrowed deserialization: types like Post<'_> can borrow data (via the CowStr<'_> 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.
Goals and Features#
- Validated, spec-compliant, easy to work with, and performant baseline types
- Batteries-included, but easily replaceable batteries.
- Easy to extend with custom lexicons using code generation or handwritten api types
- Straightforward OAuth
- Stateless options (or options where you handle the state) for rolling your own
- All the building blocks of the convenient abstractions are available
- Server-side convenience features
- Lexicon Data value type for working with unknown atproto data (dag-cbor or json)
- An order of magnitude less boilerplate than some existing crates
- Use as much or as little from the crates as you need
Example#
Dead simple API client. Logs in with OAuth and prints the latest 5 posts from your timeline.
// Note: this requires the `loopback` feature enabled (it is currently by default)
use clap::Parser;
use jacquard::CowStr;
use jacquard::api::app_bsky::feed::get_timeline::GetTimeline;
use jacquard::client::{Agent, FileAuthStore};
use jacquard::oauth::client::OAuthClient;
use jacquard::oauth::loopback::LoopbackConfig;
use jacquard::types::xrpc::XrpcClient;
use miette::IntoDiagnostic;
#[derive(Parser, Debug)]
#[command(author, version, about = "Jacquard - OAuth (DPoP) loopback demo")]
struct Args {
/// Handle (e.g., alice.bsky.social), DID, or PDS URL
input: CowStr<'static>,
/// Path to auth store file (will be created if missing)
#[arg(long, default_value = "/tmp/jacquard-oauth-session.json")]
store: String,
}
#[tokio::main]
async fn main() -> miette::Result<()> {
let args = Args::parse();
// Build an OAuth client with file-backed auth store and default localhost config
let oauth = OAuthClient::with_default_config(FileAuthStore::new(&args.store));
// Authenticate with a PDS, using a loopback server to handle the callback flow
let session = oauth
.login_with_local_server(
args.input.clone(),
Default::default(),
LoopbackConfig::default(),
)
.await?;
// Wrap in Agent and fetch the timeline
let agent: Agent<_> = Agent::from(session);
let timeline = agent
.send(&GetTimeline::new().limit(5).build())
.await?
.into_output()?;
for (i, post) in timeline.feed.iter().enumerate() {
println!("\n{}. by {}", i + 1, post.post.author.handle);
println!(
" {}",
serde_json::to_string_pretty(&post.post.record).into_diagnostic()?
);
}
Ok(())
}
If you have just installed, you can run the examples using just example {example-name} {ARGS} or just examples to see what's available.
Component crates#
Jacquard 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.
Changelog#
Highlights:
- initial streaming support
- experimental WASM support
- better value type deserialization helpers
- service auth implementation
- XrpcRequest derive Macros
- more builders in generated api to make constructing things easier (lmk if compile time is awful)
AgentSessionExttrait with a host of convenience methods for working with records and preferences- Improvements to the
Collectiontrait, code generation, and addition of theVecUpdatetrait to enable that
Experimental WASM Support#
Core crates (jacquard-common, jacquard-api, jacquard-identity, jacquard-oauth) compile for wasm32-unknown-unknown. Traits use trait-variant to conditionally exclude Send bounds on WASM targets. DNS-based handle resolution is gated behind the dns feature and unavailable on WASM (HTTPS well-known and PDS resolution still work).
Test WASM compilation:
just check-wasm
# or: cargo build --target wasm32-unknown-unknown -p jacquard-common --no-default-features
Initial Streaming Support#
Jacquard is building out support for efficient streaming for large payloads:
- Blob uploads/downloads: Stream media without loading into memory
- CAR file streaming: Efficient repo sync operations
- Thin forwarding: Pipe data between endpoints
- WebSocket support: Bidirectional streaming connections
Enable with the streaming feature flag. See jacquard-common documentation for details.
Development#
This repo uses Flakes
# Dev shell
nix develop
# or run via cargo
nix develop -c cargo run
# build
nix build
There's also a justfile 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.