Release Notes
Version 1.3.1
📅 Release Date
September 17, 2026
📖 Overview
The kernel has declared how hard an application should retry since 1.1.0: delivery.retries and
delivery.retryBackoffMs are in systemDefaults. Until this release, nothing in the package read them. A consumer that
wanted to honour those settings had to write the loop itself, and at least one did.
Retry is that loop, moved in beside the settings it reads. It lives in core/ rather than utils/, because it
resolves configuration and waits, and utils/ admits only pure functions.
This reverses a decision recorded in the 2026-09-11 changes log. The 2026-09-17 changes log says which of that decision's reasons still hold and why the move went ahead anyway.
⚠️ Breaking Changes
- None.
RetryandRetryOptionsare new exports. No existing export is removed, renamed, or narrowed.
🚀 Features
| Export | Member | For |
|---|---|---|
Retry |
run |
Running an operation that is safe to repeat, within a budget |
Retry |
isRetryable |
The default judgement on whether a failure is transient |
RetryOptions |
— | Per-call overrides of the delivery defaults, plus logger/event |
Retry.run(operation, options)attemptsdelivery.retries + 1times, waitingdelivery.retryBackoffMsbefore the second attempt and doubling after that. With the shipped defaults: one attempt, 250 ms, a second, 500 ms, a third. The last failure is rethrown unchanged.deliveryis resolved inside the call, and only whenattemptsorbackoffMsis left to default, so a call that overrides both works beforeconfigure().- An attempt count below one runs once.
0, a negative, andNaNall mean a single attempt, which is howList.chunkalready treats an unusable size: a setting read from a missing environment variable arrives asNaN, and one attempt is the safe way to read it. Retry.isRetryable(error)returnsfalsefor anAbortErrororTimeoutError, because the caller's own timeout already spent the budget. For anErrorwith a numericstatus, it returnstrueonly for429,500,502,503and504. Everything else is treated as a network failure and retried.options.isRetryablereplaces the default rather than adding to it. A caller with its own non-retryable failures refuses those and defers toRetry.isRetryablefor the rest.options.loggerreceives onewarnline before each re-attempt, underoptions.event('retry'by default), carryingattempt,of,delayMsanderror.
🔧 Enhancements
- None.
🐛 Bug Fixes
- None. No prior behaviour changed.
🔐 Security
- The kernel is still dependency-free. HTTP failures are recognised by shape, not by importing a client's error
class, so
dependenciesandpeerDependenciesboth remain empty and the vendor-neutral import rule still passes.
🧪 Tests
npm run lint, npm run test:run, npm run build, and npm run build:docs all clean. 190 specs across 13 suites,
up from 164 across 12. Retry.ts has 100% statement and branch coverage.
test/core/Retry.spec.ts pins the bound: the exact attempt count, the delivery default, a single run for 0, a
negative or NaN, and no configuration needed when both defaults are overridden. It checks backoff doubling and the
logged event under fake timers, and covers each branch of the default judgement, including a non-numeric status and a
thrown value that is not an Error.
📚 Documentation
- The
core/module remarks listRetryamong the things in the package that behave. docs/changes-log/2026-09-17.mdrecords why the move reverses 2026-09-11 and what stays with the caller.
⬆️ Upgrading
npm install @bayudwiyansatria/core@1.3.1
Nothing to change on upgrade. To adopt, delete the local retry, import the kernel's at each call site, and keep only the judgement that is genuinely the caller's:
import { Retry } from '@bayudwiyansatria/core'
const isRetryable = (error: unknown): boolean => !(error instanceof ValidationError) && Retry.isRetryable(error)
await Retry.run(() => source.fetch(), { isRetryable, logger, event: 'source.retry' })
Pass the event name the old implementation logged under, or dashboards and alerts that query it go quiet.
🚨 Known Issues
- One consumer. A single application calls
Retrytoday. The 2026-09-17 changes log explains why the move went ahead with one consumer.
📦 Dependencies
| Kind | Package |
|---|---|
| Runtime | None |
| Peer | None |
Unchanged.
👥 Contributors
- Bayu Dwiyan Satria
For more information, visit the project's GitHub repository.