Skip to content

Harden pagination, retries, and interceptor error handling (1.1.0) - #1

Merged
erelsop merged 1 commit into
mainfrom
fix/audit-hardening
Jul 22, 2026
Merged

Harden pagination, retries, and interceptor error handling (1.1.0)#1
erelsop merged 1 commit into
mainfrom
fix/audit-hardening

Conversation

@erelsop

@erelsop erelsop commented Jul 22, 2026

Copy link
Copy Markdown
Owner

Summary

Data-integrity and resilience release (1.1.0). One behavioural change to pagination plus two additive config options. No public type or method removed or renamed.

Surfaced while building a large (~100K-record) Keap audit consumer on top of this library.

Changed — pagination no longer silently truncates ⚠️

Page-based and cursor-based pagination previously stopped at the built-in maxPages safety cap (default 100) and returned a truncated array with no signal. At common page sizes this silently dropped data — e.g. paging 100 000 records at 1 000/page hits exactly 100 pages, and anything larger than pageSize × 100 came back short with no error.

Now, reaching the default cap while the server still reports more data throws the new PaginationLimitError instead of returning a partial result.

Opt-in escape hatch (unchanged from 1.0.0 for existing callers): passing an explicit maxPages — any number, or maxPages: Infinity for unbounded — is treated as a deliberate limit and stops silently without throwing.

Migration: callers who never set maxPages and relied on the implicit 100-page stop should either pass an explicit maxPages to restore the old behaviour, or (recommended) use maxPages: Infinity and page the full set. Callers already passing maxPages are unaffected.

Added

  • PaginationLimitError (exported from root; code: 'EPAGINATION_LIMIT', maxPages). Not retried by the retry loop, like InterceptorAbortError.
  • RetryConfig.maxBackoffMs — configurable exponential-backoff cap (default 2000, unchanged). Does not affect a Retry-After value (capped separately at 60s) or a custom BackoffStrategy.

Fixed

  • Errors thrown by a response interceptor are no longer misclassified as transient network failures. Previously such a throw was caught, retried, and wrapped in RetryError; it now propagates untouched (no retry, no wrapping), consistent with InterceptorAbortError.

Tests

New coverage for: throw-on-overflow, the explicit-maxPages opt-in, natural termination exactly at the cap boundary, the configurable backoff cap, and interceptor-throw propagation. Full suite: 252 passing across 10 suites.

Notes

  • CHANGELOG.md updated with a [1.1.0] section and a prominent migration note.
  • Version bumped to 1.1.0 in package.json / package-lock.json; not yet published to npm.

Pagination no longer silently truncates at the built-in maxPages safety
cap. Reaching the default cap while the server still reports more data now
throws PaginationLimitError instead of returning a partial result set; an
explicit maxPages (including Infinity) remains an opt-in that stops silently.
This converts a silent data-loss footgun into a loud, actionable failure.

Also:
- Add RetryConfig.maxBackoffMs to make the exponential-backoff cap
  configurable (default 2000, unchanged) for riding out sustained 429 bursts.
- Errors thrown by a response interceptor are no longer misclassified as
  transient network failures: they now propagate untouched instead of being
  retried and wrapped in RetryError, matching InterceptorAbortError semantics.

New tests cover the throw-on-overflow behaviour, the explicit-maxPages
opt-in, natural termination at the cap boundary, the configurable backoff
cap, and interceptor-throw propagation. Full suite: 252 passing.
@erelsop
erelsop merged commit 8b64e21 into main Jul 22, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants