2026-09-17 — Retry moves in after all

2026-09-11 examined an application's private Retry and kept it out of this package, for three reasons. This session moved it in. Here is what changed about each reason, and the one that did not.

"It reads configuration, so it is not a utility"

Still true, and it still keeps Retry out of utils/. But utils/ was never the only place it could go. core/ is the layer that behaves: Logger resolves logging on every line, and nobody calls Logger a capability for it. Retry resolves delivery the same way and sits beside it.

The capability reading had the relationship backwards. A capability is something the kernel names and cannot provide itself, such as a cache or a queue. Retrying needs no platform resource. The kernel had already declared the retry policy, delivery.retries and delivery.retryBackoffMs, and simply had no code that read it. A policy in one package with its only implementation somewhere else is a split worth removing.

"It depends on an HTTP client's error and on the application's own validation error"

Both couplings were real, and neither came across.

  • The HTTP error is recognised by shape: an Error carrying a numeric status. @bayudwiyansatria/web-worker's HttpError has exactly that, so the judgement is unchanged for errors it raises, and the kernel still imports no HTTP library. Any client with the same shape gets the same treatment, which is what a transport-neutral default should do.
  • The validation error stays in the application, which passes its own isRetryable: refuse a validation failure, and defer to Retry.isRetryable for everything else. That was the one piece of the old judgement that knew anything about the application.

One side effect: the old implementation logged under a hard-coded event name. That became an option, event, defaulting to 'retry'. A caller adopting this can pass its old name so nothing querying the logs goes quiet.

"It has one consumer" — still true

This is the reason that stands, and the move went ahead anyway. What outweighed it: the kernel has published a retry setting with no reader since 1.1.0, so the next consumer to honour delivery.retries would otherwise write a second loop, and the two private copies of chunk had disagreed before anyone noticed.

That is a judgement call, not a rule, so it is written down. If a second consumer never appears, this is the entry to revisit.

Small behaviours settled on the way

  • An attempt count below one runs once, with NaN included, matching how List.chunk treats an unusable size. The old code used Math.max(attempts, 1), and Math.max(NaN, 1) is NaN: the loop never ran, the operation was never called, and the call threw undefined.
  • delivery is resolved only when a default is used. A call overriding both attempts and backoffMs works before configure(). The old code resolved on every call.

Consumer adoption

The consumer was switched over and verified against a packed tarball of this working tree before publication, then re-verified against the registry version once 1.3.1 was published. Both builds produced the same bundle size.

results matching ""

    No results matching ""