Skip to content

fix: defer query refresh until pending offline transactions complete#1390

Merged
samwillis merged 14 commits intomainfrom
offline-tx-refresh
Mar 21, 2026
Merged

fix: defer query refresh until pending offline transactions complete#1390
samwillis merged 14 commits intomainfrom
offline-tx-refresh

Conversation

@kevin-dp
Copy link
Contributor

@kevin-dp kevin-dp commented Mar 19, 2026

Fixes #1323

Summary

  • Adds an integration test reproducing the race condition where a query-backed collection reverts to stale server state when coming online with pending offline transactions
  • The fix (to be committed separately) adds a deferDataRefresh barrier on the Collection that the offline executor sets when replaying pending transactions, and that the query collection checks before processing query results

Test plan

  • New e2e test offline-refresh.e2e.test.ts reproduces the bug (fails without fix, passes with fix)
  • Existing query.test.ts tests pass (170/170)
  • Existing offline-e2e.test.ts tests pass

🤖 Generated with Claude Code

@pkg-pr-new
Copy link

pkg-pr-new bot commented Mar 19, 2026

More templates

@tanstack/angular-db

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

@tanstack/db

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

@tanstack/db-browser-wa-sqlite-persisted-collection

npm i https://pkg.pr.new/TanStack/db/@tanstack/db-browser-wa-sqlite-persisted-collection@1390

@tanstack/db-capacitor-sqlite-persisted-collection

npm i https://pkg.pr.new/TanStack/db/@tanstack/db-capacitor-sqlite-persisted-collection@1390

@tanstack/db-cloudflare-do-sqlite-persisted-collection

npm i https://pkg.pr.new/TanStack/db/@tanstack/db-cloudflare-do-sqlite-persisted-collection@1390

@tanstack/db-electron-sqlite-persisted-collection

npm i https://pkg.pr.new/TanStack/db/@tanstack/db-electron-sqlite-persisted-collection@1390

@tanstack/db-expo-sqlite-persisted-collection

npm i https://pkg.pr.new/TanStack/db/@tanstack/db-expo-sqlite-persisted-collection@1390

@tanstack/db-ivm

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

@tanstack/db-node-sqlite-persisted-collection

npm i https://pkg.pr.new/TanStack/db/@tanstack/db-node-sqlite-persisted-collection@1390

@tanstack/db-react-native-sqlite-persisted-collection

npm i https://pkg.pr.new/TanStack/db/@tanstack/db-react-native-sqlite-persisted-collection@1390

@tanstack/db-sqlite-persisted-collection-core

npm i https://pkg.pr.new/TanStack/db/@tanstack/db-sqlite-persisted-collection-core@1390

@tanstack/db-tauri-sqlite-persisted-collection

npm i https://pkg.pr.new/TanStack/db/@tanstack/db-tauri-sqlite-persisted-collection@1390

@tanstack/electric-db-collection

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

@tanstack/offline-transactions

npm i https://pkg.pr.new/TanStack/db/@tanstack/offline-transactions@1390

@tanstack/powersync-db-collection

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

@tanstack/query-db-collection

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

@tanstack/react-db

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

@tanstack/rxdb-db-collection

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

@tanstack/solid-db

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

@tanstack/svelte-db

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

@tanstack/trailbase-db-collection

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

@tanstack/vue-db

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

commit: 3fe0b77

@github-actions
Copy link
Contributor

github-actions bot commented Mar 19, 2026

Size Change: +15 B (+0.01%)

Total Size: 111 kB

Filename Size Change
./packages/db/dist/esm/collection/index.js 3.7 kB +15 B (+0.41%)
ℹ️ View Unchanged
Filename Size
./packages/db/dist/esm/collection/change-events.js 1.39 kB
./packages/db/dist/esm/collection/changes.js 1.38 kB
./packages/db/dist/esm/collection/cleanup-queue.js 810 B
./packages/db/dist/esm/collection/events.js 434 B
./packages/db/dist/esm/collection/indexes.js 2.35 kB
./packages/db/dist/esm/collection/lifecycle.js 1.76 kB
./packages/db/dist/esm/collection/mutations.js 2.47 kB
./packages/db/dist/esm/collection/state.js 5.26 kB
./packages/db/dist/esm/collection/subscription.js 3.71 kB
./packages/db/dist/esm/collection/sync.js 2.88 kB
./packages/db/dist/esm/collection/transaction-metadata.js 144 B
./packages/db/dist/esm/deferred.js 207 B
./packages/db/dist/esm/errors.js 4.83 kB
./packages/db/dist/esm/event-emitter.js 748 B
./packages/db/dist/esm/index.js 2.85 kB
./packages/db/dist/esm/indexes/auto-index.js 777 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.24 kB
./packages/db/dist/esm/indexes/reverse-index.js 538 B
./packages/db/dist/esm/local-only.js 890 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 792 B
./packages/db/dist/esm/query/builder/index.js 5.15 kB
./packages/db/dist/esm/query/builder/ref-proxy.js 1.05 kB
./packages/db/dist/esm/query/compiler/evaluators.js 1.62 kB
./packages/db/dist/esm/query/compiler/expressions.js 430 B
./packages/db/dist/esm/query/compiler/group-by.js 2.69 kB
./packages/db/dist/esm/query/compiler/index.js 3.62 kB
./packages/db/dist/esm/query/compiler/joins.js 2.11 kB
./packages/db/dist/esm/query/compiler/order-by.js 1.5 kB
./packages/db/dist/esm/query/compiler/select.js 1.11 kB
./packages/db/dist/esm/query/effect.js 4.78 kB
./packages/db/dist/esm/query/expression-helpers.js 1.43 kB
./packages/db/dist/esm/query/ir.js 784 B
./packages/db/dist/esm/query/live-query-collection.js 360 B
./packages/db/dist/esm/query/live/collection-config-builder.js 7.63 kB
./packages/db/dist/esm/query/live/collection-registry.js 264 B
./packages/db/dist/esm/query/live/collection-subscriber.js 1.94 kB
./packages/db/dist/esm/query/live/internal.js 145 B
./packages/db/dist/esm/query/live/utils.js 1.57 kB
./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/query-once.js 359 B
./packages/db/dist/esm/query/subset-dedupe.js 960 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 927 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 1.05 kB
./packages/db/dist/esm/utils/cursor.js 457 B
./packages/db/dist/esm/utils/index-optimization.js 1.54 kB
./packages/db/dist/esm/utils/type-guards.js 157 B
./packages/db/dist/esm/virtual-props.js 360 B

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

@github-actions
Copy link
Contributor

github-actions bot commented Mar 19, 2026

Size Change: 0 B

Total Size: 4.23 kB

ℹ️ View Unchanged
Filename Size
./packages/react-db/dist/esm/index.js 249 B
./packages/react-db/dist/esm/useLiveInfiniteQuery.js 1.32 kB
./packages/react-db/dist/esm/useLiveQuery.js 1.34 kB
./packages/react-db/dist/esm/useLiveQueryEffect.js 355 B
./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

@kevin-dp kevin-dp force-pushed the offline-tx-refresh branch from 41f3a33 to d33025d Compare March 19, 2026 14:38
kevin-dp and others added 7 commits March 19, 2026 16:24
…h race

Adds an integration test that reproduces the race condition where a
query-backed collection reverts to stale server state when coming back
online with pending offline transactions. The queryFn refetch returns
pre-mutation data before the offline transaction reaches the server,
and when the transaction completes the optimistic state is cleaned up
with nothing to replace it.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The e2e tests import @tanstack/offline-transactions which may not be
built during the default `pnpm test` CI step. Adds a vitest.config.ts
that excludes the e2e directory from the default run while preserving
typecheck on the regular test files.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@kevin-dp kevin-dp force-pushed the offline-tx-refresh branch from 9d4914b to 0d389b1 Compare March 19, 2026 15:26
kevin-dp and others added 4 commits March 19, 2026 16:30
The new e2e test imports @tanstack/offline-transactions, which needs
to be built before the query e2e tests run.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When coming back online with pending offline transactions, query-backed
collections would refetch stale server state before the mutations
reached the server. After the transaction completed, the optimistic
state was cleaned up but syncedData still had the stale data, causing
items to temporarily disappear.

The fix adds a `deferDataRefresh` property on Collection that the
offline executor sets while replaying pending transactions. The
query-db-collection checks this barrier in handleQueryResult: stale
results are skipped, and a fresh refetch is triggered once the
barrier resolves.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Collaborator

@samwillis samwillis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit: just needs a changeset

@samwillis samwillis merged commit 85f5435 into main Mar 21, 2026
7 checks passed
@samwillis samwillis deleted the offline-tx-refresh branch March 21, 2026 12:44
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.

Race between transactions + query refresh when coming online

2 participants