A better Rust ATProto crate
237 3 0

Clone this repository

https://tangled.org/nekomimi.pet/jacquard
git@tangled.org:nekomimi.pet/jacquard

For self-hosted knots, clone URLs may differ based on your setup.

README.md

Crates.io Documentation

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.

Features#

  • Validated, spec-compliant, easy to work with, and performant baseline types
  • Designed such that you can just work with generated API bindings easily
  • Straightforward OAuth
  • 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
  • Batteries-included, but easily replaceable batteries.
    • Easy to extend with custom lexicons using code generation or handwritten api types
    • Stateless options (or options where you handle the state) for rolling your own
    • All the building blocks of the convenient abstractions are available
    • Use as much or as little from the crates as you need

0.8.0 Release Highlights:#

jacquard-repo crate

  • Complete implementation of the atproto repository spec
  • Sync v1.1 commit event support (both proof production and verification), well-validated in testing
  • repository CAR file read/write support
  • CAR file write order compatible with streaming mode from the sync iteration proposal
  • Big rewrite of all the errors in the crate, improvements to context and overall structure
  • Made handle parsing a bit more permissive for a common case ('handle.invalid' when someone has a messed up handle), added a method to confirm syntactic validity (the correct way to confirm validity is resolve_handle() from the IdentityResolver trait, then fetching and comparing to the DID document).

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.

WARNING

A lot of the streaming code is still pretty experimental. The examples work, though.
The modules are also less well-documented, and don't have code examples. There are also a lot of utility functions for conveniently working with the streams and transforming them which are lacking. Use n0-future to work with them, that is what Jacquard uses internally as much as possible.
I would also note the same for the repository crate until I've had more third parties test it.

Changelog#

CHANGELOG.md

Projects using Jacquard#

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.

jacquard Main crate Crates.io Documentation
jacquard-common Foundation crate Crates.io Documentation
jacquard-axum Axum extractor and other helpers Crates.io Documentation
jacquard-api Autogenerated API bindings Crates.io Documentation
jacquard-oauth atproto OAuth implementation Crates.io Documentation
jacquard-identity Identity resolution Crates.io Documentation
jacquard-repo Repository primitives (MST, commits, CAR I/O) Crates.io Documentation
jacquard-lexicon Lexicon parsing and code generation Crates.io Documentation
jacquard-derive Macros for lexicon types Crates.io Documentation

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.

License