Skip to content

fix(query-db): compose accepted ownership before publication - #2198

Merged
KyleAMathews merged 2 commits into
mainfrom
codex/fix-refetch-ordered-prefix-oracle
Oct 18, 2026
Merged

KyleAMathews merged 2 commits into
mainfrom
codex/fix-refetch-ordered-prefix-oracle

Conversation

@KyleAMathews

@KyleAMathews KyleAMathews commented Oct 18, 2026 •

Copy link
Copy Markdown
Collaborator

Why this matters

An ordered refetch could retain a backend-absent row after another Query observed that row. The failure required an optimistic mutation to delay publication.

PR #2030 introduced the accepted-before-publication interval. PR #2154 exposed the regression through its ordered-prefix oracle.

The failing CI replay showed the concrete result. The ordered window disagreed with the backend after publication resumed.

Cause

The Query ownership indexes accepted newer results before the Collection published their metadata. Cleanup could then rebuild ownership from older published metadata.

That stale metadata could restore an owner that an accepted result retired. An older cleanup could also invalidate ownership from a later reacquisition.

The same authority problem made cold restore depend on row order. Processing one row could make a partially rebuilt Query appear fully resolved.

Repair

Resolved Query ownership now takes precedence over persisted ownership. Persisted metadata seeds only Queries that do not yet have a resolved ownership result.

Each accepted result installs a new ownership generation. Cleanup removes its accepted edges immediately and keeps an empty tombstone until publication completes.

The tombstone retires only when it still belongs to that cleanup. Therefore, an older cleanup cannot invalidate a later acquisition.

Cold persisted and hydrated rows project their owners before they update the ownership indexes. This makes reconstruction independent of row order.

This repair reuses the existing ownership indexes. Production source changes by 54 additions and 60 deletions.

Excluding comments and blank lines, production code changes by 49 additions and 53 deletions.

Oracle model

The ownership oracle does not copy the production maps or cleanup algorithm. Its model stores each active Query's latest complete result.

The expected public Collection is the union of those results. The histories compare that model with public rows and durable effects at defined checkpoints.

New histories cover these cases:

  • Three overlapping owners accept two empty results while an optimistic mutation delays publication.
  • A Query releases and reacquires before its older cleanup publishes.
  • A cold two-row result restores in both persisted scan orders.
  • A hydrated two-row result restores in both hydration orders.
  • The original ordered-refetch history excludes a backend-absent row after publication resumes.

The oracle documentation records the covered histories and the remaining limits.

Verification

  • pnpm vitest run packages/query-db-collection/tests/ownership-lifecycle.oracle.test.ts packages/query-db-collection/tests/refetch-ordered-prefix-oracle.test.ts --pool-options.threads.maxThreads=2 passed 379 tests.
  • The exact query-db.refetch-ordered-prefix CI replay passed with seed -1338032236 and its recorded path.
  • pnpm --filter @tanstack/query-db-collection build passed.
  • The Prettier check and git diff --check passed.

Summary by CodeRabbit

  • Bug Fixes
    • Improved query cleanup during overlapping refetches and observer changes, helping prevent outdated results from removing data that is still needed.
    • Updated complete query results more reliably when replacing persisted or hydrated data, including removing rows omitted from the latest result.
    • Preserved refreshed results when an invalidation and refetch overlap with an optimistic update.

@changeset-bot

changeset-bot Bot commented Oct 18, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: de1ea0d

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

This PR includes changesets to release 1 package
Name Type
@tanstack/query-db-collection 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

@coderabbitai

coderabbitai Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 9291fda4-f97b-474c-937f-8b47a9657e58
📥 Commits

Reviewing files that changed from the base of the PR and between 7d60818 and de1ea0d.

📒 Files selected for processing: 5
  • .changeset/fix-refetch-query-ownership.md
  • docs/contributing/oracle-coverage.md
  • packages/query-db-collection/src/query.ts
  • packages/query-db-collection/tests/ownership-lifecycle.oracle.test.ts
  • packages/query-db-collection/tests/refetch-ordered-prefix-oracle.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review.

