···11# Changelog
2233+## [0.10.0] - 2026-03-20
44+55+### Breaking changes
66+77+**URL type migration** (`jacquard-common`, `jacquard`, `jacquard-oauth`, `jacquard-identity`, `jacquard-api`)
88+- Migrated from `url` crate to `fluent_uri` for validated URL/URI types
99+- All `Url` types are now `Uri` from `fluent_uri`
1010+- Affects any code that constructs, passes, or pattern-matches on endpoint URLs
1111+1212+**Re-exported crate paths** (`jacquard-api`, `jacquard-common`)
1313+- Re-exported crates (including non-proc-macro dependencies of the generated API crate) are now centralized into a distinct module
1414+- Import paths for re-exported types have changed as a result
1515+1616+### Added
1717+1818+**`no_std` groundwork** (`jacquard-common`, `jacquard-api`)
1919+- Initial steps toward `no_std` support for core types
2020+- `jacquard-api` gains feature gating for `std`/`no_std` usage
2121+2222+**Datetime improvements** (`jacquard-common`)
2323+- [PR from @blyoom.dev](https://tangled.org/nonbinary.computer/jacquard/pulls/6/round/0) exposing timestamps directly on `Datetime` type
2424+- Naming aligned with `chrono` conventions
2525+2626+**Handle normalization** (`jacquard-common`)
2727+- Handles are now lowercase-normalized on construction
2828+2929+**Embedded PDS primitives** (`jacquard-repo`)
3030+- Initial lazy disk-spilling collection types for embedded PDS use cases
3131+- Repo firehose types now use generated API types instead of hand-written equivalents
3232+3333+**Lexicon codegen improvements** (`jacquard-lexicon`, `jacquard-api`)
3434+- `knownValues` generation now aligned with AT Protocol spec and triggers more frequently
3535+- Improved feature dependency tracking for API crate features
3636+3737+### Fixed
3838+3939+**Identity resolution** (`jacquard-identity`)
4040+- [PR from @alephcubed.com](https://tangled.org/nonbinary.computer/jacquard/pulls/7/round/0) fixing `DidDocument::handles()` always failing when parsed from `MiniDoc`
4141+4242+**Error handling** (`jacquard-common`, `jacquard`, `jacquard-oauth`)
4343+- Big error quality-of-life pass with richer, more actionable diagnostics
4444+- More resilient error parsing for auth errors
4545+- Better lexicon parsing error messages
4646+4747+**WASM** (`jacquard-common`)
4848+- Fixed WASM CI smoke test compilation
4949+5050+### Changed
5151+5252+**Lexicons** (`jacquard-api`)
5353+- Large batch of lexicon schema updates with manual cleanup
5454+355## [0.9.6] - 2025-12-19
456557### Changed
···759**Logging** (`jacquard`, `jacquard-axum`)
860- [PR from @nekomimi.pet](https://tangled.org/nonbinary.computer/jacquard/pulls/5) cleaning up more debug logs, and adding tracing feature gate to jacquard-axum
9611010-## Fixed
6262+### Fixed
11631264**Repo commit signatures** (`jacquard-repo`)
1365- commit signatures generated by `jacquard-repo` should now be consistent with other implementations
···2222 - All the building blocks of the convenient abstractions are available
2323 - Use as much or as little from the crates as you need
24242525-## 0.9.X Release Highlights:
2626-2727-**`#[derive(LexiconSchema)]` + `#[lexicon_union]` macros**
2828-- Automatic schema generation for custom lexicons from Rust structs
2929-- Supports all lexicon constraints via attributes (max_length, max_graphemes, min/max, etc.)
3030-- Generates `LexiconDoc` at compile time for runtime validation
3131-3232-**Runtime lexicon data validation**
3333-- Validation of structural and/or value contraints of data against a lexicon
3434-- caching for value validations
3535-- LexiconSchema trait generated implementations for runtime validation
3636-- detailed validation error results
3737-3838-**Lexicon resolver**
3939-- Fetch lexicons at runtime for addition to schema registry
4040-4141-**Query and path DSLs for `Data` and `RawData` value types**
4242-- Pattern-based querying of nested `Data` structures
4343-- `data.query(pattern)` with expressive syntax:
4444- - `field.nested` - exact path navigation
4545- - `[..]` - wildcard over collections (array elements or object values)
4646- - `field..nested` - scoped recursion (find nested within field, expect one)
4747- - `...field` - global recursion (find all occurrences anywhere)
4848-- `get_at_path()` for simple path-based field access on `Data` and `RawData`
4949-- Path syntax: `embed.images[0].alt` for navigating nested structures
5050-- `type_discriminator()` helper methods for AT Protocol union discrimination
5151-- Collection helper methods: `get()`, `contains_key()`, `len()`, `is_empty()`, `iter()`, `keys()`, `values()`
5252-- Index trait implemented: `obj["key"]` and `arr[0]`
5353-5454-**Caching in identity/lexicon resolver**
5555-- Basic LRU in-memory cache implementation using `mini-moka`
5656-- Reduces number of network requests for certain operations
5757-- Works on both native and WebAssembly via vendored patched version of mini-moka
5858-5959-6060-**XRPC client improvements**
6161-- `set_options()` and `set_endpoint()` methods on `XrpcClient` trait
6262-- Default no-op implementations for stateless clients
6363-- Enables runtime reconfiguration of stateful clients
6464-- Better support for custom endpoint and option overrides
6565-- Fixed bug where setting a custom 'Content-Type' header wouldn't be respected
6666-6767-**Major generated API compilation time improvements**
6868-- Generated code output now includes a typestate builder implementation, similar to the `bon` crate
6969-- Moves the substantial `syn` tax of generating the builders to code generation time, not compile time.
7070-7171-**New `jacquard-lexgen` crate**
7272-- Moves binaries out of jacquard-lexicon to reduce size further
7373-- Flake app for `lex-fetch`
74257526## Example
7627···13485If 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.
1358613687> [!WARNING]
137137-> A lot of the streaming code is still pretty experimental. The examples work, though.\
138138-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`](https://docs.rs/n0-future/latest/n0_future/index.html) to work with them, that is what Jacquard uses internally as much as possible.\
139139->I would also note the same for the repository crate until I've had more third parties test it.
8888+> 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.
1408914190### Changelog
1429114392[CHANGELOG.md](./CHANGELOG.md)
14493145145-<!--### Testimonials
9494+#### 0.10 Release Highlights:
9595+9696+**URL type migration**
9797+- Migrated from `url` crate to `fluent_uri` for validated URL/URI types
9898+- All `Url` types are now `Uri` from `fluent_uri`
9999+- Affects any code that constructs, passes, or pattern-matches on endpoint URLs
100100+101101+**Re-exported crate paths**
102102+- Re-exported crates (including non-proc-macro dependencies of the generated API crate) are now centralized into a distinct module
103103+- Import paths for re-exported types have changed
104104+105105+**`no_std` groundwork**
106106+- Initial work toward allowing jacquard to function on platforms without access to the standard library.
107107+- `std` usage is now feature-gated. the library currently *does not compile* without `std` due to some remaining dependencies.
108108+109109+### Testimonials
146110147111- ["the most straightforward interface to atproto I've encountered so far."](https://bsky.app/profile/offline.mountainherder.xyz/post/3m3xwewzs3k2v) - @offline.mountainherder.xyz
148148-149149-- "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)-->
112112+- "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)
150113151114### Projects using Jacquard
152115153116- [skywatch-phash-rs](https://tangled.org/skywatch.blue/skywatch-phash-rs)
154154-- [Weaver](https://alpha.weaver.sh/) - [tangled repository](https://tangled.org/nonbinary.computer/weaver)
155155-- [wisp.place CLI tool](https://docs.wisp.place/cli/)
117117+- [Weaver](https://weaver.sh/) - [tangled repository](https://tangled.org/nonbinary.computer/weaver)
118118+- [wisp.place CLI tool](https://docs.wisp.place/cli/) - formerly
156119- [PDS MOOver](https://pdsmoover.com/) - [tangled repository](https://tangled.org/baileytownsend.dev/pds-moover)
157120158121## Component crates
···188151```
189152190153There'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.
154154+155155+191156192157[](./LICENSE)
···227227pub mod error;
228228pub mod http_client;
229229pub mod macros;
230230+pub mod opt_serde_bytes_helper;
231231+pub mod serde_bytes_helper;
230232#[cfg(feature = "service-auth")]
231233pub mod service_auth;
232234pub mod session;
235235+#[cfg(feature = "streaming")]
236236+pub mod stream;
233237/// Compile-time TLD lookup for disambiguating handles from NSIDs.
234238pub(crate) mod tld;
235239/// Baseline fundamental AT Protocol data types.
236240pub mod types;
237237-// XRPC protocol types and traits
238238-pub mod opt_serde_bytes_helper;
239239-pub mod serde_bytes_helper;
240240-#[cfg(feature = "streaming")]
241241-pub mod stream;
242241pub mod xrpc;
243242244243#[cfg(feature = "streaming")]
···289288 let value = T::deserialize(deserializer)?;
290289 Ok(value.into_static())
291290}
292292-293293-#[cfg(test)]
294294-mod tests {
295295- use crate::deps::bytes;
296296- use crate::deps::chrono;
297297- use crate::deps::smol_str::SmolStr;
298298-299299- #[test]
300300- fn deps_smol_str() {
301301- let s = SmolStr::new_static("test");
302302- assert_eq!(s, "test");
303303- }
304304-305305- #[test]
306306- fn deps_bytes() {
307307- let _x = bytes::Bytes::from_static(b"hello");
308308- }
309309-310310- #[test]
311311- fn deps_chrono() {
312312- let _now = chrono::Utc::now();
313313- }
314314-}
+1-1
crates/jacquard-common/src/types/value.rs
···11771177/// A single match from a query operation
11781178#[derive(Debug, Clone, PartialEq)]
11791179pub struct QueryMatch<'s> {
11801180- /// Path where this value was found (e.g., "actors[0].handle")
11801180+ /// Path where this value was found (e.g., "actors\[0\].handle")
11811181 pub path: SmolStr,
11821182 /// The value (None if field was missing during wildcard iteration)
11831183 pub value: Option<&'s Data<'s>>,
+1-1
crates/jacquard-common/src/xrpc.rs
···5858/// Normalize a base URI by removing trailing slashes.
5959///
6060/// This is useful for XRPC clients where the base URI might be provided with
6161-/// a trailing slash (e.g., "https://bsky.social/") but needs to be normalized
6161+/// a trailing slash (e.g., "<https://bsky.social/>") but needs to be normalized
6262/// for consistent path building. Since trimming a trailing slash from a valid URI
6363/// always yields a valid URI, the result is guaranteed to be valid.
6464pub fn normalize_base_uri(uri: Uri<String>) -> Uri<String> {
+1-1
crates/jacquard-common/src/xrpc/subscription.rs
···720720/// This exists primarily for server-side frameworks (like Axum) to extract
721721/// typed subscription parameters without lifetime issues.
722722pub trait SubscriptionEndpoint {
723723- /// Fully-qualified path ('/xrpc/[nsid]') where this subscription endpoint lives
723723+ /// Fully-qualified path ('/xrpc/{nsid}') where this subscription endpoint lives
724724 const PATH: &'static str;
725725726726 /// Message encoding (JSON or DAG-CBOR)
···11//! State module generation for builders
22//!
33-//! Generates the state trait, Empty state, and SetX<S> transition types
33+//! Generates the state trait, Empty state, and `SetX<S>` transition types
44//! that enable type-safe builder patterns.
5566use std::collections::HashSet;
···262262 Ok(None)
263263}
264264265265-/// Extract T from Option<T>, return (type, is_required)
265265+/// Extract T from `Option<T>`, return (type, is_required)
266266pub fn extract_option_inner(ty: &syn::Type) -> (&syn::Type, bool) {
267267 if let syn::Type::Path(type_path) = ty {
268268 if let Some(segment) = type_path.path.segments.last() {
···278278 (ty, true)
279279}
280280281281-/// Check if type has #[open_union] attribute
281281+/// Check if type has `#[open_union]` attribute
282282pub fn has_open_union_attr(attrs: &[Attribute]) -> bool {
283283 attrs.iter().any(|attr| attr.path().is_ident("open_union"))
284284}
···4646//!
4747//! See [`atproto`] module for AT Protocol-specific metadata helpers.
48484949+#![warn(missing_docs)]
4950pub mod atproto;
5051pub mod authstore;
5152pub mod client;
+129-32
crates/jacquard-oauth/src/loopback.rs
···11+//!
22+//! Helpers for the local loopback server method of atproto OAuth.
33+//!
44+//! `OAuthClient::login_with_local_server()` is the nice helper. Here is where
55+//! it and its components live. Below is what it does, so you can have more
66+//! granular control without having to make your own loopback server.
77+//!
88+//! ```ignore
99+//! let input = "your_handle_here";
1010+//! let cfg = LoopbackConfig::default();
1111+//! let opts = AuthorizeOptions::default();
1212+//! let port = match cfg.port {
1313+//! LoopbackPort::Fixed(p) => p,
1414+//! LoopbackPort::Ephemeral => 0,
1515+//! };
1616+//! // TODO: fix this to it also accepts ipv6 and properly finds a free port
1717+//! let bind_addr: SocketAddr = format!("0.0.0.0:{}", port)
1818+//! .parse()
1919+//! .expect("invalid loopback host/port");
2020+//! let oauth = OAuthClient::with_default_config(FileAuthStore::new(&args.store));
2121+//!
2222+//! let (local_addr, handle) = one_shot_server(bind_addr);
2323+//! println!("Listening on {}", local_addr);
2424+//!
2525+//! let client_data = oauth.build_localhost_client_data(&cfg, &opts, local_addr);
2626+//! // Build client using store and resolver
2727+//! let flow_client = OAuthClient::new_with_shared(
2828+//! self.registry.store.clone(),
2929+//! self.client.clone(),
3030+//! client_data,
3131+//! );
3232+//!
3333+//! // Start auth and get authorization URL
3434+//! let auth_url = flow_client.start_auth(input.as_ref(), opts).await?;
3535+//! // Print URL for copy/paste
3636+//! println!("To authenticate with your PDS, visit:\n{}\n", auth_url);
3737+//! // Optionally open browser
3838+//! if cfg.open_browser {
3939+//! let _ = try_open_in_browser(&auth_url);
4040+//! }
4141+//!
4242+//! handle_localhost_callback(handle, &flow_client, &cfg).await
4343+//! ```
4444+//!
4545+//!
146#![cfg(feature = "loopback")]
22-347use crate::{
448 atproto::AtprotoClientMetadata,
549 authstore::ClientAuthStore,
···4185}
42864387#[cfg(feature = "browser-open")]
4444-fn try_open_in_browser(url: &str) -> bool {
8888+pub fn try_open_in_browser(url: &str) -> bool {
4589 webbrowser::open(url).is_ok()
4690}
4791#[cfg(not(feature = "browser-open"))]
4848-fn try_open_in_browser(_url: &str) -> bool {
9292+pub fn try_open_in_browser(_url: &str) -> bool {
4993 false
5094}
51955252-pub fn create_callback_router(
9696+fn create_callback_router(
5397 request: &rouille::Request,
5498 tx: mpsc::Sender<CallbackParams>,
5599) -> rouille::Response {
···70114 )
71115}
721167373-struct CallbackHandle {
117117+pub struct CallbackHandle {
74118 #[allow(dead_code)]
75119 server_handle: std::thread::JoinHandle<()>,
76120 server_stop: std::sync::mpsc::Sender<()>,
77121 callback_rx: mpsc::Receiver<CallbackParams<'static>>,
78122}
791238080-fn one_shot_server(addr: SocketAddr) -> (SocketAddr, CallbackHandle) {
124124+/// One-shot OAuth callback server.
125125+///
126126+/// Starts an ephemeral in-process web server that listens for the OAuth
127127+/// callback redirect. Returns the server address and a [`CallbackHandle`]
128128+/// that can be used to wait for the callback and stop the server.
129129+///
130130+/// Use in combination with [`handle_localhost_callback`] to handle the
131131+/// callback for the localhost loopback server.
132132+pub fn one_shot_server(addr: SocketAddr) -> (SocketAddr, CallbackHandle) {
81133 let (tx, callback_rx) = mpsc::channel(5);
82134 let server = Server::new(addr, move |request| {
83135 create_callback_router(request, tx.clone())
···92144 (addr, handle)
93145}
94146147147+/// Handles the OAuth callback for the localhost loopback server.
148148+///
149149+/// Returns a session if the callback succeeds within the configured timeout
150150+/// and shuts down the server.
151151+pub async fn handle_localhost_callback<T, S>(
152152+ handle: CallbackHandle,
153153+ flow_client: &super::client::OAuthClient<T, S>,
154154+ cfg: &LoopbackConfig,
155155+) -> crate::error::Result<super::client::OAuthSession<T, S>>
156156+where
157157+ T: OAuthResolver + DpopExt + Send + Sync + 'static,
158158+ S: ClientAuthStore + Send + Sync + 'static,
159159+{
160160+ // Await callback or timeout
161161+ let mut callback_rx = handle.callback_rx;
162162+ let cb = tokio::time::timeout(
163163+ std::time::Duration::from_millis(cfg.timeout_ms),
164164+ callback_rx.recv(),
165165+ )
166166+ .await;
167167+ // trigger shutdown
168168+ let _ = handle.server_stop.send(());
169169+ if let Ok(Some(cb)) = cb {
170170+ // Handle callback and create a session
171171+ Ok(flow_client.callback(cb).await?)
172172+ } else {
173173+ Err(OAuthError::Callback(CallbackError::Timeout))
174174+ }
175175+}
176176+95177impl<T, S> OAuthClient<T, S>
96178where
97179 T: OAuthResolver + DpopExt + Send + Sync + 'static,
98180 S: ClientAuthStore + Send + Sync + 'static,
99181{
100182 /// Drive the full OAuth flow using a local loopback server.
183183+ ///
184184+ /// This uses localhost OAuth and an ephemeral in-process web server to
185185+ /// handle the OAuth callback redirect. It has a bunch of nice friendly
186186+ /// defaults to help you get started and will basically drive the *entire*
187187+ /// callback flow itself.
188188+ ///
189189+ /// Best used for development and for small CLI applications that don't
190190+ /// require long session lengths. For long-running unattended sessions,
191191+ /// app passwords (via CredentialSession in the jacquard crate) remain
192192+ /// the best option. For more complex OAuth, or if you want more control
193193+ /// over the process, use the other methods on OAuthClient.
194194+ ///
195195+ /// 'input' parameter is what you type in the login box (usually, your handle)
196196+ /// for it to look up your PDS and redirect to its authentication interface.
197197+ ///
198198+ /// If the `browser-open` feature is enabled, this will open a web browser
199199+ /// for you to authenticate with your PDS. It will also print the
200200+ /// callback url to the console for you to copy.
101201 pub async fn login_with_local_server(
102202 &self,
103203 input: impl AsRef<str>,
···114214 .expect("invalid loopback host/port");
115215 let (local_addr, handle) = one_shot_server(bind_addr);
116216 println!("Listening on {}", local_addr);
117117- // build redirect uri
118118- let redirect_uri = format!("http://{}:{}/oauth/callback", cfg.host, local_addr.port(),);
119119- let redirect = Uri::parse(redirect_uri).unwrap();
120120-121121- let scopes = if opts.scopes.is_empty() {
122122- Some(self.registry.client_data.config.scopes.clone())
123123- } else {
124124- Some(opts.scopes.clone().into_static())
125125- };
126217127127- let client_data = crate::session::ClientData {
128128- keyset: self.registry.client_data.keyset.clone(),
129129- config: AtprotoClientMetadata::new_localhost(Some(vec![redirect]), scopes),
130130- };
218218+ let client_data = self.build_localhost_client_data(&cfg, &opts, local_addr);
131219 // Build client using store and resolver
132220 let flow_client = OAuthClient::new_with_shared(
133221 self.registry.store.clone(),
···144232 let _ = try_open_in_browser(&auth_url);
145233 }
146234147147- // Await callback or timeout
148148- let mut callback_rx = handle.callback_rx;
149149- let cb = tokio::time::timeout(
150150- std::time::Duration::from_millis(cfg.timeout_ms),
151151- callback_rx.recv(),
152152- )
153153- .await;
154154- // trigger shutdown
155155- let _ = handle.server_stop.send(());
156156- if let Ok(Some(cb)) = cb {
157157- // Handle callback and create a session
158158- Ok(flow_client.callback(cb).await?)
235235+ handle_localhost_callback(handle, &flow_client, &cfg).await
236236+ }
237237+238238+ /// Builds a [`crate::session::ClientData`] for use with the local loopback server method of OAuth.
239239+ pub fn build_localhost_client_data(
240240+ &self,
241241+ cfg: &LoopbackConfig,
242242+ opts: &AuthorizeOptions<'_>,
243243+ local_addr: SocketAddr,
244244+ ) -> crate::session::ClientData<'static> {
245245+ let redirect_uri = format!("http://{}:{}/oauth/callback", cfg.host, local_addr.port(),);
246246+ let redirect = Uri::parse(redirect_uri).unwrap();
247247+248248+ let scopes = if opts.scopes.is_empty() {
249249+ Some(self.registry.client_data.config.scopes.clone())
159250 } else {
160160- Err(OAuthError::Callback(CallbackError::Timeout))
251251+ Some(opts.scopes.clone().into_static())
252252+ };
253253+254254+ crate::session::ClientData {
255255+ keyset: self.registry.client_data.keyset.clone(),
256256+ config: AtprotoClientMetadata::new_localhost(Some(vec![redirect]), scopes),
161257 }
258258+ .into_static()
162259 }
163260}
···120120required-features = ["api_bluesky", "loopback"]
121121122122[dependencies]
123123-jacquard-api = { version = "0.9", path = "../jacquard-api" }
124124-jacquard-common = { version = "0.9", path = "../jacquard-common", features = [
123123+jacquard-api = { version = "0.10", path = "../jacquard-api" }
124124+jacquard-common = { version = "0.10", path = "../jacquard-common", features = [
125125 "reqwest-client",
126126] }
127127-jacquard-oauth = { version = "0.9", path = "../jacquard-oauth" }
128128-jacquard-derive = { version = "0.9", path = "../jacquard-derive", optional = true }
129129-jacquard-identity = { version = "0.9", path = "../jacquard-identity" }
127127+jacquard-oauth = { version = "0.10", path = "../jacquard-oauth" }
128128+jacquard-derive = { version = "0.10", path = "../jacquard-derive", optional = true }
129129+jacquard-identity = { version = "0.10", path = "../jacquard-identity" }
130130131131132132···149149150150151151[target.'cfg(not(target_family = "wasm"))'.dependencies]
152152-jacquard-identity = { version = "0.9", path = "../jacquard-identity", features = ["cache"] }
152152+jacquard-identity = { version = "0.10", path = "../jacquard-identity", features = ["cache"] }
153153reqwest = { workspace = true, features = [
154154 "http2",
155155 "gzip",
+9-3
crates/jacquard/src/client.rs
···11//! XRPC client implementation for AT Protocol
22//!
33//! This module provides HTTP and XRPC client traits along with session management
44-//! for both app-password and OAuth authentication.
44+//! for both app password and OAuth authentication.
55//!
66//! ## Key types
77//!
···1515//! - [`credential_session`] - App-password session implementation
1616//! - [`token`] - Token storage and persistence
1717//! - [`vec_update`] - Trait for fetch-modify-put patterns on array endpoints
1818+//!
1919+//!
2020+//! "Agent" in this context is derived from Bluesky's own library usage of the term.
2121+//! It represents a (persistent) user session, and includes a number of helpful
2222+//! methods which are available via the `AgentSessionExt` extension trait
2323+//! on anything that implements `AgentSession` + `IdentityResolver`.
18241925//pub mod bff_session;
2020-/// App-password session implementation with auto-refresh
2626+/// App password session implementation with auto-refresh
2127pub mod credential_session;
2228/// Agent error type
2329pub mod error;
···801807 }
802808803809 /// Untyped, freeform record fetcher.
804804- /// Hits [https://slingshot.microcosm.blue]
810810+ /// Hits <https://slingshot.microcosm.blue>
805811 fn fetch_record_slingshot(
806812 &self,
807813 uri: &AtUri<'_>,
···22//!
33//! This is an attempt to semi-generalize the Bluesky moderation system. It avoids
44//! depending on their lexicons as much as reasonably possible. This works via a
55-//! trait, [`Labeled`][crate::moderation::Labeled], which represents things that have labels for moderation
55+//! trait, [`Labeled`], which represents things that have labels for moderation
66//! applied to them. This way the moderation application functions can operate
77//! primarily via the trait, and are thus generic over lexicon types, and are
88//! easy to use with your own types.
99//!
1010//! For more complex types which might have labels applied to components,
1111-//! there is the [`Moderateable`][crate::moderation::Moderateable] trait. A mostly complete implementation for
1111+//! there is the [`Moderateable`] trait. A mostly complete implementation for
1212//! `FeedViewPost` is available for reference. The trait method outputs a `Vec`
1313//! of tuples, where the first element is a string tag and the second is the
1414//! moderation decision for the tagged element. This lets application developers
···1616//! mostly match Bluesky behaviour (respecting "!hide", and such) by default.
1717//!
1818//! I've taken the time to go through the generated API bindings and implement
1919-//! the [`Labeled`][crate::moderation::Labeled] trait for a number of types. It's a fairly easy trait to
1919+//! the [`Labeled`] trait for a number of types. It's a fairly easy trait to
2020//! implement, just not really automatable.
2121//!
2222//!
+2-2
crates/lazy-collections/src/io.rs
···137137 ///
138138 /// All bytes read from this source will be appended to the specified buffer
139139 /// `buf`. This function will continuously call [`read()`] to append more data to
140140- /// `buf` until [`read()`] returns either [`Ok(0)`] or an error of
140140+ /// `buf` until [`read()`] returns either \[`Ok(0)`\] or an error of
141141 /// non-[`ErrorKind::Interrupted`] kind.
142142 ///
143143 /// If successful, this function will return the total number of bytes read.
···323323 /// Creates an adaptor which will read at most `limit` bytes from it.
324324 ///
325325 /// This function returns a new instance of `Read` which will read at most
326326- /// `limit` bytes, after which it will always return EOF ([`Ok(0)`]). Any
326326+ /// `limit` bytes, after which it will always return EOF (\[`Ok(0)`\]). Any
327327 /// read errors will not count towards the number of bytes read and future
328328 /// calls to [`read()`](Self::read) may succeed.
329329 fn take(self, limit: u64) -> Take<Self>