adopted from the notes repo during the 2026-08-23 curation pass; project design/retro content lives with its project.
Comptime endpoint inventories #
written against zig 0.16.
The API reference in ZDS (our zig PDS) is not reflection in the usual runtime sense. It is a comptime-known data inventory.
In Zig, top-level const arrays of structs are available to any importing
module at compile time and runtime. If the fields are string literals, enum
values, and slices of string literals, the compiler can type-check the whole
inventory without needing a registry object or AST parser.
For ZDS, each endpoint is a struct with fields like:
pub const Endpoint = struct {
route: Route,
method: []const u8,
path: []const u8,
group: []const u8,
auth: []const u8,
summary: []const u8,
params: []const []const u8 = &.{},
body: []const []const u8 = &.{},
response: []const []const u8 = &.{},
notes: []const u8 = "",
};
Then:
- routing loops over
endpoints - docs rendering loops over
endpoints - OpenAPI export loops over
endpoints - tests assert that important method/path pairs map to the intended route enum
This is compile-time friendly, but not magic. We are manually declaring a table that is more structured than comments and less ambitious than a full router DSL.
Why not AST parsing #
AST parsing would let documentation be inferred from calls like
router.get("/xrpc/...", handler, ...), but it would also bind the docs system
to source syntax rather than program structure. That tends to be brittle in Zig
because route registration can be hidden behind helper functions, inline for,
comptime parameters, or imported modules.
The current inventory is boring, but boring is valuable:
- the compiler checks field names and route enum values
- code search finds every route declaration
- docs do not depend on a separate source parser
- metadata can grow naturally
How this could get better #
The next improvement is probably not runtime reflection. It is a stronger comptime registration shape.
Possible direction:
const routes = api_reference.routes(.{
.{ .GET, "/api", .api_docs, .{ .group = "zds", .summary = "..." } },
.{ .POST, "/xrpc/com.atproto.repo.createRecord", .repo_create_record, .{
.group = "repo",
.auth = "bearer",
.lexicon = "com.atproto.repo.createRecord",
} },
});
The helper could validate at comptime that:
- methods are from a known method enum
- XRPC paths contain valid NSIDs
- route enum values are unique where required
- HEAD and GET pairings are intentional
- lexicon IDs match path NSIDs
Zig tools that matter here:
comptimeparameters for route DSL helpersinline forover tuple literals or arrays@typeInfoif we later want to inspect handler signatures or schema structs@hasDeclfor optional module-provided metadatastd.metahelpers for enum and struct introspection
httpz and route metadata #
Karl's httpz has a real router with method trees, path params, groups, route
configuration, middleware, and custom dispatch. It does not expose a public
documentation registry in the pinned version used by ZDS.
That means ZDS has two reasonable options:
- keep its own endpoint inventory and dispatch switch, as it does now
- wrap
httpzroute registration with a ZDS-owned registration helper that records metadata and registers the route at the same time
The second option is attractive if ZDS later moves from its current route enum
switch to direct httpz actions. For now, the explicit inventory is a good
middle ground.
HTMX #
HTMX is attractive when the server owns small HTML fragments and the UI needs stateful server interactions: endpoint try-it forms, authenticated request probes, schema expansion, saved examples, or live health checks.
For plain search, group filtering, and clipboard copy, vanilla JavaScript is lighter and keeps the reference page deployable as one rendered HTML document.