📝 Walkthrough

Query ownership resolution and cleanup now account for persisted ownership, complete query results, and overlapping refetch and observer lifecycles. Added oracle tests cover ownership reconstruction, delayed publication, reacquisition, and refetch ordering.

Changes: Query ownership lifecycle

Layer / File(s) Summary
Resolve and reconstruct query ownership
packages/query-db-collection/src/query.ts, packages/query-db-collection/tests/ownership-lifecycle.oracle.test.ts, docs/contributing/oracle-coverage.md
Ownership resolution combines persisted and in-memory claims. Persisted owner sets are snapshotted before reconstruction. Successful query results initialize the ownership baseline from previously owned rows. Tests cover cold scans and hydration in both row orders.
Retire ownership across query lifecycles
packages/query-db-collection/src/query.ts, packages/query-db-collection/tests/ownership-lifecycle.oracle.test.ts, packages/query-db-collection/tests/refetch-ordered-prefix-oracle.test.ts, docs/contributing/oracle-coverage.md, .changeset/fix-refetch-query-ownership.md
Cleanup updates ownership maps and retires ownership after transaction application. Tests cover delayed releases, reacquisition, accepted empty results, and refetch ordering. The changeset describes the ownership cleanup fix.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix

Suggested reviewers: samwillis

Merge Risk: ⚪ Minimal · up to de1ea

