Last updated on
Ten Functions, Three Shapes
Filed under AI & Tooling
Third in this series on an automated architecture review of this blog's codebase, run with Matt Pocock's improve-codebase-architecture skill (see the first post for how the review itself works): the module every page on the front end goes through to actually fetch content.
Ten exported functions, each wrapping a Payload query, fell cleanly into four repeated shapes: fetch every document in a collection, fetch one document by its slug with an option for draft preview, fetch one document by slug without that option, and fetch documents by a relationship id. Every instance differed only in which collection it queried, what it sorted by, and whether a "published only" filter applied.
The tempting version of this fix is mechanical: three generic helpers, parameterized by collection, and ten one-line functions calling into them. That's roughly what happened, but two things made it more interesting than a find-and-replace.
Losing the comments would have been the actual cost
Each of the ten original functions carried real, specific documentation: why one function accepts a draft-preview flag and another doesn't, why a particular field is excluded from a query for privacy reasons, why a particular query depth was chosen. None of that context lives in the code's types. It lives in comments that a mechanical extraction is very good at silently deleting.
So the generic helpers absorbed only the reasoning that was actually general, and every function-specific caveat stayed attached to its own one-line call site instead of being flattened into a shared parameter list. This is where the report's own rating system mattered in practice: this candidate was marked "worth exploring" rather than "strong," specifically because the collapsing risked trading away exactly this kind of clarity. It was worth doing, just worth doing slowly.
The gap an automated reviewer actually caught
The genuinely useful find came from the same adversarial-review pass that's shown up in every post in this series so far. Two of the three new generic helpers had a "published only" filter that was optional, defaulting to off. That was safe today, because the only two collections using the simpler helper don't have a published/draft distinction at all. But it meant the type system had no opinion about it. A future collection that does support drafts could call the same helper, forget the filter, and start serving draft content to anonymous visitors, and nothing would complain until someone noticed a post live before it should have been.
The fix was to make that filter a required argument with no default on both helpers. It's a small change, but it moves an entire category of mistake from "possible if you forget" to "does not compile if you forget," a better place for that particular mistake to live given what's actually at stake if it slips through.
All ten original functions kept their exact names and signatures; nothing outside this one file needed to change, and the existing test suite, which happened to assert byte-for-byte on the exact arguments each function passed to Payload, passed completely unmodified against the new implementation. That was as good a proof of "nothing changed except where the code lives" as I could have asked for.
Next, the last post in the series: what happens when the type system is only checking half of what it looks like it's checking.
Comments
No comments yet. Be the first to comment.