+12
.claude/settings.local.json
+12
.claude/settings.local.json
+2
-1
.gitignore
+2
-1
.gitignore
+115
CLAUDE.md
+115
CLAUDE.md
···+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.+Jacquard is a suite of Rust crates for the AT Protocol (atproto/Bluesky). The project emphasizes spec-compliant, validated, performant baseline types with minimal boilerplate. Key design goals:+- **jacquard-common**: Core AT Protocol types (DIDs, handles, at-URIs, NSIDs, TIDs, CIDs, etc.) and the `CowStr` type for efficient string handling+- `new_static()`: Construct from `&'static str` using `SmolStr`/`CowStr`'s static constructor (no allocation)+- ✅ Comprehensive validation tests for all core string types (handle, DID, NSID, TID, record key, AT-URI, datetime, language, identifier)+- ✅ Validated implementations against AT Protocol specs and TypeScript reference implementation+- ✅ String type interface standardization (Language now has `new_static()`, Datetime has full conversion traits)+- ✅ Data serialization: Full serialize/deserialize for `Data<'_>`, `Array`, `Object` with format-specific handling (JSON vs CBOR)+1. **Lexicon Code Generation**: Begin work on lexicon-to-Rust code generation now that core types are stable
+80
-8
Cargo.lock
+80
-8
Cargo.lock
······························
+356
codegen_plan.md
+356
codegen_plan.md
···+Generate idiomatic Rust types from AT Protocol lexicon schemas with minimal nesting/indirection.+- **lexicon.rs**: Complete lexicon parsing types (`LexiconDoc`, `LexUserType`, `LexObject`, etc)+6. **XrpcRequest location**: Should trait live in jacquard-common or separate jacquard-xrpc crate?
+14
crates/jacquard-api/Cargo.toml
+14
crates/jacquard-api/Cargo.toml
···
+16
crates/jacquard-api/src/lib.rs
+16
crates/jacquard-api/src/lib.rs
+1
crates/jacquard-common/Cargo.toml
+1
crates/jacquard-common/Cargo.toml
+22
crates/jacquard-common/src/cowstr.rs
+22
crates/jacquard-common/src/cowstr.rs
···
-2
crates/jacquard-common/src/types.rs
-2
crates/jacquard-common/src/types.rs
+77
-1
crates/jacquard-common/src/types/aturi.rs
+77
-1
crates/jacquard-common/src/types/aturi.rs
···-Regex::new(r##"^at://(?<authority>[a-zA-Z0-9._:%-]+)(/(?<collection>[a-zA-Z0-9-.]+)(/(?<rkey>[a-zA-Z0-9._~:@!$&%')(*+,;=-]+))?)?(#(?<fragment>/[a-zA-Z0-9._~:@!$&%')(*+,;=-[]/\]*))?$"##).unwrap()+Regex::new(r##"^at://(?<authority>[a-zA-Z0-9._:%-]+)(/(?<collection>[a-zA-Z0-9-.]+)(/(?<rkey>[a-zA-Z0-9._~:@!$&%')(*+,;=-]+))?)?(#(?<fragment>/[a-zA-Z0-9._~:@!$&%')(*+,;=\-\[\]/\\]*))?$"##).unwrap()···
+33
-1
crates/jacquard-common/src/types/blob.rs
+33
-1
crates/jacquard-common/src/types/blob.rs
···
+266
crates/jacquard-common/src/types/cid.rs
+266
crates/jacquard-common/src/types/cid.rs
···+assert_eq!(json, r#"{"$link":"bafyreih4g7bvo6hdq2juolev5bfzpbo4ewkxh5mzxwgvkjp3kitc6hqkha"}"#);
+73
-1
crates/jacquard-common/src/types/datetime.rs
+73
-1
crates/jacquard-common/src/types/datetime.rs
······
+97
crates/jacquard-common/src/types/did.rs
+97
crates/jacquard-common/src/types/did.rs
···+/// Note: This regex allows `%` in the identifier but prevents DIDs from ending with `:` or `%`.+/// It does NOT validate that percent-encoding is well-formed (i.e., `%XX` where XX are hex digits).···
+120
-26
crates/jacquard-common/src/types/handle.rs
+120
-26
crates/jacquard-common/src/types/handle.rs
···············
+37
crates/jacquard-common/src/types/ident.rs
+37
crates/jacquard-common/src/types/ident.rs
···
+35
-3
crates/jacquard-common/src/types/language.rs
+35
-3
crates/jacquard-common/src/types/language.rs
·········
+94
crates/jacquard-common/src/types/nsid.rs
+94
crates/jacquard-common/src/types/nsid.rs
···
+69
crates/jacquard-common/src/types/recordkey.rs
+69
crates/jacquard-common/src/types/recordkey.rs
···
+1
-1
crates/jacquard-common/src/types/string.rs
+1
-1
crates/jacquard-common/src/types/string.rs
+70
crates/jacquard-common/src/types/tid.rs
+70
crates/jacquard-common/src/types/tid.rs
···+assert!(Tid::new("j7777777777777").is_err()); // j is valid for first char but makes high bit set
+21
-324
crates/jacquard-common/src/types/value.rs
+21
-324
crates/jacquard-common/src/types/value.rs
······························-pub fn json_to_blob<'b>(blob: &'b serde_json::Map<String, serde_json::Value>) -> Option<Blob<'b>> {
+320
crates/jacquard-common/src/types/value/parsing.rs
+320
crates/jacquard-common/src/types/value/parsing.rs
···+pub fn json_to_blob<'b>(blob: &'b serde_json::Map<String, serde_json::Value>) -> Option<Blob<'b>> {
+390
crates/jacquard-common/src/types/value/serde_impl.rs
+390
crates/jacquard-common/src/types/value/serde_impl.rs
···+fn apply_type_inference<'s>(mut map: BTreeMap<SmolStr, Data<'s>>) -> Result<Data<'s>, AtDataError> {
+364
crates/jacquard-common/src/types/value/test_thread.json
+364
crates/jacquard-common/src/types/value/test_thread.json
···+"avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:hbpefio3f5csc44msmbgioxz/bafkreia7dcruptjvvv7t46322zqsuqukkwblihzrm3f45r246o5zjulyn4@jpeg",+"avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:2whlowi5jjjqrdrrj4lxh2lx/bafkreiarcjakxx7hkgtfocqilj22vxgmyskl43blurk6vwu2nfvd5ihueu@jpeg",+"text": "Really good news for fans of garbage in their timelines, Sora 2 allows anyone to use copyrighted characters to sell you cryptocurrency."+"playlist": "https://video.bsky.app/watch/did%3Aplc%3A2whlowi5jjjqrdrrj4lxh2lx/bafkreid7ybejd5s2vv2j7d4aajjlmdgazguemcnuliiyfn6coxpwp2mi6y/playlist.m3u8",+"thumbnail": "https://video.bsky.app/watch/did%3Aplc%3A2whlowi5jjjqrdrrj4lxh2lx/bafkreid7ybejd5s2vv2j7d4aajjlmdgazguemcnuliiyfn6coxpwp2mi6y/thumbnail.jpg",+"viewer": { "bookmarked": false, "threadMuted": false, "replyDisabled": false, "embeddingDisabled": false },+"avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:hbpefio3f5csc44msmbgioxz/bafkreia7dcruptjvvv7t46322zqsuqukkwblihzrm3f45r246o5zjulyn4@jpeg",+"text": "it's funny how these all feel like stuff that would be playing on the Cyberpunk 2077 mediafeeds"+"viewer": { "bookmarked": false, "threadMuted": false, "replyDisabled": false, "embeddingDisabled": false },+"avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:hbpefio3f5csc44msmbgioxz/bafkreia7dcruptjvvv7t46322zqsuqukkwblihzrm3f45r246o5zjulyn4@jpeg",+"viewer": { "bookmarked": false, "threadMuted": false, "replyDisabled": false, "embeddingDisabled": false },+"avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:duaatzbzy7qm4ppl2hluilpg/bafkreifqxgriyccwjt5pwahsx6sju2bdmtoytqyubxoxlfwk4rv3eedkia@jpeg",+"viewer": { "bookmarked": false, "threadMuted": false, "replyDisabled": false, "embeddingDisabled": false },
+305
crates/jacquard-common/src/types/value/tests.rs
+305
crates/jacquard-common/src/types/value/tests.rs
···+let data = Data::CidLink(Cid::str("bafyreih4g7bvo6hdq2juolev5bfzpbo4ewkxh5mzxwgvkjp3kitc6hqkha"));+Data::CidLink(cid) => assert_eq!(cid.as_str(), "bafyreih4g7bvo6hdq2juolev5bfzpbo4ewkxh5mzxwgvkjp3kitc6hqkha"),+assert_eq!(original_canonical, serialized_canonical, "Serialized JSON should match original structure")
+29
crates/jacquard-derive/Cargo.toml
+29
crates/jacquard-derive/Cargo.toml
···
+117
crates/jacquard-derive/src/lib.rs
+117
crates/jacquard-derive/src/lib.rs
···
+89
crates/jacquard-derive/tests/lexicon.rs
+89
crates/jacquard-derive/tests/lexicon.rs
···
+117
crates/jacquard-derive/tests/open_union.rs
+117
crates/jacquard-derive/tests/open_union.rs
···
+11
crates/jacquard-lexicon/Cargo.toml
+11
crates/jacquard-lexicon/Cargo.toml
···
+36
crates/jacquard-lexicon/src/fs.rs
+36
crates/jacquard-lexicon/src/fs.rs
···
+433
crates/jacquard-lexicon/src/lexicon.rs
+433
crates/jacquard-lexicon/src/lexicon.rs
···
+49
-12
crates/jacquard-lexicon/src/lib.rs
+49
-12
crates/jacquard-lexicon/src/lib.rs
···
+41
crates/jacquard-lexicon/src/output.rs
+41
crates/jacquard-lexicon/src/output.rs
···
+142
crates/jacquard-lexicon/src/schema.rs
+142
crates/jacquard-lexicon/src/schema.rs
···