+45
README.md
+45
README.md
···
1
+
# zat
2
+
3
+
zig atproto primitives. parsing utilities for TID, AT-URI, and DID.
4
+
5
+
## status
6
+
7
+
alpha (`0.0.1-alpha`). APIs are in `internal` module while we iterate.
8
+
9
+
## install
10
+
11
+
```zig
12
+
// build.zig.zon
13
+
.dependencies = .{
14
+
.zat = .{
15
+
.url = "https://tangled.sh/@zzstoatzz.io/zat/archive/main.tar.gz",
16
+
.hash = "...", // zig build will tell you
17
+
},
18
+
},
19
+
```
20
+
21
+
## usage
22
+
23
+
```zig
24
+
const zat = @import("zat");
25
+
26
+
// TID - timestamp identifiers
27
+
const tid = zat.internal.Tid.parse("3jui7kze2c22s") orelse return error.InvalidTid;
28
+
const ts = tid.timestamp(); // microseconds since epoch
29
+
const clock = tid.clockId(); // 10-bit clock id
30
+
31
+
// AT-URI - at://did/collection/rkey
32
+
const uri = zat.internal.AtUri.parse("at://did:plc:xyz/app.bsky.feed.post/abc123") orelse return error.InvalidUri;
33
+
const did = uri.did(); // "did:plc:xyz"
34
+
const collection = uri.collection(); // "app.bsky.feed.post"
35
+
const rkey = uri.rkey(); // "abc123"
36
+
37
+
// DID - did:plc and did:web
38
+
const d = zat.internal.Did.parse("did:plc:z72i7hdynmk6r22z27h6tvur") orelse return error.InvalidDid;
39
+
const id = d.identifier(); // "z72i7hdynmk6r22z27h6tvur"
40
+
const is_plc = d.isPlc(); // true
41
+
```
42
+
43
+
## why internal?
44
+
45
+
new APIs start in `internal` and get promoted to root when stable. if you need bleeding edge, use `zat.internal.*` and expect breakage.
+10
-2
src/internal/tid.zig
+10
-2
src/internal/tid.zig
···
2
2
//!
3
3
//! tids encode a timestamp and clock id in a base32-sortable format.
4
4
//! format: 13 characters using alphabet "234567abcdefghijklmnopqrstuvwxyz"
5
-
//! - first 11 chars: 53-bit timestamp (microseconds since epoch)
6
-
//! - last 2 chars: 10-bit clock identifier
5
+
//! - first char must be 2-7 (high bit 0x40 must be 0)
6
+
//! - remaining chars encode 53-bit timestamp + 10-bit clock id
7
7
//!
8
8
//! the encoding is designed to be lexicographically sortable by time.
9
+
//! see: https://atproto.com/specs/record-key#record-key-type-tid
9
10
10
11
const std = @import("std");
11
12
···
17
18
/// parse a tid string. returns null if invalid.
18
19
pub fn parse(s: []const u8) ?Tid {
19
20
if (s.len != 13) return null;
21
+
22
+
// first char high bit (0x40) must be 0, meaning only '2'-'7' allowed
23
+
if (s[0] & 0x40 != 0) return null;
20
24
21
25
var result: Tid = undefined;
22
26
for (s, 0..) |c, i| {
···
101
105
// invalid chars
102
106
try std.testing.expect(Tid.parse("0000000000000") == null);
103
107
try std.testing.expect(Tid.parse("1111111111111") == null);
108
+
109
+
// first char must be 2-7 (high bit 0x40 must be 0)
110
+
try std.testing.expect(Tid.parse("a222222222222") == null);
111
+
try std.testing.expect(Tid.parse("z222222222222") == null);
104
112
}
105
113
106
114
test "roundtrip" {