atproto utils for zig zat.dev

consider `xrpc.SubscriptionServer` #14

Open zzstoatzz.io
Open opened by zzstoatzz.io

asymmetric encapsulation: zat hides websocket on consume, exposes it on serve #

symptom #

bumping zat in labelz from v0.3.0-alpha.7 to v0.3.2 hit a duplicate-module error: labelz declares its own websocket dep (to serve com.atproto.label.subscribeLabels over WebSocket), and zat brings websocket transitively. when zat bumped websocket v0.1.0 → v0.1.1, the two roots conflicted until labelz matched the pin.

diagnosis #

the version coupling is the surface. the deeper issue is asymmetric abstraction:

  • consume side — FirehoseClient and JetstreamClient take (io, allocator, options) and emit typed events. consumers never touch websocket. clean.
  • serve side — nothing. labelz reaches around zat to use websocket.Server, websocket.Conn, websocket.Handshake.KeyValue directly. the choice of WebSocket library leaks into the consumer's build.zig.zon and source.

any future labeler, relay, or feed-stream server will hit the same wall. labelz also reinvents the XRPC subscription frame format (label.zig:100 — header CBOR ‖ payload CBOR), which zat already decodes internally but doesn't expose for encoding.

proposal #

add a server-side counterpart that mirrors the client abstraction:

var server = zat.xrpc.SubscriptionServer(Handler).init(io, allocator, .{
    .port = 4100,
    .nsid = "com.atproto.label.subscribeLabels",
});
defer server.deinit();

// Handler interface:
//   onSubscribe(cursor: ?i64) -> backfill iterator
//   broadcast(payload: cbor.Value) — server handles frame encoding
//   onClose(conn_id)
try server.listen();

server owns:

  • websocket transport (consumers drop their websocket dep entirely)
  • XRPC event frame encoding (header {op, t?} ‖ payload)
  • error frames (FutureCursor, OutdatedCursor, etc.)
  • HTTP fallback for non-WebSocket XRPC endpoints on the same port

non-goals #

  • not a general HTTP server — only XRPC subscription pattern
  • not a replacement for HttpTransport (that's the client transport)
  • not auth/PDS server functionality

prior art / migration target #

labelz src/server.zig is the concrete shape this API needs to absorb. once landed, labelz drops websocket from build.zig.zon, deletes encodeEventFrame / httpRespond / queryParam, and the duplicate-module class of error becomes structurally impossible.

can prob close?

Sign up or Sign in to add to the discussion.