2026-09-11 — One utility survivor and a publication-ready repository
A follow-on to 2026-09-05, which moved chunk here after a cross-repository review of four
applications. That review left three candidates unexamined. This session ran the same test on all three, and only one
passed.
The test is the one utils/ states about itself: pure, vendor-neutral, no environment, no binding, no configuration —
and already written twice in two consumers, because a helper with one consumer buys a release cycle and no reuse.
Json qualified
Two applications each carried src/utils/Json.ts. The two files were identical down to the doc comment, differing only
in the @example — one showed { symbol?: string }, the other { symbols?: string[] }. Not a divergence, just a copy
that had not had time to drift yet.
It wraps JSON.parse so a route reading an untrusted body gets a value instead of an exception, and answers null for
every way that input can fail to be usable: malformed syntax, an empty body, a bare null literal. It names no vendor,
touches no configuration, and knows nothing about what it is parsing. It moves.
The one behaviour worth stating in a test rather than a comment is that 0 and false survive. A naive
if (!parsed) return null gets that wrong, and the whole point of the helper is to be the thing callers do not have to
re-derive.
ContentHash was never a duplication
Two applications export a class called ContentHash with a static of. The name is where the similarity ends:
| Copy | Signature | What it hashes |
|---|---|---|
| First | of(values: readonly unknown[]) |
A positional list, joined on a U+001F separator, each value stringified |
| Second | of(value: unknown) |
One value, canonicalised by sorting object keys recursively, then JSON |
Different arity, different input shape, different answer for the same data. One is "these fields, in this order, changed or did not"; the other is "this object, however its keys happened to be ordered, changed or did not". Both are correct for their caller and neither is correct for the other's.
Merging them would mean inventing a third function that neither call site asked for, then rewriting both to use it. That is not de-duplication — it is a rewrite wearing de-duplication's clothes. Two functions that share a name are not two copies of one function, and the shared name is the only evidence anyone had that they were.
Both stay where they are. If either ever needs the other's behaviour, that is the moment to look again.
Retry is a capability, not a utility
One application's Retry.run reads its own defaults through resolve<DeliverySettings>('delivery'). That single line
disqualifies it, by the rule utils/ already states: a helper that needs environment, binding, or configuration is a
capability, and belongs behind an interface with an adapter to implement it — not in a module whose contract is "pure
function over its arguments".
Two further couplings point the same way. isRetryable branches on a validation error that is that application's own
domain exception, and on HttpError from @bayudwiyansatria/web-worker. The second is the more interesting one: the
kernel takes no dependencies, so importing HttpError here is not available, and dropping the branch would change what
the helper does. A retry policy that understands HTTP status codes is better placed beside the HTTP client that already
owns HttpError than in a package that must name no protocol.
It also has one consumer. Either reason alone would be enough.
The version this lands under
Adding an export is a minor bump by the surface rule the family's publishing guide states, so this is 1.3.0 rather than
a patch. Worth writing down because the release carries two new exports — List from the previous session and Json
from this one — and a patch number would have let every consumer on ^1.2.1 pick both up with nothing in the version
signalling that the surface had grown.
What this does not do
The consumers still carry their own copies of chunk and Json. They cannot import either until 1.3.0 is on the
registry, and removing a working local helper before its replacement exists would break four builds for the sake of
tidiness. Adoption is a separate pass, one repository at a time, after publication.
Public repository documentation
After the 1.3.0 release, the repository documentation received a publication-readiness pass. It corrected links left
from the original template, brought the security version table up to date, documented GitHub Packages authentication and
the Node.js 22 development baseline, and made the README examples self-contained.
The contributing and support guides now explain how outside contributors should report problems, validate changes, and handle security reports. Issue and pull request templates collect library-specific reproduction, compatibility, testing, and documentation information without leaving instructional boilerplate in submitted reports.
AGENTS.md was rewritten around the actual package, output names, configuration, workflows, and layer boundaries. Its
previous contents still described the repository as templates-project-nodejs, referenced files that did not exist, and
gave commands and output names that no longer matched the build.