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.

results matching ""

    No results matching ""