No change-specific issue remains identified before merge; normal checks still apply.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 3 files. (2 skipped: 2 … Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Title check Passed The title is concise, specific, and accurately describes the main ownership-composition fix.
Description check Passed The description provides detailed motivation, cause, repair, oracle coverage, and verification results. It does not use the template headings or include the required Checklist and Release Impact secti…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Description check

Explanation

The description provides detailed motivation, cause, repair, oracle coverage, and verification results. It does not use the template headings or include the required Checklist and Release Impact sections, but the substantive information is mostly complete.

Full details: Docstring Coverage

Explanation

Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 3 files. (2 skipped: 2 unsupported.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

  • Fix all pre-merge checks with AI
✨ Finishing Touches: 💡 1
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

Size Change: 0 B

Total Size: 218 kB

ℹ️ View Unchanged
Filename Size
packages/db/dist/esm/client.js 4.22 kB
packages/db/dist/esm/collection-options-brand.js 219 B
packages/db/dist/esm/collection-options.js 258 B
packages/db/dist/esm/collection/change-events.js 1.76 kB
packages/db/dist/esm/collection/changes.js 2.96 kB
packages/db/dist/esm/collection/cleanup-queue.js 901 B
packages/db/dist/esm/collection/config-errors.js 547 B
packages/db/dist/esm/collection/events.js 481 B
packages/db/dist/esm/collection/index.js 5.27 kB
packages/db/dist/esm/collection/indexes.js 2.3 kB
packages/db/dist/esm/collection/lifecycle.js 2.94 kB
packages/db/dist/esm/collection/mutations.js 2.99 kB
packages/db/dist/esm/collection/state.js 7.16 kB
packages/db/dist/esm/collection/subscription.js 8.9 kB
packages/db/dist/esm/collection/sync.js 5.4 kB
packages/db/dist/esm/collection/validate-config.js 1.17 kB
packages/db/dist/esm/deferred.js 207 B
packages/db/dist/esm/error-message.js 642 B
packages/db/dist/esm/errors.js 6.53 kB
packages/db/dist/esm/event-emitter.js 1.06 kB
packages/db/dist/esm/index.js 4.52 kB
packages/db/dist/esm/indexed-db-wrapper.js 1.91 kB
packages/db/dist/esm/indexed-db.js 3.7 kB
packages/db/dist/esm/indexes/auto-index.js 924 B
packages/db/dist/esm/indexes/base-index.js 1.92 kB
packages/db/dist/esm/indexes/basic-index.js 1.93 kB
packages/db/dist/esm/indexes/btree-index.js 1.62 kB
packages/db/dist/esm/indexes/index-registry.js 916 B
packages/db/dist/esm/indexes/reverse-index.js 1.01 kB
packages/db/dist/esm/live-query-adapter.js 338 B
packages/db/dist/esm/live-query-observer.js 4.8 kB
packages/db/dist/esm/live-query-options.js 1.41 kB
packages/db/dist/esm/live-query-window-controller.js 4.27 kB
packages/db/dist/esm/local-only.js 1.08 kB
packages/db/dist/esm/local-storage.js 3.93 kB
packages/db/dist/esm/optimistic-action.js 359 B
packages/db/dist/esm/paced-mutations.js 1.11 kB
packages/db/dist/esm/persisted-readiness.js 195 B
packages/db/dist/esm/proxy.js 3.51 kB
packages/db/dist/esm/query/builder/clone-query.js 849 B
packages/db/dist/esm/query/builder/functions.js 1.56 kB
packages/db/dist/esm/query/builder/index.js 7.48 kB
packages/db/dist/esm/query/builder/query-ir.js 312 B
packages/db/dist/esm/query/builder/ref-proxy-identity.js 292 B
packages/db/dist/esm/query/builder/ref-proxy.js 1.67 kB
packages/db/dist/esm/query/builder/wrapper-identity.js 221 B
packages/db/dist/esm/query/compiler/evaluators.js 2.51 kB
packages/db/dist/esm/query/compiler/expressions.js 603 B
packages/db/dist/esm/query/compiler/group-by.js 4.48 kB
packages/db/dist/esm/query/compiler/index.js 10.3 kB
packages/db/dist/esm/query/compiler/joins.js 3.42 kB
packages/db/dist/esm/query/compiler/lazy-targets.js 1.11 kB
packages/db/dist/esm/query/compiler/order-by.js 2.8 kB
packages/db/dist/esm/query/compiler/parent-routes.js 319 B
packages/db/dist/esm/query/compiler/query-equivalence.js 568 B
packages/db/dist/esm/query/compiler/route-metadata.js 1.24 kB
packages/db/dist/esm/query/compiler/select.js 1.5 kB
packages/db/dist/esm/query/effect.js 5.36 kB
packages/db/dist/esm/query/equality-conjunct.js 486 B
packages/db/dist/esm/query/equality-value-identity.js 735 B
packages/db/dist/esm/query/expression-helpers.js 1.61 kB
packages/db/dist/esm/query/ir-stable-identity.js 4.2 kB
packages/db/dist/esm/query/ir.js 2.2 kB
packages/db/dist/esm/query/join-conditions.js 257 B
packages/db/dist/esm/query/live-query-collection.js 391 B
packages/db/dist/esm/query/live/bucket-facade-adapter.js 3.17 kB
packages/db/dist/esm/query/live/collection-config-builder.js 6.84 kB
packages/db/dist/esm/query/live/collection-registry.js 264 B
packages/db/dist/esm/query/live/collection-subscriber.js 2.28 kB
packages/db/dist/esm/query/live/graph-scheduler.js 305 B
packages/db/dist/esm/query/live/internal.js 145 B
packages/db/dist/esm/query/live/materialized-pipeline.js 2.39 kB
packages/db/dist/esm/query/live/ordered-source-loader.js 5.35 kB
packages/db/dist/esm/query/live/subset-demand-controller.js 1.73 kB
packages/db/dist/esm/query/live/utils.js 1.35 kB
packages/db/dist/esm/query/optimizer.js 2.95 kB
packages/db/dist/esm/query/pooled-live-query.js 4.13 kB
packages/db/dist/esm/query/query-once.js 359 B
packages/db/dist/esm/query/runtime-reference-identity.js 630 B
packages/db/dist/esm/query/subset-dedupe.js 493 B
packages/db/dist/esm/scheduler.js 1.23 kB
packages/db/dist/esm/SortedMap.js 1.6 kB
packages/db/dist/esm/strategies/commit-completion.js 156 B
packages/db/dist/esm/strategies/debounceStrategy.js 658 B
packages/db/dist/esm/strategies/queueStrategy.js 523 B
packages/db/dist/esm/strategies/serial-pacer.js 486 B
packages/db/dist/esm/strategies/throttleStrategy.js 738 B
packages/db/dist/esm/sync-persistence.js 626 B
packages/db/dist/esm/sync-receipt.js 211 B
packages/db/dist/esm/transaction-commit-work.js 351 B
packages/db/dist/esm/transactions.js 4.61 kB
packages/db/dist/esm/utils.js 1.67 kB
packages/db/dist/esm/utils/array-utils.js 270 B
packages/db/dist/esm/utils/browser-polyfills.js 304 B
packages/db/dist/esm/utils/btree.js 3.02 kB
packages/db/dist/esm/utils/callbacks.js 174 B
packages/db/dist/esm/utils/comparison.js 1.94 kB
packages/db/dist/esm/utils/cursor.js 753 B
packages/db/dist/esm/utils/error.js 262 B
packages/db/dist/esm/utils/get-or-create.js 155 B
packages/db/dist/esm/utils/index-optimization.js 2.58 kB
packages/db/dist/esm/utils/source-record.js 140 B
packages/db/dist/esm/utils/type-guards.js 230 B
packages/db/dist/esm/utils/uuid.js 530 B
packages/db/dist/esm/virtual-props.js 413 B
packages/react-db/dist/esm/DbProvider.js 317 B
packages/react-db/dist/esm/development.js 190 B
packages/react-db/dist/esm/HydrationBoundary.js 263 B
packages/react-db/dist/esm/index.js 330 B
packages/react-db/dist/esm/live-query-internals.js 282 B
packages/react-db/dist/esm/source-id-bindings.js 746 B
packages/react-db/dist/esm/useLiveInfiniteQuery.js 2.15 kB
packages/react-db/dist/esm/useLiveQuery.js 3.34 kB
packages/react-db/dist/esm/useLiveQueryEffect.js 504 B
packages/react-db/dist/esm/useLiveSuspenseQuery.js 1.33 kB
packages/react-db/dist/esm/usePacedMutations.js 429 B

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

@pkg-pr-new

pkg-pr-new Bot commented Oct 18, 2026

Copy link
Copy Markdown
More templates

@tanstack/angular-db

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

@tanstack/browser-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/browser-db-sqlite-persistence@2198

@tanstack/capacitor-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/capacitor-db-sqlite-persistence@2198

@tanstack/cloudflare-durable-objects-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/cloudflare-durable-objects-db-sqlite-persistence@2198

@tanstack/db

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

@tanstack/db-ivm

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

@tanstack/db-sqlite-persistence-core

npm i https://pkg.pr.new/@tanstack/db-sqlite-persistence-core@2198

@tanstack/electric-db-collection

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

@tanstack/electron-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/electron-db-sqlite-persistence@2198

@tanstack/expo-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/expo-db-sqlite-persistence@2198

@tanstack/node-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/node-db-sqlite-persistence@2198

@tanstack/offline-transactions

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

@tanstack/powersync-db-collection

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

@tanstack/query-db-collection

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

@tanstack/react-db

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

@tanstack/react-native-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/react-native-db-sqlite-persistence@2198

@tanstack/react-router-with-db

npm i https://pkg.pr.new/@tanstack/react-router-with-db@2198

@tanstack/rxdb-db-collection

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

@tanstack/solid-db

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

@tanstack/svelte-db

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

@tanstack/tauri-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/tauri-db-sqlite-persistence@2198

@tanstack/trailbase-db-collection

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

@tanstack/vue-db

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

commit: de1ea0d

@KyleAMathews
KyleAMathews merged commit db0ac81 into main Oct 18, 2026
25 checks passed
@KyleAMathews
KyleAMathews deleted the codex/fix-refetch-ordered-prefix-oracle branch October 18, 2026 16:43
@github-actions github-actions Bot mentioned this pull request Oct 18, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🎉 This PR has been released!

Thank you for your contribution!

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