Skip to content

fix(offline-transactions): infinite retries by default and online-status gating#1301

Open
KyleAMathews wants to merge 4 commits intomainfrom
fix/offline-retry-and-online-gating
Open

fix(offline-transactions): infinite retries by default and online-status gating#1301
KyleAMathews wants to merge 4 commits intomainfrom
fix/offline-retry-and-online-gating

Conversation

@KyleAMathews
Copy link
Collaborator

@KyleAMathews KyleAMathews commented Feb 25, 2026

Fix offline transaction retry behavior and add online-status gating

Transactions were being permanently dropped after 10 failed retries, and retry attempts were wasted while the device was offline. Both issues are now fixed — retries are infinite by default and gated on connectivity.

Root Cause

Two separate issues in @tanstack/offline-transactions:

  1. DefaultRetryPolicy hardcoded maxRetries = 10 — After 10 transient failures (e.g., a brief server outage), transactions were treated as permanent failures, removed from the outbox, and lost.

  2. No online-status awareness in the execution loop — The executor would attempt transactions regardless of connectivity, burning through retries against an unreachable server.

Approach

Infinite retries by default: Changed DefaultRetryPolicy from maxRetries = 10 to maxRetries = Infinity. Retriable errors (anything that isn't NonRetriableError, AbortError, or a 4xx status) will keep retrying with exponential backoff until the server responds.

Online-status gating: Added isOnline() checks at two points:

  • TransactionExecutor.runExecution() — breaks out of the batch loop when offline
  • TransactionExecutor.scheduleNextRetry() — skips scheduling retry timers when offline

When the device comes back online, the OnlineDetector subscription handler resets retry delays and triggers execution immediately.

OnlineDetector.isOnline() is now required on the interface. Both existing implementations (WebOnlineDetector, ReactNativeOnlineDetector) already provided it. Making it required eliminates typeof guards with dangerous return true fallbacks.

OfflineExecutor.notifyOnline() removed — this method was redundant with the OnlineDetector subscription. The detector is the single source of truth for connectivity. Users who need imperative control over online status should implement a custom OnlineDetector.

Key Invariants

  • Retriable errors never stop retrying (only NonRetriableError and specific status codes are permanent)
  • No transaction execution is attempted while isOnline() returns false
  • The OnlineDetector is the single source of truth for connectivity — no force flags or override mechanisms

Non-goals

  • Configurable maxRetries/retryDelay/retryPolicy — stripped from the PR to avoid unnecessary API surface. Can be added when there's demand.
  • Retry timer suppression test coverage for online→offline→online transitions — the guard logic is simple and the offline→online path is tested.

Trade-offs

Breaking interface changes shipped as patch: OnlineDetector.isOnline() is now required and OfflineExecutor.notifyOnline() is removed. This is technically a breaking change, but this package is pre-1.0 with very few users, making a patch appropriate.

Verification

pnpm --filter @tanstack/offline-transactions test

Files changed

  • src/retry/RetryPolicy.ts — Default maxRetries changed from 10 to Infinity
  • src/executor/TransactionExecutor.ts — Online gating in execution loop and retry scheduling; TransactionSignaler interface replaces any type
  • src/OfflineExecutor.ts — Removed notifyOnline(); added isOnline() method; subscription handler triggers execution on connectivity change
  • src/connectivity/ReactNativeOnlineDetector.ts — Added isOnline(), initial NetInfo.fetch(), extracted toConnectivityState()
  • src/types.tsOnlineDetector.isOnline() now required; added TransactionSignaler interface
  • tests/offline-e2e.test.ts — Regression test for >10 retries; offline execution gating test
  • tests/harness.ts — Simplified config passthrough with spread
  • README.md — Updated API reference, removed speculative retry config sections

🤖 Generated with Claude Code

KyleAMathews and others added 2 commits February 25, 2026 15:32
…tus gating

The hardcoded 10-retry limit caused transactions to be permanently
dropped. Retries were also burned while offline. This changes the
default to infinite retries with exponential backoff and gates
execution on online status so retries are only attempted when
connected.

- DefaultRetryPolicy defaults to Infinity instead of 10
- Execution loop and retry timer skip while offline via isOnline()
- OnlineDetector.isOnline() now required on the interface
- ReactNativeOnlineDetector adds isOnline() and initial NetInfo.fetch
- Extract TransactionSignaler interface to replace any type
- Simplify test harness config with spread

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@changeset-bot
Copy link

changeset-bot bot commented Feb 25, 2026

🦋 Changeset detected

Latest commit: 533ba70

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@tanstack/offline-transactions Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 25, 2026

More templates

@tanstack/angular-db

npm i https://pkg.pr.new/@tanstack/angular-db@1301

@tanstack/db

npm i https://pkg.pr.new/@tanstack/db@1301

@tanstack/db-ivm

npm i https://pkg.pr.new/@tanstack/db-ivm@1301

@tanstack/electric-db-collection

npm i https://pkg.pr.new/@tanstack/electric-db-collection@1301

@tanstack/offline-transactions

npm i https://pkg.pr.new/@tanstack/offline-transactions@1301

@tanstack/powersync-db-collection

npm i https://pkg.pr.new/@tanstack/powersync-db-collection@1301

@tanstack/query-db-collection

npm i https://pkg.pr.new/@tanstack/query-db-collection@1301

@tanstack/react-db

npm i https://pkg.pr.new/@tanstack/react-db@1301

@tanstack/rxdb-db-collection

npm i https://pkg.pr.new/@tanstack/rxdb-db-collection@1301

@tanstack/solid-db

npm i https://pkg.pr.new/@tanstack/solid-db@1301

@tanstack/svelte-db

npm i https://pkg.pr.new/@tanstack/svelte-db@1301

@tanstack/trailbase-db-collection

npm i https://pkg.pr.new/@tanstack/trailbase-db-collection@1301

@tanstack/vue-db

npm i https://pkg.pr.new/@tanstack/vue-db@1301

commit: 533ba70

@github-actions
Copy link
Contributor

github-actions bot commented Feb 25, 2026

Size Change: 0 B

Total Size: 92.6 kB

ℹ️ View Unchanged
Filename Size
./packages/db/dist/esm/collection/change-events.js 1.39 kB
./packages/db/dist/esm/collection/changes.js 1.22 kB
./packages/db/dist/esm/collection/events.js 388 B
./packages/db/dist/esm/collection/index.js 3.32 kB
./packages/db/dist/esm/collection/indexes.js 1.1 kB
./packages/db/dist/esm/collection/lifecycle.js 1.75 kB
./packages/db/dist/esm/collection/mutations.js 2.34 kB
./packages/db/dist/esm/collection/state.js 3.49 kB
./packages/db/dist/esm/collection/subscription.js 3.71 kB
./packages/db/dist/esm/collection/sync.js 2.41 kB
./packages/db/dist/esm/deferred.js 207 B
./packages/db/dist/esm/errors.js 4.7 kB
./packages/db/dist/esm/event-emitter.js 748 B
./packages/db/dist/esm/index.js 2.69 kB
./packages/db/dist/esm/indexes/auto-index.js 742 B
./packages/db/dist/esm/indexes/base-index.js 766 B
./packages/db/dist/esm/indexes/btree-index.js 2.17 kB
./packages/db/dist/esm/indexes/lazy-index.js 1.1 kB
./packages/db/dist/esm/indexes/reverse-index.js 538 B
./packages/db/dist/esm/local-only.js 808 B
./packages/db/dist/esm/local-storage.js 2.1 kB
./packages/db/dist/esm/optimistic-action.js 359 B
./packages/db/dist/esm/paced-mutations.js 496 B
./packages/db/dist/esm/proxy.js 3.75 kB
./packages/db/dist/esm/query/builder/functions.js 733 B
./packages/db/dist/esm/query/builder/index.js 4.09 kB
./packages/db/dist/esm/query/builder/ref-proxy.js 1.05 kB
./packages/db/dist/esm/query/compiler/evaluators.js 1.43 kB
./packages/db/dist/esm/query/compiler/expressions.js 430 B
./packages/db/dist/esm/query/compiler/group-by.js 2.23 kB
./packages/db/dist/esm/query/compiler/index.js 2.04 kB
./packages/db/dist/esm/query/compiler/joins.js 2.11 kB
./packages/db/dist/esm/query/compiler/order-by.js 1.45 kB
./packages/db/dist/esm/query/compiler/select.js 1.09 kB
./packages/db/dist/esm/query/expression-helpers.js 1.43 kB
./packages/db/dist/esm/query/ir.js 673 B
./packages/db/dist/esm/query/live-query-collection.js 360 B
./packages/db/dist/esm/query/live/collection-config-builder.js 5.55 kB
./packages/db/dist/esm/query/live/collection-registry.js 264 B
./packages/db/dist/esm/query/live/collection-subscriber.js 2.42 kB
./packages/db/dist/esm/query/live/internal.js 145 B
./packages/db/dist/esm/query/optimizer.js 2.62 kB
./packages/db/dist/esm/query/predicate-utils.js 2.97 kB
./packages/db/dist/esm/query/subset-dedupe.js 921 B
./packages/db/dist/esm/scheduler.js 1.3 kB
./packages/db/dist/esm/SortedMap.js 1.3 kB
./packages/db/dist/esm/strategies/debounceStrategy.js 247 B
./packages/db/dist/esm/strategies/queueStrategy.js 428 B
./packages/db/dist/esm/strategies/throttleStrategy.js 246 B
./packages/db/dist/esm/transactions.js 2.9 kB
./packages/db/dist/esm/utils.js 924 B
./packages/db/dist/esm/utils/browser-polyfills.js 304 B
./packages/db/dist/esm/utils/btree.js 5.61 kB
./packages/db/dist/esm/utils/comparison.js 952 B
./packages/db/dist/esm/utils/cursor.js 457 B
./packages/db/dist/esm/utils/index-optimization.js 1.51 kB
./packages/db/dist/esm/utils/type-guards.js 157 B

compressed-size-action::db-package-size

@github-actions
Copy link
Contributor

github-actions bot commented Feb 25, 2026

Size Change: 0 B

Total Size: 3.7 kB

ℹ️ View Unchanged
Filename Size
./packages/react-db/dist/esm/index.js 225 B
./packages/react-db/dist/esm/useLiveInfiniteQuery.js 1.17 kB
./packages/react-db/dist/esm/useLiveQuery.js 1.34 kB
./packages/react-db/dist/esm/useLiveSuspenseQuery.js 559 B
./packages/react-db/dist/esm/usePacedMutations.js 401 B

compressed-size-action::react-db-package-size

…iber gate

notifyOnline() on OfflineExecutor was broken after online-gating changes
(execution loop bails when detector reports offline). Rather than adding
a force-bypass mechanism, remove the method — the OnlineDetector handles
connectivity changes automatically. Users who need imperative control
can build a custom OnlineDetector.

Also removes the redundant isOnline() guard in the subscriber callback
since the execution loop already gates on online status.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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.

1 participant