···11+# Changelog
22+33+## [0.4.0] - 2025-10-11
44+55+### Breaking Changes
66+77+**Zero-copy deserialization** (`jacquard-common`, `jacquard-api`)
88+- `XrpcRequest` now takes a `'de` lifetime parameter and requires `Deserialize<'de>`
99+- For raw data, `Response::parse_data()` gives validated loosely-typed atproto data, while `Response::parse_raw()` gives the raw values, with minimal validation.
1010+1111+**XRPC module moved** (`jacquard-common`)
1212+- `xrpc.rs` is now top-level instead of under `types`
1313+- Import from `jacquard_common::xrpc::*` not `jacquard_common::types::xrpc::*`
1414+1515+**Response API changes** (`jacquard-common`)
1616+- `XrpcRequest::Output` and `XrpcRequest::Err` are associated types with lifetimes
1717+- Split response and request traits: `XrpcRequest<'de>` for client, `XrpcEndpoint` for server
1818+- Added `XrpcResp` marker trait
1919+2020+**Various traits** (`jacquard`, `jacquard-common`, `jacquard-lexicon`, `jacquard-oauth`)
2121+- Removed #[async_trait] attribute macro usage in favour of `impl Future` return types with manual bounds.
2222+- Boxing imposed by asyc_trait negatively affected borrowing modes in async methods.
2323+- Currently no semver guarantees on API trait bounds, if they need to tighten, they will.
2424+2525+### Added
2626+2727+**New crate: `jacquard-axum`**
2828+- Server-side XRPC handlers for Axum
2929+- `ExtractXrpc<R>` deserializes incoming requests (query params for Query, body for Procedure)
3030+- Automatic error responses
3131+3232+**Lexicon codegen fixes** (`jacquard-lexicon`)
3333+- Union variant collision detection: when multiple namespaces have similar type names, foreign ones get prefixed (e.g., `Images` vs `BskyImages`)
3434+- Token types generate unit structs with `Display` instead of being skipped
3535+- Namespace dependency tracking during union generation
3636+- `generate_cargo_features()` outputs Cargo.toml features with correct deps
3737+- `sanitize_name()` ensures valid Rust identifiers
3838+3939+**Lexicons** (`jacquard-api`)
4040+4141+Added 646 lexicon schemas. Highlights:
4242+4343+Core ATProto:
4444+- `com.atproto.*`
4545+- `com.bad-example.*` for identity resolution
4646+4747+Bluesky:
4848+- `app.bsky.*` bluesky app
4949+- `chat.bsky.*` chat client
5050+- `tools.ozone.*` moderation
5151+5252+Third-party:
5353+- `sh.tangled.*` - git forge
5454+- `sh.weaver.*` - orual's WIP markdown blog platform
5555+- `pub.leaflet.*` - longform publishing
5656+- `net.anisota.*` - gamified and calming take on bluesky
5757+- `network.slices.*` - serverless atproto hosting
5858+- `tools.smokesignal.*` - automation
5959+- `com.whtwnd.*` - markdown blogging
6060+- `place.stream.*` - livestreaming
6161+- `blue.2048.*` - 2048 game
6262+- `community.lexicon.*` - community extensions (bookmarks, calendar, location, payments)
6363+- `my.skylights.*` - media tracking
6464+- `social.psky.*` - social extensions
6565+- `blue.linkat.*` - link boards
6666+6767+Plus 30+ more experimental/community namespaces.
6868+6969+**Value types** (`jacquard-common`)
7070+- `RawData` to `Data` conversion with type inference
7171+- `from_data`, `from_raw_data`, `to_data`, and `to_raw_data` to serialize to and deserialize from the loosely typed value data formats. Particularly useful for second-stage deserialization of type "unknown" fields in lexicons, such as `PostView.record`.
7272+7373+### Changed
7474+7575+- `generate_union()` takes current NSID for dependency tracking
7676+- Generated code uses `sanitize_name()` for identifiers more consistently
7777+- Added derive macro for IntoStatic trait implementation
7878+7979+### Fixed
8080+8181+- Methods to extract the output from an XRPC response now behave well with respect to lifetimes and borrowing.
8282+- Now possible to use jacquard types in places like axum extractors due to lifetime improvements
8383+- Union variants don't collide when multiple namespaces define similar types and another namespace includes them
8484+8585+---
···26062606 if let Some(lib_rs) = lib_rs_path {
26072607 if let Ok(content) = std::fs::read_to_string(lib_rs) {
26082608 for line in content.lines() {
26092609- if let Some(feature) = line.trim()
26092609+ if let Some(feature) = line
26102610+ .trim()
26102611 .strip_prefix("#[cfg(feature = \"")
26112612 .and_then(|s| s.strip_suffix("\")]"))
26122613 {
···2618261926192620 let mut output = String::new();
26202621 writeln!(&mut output, "# Generated namespace features").unwrap();
26212621- writeln!(&mut output, "# Each namespace feature automatically enables its dependencies").unwrap();
2622262226232623 // Convert namespace to feature name (matching module path sanitization)
26242624 let to_feature_name = |ns: &str| {
···26482648 feature_names.sort();
2649264926502650 // Map namespace to feature name for dependency lookup
26512651- let mut ns_to_feature: std::collections::HashMap<&str, String> = std::collections::HashMap::new();
26512651+ let mut ns_to_feature: std::collections::HashMap<&str, String> =
26522652+ std::collections::HashMap::new();
26522653 for ns in &all_namespaces {
26532654 ns_to_feature.insert(ns.as_str(), to_feature_name(ns));
26542655 }