atproto tools in zig zat.dev
sdk atproto zig

add sync types for firehose consumption

new enums from com.atproto.sync.subscribeRepos lexicon:
- CommitAction: create, update, delete
- EventKind: commit, sync, identity, account, info
- AccountStatus: takendown, suspended, deleted, etc.

these work with std.json automatic enum parsing, enabling
exhaustive switches instead of mem.eql string comparisons.

bumps version to 0.1.0 (new feature, backwards compatible).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

+129 -1
+32
README.md
··· 93 93 </details> 94 94 95 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: 100 + const RepoOp = struct { 101 + action: zat.CommitAction, // .create, .update, .delete 102 + path: []const u8, 103 + cid: ?[]const u8, 104 + }; 105 + 106 + // then exhaustive switch: 107 + switch (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> 96 120 <summary><strong>json helpers</strong> - navigate nested json without verbose if-chains</summary> 97 121 98 122 ```zig ··· 149 173 ## specs 150 174 151 175 validation follows [atproto.com/specs](https://atproto.com/specs/atp). 176 + 177 + ## versioning 178 + 179 + pre-1.0 semver: 180 + - `0.x.0` - new features (backwards compatible) 181 + - `0.x.y` - bug fixes 182 + 183 + breaking changes bump the minor version and are documented in commit messages. 152 184 153 185 ## license 154 186
+1 -1
build.zig.zon
··· 1 1 .{ 2 2 .name = .zat, 3 - .version = "0.0.2", 3 + .version = "0.1.0", 4 4 .fingerprint = 0x8da9db57ee82fbe4, 5 5 .minimum_zig_version = "0.15.0", 6 6 .paths = .{
+90
src/internal/sync.zig
··· 1 + //! sync types - com.atproto.sync.subscribeRepos 2 + //! 3 + //! enums for firehose/event stream consumption. 4 + //! see: https://atproto.com/specs/event-stream 5 + 6 + const std = @import("std"); 7 + 8 + /// repo operation action (create/update/delete) 9 + /// 10 + /// from com.atproto.sync.subscribeRepos#repoOp 11 + /// used in firehose commit messages to indicate what happened to a record. 12 + pub const CommitAction = enum { 13 + create, 14 + update, 15 + delete, 16 + 17 + /// parse from string (for manual parsing) 18 + pub fn parse(s: []const u8) ?CommitAction { 19 + return std.meta.stringToEnum(CommitAction, s); 20 + } 21 + }; 22 + 23 + /// event stream message types 24 + /// 25 + /// from com.atproto.sync.subscribeRepos message union 26 + /// the top-level discriminator for firehose messages. 27 + pub const EventKind = enum { 28 + commit, 29 + sync, 30 + identity, 31 + account, 32 + info, 33 + 34 + pub fn parse(s: []const u8) ?EventKind { 35 + return std.meta.stringToEnum(EventKind, s); 36 + } 37 + }; 38 + 39 + /// account status reasons 40 + /// 41 + /// from com.atproto.sync.subscribeRepos#account status field 42 + /// indicates why an account is inactive. 43 + pub const AccountStatus = enum { 44 + takendown, 45 + suspended, 46 + deleted, 47 + deactivated, 48 + desynchronized, 49 + throttled, 50 + 51 + pub fn parse(s: []const u8) ?AccountStatus { 52 + return std.meta.stringToEnum(AccountStatus, s); 53 + } 54 + }; 55 + 56 + // === tests === 57 + 58 + test "CommitAction parse" { 59 + try std.testing.expectEqual(CommitAction.create, CommitAction.parse("create").?); 60 + try std.testing.expectEqual(CommitAction.update, CommitAction.parse("update").?); 61 + try std.testing.expectEqual(CommitAction.delete, CommitAction.parse("delete").?); 62 + try std.testing.expect(CommitAction.parse("invalid") == null); 63 + } 64 + 65 + test "CommitAction json parsing" { 66 + const json_str = 67 + \\{"action": "create", "path": "app.bsky.feed.post/abc"} 68 + ; 69 + 70 + const Op = struct { 71 + action: CommitAction, 72 + path: []const u8, 73 + }; 74 + 75 + const parsed = try std.json.parseFromSlice(Op, std.testing.allocator, json_str, .{}); 76 + defer parsed.deinit(); 77 + 78 + try std.testing.expectEqual(CommitAction.create, parsed.value.action); 79 + } 80 + 81 + test "EventKind parse" { 82 + try std.testing.expectEqual(EventKind.commit, EventKind.parse("commit").?); 83 + try std.testing.expectEqual(EventKind.identity, EventKind.parse("identity").?); 84 + try std.testing.expect(EventKind.parse("unknown") == null); 85 + } 86 + 87 + test "AccountStatus parse" { 88 + try std.testing.expectEqual(AccountStatus.takendown, AccountStatus.parse("takendown").?); 89 + try std.testing.expectEqual(AccountStatus.suspended, AccountStatus.parse("suspended").?); 90 + }
+6
src/root.zig
··· 26 26 pub const Jwt = @import("internal/jwt.zig").Jwt; 27 27 pub const multibase = @import("internal/multibase.zig"); 28 28 pub const multicodec = @import("internal/multicodec.zig"); 29 + 30 + // sync / firehose 31 + const sync = @import("internal/sync.zig"); 32 + pub const CommitAction = sync.CommitAction; 33 + pub const EventKind = sync.EventKind; 34 + pub const AccountStatus = sync.AccountStatus;