atproto utils for zig zat.dev
atproto sdk zig
1# zat 2 3zig primitives for AT Protocol. 4 5## install 6 7```bash 8zig fetch --save https://tangled.sh/zzstoatzz.io/zat/archive/main 9``` 10 11then in `build.zig`: 12 13```zig 14const zat = b.dependency("zat", .{}).module("zat"); 15exe.root_module.addImport("zat", zat); 16``` 17 18## what's here 19 20<details> 21<summary><strong>string primitives</strong> - parsing and validation for atproto identifiers</summary> 22 23- **Tid** - timestamp identifiers (base32-sortable) 24- **Did** - decentralized identifiers 25- **Handle** - domain-based handles 26- **Nsid** - namespaced identifiers (lexicon types) 27- **Rkey** - record keys 28- **AtUri** - `at://` URIs 29 30```zig 31const zat = @import("zat"); 32 33if (zat.AtUri.parse(uri_string)) |uri| { 34 const authority = uri.authority(); 35 const collection = uri.collection(); 36 const rkey = uri.rkey(); 37} 38``` 39 40</details> 41 42<details> 43<summary><strong>did resolution</strong> - resolve did:plc and did:web to documents</summary> 44 45```zig 46var resolver = zat.DidResolver.init(allocator); 47defer resolver.deinit(); 48 49const did = zat.Did.parse("did:plc:z72i7hdynmk6r22z27h6tvur").?; 50var doc = try resolver.resolve(did); 51defer doc.deinit(); 52 53const handle = doc.handle(); // "bsky.app" 54const pds = doc.pdsEndpoint(); // "https://..." 55const key = doc.signingKey(); // verification method 56``` 57 58</details> 59 60<details> 61<summary><strong>handle resolution</strong> - resolve handles to DIDs via HTTP well-known</summary> 62 63```zig 64var resolver = zat.HandleResolver.init(allocator); 65defer resolver.deinit(); 66 67const handle = zat.Handle.parse("bsky.app").?; 68const did = try resolver.resolve(handle); 69defer allocator.free(did); 70// did = "did:plc:z72i7hdynmk6r22z27h6tvur" 71``` 72 73</details> 74 75<details> 76<summary><strong>xrpc client</strong> - call AT Protocol endpoints</summary> 77 78```zig 79var client = zat.XrpcClient.init(allocator, "https://bsky.social"); 80defer client.deinit(); 81 82const nsid = zat.Nsid.parse("app.bsky.actor.getProfile").?; 83var response = try client.query(nsid, params); 84defer response.deinit(); 85 86if (response.ok()) { 87 var json = try response.json(); 88 defer json.deinit(); 89 // use json.value 90} 91``` 92 93</details> 94 95<details> 96<summary><strong>sync types</strong> - enums for firehose/event stream consumption</summary> 97 98```zig 99// use in struct definitions for automatic json parsing: 100const RepoOp = struct { 101 action: zat.CommitAction, // .create, .update, .delete 102 path: []const u8, 103 cid: ?[]const u8, 104}; 105 106// then exhaustive switch: 107switch (op.action) { 108 .create, .update => processUpsert(op), 109 .delete => processDelete(op), 110} 111``` 112 113- **CommitAction** - `.create`, `.update`, `.delete` 114- **EventKind** - `.commit`, `.sync`, `.identity`, `.account`, `.info` 115- **AccountStatus** - `.takendown`, `.suspended`, `.deleted`, `.deactivated`, `.desynchronized`, `.throttled` 116 117</details> 118 119<details> 120<summary><strong>json helpers</strong> - navigate nested json without verbose if-chains</summary> 121 122```zig 123// runtime paths for one-offs: 124const uri = zat.json.getString(value, "embed.external.uri"); 125const count = zat.json.getInt(value, "meta.count"); 126 127// comptime extraction for complex structures: 128const FeedPost = struct { 129 uri: []const u8, 130 cid: []const u8, 131 record: struct { 132 text: []const u8 = "", 133 }, 134}; 135const post = try zat.json.extractAt(FeedPost, allocator, value, .{"post"}); 136``` 137 138</details> 139 140<details> 141<summary><strong>jwt verification</strong> - verify service auth tokens</summary> 142 143```zig 144var jwt = try zat.Jwt.parse(allocator, token_string); 145defer jwt.deinit(); 146 147// check claims 148if (jwt.isExpired()) return error.TokenExpired; 149if (!std.mem.eql(u8, jwt.payload.aud, expected_audience)) return error.InvalidAudience; 150 151// verify signature against issuer's public key (from DID document) 152try jwt.verify(public_key_multibase); 153``` 154 155supports ES256 (P-256) and ES256K (secp256k1) signing algorithms. 156 157</details> 158 159<details> 160<summary><strong>multibase decoding</strong> - decode public keys from DID documents</summary> 161 162```zig 163const key_bytes = try zat.multibase.decode(allocator, "zQ3sh..."); 164defer allocator.free(key_bytes); 165 166const parsed = try zat.multicodec.parsePublicKey(key_bytes); 167// parsed.key_type: .secp256k1 or .p256 168// parsed.raw: 33-byte compressed public key 169``` 170 171</details> 172 173## specs 174 175validation follows [atproto.com/specs](https://atproto.com/specs/atp). 176 177## versioning 178 179pre-1.0 semver: 180- `0.x.0` - new features (backwards compatible) 181- `0.x.y` - bug fixes 182 183breaking changes bump the minor version and are documented in commit messages. 184 185## license 186 187MIT