A better Rust ATProto crate
100
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 170 lines 10 kB view raw view rendered
1[![Crates.io](https://img.shields.io/crates/v/jacquard.svg)](https://crates.io/crates/jacquard) [![Documentation](https://docs.rs/jacquard/badge.svg)](https://docs.rs/jacquard) [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/S6S8170HQ8) 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 12## Features 13 14- Validated, spec-compliant, easy to work with, and performant baseline types 15- Designed such that you can just work with generated API bindings easily 16- Straightforward OAuth 17- Server-side convenience features 18- Lexicon Data value type for working with unknown atproto data (dag-cbor or json) 19- An order of magnitude less boilerplate than some existing crates 20- Batteries-included, but easily replaceable batteries. 21 - Easy to extend with custom lexicons using code generation or handwritten api types 22 - Stateless options (or options where you handle the state) for rolling your own 23 - All the building blocks of the convenient abstractions are available 24 - Use as much or as little from the crates as you need 25 26 27## Example 28 29Dead simple API client. Logs in with OAuth and prints the latest 5 posts from your timeline. 30 31```rust 32// Note: this requires the `loopback` feature enabled (it is currently by default) 33use clap::Parser; 34use jacquard::CowStr; 35use jacquard::api::app_bsky::feed::get_timeline::GetTimeline; 36use jacquard::client::{Agent, FileAuthStore}; 37use jacquard::oauth::client::OAuthClient; 38use jacquard::oauth::loopback::LoopbackConfig; 39use jacquard::types::xrpc::XrpcClient; 40use miette::IntoDiagnostic; 41 42#[derive(Parser, Debug)] 43#[command(author, version, about = "Jacquard - OAuth (DPoP) loopback demo")] 44struct Args { 45 /// Handle (e.g., alice.bsky.social), DID, or PDS URL 46 input: CowStr<'static>, 47 48 /// Path to auth store file (will be created if missing) 49 #[arg(long, default_value = "/tmp/jacquard-oauth-session.json")] 50 store: String, 51} 52 53#[tokio::main] 54async fn main() -> miette::Result<()> { 55 let args = Args::parse(); 56 57 // Build an OAuth client with file-backed auth store and default localhost config 58 let oauth = OAuthClient::with_default_config(FileAuthStore::new(&args.store)); 59 // Authenticate with a PDS, using a loopback server to handle the callback flow 60 let session = oauth 61 .login_with_local_server( 62 args.input.clone(), 63 Default::default(), 64 LoopbackConfig::default(), 65 ) 66 .await?; 67 // Wrap in Agent and fetch the timeline 68 let agent: Agent<_> = Agent::from(session); 69 let timeline = agent 70 .send(&GetTimeline::new().limit(5).build()) 71 .await? 72 .into_output()?; 73 for (i, post) in timeline.feed.iter().enumerate() { 74 println!("\n{}. by {}", i + 1, post.post.author.handle); 75 println!( 76 " {}", 77 serde_json::to_string_pretty(&post.post.record).into_diagnostic()? 78 ); 79 } 80 81 Ok(()) 82} 83 84``` 85 86If 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. 87 88> [!WARNING] 89> 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. 90 91### Changelog 92 93[CHANGELOG.md](./CHANGELOG.md) 94 95#### 0.11 Release Highlights: 96 97- `jacquard-lexgen` and `jacquard-identity` no longer depend on the generated API crate. This is mostly for my own benefit. 98 99**Code generation pipeline overhaul** (`jacquard-lexicon`, `jacquard-lexgen`) 100- Jacquard's codegen output already was nice to *use*. now it's going to be nice to read. 101- New code generation tracks the types used, makes an import block for the file, and then organizes the file with stuff you care about at the top and internal stuff, like the builders, at the bottom. 102- Import resolution pass now conditionally generates short paths when types are unambiguous within a module, falling back to fully-qualified paths when collisions exist 103 104#### 0.10 Release Highlights: 105 106**URL type migration** 107- Migrated from `url` crate to `fluent_uri` for validated URL/URI types 108- All `Url` types are now `Uri` from `fluent_uri` 109- Affects any code that constructs, passes, or pattern-matches on endpoint URLs 110 111**Re-exported crate paths** 112- Re-exported crates (including non-proc-macro dependencies of the generated API crate) are now centralized into a distinct module 113- Import paths for re-exported types have changed 114 115**`no_std` groundwork** 116- Initial work toward allowing jacquard to function on platforms without access to the standard library. 117- `std` usage is now feature-gated. the library currently *does not compile* without `std` due to some remaining dependencies. 118 119### Projects using Jacquard 120 121- [Tranquil PDS](https://tangled.org/tranquil.farm/tranquil-pds) 122- [skywatch-phash-rs](https://tangled.org/skywatch.blue/skywatch-phash-rs) 123- [Weaver](https://weaver.sh/) - [tangled repository](https://tangled.org/nonbinary.computer/weaver) 124- [wisp.place CLI tool](https://docs.wisp.place/cli/) - formerly 125- [PDS MOOver](https://pdsmoover.com/) - [tangled repository](https://tangled.org/baileytownsend.dev/pds-moover) 126 127## Component crates 128 129Jacquard 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. 130 131| | | | 132| --- | --- | --- | 133| `jacquard` | Main crate | [![Crates.io](https://img.shields.io/crates/v/jacquard.svg)](https://crates.io/crates/jacquard) [![Documentation](https://docs.rs/jacquard/badge.svg)](https://docs.rs/jacquard) | 134|`jacquard-common` | Foundation crate | [![Crates.io](https://img.shields.io/crates/v/jacquard-common.svg)](https://crates.io/crates/jacquard-common) [![Documentation](https://docs.rs/jacquard-common/badge.svg)](https://docs.rs/jacquard-common)| 135| `jacquard-axum` | Axum extractor and other helpers | [![Crates.io](https://img.shields.io/crates/v/jacquard-axum.svg)](https://crates.io/crates/jacquard-axum) [![Documentation](https://docs.rs/jacquard-axum/badge.svg)](https://docs.rs/jacquard-axum) | 136| `jacquard-api` | Autogenerated API bindings | [![Crates.io](https://img.shields.io/crates/v/jacquard-api.svg)](https://crates.io/crates/jacquard-api) [![Documentation](https://docs.rs/jacquard-api/badge.svg)](https://docs.rs/jacquard-api) | 137| `jacquard-oauth` | atproto OAuth implementation | [![Crates.io](https://img.shields.io/crates/v/jacquard-oauth.svg)](https://crates.io/crates/jacquard-oauth) [![Documentation](https://docs.rs/jacquard-oauth/badge.svg)](https://docs.rs/jacquard-oauth) | 138| `jacquard-identity` | Identity resolution | [![Crates.io](https://img.shields.io/crates/v/jacquard-identity.svg)](https://crates.io/crates/jacquard-identity) [![Documentation](https://docs.rs/jacquard-identity/badge.svg)](https://docs.rs/jacquard-identity) | 139| `jacquard-repo` | Repository primitives (MST, commits, CAR I/O) | [![Crates.io](https://img.shields.io/crates/v/jacquard-repo.svg)](https://crates.io/crates/jacquard-repo) [![Documentation](https://docs.rs/jacquard-repo/badge.svg)](https://docs.rs/jacquard-repo) | 140| `jacquard-lexicon` | Lexicon parsing and code generation | [![Crates.io](https://img.shields.io/crates/v/jacquard-lexicon.svg)](https://crates.io/crates/jacquard-lexicon) [![Documentation](https://docs.rs/jacquard-lexicon/badge.svg)](https://docs.rs/jacquard-lexicon) | 141| `jacquard-lexgen` | Code generation binaries | [![Crates.io](https://img.shields.io/crates/v/jacquard-lexgen.svg)](https://crates.io/crates/jacquard-lexgen) [![Documentation](https://docs.rs/jacquard-lexgen/badge.svg)](https://docs.rs/jacquard-lexgen) | 142| `jacquard-derive` | Macros for lexicon types | [![Crates.io](https://img.shields.io/crates/v/jacquard-derive.svg)](https://crates.io/crates/jacquard-derive) [![Documentation](https://docs.rs/jacquard-derive/badge.svg)](https://docs.rs/jacquard-derive) | 143 144### Testimonials 145 146- ["the most straightforward interface to atproto I've encountered so far."](https://bsky.app/profile/offline.mountainherder.xyz/post/3m3xwewzs3k2v) - @offline.mountainherder.xyz 147- "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) 148- ["This is what your library allowed me to do in an hour!!! Thank you!!!"](https://bsky.app/profile/desertthunder.dev/post/3mhhbcula6224) - @desertthunder.dev 149 150 151## Development 152 153This repo uses [Flakes](https://nixos.asia/en/flakes) 154 155```bash 156# Dev shell 157nix develop 158 159# or run via cargo 160nix develop -c cargo run 161 162# build 163nix build 164``` 165 166There'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. 167 168 169 170[![License](https://img.shields.io/crates/l/jacquard.svg)](./LICENSE)