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. Retry and RetryOptions are 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) attempts delivery.retries + 1 times, waiting delivery.retryBackoffMs before 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. delivery is resolved inside the call, and only when attempts or backoffMs is left to default, so a call that overrides both works before configure().
  • An attempt count below one runs once. 0, a negative, and NaN all mean a single attempt, which is how List.chunk already treats an unusable size: a setting read from a missing environment variable arrives as NaN, and one attempt is the safe way to read it.
  • Retry.isRetryable(error) returns false for an AbortError or TimeoutError, because the caller's own timeout already spent the budget. For an Error with a numeric status, it returns true only for 429, 500, 502, 503 and 504. Everything else is treated as a network failure and retried.
  • options.isRetryable replaces the default rather than adding to it. A caller with its own non-retryable failures refuses those and defers to Retry.isRetryable for the rest.
  • options.logger receives one warn line before each re-attempt, under options.event ('retry' by default), carrying attempt, of, delayMs and error.

🔧 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 dependencies and peerDependencies both 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 list Retry among the things in the package that behave.
  • docs/changes-log/2026-09-17.md records 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 Retry today. 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.

results matching ""

    No results matching ""