A better Rust ATProto crate

version bump and doc updates, fix to content-type header overrides

Orual d533482a ee0f31db

Changed files
+269 -105
crates
jacquard
jacquard-api
jacquard-axum
jacquard-common
jacquard-derive
jacquard-identity
jacquard-lexgen
jacquard-lexicon
jacquard-oauth
jacquard-repo
+98
CHANGELOG.md
··· 1 1 # Changelog 2 2 3 + ## [0.9.0] - 2025-11-03 4 + 5 + ### Added 6 + 7 + **Runtime schema validation** (`jacquard-lexicon`) 8 + - `SchemaValidator` for validating `Data` values against lexicon schemas 9 + - CID-based validation caching for efficient repeated validation 10 + - `ValidationResult` with structural and constraint error separation 11 + - Comprehensive error types: `StructuralError` (type mismatches, missing fields, union errors) and `ConstraintError` (length, grapheme, numeric bounds) 12 + - `ValidationPath` for precise error location reporting 13 + - Ref cycle detection with configurable max depth 14 + - Support for validating partial/malformed data without full deserialization 15 + 16 + **Value query DSL** (`jacquard-common`) 17 + - Pattern-based querying of nested `Data` structures 18 + - `data.query(pattern)` with expressive syntax: 19 + - `field.nested` - exact path navigation 20 + - `[..]` - wildcard over collections (array elements or object values) 21 + - `field..nested` - scoped recursion (find nested within field, expect one) 22 + - `...field` - global recursion (find all occurrences anywhere) 23 + - `QueryResult` enum with `Single`, `Multiple`, and `None` variants 24 + - `QueryMatch` with path tracking for multiple results 25 + - Iterator support via `.values()`, `.first()`, `.single()`, `.multiple()` 26 + 27 + **Data value enhancements** (`jacquard-common`) 28 + - `get_at_path()` for simple path-based field access on `Data` and `RawData` 29 + - Path syntax: `embed.images[0].alt` for navigating nested structures 30 + - `type_discriminator()` helper methods for AT Protocol union discrimination 31 + - Returns `$type` field value for objects with type discriminators 32 + - Added on `Data`, `Object`, and `RawData` types 33 + - Collection helper methods: `get()`, `contains_key()`, `len()`, `is_empty()`, `iter()`, `keys()`, `values()` 34 + - Index operator support: `obj["key"]` and `arr[0]` 35 + 36 + **Lexicon resolution** (`jacquard-identity`) 37 + - `LexiconResolver` for fetching lexicon schemas from AT Protocol services 38 + - Resolves lexicons from PDS instances and lexicon hosts 39 + - `resolve_lexicon()` fetches and parses lexicon schemas 40 + - `resolve_lexicon_raw()` fetches raw schema JSON 41 + - New example: `resolve_lexicon.rs` 42 + 43 + **Identity resolver caching** (`jacquard-identity`) 44 + - Optional `cache` feature with configurable in-memory caching 45 + - `JacquardResolver::with_cache()` constructor for cached resolver 46 + - Separate TTLs for handle→DID, DID→doc, and lexicon resolution 47 + 48 + **XRPC client improvements** (`jacquard-common`, `jacquard`, `jacquard-oauth`) 49 + - `set_options()` and `set_endpoint()` methods on `XrpcClient` trait 50 + - Default no-op implementations for stateless clients 51 + - Enables runtime reconfiguration of stateful clients 52 + - Better support for custom endpoint and option overrides 53 + 54 + **Lexicon schema generation from Rust types** (`jacquard-derive`, `jacquard-lexicon`) 55 + - New `#[derive(LexiconSchema)]` macro for generating lexicon schemas from Rust structs 56 + - New `#[lexicon_union]` attribute macro for lexicon union types (tagged enums) 57 + - Automatic schema generation for custom lexicons without writing JSON manually 58 + - Field-level attributes: `ref` for explicit type references, `union` for union fields 59 + - Fragment support for multi-def lexicons via `fragment = "..."` attribute 60 + - Generates `LexiconDoc` at compile time for runtime validation 61 + - Enables type-safe custom lexicon development 62 + 63 + **Lexicon codegen improvements** (`jacquard-lexicon`, `jacquard-api`) 64 + - Vendored in an implementation of the typed builder pattern from `bon` to **substantially** improve compile times 65 + - Feature-gated heavy code generation features so `jacquard-api` and other consumers of the validation capabilities don't pay the `syn` tax as badly. 66 + - LexiconSchema trait generated implementations for runtime validation 67 + 68 + **Session store improvements** (`jacquard`) 69 + - Improved trait bounds for `SessionStore` implementations 70 + - Better ergonomics for credential session types 71 + - Memory-based credential session helpers 72 + 73 + **New crate: `jacquard-lexgen`** 74 + - Lexicon code generation tooling extracted from `jacquard-lexicon` 75 + - Separates binary/CLI tools from library code 76 + - Contains lexicon fetching and code generation binaries 77 + - `jacquard-lexicon` remains as pure library for lexicon parsing, code generation, and validation 78 + 79 + **Examples** 80 + - `app_password_create_post.rs`: App password authentication example 81 + 82 + ### Changed 83 + 84 + **Feature gating** (`jacquard-identity`) 85 + - Better conditional compilation for platform-specific features 86 + - Improved WASM target support 87 + 88 + **Dependency updates** 89 + - Updated to latest lexicons from atproto/bluesky 90 + - Added workspace dependencies: sha2, multihash, dashmap, cid 91 + - Various minor dependency version updates 92 + 93 + ### Fixed 94 + 95 + **File auth store** (`jacquard`) 96 + - Fixed serialization/deserialization bugs in `FileAuthStore` implementation 97 + 98 + **Packaging** (`jacquard-lexgen`) 99 + - Added Nix flake apps for lexicon tools 100 + 3 101 ## [0.8.0] - 2025-10-23 4 102 5 103 ### Breaking Changes
+35 -44
Cargo.lock
··· 1796 1796 ] 1797 1797 1798 1798 [[package]] 1799 - name = "home" 1800 - version = "0.5.12" 1801 - source = "registry+https://github.com/rust-lang/crates.io-index" 1802 - checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" 1803 - dependencies = [ 1804 - "windows-sys 0.61.2", 1805 - ] 1806 - 1807 - [[package]] 1808 1799 name = "html5ever" 1809 1800 version = "0.27.0" 1810 1801 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2243 2234 2244 2235 [[package]] 2245 2236 name = "jacquard" 2246 - version = "0.8.0" 2237 + version = "0.9.0" 2247 2238 dependencies = [ 2248 2239 "bytes", 2249 2240 "clap", ··· 2276 2267 2277 2268 [[package]] 2278 2269 name = "jacquard-api" 2279 - version = "0.8.0" 2270 + version = "0.9.0" 2280 2271 dependencies = [ 2281 2272 "bon", 2282 2273 "bytes", ··· 2293 2284 2294 2285 [[package]] 2295 2286 name = "jacquard-axum" 2296 - version = "0.8.0" 2287 + version = "0.9.0" 2297 2288 dependencies = [ 2298 2289 "axum", 2299 2290 "axum-macros", ··· 2323 2314 2324 2315 [[package]] 2325 2316 name = "jacquard-common" 2326 - version = "0.8.0" 2317 + version = "0.9.0" 2327 2318 dependencies = [ 2328 2319 "base64 0.22.1", 2329 2320 "bon", ··· 2367 2358 2368 2359 [[package]] 2369 2360 name = "jacquard-derive" 2370 - version = "0.8.0" 2361 + version = "0.9.0" 2371 2362 dependencies = [ 2372 2363 "heck 0.5.0", 2373 2364 "inventory", ··· 2383 2374 2384 2375 [[package]] 2385 2376 name = "jacquard-identity" 2386 - version = "0.8.0" 2377 + version = "0.9.0" 2387 2378 dependencies = [ 2388 2379 "bon", 2389 2380 "bytes", ··· 2410 2401 2411 2402 [[package]] 2412 2403 name = "jacquard-lexgen" 2413 - version = "0.8.0" 2404 + version = "0.9.0" 2414 2405 dependencies = [ 2415 2406 "clap", 2416 2407 "clap_complete", ··· 2437 2428 2438 2429 [[package]] 2439 2430 name = "jacquard-lexicon" 2440 - version = "0.8.0" 2431 + version = "0.9.0" 2441 2432 dependencies = [ 2442 2433 "bytes", 2443 2434 "cid", ··· 2465 2456 2466 2457 [[package]] 2467 2458 name = "jacquard-oauth" 2468 - version = "0.8.0" 2459 + version = "0.9.0" 2469 2460 dependencies = [ 2470 2461 "base64 0.22.1", 2471 2462 "bytes", ··· 2498 2489 2499 2490 [[package]] 2500 2491 name = "jacquard-repo" 2501 - version = "0.8.0" 2492 + version = "0.9.0" 2502 2493 dependencies = [ 2503 2494 "anyhow", 2504 2495 "bytes", ··· 2776 2767 version = "0.1.1" 2777 2768 source = "registry+https://github.com/rust-lang/crates.io-index" 2778 2769 checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" 2779 - 2780 - [[package]] 2781 - name = "malloc_buf" 2782 - version = "0.0.6" 2783 - source = "registry+https://github.com/rust-lang/crates.io-index" 2784 - checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" 2785 - dependencies = [ 2786 - "libc", 2787 - ] 2788 2770 2789 2771 [[package]] 2790 2772 name = "markup5ever" ··· 3197 3179 ] 3198 3180 3199 3181 [[package]] 3200 - name = "objc" 3201 - version = "0.2.7" 3182 + name = "objc2" 3183 + version = "0.6.3" 3202 3184 source = "registry+https://github.com/rust-lang/crates.io-index" 3203 - checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" 3185 + checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" 3204 3186 dependencies = [ 3205 - "malloc_buf", 3187 + "objc2-encode", 3188 + ] 3189 + 3190 + [[package]] 3191 + name = "objc2-encode" 3192 + version = "4.1.0" 3193 + source = "registry+https://github.com/rust-lang/crates.io-index" 3194 + checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" 3195 + 3196 + [[package]] 3197 + name = "objc2-foundation" 3198 + version = "0.3.2" 3199 + source = "registry+https://github.com/rust-lang/crates.io-index" 3200 + checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" 3201 + dependencies = [ 3202 + "bitflags", 3203 + "objc2", 3206 3204 ] 3207 3205 3208 3206 [[package]] ··· 3782 3780 "rayon", 3783 3781 "rgb", 3784 3782 ] 3785 - 3786 - [[package]] 3787 - name = "raw-window-handle" 3788 - version = "0.5.2" 3789 - source = "registry+https://github.com/rust-lang/crates.io-index" 3790 - checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" 3791 3783 3792 3784 [[package]] 3793 3785 name = "rayon" ··· 5553 5545 5554 5546 [[package]] 5555 5547 name = "webbrowser" 5556 - version = "0.8.15" 5548 + version = "1.0.6" 5557 5549 source = "registry+https://github.com/rust-lang/crates.io-index" 5558 - checksum = "db67ae75a9405634f5882791678772c94ff5f16a66535aae186e26aa0841fc8b" 5550 + checksum = "00f1243ef785213e3a32fa0396093424a3a6ea566f9948497e5a2309261a4c97" 5559 5551 dependencies = [ 5560 - "core-foundation 0.9.4", 5561 - "home", 5552 + "core-foundation 0.10.1", 5562 5553 "jni", 5563 5554 "log", 5564 5555 "ndk-context", 5565 - "objc", 5566 - "raw-window-handle", 5556 + "objc2", 5557 + "objc2-foundation", 5567 5558 "url", 5568 5559 "web-sys", 5569 5560 ]
+1 -1
Cargo.toml
··· 5 5 6 6 [workspace.package] 7 7 edition = "2024" 8 - version = "0.8.0" 8 + version = "0.9.0" 9 9 authors = ["Orual <orual@nonbinary.computer>"] 10 10 #repository = "https://github.com/rsform/jacquard" 11 11 repository = "https://tangled.org/@nonbinary.computer/jacquard"
+47 -8
README.md
··· 22 22 - All the building blocks of the convenient abstractions are available 23 23 - Use as much or as little from the crates as you need 24 24 25 - ## 0.8.0 Release Highlights: 25 + ## 0.9.0 Release Highlights: 26 + 27 + **`#[derive(LexiconSchema)]` + `#[lexicon_union]` macros** 28 + - Automatic schema generation for custom lexicons from Rust structs 29 + - Supports all lexicon constraints via attributes (max_length, max_graphemes, min/max, etc.) 30 + - Generates `LexiconDoc` at compile time for runtime validation 31 + 32 + **Runtime lexicon data validation** 33 + - Validation of structural and/or value contraints of data against a lexicon 34 + - caching for value validations 35 + - LexiconSchema trait generated implementations for runtime validation 36 + - detailed validation error results 37 + 38 + **Lexicon resolver** 39 + - Fetch lexicons at runtime for addition to schema registry 40 + 41 + **Query and path DSLs for `Data` and `RawData` value types** 42 + - Pattern-based querying of nested `Data` structures 43 + - `data.query(pattern)` with expressive syntax: 44 + - `field.nested` - exact path navigation 45 + - `[..]` - wildcard over collections (array elements or object values) 46 + - `field..nested` - scoped recursion (find nested within field, expect one) 47 + - `...field` - global recursion (find all occurrences anywhere) 48 + - `get_at_path()` for simple path-based field access on `Data` and `RawData` 49 + - Path syntax: `embed.images[0].alt` for navigating nested structures 50 + - `type_discriminator()` helper methods for AT Protocol union discrimination 51 + - Collection helper methods: `get()`, `contains_key()`, `len()`, `is_empty()`, `iter()`, `keys()`, `values()` 52 + - Index trait implemented: `obj["key"]` and `arr[0]` 53 + 54 + **Caching in identity/lexicon resolver** 55 + - Basic LRU in-memory cache implementation using `mini-moka` 56 + - Reduces number of network requests for certain operations 57 + - Works on both native and WebAssembly 58 + 59 + **XRPC client improvements** 60 + - `set_options()` and `set_endpoint()` methods on `XrpcClient` trait 61 + - Default no-op implementations for stateless clients 62 + - Enables runtime reconfiguration of stateful clients 63 + - Better support for custom endpoint and option overrides 64 + - Fixed bug where setting a custom 'Content-Type' header wouldn't be respected 65 + 66 + **Major generated API compilation time improvements** 67 + - Generated code output now includes a typestate builder implementation, similar to the `bon` crate 68 + - Moves the substantial `syn` tax of generating the builders to code generation time, not compile time. 26 69 27 - **`jacquard-repo` crate** 28 - - Complete implementation of the atproto repository spec 29 - - [Sync v1.1](https://github.com/bluesky-social/proposals/blob/main/0006-sync-iteration/README.md#commit-validation-mst-operation-inversion) commit event support (both proof production and verification), well-validated in testing 30 - - repository CAR file read/write support 31 - - CAR file write order compatible with streaming mode from the [sync iteration proposal](https://github.com/bluesky-social/proposals/blob/main/0006-sync-iteration/README.md#streaming-car-processing) 32 - - Big rewrite of all the errors in the crate, improvements to context and overall structure 33 - - 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). 70 + **New `jacquard-lexgen` crate** 71 + - Moves binaries out of jacquard-lexicon to reduce size further 72 + - Flake app for `lex-fetch` 34 73 35 74 ## Example 36 75
+4 -4
crates/jacquard-api/Cargo.toml
··· 2 2 name = "jacquard-api" 3 3 description = "Generated AT Protocol API bindings for Jacquard" 4 4 edition.workspace = true 5 - version = "0.8.0" 5 + version = "0.9.0" 6 6 authors.workspace = true 7 7 repository.workspace = true 8 8 keywords.workspace = true ··· 17 17 [dependencies] 18 18 bon.workspace = true 19 19 bytes = { workspace = true, features = ["serde"] } 20 - jacquard-common = { version = "0.8", path = "../jacquard-common" } 21 - jacquard-derive = { version = "0.8", path = "../jacquard-derive" } 22 - jacquard-lexicon = { version = "0.8", path = "../jacquard-lexicon", default-features = false } 20 + jacquard-common = { version = "0.9", path = "../jacquard-common" } 21 + jacquard-derive = { version = "0.9", path = "../jacquard-derive" } 22 + jacquard-lexicon = { version = "0.9", path = "../jacquard-lexicon", default-features = false } 23 23 miette.workspace = true 24 24 rustversion = "1.0" 25 25 serde.workspace = true
+5 -5
crates/jacquard-axum/Cargo.toml
··· 1 1 [package] 2 2 name = "jacquard-axum" 3 3 edition.workspace = true 4 - version = "0.8.0" 4 + version = "0.9.0" 5 5 authors.workspace = true 6 6 repository.workspace = true 7 7 keywords.workspace = true ··· 22 22 [dependencies] 23 23 axum = "0.8.6" 24 24 bytes.workspace = true 25 - jacquard = { version = "0.8", path = "../jacquard", default-features = false, features = ["api"] } 26 - jacquard-common = { version = "0.8", path = "../jacquard-common", features = ["reqwest-client"] } 27 - jacquard-derive = { version = "0.8", path = "../jacquard-derive" } 28 - jacquard-identity = { version = "0.8", path = "../jacquard-identity", optional = true } 25 + jacquard = { version = "0.9", path = "../jacquard", default-features = false, features = ["api"] } 26 + jacquard-common = { version = "0.9", path = "../jacquard-common", features = ["reqwest-client"] } 27 + jacquard-derive = { version = "0.9", path = "../jacquard-derive" } 28 + jacquard-identity = { version = "0.9", path = "../jacquard-identity", optional = true } 29 29 miette.workspace = true 30 30 multibase = { version = "0.9.1", optional = true } 31 31 serde.workspace = true
+1 -1
crates/jacquard-common/Cargo.toml
··· 2 2 name = "jacquard-common" 3 3 description = "Core AT Protocol types and utilities for Jacquard" 4 4 edition.workspace = true 5 - version = "0.8.0" 5 + version = "0.9.0" 6 6 authors.workspace = true 7 7 repository.workspace = true 8 8 keywords.workspace = true
+11 -1
crates/jacquard-common/src/xrpc.rs
··· 542 542 path.push_str("/xrpc/"); 543 543 path.push_str(<R as XrpcRequest>::NSID); 544 544 url.set_path(&path); 545 + // Check if extra_headers already contains Content-Type 545 546 546 547 if let XrpcMethod::Query = <R as XrpcRequest>::METHOD { 547 548 let qs = serde_html_form::to_string(&req).map_err(|e| { ··· 561 562 562 563 let mut builder = Request::builder().method(method).uri(url.as_str()); 563 564 565 + let has_content_type = opts 566 + .extra_headers 567 + .iter() 568 + .any(|(name, _)| name == CONTENT_TYPE); 569 + 564 570 if let XrpcMethod::Procedure(encoding) = <R as XrpcRequest>::METHOD { 565 - builder = builder.header(Header::ContentType, encoding); 571 + // Only set default Content-Type if not provided in extra_headers 572 + if !has_content_type { 573 + builder = builder.header(Header::ContentType, encoding); 574 + } 566 575 } 567 576 let output_encoding = <R::Response as XrpcResp>::ENCODING; 568 577 builder = builder.header(http::header::ACCEPT, output_encoding); ··· 1118 1127 builder = builder.header(Header::AtprotoAcceptLabelers, joined); 1119 1128 } 1120 1129 } 1130 + 1121 1131 for (name, value) in &self.opts.extra_headers { 1122 1132 builder = builder.header(name, value); 1123 1133 }
+3 -3
crates/jacquard-derive/Cargo.toml
··· 16 16 17 17 [dependencies] 18 18 heck.workspace = true 19 - jacquard-lexicon = { version = "0.8", path = "../jacquard-lexicon", features = ["codegen"] } 19 + jacquard-lexicon = { version = "0.9", path = "../jacquard-lexicon", features = ["codegen"] } 20 20 proc-macro2.workspace = true 21 21 quote.workspace = true 22 22 syn.workspace = true 23 23 24 24 [dev-dependencies] 25 25 inventory = "0.3" 26 - jacquard-common = { version = "0.8", path = "../jacquard-common" } 27 - jacquard-lexicon = { version = "0.8", path = "../jacquard-lexicon" } 26 + jacquard-common = { version = "0.9", path = "../jacquard-common" } 27 + jacquard-lexicon = { version = "0.9", path = "../jacquard-lexicon" } 28 28 serde.workspace = true 29 29 serde_json.workspace = true 30 30 unicode-segmentation = "1.12"
+39 -13
crates/jacquard-derive/src/lib.rs
··· 84 84 //! 85 85 //! **Type-level attributes** (`#[lexicon(...)]`): 86 86 //! - `nsid = "..."`: The lexicon NSID (required) 87 - //! - `record`: Mark as a record type (requires `key`) 87 + //! - `record`: Mark as a record type 88 + //! - `key = "..."`: Record key type (`"tid"`, `"literal:self"`, or custom) - optional 88 89 //! - `object`: Mark as an object type (default if neither record/procedure/query) 89 - //! - `key = "..."`: Record key type (`"tid"`, `"literal:self"`, or custom) 90 - //! - `fragment = "..."`: Fragment name for non-main defs 90 + //! - `fragment = "..."`: Fragment name for non-main defs (e.g., `fragment = "textSlice"`) 91 91 //! 92 92 //! **Field-level attributes** (`#[lexicon(...)]`): 93 93 //! - `max_length = N`: Max byte length for strings 94 94 //! - `max_graphemes = N`: Max grapheme count for strings 95 95 //! - `min_length = N`, `min_graphemes = N`: Minimum constraints 96 96 //! - `minimum = N`, `maximum = N`: Integer range constraints 97 + //! - `max_items = N`: Max array length 98 + //! - `item_max_length = N`, `item_max_graphemes = N`: Constraints on array items 99 + //! - `ref = "..."`: Explicit type ref (e.g., `ref = "com.atproto.repo.strongRef"` or `ref = "#textSlice"`) 100 + //! - `union`: Mark field as union type (use with `#[lexicon_union]` enum) 97 101 //! 98 102 //! **Serde integration**: Respects `#[serde(rename)]`, `#[serde(rename_all)]`, and 99 103 //! `#[serde(skip)]`. Defaults to camelCase for field names (lexicon standard). 100 104 //! 101 - //! **Enums**: Closed unions by default. Add `#[open_union]` for open unions. Variant 102 - //! refs resolved via `#[nsid = "..."]` > `#[serde(rename = "...")]` > fragment inference. 105 + //! **Unions**: Use `#[lexicon_union]` attribute macro, not `#[derive(LexiconSchema)]`. 106 + //! Mark union fields with `#[lexicon(union)]`. 103 107 //! 104 108 //! ```ignore 105 - //! // Record with constraints 109 + //! // Record with constraints and fragments 106 110 //! #[derive(LexiconSchema)] 107 111 //! #[lexicon(nsid = "app.bsky.feed.post", record, key = "tid")] 112 + //! #[serde(rename_all = "camelCase")] 108 113 //! struct Post<'a> { 109 114 //! #[lexicon(max_graphemes = 300, max_length = 3000)] 110 115 //! pub text: CowStr<'a>, 116 + //! 111 117 //! pub created_at: Datetime, // -> "createdAt" (camelCase) 118 + //! 119 + //! #[lexicon(union)] 120 + //! pub embed: Option<PostEmbed<'a>>, 121 + //! 122 + //! #[lexicon(max_items = 8, item_max_length = 640, item_max_graphemes = 64)] 123 + //! pub tags: Option<Vec<CowStr<'a>>>, 124 + //! 125 + //! #[lexicon(ref = "app.bsky.richtext.facet")] 126 + //! pub facets: Option<Vec<CowStr<'a>>>, 112 127 //! } 113 128 //! 114 - //! // Closed union 129 + //! // Fragment (non-main def) 115 130 //! #[derive(LexiconSchema)] 116 - //! #[lexicon(nsid = "app.bsky.feed.defs")] 117 - //! enum FeedViewPref { 118 - //! #[nsid = "app.bsky.feed.defs#feedViewPref"] 119 - //! Feed, 120 - //! #[nsid = "app.bsky.feed.defs#threadViewPref"] 121 - //! Thread, 131 + //! #[lexicon(nsid = "app.bsky.feed.post", fragment = "textSlice")] 132 + //! #[serde(rename_all = "camelCase")] 133 + //! struct TextSlice { 134 + //! #[lexicon(minimum = 0)] 135 + //! pub start: i64, 136 + //! #[lexicon(minimum = 0)] 137 + //! pub end: i64, 138 + //! } 139 + //! 140 + //! // Union (uses lexicon_union, not LexiconSchema) 141 + //! #[lexicon_union] 142 + //! #[serde(tag = "$type")] 143 + //! enum PostEmbed<'a> { 144 + //! #[serde(rename = "app.bsky.embed.images")] 145 + //! Images(CowStr<'a>), 146 + //! #[serde(rename = "app.bsky.embed.video")] 147 + //! Video(CowStr<'a>), 122 148 //! } 123 149 //! ``` 124 150
+4 -4
crates/jacquard-identity/Cargo.toml
··· 1 1 [package] 2 2 name = "jacquard-identity" 3 3 edition.workspace = true 4 - version = "0.8.0" 4 + version = "0.9.0" 5 5 authors.workspace = true 6 6 repository.workspace = true 7 7 keywords.workspace = true ··· 22 22 trait-variant.workspace = true 23 23 bon.workspace = true 24 24 bytes.workspace = true 25 - jacquard-common = { version = "0.8", path = "../jacquard-common", features = ["reqwest-client"] } 26 - jacquard-api = { version = "0.8", path = "../jacquard-api", default-features = false, features = ["minimal"] } 27 - jacquard-lexicon = { version = "0.8", path = "../jacquard-lexicon", default-features = false } 25 + jacquard-common = { version = "0.9", path = "../jacquard-common", features = ["reqwest-client"] } 26 + jacquard-api = { version = "0.9", path = "../jacquard-api", default-features = false, features = ["minimal"] } 27 + jacquard-lexicon = { version = "0.9", path = "../jacquard-lexicon", default-features = false } 28 28 percent-encoding.workspace = true 29 29 reqwest.workspace = true 30 30 url.workspace = true
+2 -2
crates/jacquard-identity/src/lib.rs
··· 122 122 { 123 123 mini_moka::sync::Cache::builder() 124 124 .max_capacity(max_capacity) 125 - .time_to_live(ttl) 125 + .time_to_idle(ttl) 126 126 .build() 127 127 } 128 128 ··· 166 166 Arc::new(Mutex::new( 167 167 mini_moka::unsync::Cache::builder() 168 168 .max_capacity(max_capacity) 169 - .time_to_live(ttl) 169 + .time_to_idle(ttl) 170 170 .build(), 171 171 )) 172 172 }
+5 -5
crates/jacquard-lexgen/Cargo.toml
··· 32 32 clap.workspace = true 33 33 glob = "0.3" 34 34 inventory = "0.3" 35 - jacquard-api = { version = "0.8", path = "../jacquard-api", default-features = false, features = [ "minimal" ] } 36 - jacquard-common = { version = "0.8", features = [ "reqwest-client" ], path = "../jacquard-common" } 37 - jacquard-derive = { version = "0.8", path = "../jacquard-derive" } 38 - jacquard-identity = { version = "0.8", path = "../jacquard-identity", features = ["dns"] } 39 - jacquard-lexicon = { version = "0.8", path = "../jacquard-lexicon" } 35 + jacquard-api = { version = "0.9", path = "../jacquard-api", default-features = false, features = [ "minimal" ] } 36 + jacquard-common = { version = "0.9", features = [ "reqwest-client" ], path = "../jacquard-common" } 37 + jacquard-derive = { version = "0.9", path = "../jacquard-derive" } 38 + jacquard-identity = { version = "0.9", path = "../jacquard-identity", features = ["dns"] } 39 + jacquard-lexicon = { version = "0.9", path = "../jacquard-lexicon" } 40 40 kdl = "6" 41 41 miette = { workspace = true, features = ["fancy"] } 42 42 reqwest = { workspace = true, features = ["json", "http2", "system-proxy", "rustls-tls"] }
+2 -2
crates/jacquard-lexicon/Cargo.toml
··· 20 20 dashmap.workspace = true 21 21 heck = { workspace = true, optional = true } 22 22 inventory = "0.3" 23 - jacquard-common = { version = "0.8", path = "../jacquard-common" } 23 + jacquard-common = { version = "0.9", path = "../jacquard-common" } 24 24 miette = { workspace = true } 25 25 multihash.workspace = true 26 26 prettyplease = { workspace = true, optional = true } ··· 38 38 39 39 [dev-dependencies] 40 40 bytes = { workspace = true } 41 - jacquard-derive = { version = "0.8", path = "../jacquard-derive" } 41 + jacquard-derive = { version = "0.9", path = "../jacquard-derive" } 42 42 tempfile = { version = "3.23.0" }
+4 -4
crates/jacquard-oauth/Cargo.toml
··· 1 1 [package] 2 2 name = "jacquard-oauth" 3 - version = "0.8.0" 3 + version = "0.9.0" 4 4 edition.workspace = true 5 5 description = "AT Protocol OAuth 2.1 core types and helpers for Jacquard" 6 6 authors.workspace = true ··· 21 21 streaming = ["jacquard-common/streaming", "dep:n0-future"] 22 22 23 23 [dependencies] 24 - jacquard-common = { version = "0.8", path = "../jacquard-common", features = ["reqwest-client"] } 25 - jacquard-identity = { version = "0.8", path = "../jacquard-identity" } 24 + jacquard-common = { version = "0.9", path = "../jacquard-common", features = ["reqwest-client"] } 25 + jacquard-identity = { version = "0.9", path = "../jacquard-identity" } 26 26 serde = { workspace = true, features = ["derive"] } 27 27 serde_json = { workspace = true } 28 28 url = { workspace = true } ··· 45 45 tokio = { workspace = true, default-features = false, features = ["sync"] } 46 46 trait-variant.workspace = true 47 47 n0-future = { workspace = true, optional = true } 48 - webbrowser = { version = "0.8", optional = true } 48 + webbrowser = { version = "1", optional = true } 49 49 tracing = { workspace = true, optional = true } 50 50 51 51 [target.'cfg(not(target_arch = "wasm32"))'.dependencies]
+2 -2
crates/jacquard-repo/Cargo.toml
··· 16 16 17 17 [dependencies] 18 18 # Internal 19 - jacquard-common = { path = "../jacquard-common", version = "0.8.0", features = ["crypto-ed25519", "crypto-k256", "crypto-p256"] } 20 - jacquard-derive = { path = "../jacquard-derive", version = "0.8.0" } 19 + jacquard-common = { path = "../jacquard-common", version = "0.9.0", features = ["crypto-ed25519", "crypto-k256", "crypto-p256"] } 20 + jacquard-derive = { path = "../jacquard-derive", version = "0.9.0" } 21 21 22 22 # Serialization 23 23 serde.workspace = true
+6 -6
crates/jacquard/Cargo.toml
··· 127 127 required-features = ["api_bluesky", "loopback"] 128 128 129 129 [dependencies] 130 - jacquard-api = { version = "0.8", path = "../jacquard-api" } 131 - jacquard-common = { version = "0.8", path = "../jacquard-common", features = [ 130 + jacquard-api = { version = "0.9", path = "../jacquard-api" } 131 + jacquard-common = { version = "0.9", path = "../jacquard-common", features = [ 132 132 "reqwest-client", 133 133 ] } 134 - jacquard-oauth = { version = "0.8", path = "../jacquard-oauth" } 135 - jacquard-derive = { version = "0.8", path = "../jacquard-derive", optional = true } 136 - jacquard-identity = { version = "0.8", path = "../jacquard-identity" } 134 + jacquard-oauth = { version = "0.9", path = "../jacquard-oauth" } 135 + jacquard-derive = { version = "0.9", path = "../jacquard-derive", optional = true } 136 + jacquard-identity = { version = "0.9", path = "../jacquard-identity" } 137 137 138 138 139 139 ··· 158 158 159 159 160 160 [target.'cfg(not(target_family = "wasm"))'.dependencies] 161 - jacquard-identity = { version = "0.8", path = "../jacquard-identity", features = ["cache"] } 161 + jacquard-identity = { version = "0.9", path = "../jacquard-identity", features = ["cache"] } 162 162 reqwest = { workspace = true, features = [ 163 163 "http2", 164 164 "system-proxy",