atproto utils for zig zat.dev
atproto sdk zig
26
fork

Configure Feed

Select the types of activity you want to include in your feed.

add jetstream smoke test and fix publish-docs for zig 0.16

- add scripts/jetstream_smoke.zig with `zig build smoke` step
- fix publish-docs.zig: posix.getenv → std.c.getenv, std.time.timestamp
→ Io.Timestamp, std.fs.cwd → Io.Dir, XrpcClient.init signature

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

+61 -6
+17
build.zig
··· 60 60 const test_step = b.step("test", "run unit tests"); 61 61 test_step.dependOn(&run_tests.step); 62 62 63 + // jetstream smoke test 64 + const jetstream_smoke = b.addExecutable(.{ 65 + .name = "jetstream-smoke", 66 + .root_module = b.createModule(.{ 67 + .root_source_file = b.path("scripts/jetstream_smoke.zig"), 68 + .target = target, 69 + .optimize = optimize, 70 + .link_libc = true, 71 + .imports = &.{.{ .name = "zat", .module = mod }}, 72 + }), 73 + }); 74 + b.installArtifact(jetstream_smoke); 75 + 76 + const run_smoke = b.addRunArtifact(jetstream_smoke); 77 + const smoke_step = b.step("smoke", "run jetstream smoke test"); 78 + smoke_step.dependOn(&run_smoke.step); 79 + 63 80 // publish-docs script (uses zat to publish docs to ATProto) 64 81 const publish_docs = b.addExecutable(.{ 65 82 .name = "publish-docs",
+38
scripts/jetstream_smoke.zig
··· 1 + const std = @import("std"); 2 + const zat = @import("zat"); 3 + 4 + pub fn main() !void { 5 + var da: std.heap.DebugAllocator(.{}) = .init; 6 + defer _ = da.deinit(); 7 + const allocator = da.allocator(); 8 + 9 + std.debug.print("smoke test starting\n", .{}); 10 + 11 + var handler = Handler{}; 12 + var client = zat.JetstreamClient.init(allocator, .{ 13 + .hosts = &.{"jetstream2.us-east.bsky.network"}, 14 + .wanted_collections = &.{"app.bsky.feed.post"}, 15 + }); 16 + client.subscribe(&handler); 17 + } 18 + 19 + const Handler = struct { 20 + count: u64 = 0, 21 + connects: u64 = 0, 22 + 23 + pub fn onEvent(self: *Handler, event: zat.JetstreamEvent) void { 24 + self.count += 1; 25 + if (self.count % 1000 == 0) { 26 + std.debug.print(" [{d}] time_us={d}\n", .{ self.count, event.timeUs() }); 27 + } 28 + } 29 + 30 + pub fn onConnect(self: *Handler, host: []const u8) void { 31 + self.connects += 1; 32 + std.debug.print("CONNECT #{d} to {s} (total events so far: {d})\n", .{ self.connects, host, self.count }); 33 + } 34 + 35 + pub fn onError(_: *Handler, err: anyerror) void { 36 + std.debug.print("ERROR: {s}\n", .{@errorName(err)}); 37 + } 38 + };
+6 -6
scripts/publish-docs.zig
··· 29 29 30 30 const handle = "zat.dev"; 31 31 32 - const password = std.posix.getenv("ATPROTO_PASSWORD") orelse { 32 + const password = if (std.c.getenv("ATPROTO_PASSWORD")) |p| std.mem.span(p) else { 33 33 std.debug.print("error: ATPROTO_PASSWORD not set\n", .{}); 34 34 return error.MissingEnv; 35 35 }; 36 36 37 - const pds = std.posix.getenv("ATPROTO_PDS") orelse "https://bsky.social"; 37 + const pds = if (std.c.getenv("ATPROTO_PDS")) |p| std.mem.span(p) else "https://bsky.social"; 38 38 39 - var client = zat.XrpcClient.init(allocator, pds); 39 + var client = zat.XrpcClient.init(std.Options.debug_io, allocator, pds); 40 40 defer client.deinit(); 41 41 42 42 const session = try createSession(&client, allocator, handle, password); ··· 69 69 const now = timestamp(); 70 70 71 71 for (docs, 0..) |doc, i| { 72 - const content = std.fs.cwd().readFileAlloc(allocator, doc.file, 1024 * 1024) catch |err| { 72 + const content = std.Io.Dir.readFileAlloc(.cwd(), std.Options.debug_io, doc.file, allocator, .limited(1024 * 1024)) catch |err| { 73 73 std.debug.print("warning: could not read {s}: {}\n", .{ doc.file, err }); 74 74 continue; 75 75 }; ··· 108 108 109 109 // publish devlog entries (clock_id 101, 102, ...) 110 110 for (devlog, 0..) |entry, i| { 111 - const content = std.fs.cwd().readFileAlloc(allocator, entry.file, 1024 * 1024) catch |err| { 111 + const content = std.Io.Dir.readFileAlloc(.cwd(), std.Options.debug_io, entry.file, allocator, .limited(1024 * 1024)) catch |err| { 112 112 std.debug.print("warning: could not read {s}: {}\n", .{ entry.file, err }); 113 113 continue; 114 114 }; ··· 236 236 } 237 237 238 238 fn timestamp() [20]u8 { 239 - const epoch_seconds = std.time.timestamp(); 239 + const epoch_seconds: i64 = @intCast(@divFloor(std.Io.Timestamp.now(std.Options.debug_io, .real).nanoseconds, std.time.ns_per_s)); 240 240 const days: i32 = @intCast(@divFloor(epoch_seconds, std.time.s_per_day)); 241 241 const day_secs: u32 = @intCast(@mod(epoch_seconds, std.time.s_per_day)); 242 242