Skip to content

docs: clarify query cancellation semantics#1694

Merged
KyleAMathews merged 3 commits into
mainfrom
codex/pr-1692-review-evaluation
Jul 24, 2026
Merged

docs: clarify query cancellation semantics#1694
KyleAMathews merged 3 commits into
mainfrom
codex/pr-1692-review-evaluation

Conversation

@KyleAMathews

@KyleAMathews KyleAMathews commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

This documents how Query Collections interact with TanStack Query cancellation, observer removal, and cache cleanup. It also aligns the repository skills with the corrected lifecycle, mutation-refetch, and preload behavior.

Root cause

The earlier wording treated Query Collection's lack of an explicit cancelQueries() call during subset unload as proof that the request would continue. Query Core can still cancel the request when removing the subset's observer removes the final observer and the query function consumed its abort signal.

The cleanup wording also treated Query cache entries as collection-owned. Explicit cleanup only visits keys the collection still tracks, and exact Query keys are shared by all consumers of the same QueryClient.

Approach

  • Show how to forward QueryFunctionContext.signal to an abortable client.
  • Scope explicit cleanup to exact Query keys that the collection currently tracks.
  • State that unloaded subset keys are not revisited by later collection cleanup.
  • Warn that cleanup can affect other consumers of the same exact Query keys.
  • Explain that subset unload removes an observer rather than calling cancelQueries(), while noting when Query Core will still abort the request.
  • Teach repository skills to forward ctx.signal and distinguish eager collection preload from on-demand live-query preload.
  • Remove redundant manual refetches from Query Collection handler examples because the adapter automatically refetches and awaits the result.

Key invariants

  • Explicit cleanup cancels and removes only currently tracked exact Query keys.
  • Final-observer removal aborts a request when the query function consumed ctx.signal.
  • Ignoring the signal, or retaining another observer for the same key, can allow the request to finish and remain cached until gcTime.
  • Query cache identity belongs to the QueryClient and exact Query key, not to one collection.

Non-goals

  • No runtime behavior changes.
  • No new cancellation or lease policy.
  • No changeset; this is documentation-only.

Trade-offs

The documentation describes the current tracking model instead of changing cleanup to retain unloaded key metadata. Changing that lifecycle policy would require a separate design and implementation review.

Verification

Run for this branch:

pnpm vitest run tests/query.test.ts -t 'should call refetch based on handler return value|should transition to ready immediately in on-demand mode without loading data|query cancellation and subset cleanup lifecycle' --pool-options.threads.maxThreads=2 --typecheck.enabled=false
pnpm prettier --check docs/collections/query-collection.md packages/db/skills/db-core/collection-setup/SKILL.md packages/db/skills/db-core/collection-setup/references/query-adapter.md packages/db/skills/db-core/mutations-optimistic/SKILL.md packages/db/skills/meta-framework/SKILL.md
git diff --check

The focused suite passed 8 tests.

The original PR also listed these broader repository checks:

pnpm test:docs
pnpm lint
pnpm test:sherif
pnpm build
pnpm --filter @tanstack/query-db-collection test

Files changed

  • docs/collections/query-collection.md — adds and corrects Query Collection cancellation and cleanup guidance.
  • packages/db/skills/db-core/collection-setup/SKILL.md — forwards request signals and routes detailed cancellation guidance.
  • packages/db/skills/db-core/collection-setup/references/query-adapter.md — documents cancellation, tracked-key cleanup, shared cache entries, and automatic mutation refetch.
  • packages/db/skills/db-core/mutations-optimistic/SKILL.md — removes contradictory manual-refetch advice.
  • packages/db/skills/meta-framework/SKILL.md — distinguishes eager source preload from on-demand live-query preload.

Closes #350

Related design work: #1657 and #1573

Summary by CodeRabbit

  • Documentation
    • Added guidance for canceling in-flight collection requests using an AbortSignal provided to query functions, including forwarding the signal to fetch.
    • Clarified cleanup behavior: how cancellation relates to tracked keys, unloading/on-demand subsets, and cases where shared client/key consumers may be impacted.
    • Updated Query Collection guidance to reflect automatic, awaited refetching after mutations and to discourage manual refetch calls inside handlers.
    • Expanded preload semantics for eager vs on-demand collections, including the recommended liveQuery preload flow.

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds documentation for abortable Query Collection requests, cleanup and unloading semantics, automatic mutation refetching, and preload behavior for eager and on-demand collections.

Changes

Query collection lifecycle documentation

Layer / File(s) Summary
Cancellation and adapter behavior
docs/collections/query-collection.md, packages/db/skills/db-core/collection-setup/SKILL.md, packages/db/skills/db-core/collection-setup/references/query-adapter.md
Documents forwarding ctx.signal to requests, cleanup cancellation boundaries, shared query-key effects, persistence-triggered refetching, and on-demand preload behavior.
Automatic mutation refetch
packages/db/skills/db-core/mutations-optimistic/SKILL.md
Explains that Query Collection awaits automatic refetch after mutation handlers and removes guidance to manually call collection.utils.refetch().
Eager and on-demand preload strategies
packages/db/skills/meta-framework/SKILL.md
Distinguishes eager collection preloading from the required liveQuery.preload() flow for on-demand sources.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

  • TanStack/db#1667: Documents QueryClient lifecycle and scoping behavior related to shared query cleanup semantics.
  • TanStack/db#1692: Contains overlapping QueryFunctionContext signal and on-demand subset unloading documentation.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The docs cover ctx.signal usage, cleanup abort behavior, subset unload/observer semantics, and the shared QueryClient/key caveat from #350.
Out of Scope Changes check ✅ Passed The edits stay within documentation and closely related skill docs; no unrelated runtime or API changes appear.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed The title is concise and accurately reflects the main change: documenting query cancellation semantics.
Description check ✅ Passed The description covers the change, motivation, verification, and release impact, though it does not use the exact template headings.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/pr-1692-review-evaluation

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.

@pkg-pr-new

pkg-pr-new Bot commented Jul 24, 2026

Copy link
Copy Markdown
More templates

@tanstack/angular-db

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

@tanstack/browser-db-sqlite-persistence

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

@tanstack/capacitor-db-sqlite-persistence

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

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

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

@tanstack/db

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

@tanstack/db-ivm

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

@tanstack/db-sqlite-persistence-core

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

@tanstack/electric-db-collection

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

@tanstack/electron-db-sqlite-persistence

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

@tanstack/expo-db-sqlite-persistence

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

@tanstack/node-db-sqlite-persistence

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

@tanstack/offline-transactions

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

@tanstack/powersync-db-collection

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

@tanstack/query-db-collection

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

@tanstack/react-db

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

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

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

@tanstack/rxdb-db-collection

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

@tanstack/solid-db

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

@tanstack/svelte-db

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

@tanstack/tauri-db-sqlite-persistence

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

@tanstack/trailbase-db-collection

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

@tanstack/vue-db

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

commit: 50be907

@github-actions

Copy link
Copy Markdown
Contributor

Size Change: 0 B

Total Size: 125 kB

ℹ️ View Unchanged
Filename Size
packages/db/dist/esm/collection/change-events.js 1.43 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/index.js 3.62 kB
packages/db/dist/esm/collection/indexes.js 1.99 kB
packages/db/dist/esm/collection/lifecycle.js 1.69 kB
packages/db/dist/esm/collection/mutations.js 2.47 kB
packages/db/dist/esm/collection/state.js 5.48 kB
packages/db/dist/esm/collection/subscription.js 3.74 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 5.1 kB
packages/db/dist/esm/event-emitter.js 748 B
packages/db/dist/esm/index.js 3.16 kB
packages/db/dist/esm/indexes/auto-index.js 829 B
packages/db/dist/esm/indexes/base-index.js 784 B
packages/db/dist/esm/indexes/basic-index.js 2.17 kB
packages/db/dist/esm/indexes/btree-index.js 2.29 kB
packages/db/dist/esm/indexes/index-registry.js 820 B
packages/db/dist/esm/indexes/reverse-index.js 557 B
packages/db/dist/esm/live-query-adapter.js 318 B
packages/db/dist/esm/local-only.js 916 B
packages/db/dist/esm/local-storage.js 2.12 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 1.47 kB
packages/db/dist/esm/query/builder/index.js 5.84 kB
packages/db/dist/esm/query/builder/ref-proxy.js 1.24 kB
packages/db/dist/esm/query/compiler/evaluators.js 1.89 kB
packages/db/dist/esm/query/compiler/expressions.js 430 B
packages/db/dist/esm/query/compiler/group-by.js 3.56 kB
packages/db/dist/esm/query/compiler/index.js 6.67 kB
packages/db/dist/esm/query/compiler/joins.js 2.5 kB
packages/db/dist/esm/query/compiler/lazy-targets.js 923 B
packages/db/dist/esm/query/compiler/order-by.js 1.74 kB
packages/db/dist/esm/query/compiler/select.js 1.53 kB
packages/db/dist/esm/query/effect.js 4.77 kB
packages/db/dist/esm/query/expression-helpers.js 1.43 kB
packages/db/dist/esm/query/ir.js 1.25 kB
packages/db/dist/esm/query/live-query-collection.js 360 B
packages/db/dist/esm/query/live/collection-config-builder.js 9.1 kB
packages/db/dist/esm/query/live/collection-registry.js 264 B
packages/db/dist/esm/query/live/collection-subscriber.js 1.93 kB
packages/db/dist/esm/query/live/internal.js 145 B
packages/db/dist/esm/query/live/utils.js 1.81 kB
packages/db/dist/esm/query/optimizer.js 2.92 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 3.04 kB
packages/db/dist/esm/utils.js 927 B
packages/db/dist/esm/utils/array-utils.js 273 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.15 kB
packages/db/dist/esm/utils/cursor.js 457 B
packages/db/dist/esm/utils/index-optimization.js 2.39 kB
packages/db/dist/esm/utils/type-guards.js 157 B
packages/db/dist/esm/utils/uuid.js 449 B
packages/db/dist/esm/virtual-props.js 360 B

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/collections/query-collection.md`:
- Around line 288-293: Update the on-demand subset unloading documentation to
distinguish remaining-observer behavior from ignored-signal behavior: explain
that another observer keeps the query active until the final observer is
removed, while an unobserved request whose signal is ignored may complete and
remain inactive until gcTime.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: eb988b21-7a06-4381-bf4c-1a0e011f9e8b

📥 Commits

Reviewing files that changed from the base of the PR and between 309a36d and 9d567c7.

📒 Files selected for processing (1)
  • docs/collections/query-collection.md

Comment on lines +288 to +293
On-demand subset unloading does not explicitly call
`queryClient.cancelQueries()`. It removes the subset's Query observer. If this
was the final observer and the query function consumed `ctx.signal`, TanStack
Query aborts the request. If the signal was ignored, or another observer still
uses the same exact Query key, the request may finish and remain cached until
`gcTime`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Files matching query-collection.md:\n'
fd -a 'query-collection\.md$' . || true

file="$(fd 'query-collection\.md$' . | head -n1)"
if [ -n "${file:-}" ]; then
  printf '\nLine count:\n'
  wc -l "$file"
  printf '\nRelevant section:\n'
  sed -n '270,310p' "$file" | cat -n
fi

printf '\nNearby query cancellation docs mentions:\n'
rg -n "gcTime|cancelQueries|Query observer|observer|remaining observer|active" docs || true

# Inspect package metadata for TanStack Query version/doc source if relevant
printf '\nLock/package TanStack Query references:\n'
rg -n "`@tanstack/react-query`|`@tanstack/query-core`|tanstack/query|query-cancellation|gcTime" package.json pnpm-lock.yaml yarn.lock package-lock.json 2>/dev/null || true

Repository: TanStack/db

Length of output: 39251


🌐 Web query:

TanStack Query 5 observer lifecycle gcTime when query becomes inactive documentation

💡 Result:

In TanStack Query v5, a query becomes inactive when it no longer has any active observers (e.g., all components using a hook like useQuery have unmounted) [1][2][3]. Once a query transitions to the inactive state, TanStack Query begins a garbage collection countdown based on the query's gcTime configuration [2][3]. Key aspects of this lifecycle include: 1. Inactive State: A query is considered inactive when it is present in the cache but is not currently being used by any observers [1][3]. Data persists in the cache in this state, allowing for instant reuse if the query becomes active again before the garbage collection timeout expires [2][3]. 2. gcTime (Garbage Collection Time): Formerly known as cacheTime in earlier versions, gcTime defines the duration (in milliseconds) that inactive query data remains in memory before being garbage collected and deleted [4][5][6]. 3. Default Behavior: The default gcTime is 5 minutes (5 * 60 * 1000 milliseconds) [1][6]. During Server-Side Rendering (SSR), this defaults to Infinity to prevent premature data removal [6]. 4. Cleanup Process: If no new observers mount for that query key before the gcTime expires, the query is removed from the cache entirely [2][6]. If a new observer mounts while the query is still inactive (i.e., before the timer expires), the garbage collection process is cancelled, and the query returns to an active state [2]. If multiple instances of a query exist with different gcTime settings, the library uses the longest duration to ensure data availability [6]. You can set gcTime to Infinity to disable garbage collection for a specific query [6].

Citations:


Separate active-observer behavior from gcTime.

A case with another observer on the same Query key is different from ignored-signal behavior. With remaining observers, the query stays active until the last observer is removed; with an ignored signal, the unobserved request can finish and only later reach gcTime while the data is inactive.

Suggested wording
-On-demand subset unloading does not explicitly call
-`queryClient.cancelQueries()`. It removes the subset's Query observer. If
-this was the final observer and the query function consumed `ctx.signal`,
-TanStack Query aborts the request. If the signal was ignored, or another
-observer still uses the same exact Query key, the request may finish and remain
-cached until `gcTime`.
+On-demand subset unloading does not explicitly call
+`queryClient.cancelQueries()`. It removes the subset's Query observer. If
+this was the final observer and the query function consumed `ctx.signal`,
+TanStack Query aborts the request. If the signal was ignored, the request may
+finish and remain cached until the query becomes inactive and reaches `gcTime`.
+If another observer still uses the same exact Query key, the query remains
+active until that observer is also removed.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
On-demand subset unloading does not explicitly call
`queryClient.cancelQueries()`. It removes the subset's Query observer. If this
was the final observer and the query function consumed `ctx.signal`, TanStack
Query aborts the request. If the signal was ignored, or another observer still
uses the same exact Query key, the request may finish and remain cached until
`gcTime`.
On-demand subset unloading does not explicitly call
`queryClient.cancelQueries()`. It removes the subset's Query observer. If this
was the final observer and the query function consumed `ctx.signal`, TanStack
Query aborts the request. If the signal was ignored, the request may finish
and remain cached until the query becomes inactive and reaches `gcTime`. If
another observer still uses the same exact Query key, the query remains
active until that observer is also removed.
🧰 Tools
🪛 LanguageTool

[style] ~292-~292: This phrase is redundant. Consider writing “same”.
Context: ...red, or another observer still uses the same exact Query key, the request may finish and r...

(SAME_EXACT)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/collections/query-collection.md` around lines 288 - 293, Update the
on-demand subset unloading documentation to distinguish remaining-observer
behavior from ignored-signal behavior: explain that another observer keeps the
query active until the final observer is removed, while an unobserved request
whose signal is ignored may complete and remain inactive until gcTime.

Source: MCP tools

@github-actions

Copy link
Copy Markdown
Contributor

Size Change: 0 B

Total Size: 4.22 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.33 kB
packages/react-db/dist/esm/useLiveQueryEffect.js 355 B
packages/react-db/dist/esm/useLiveSuspenseQuery.js 567 B
packages/react-db/dist/esm/usePacedMutations.js 401 B

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
packages/db/skills/meta-framework/SKILL.md (1)

76-104: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Make the source collection’s on-demand mode explicit.

This example reuses todoCollection without showing or stating that it was created with syncMode: "on-demand", so readers may not understand why direct preload() is a no-op. Add the configuration or a concise comment linking to it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/db/skills/meta-framework/SKILL.md` around lines 76 - 104, Update the
“On-demand Query Collection preload” example to explicitly show that
todoCollection is created with syncMode: "on-demand", or add a concise comment
linking that configuration to the preload behavior. Keep the existing
activeTodos, loader, and useLiveQuery flow unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/db/skills/meta-framework/SKILL.md`:
- Around line 76-104: Update the “On-demand Query Collection preload” example to
explicitly show that todoCollection is created with syncMode: "on-demand", or
add a concise comment linking that configuration to the preload behavior. Keep
the existing activeTodos, loader, and useLiveQuery flow unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5530cb53-c421-47cf-a873-6793535dddc4

📥 Commits

Reviewing files that changed from the base of the PR and between 9d567c7 and 50be907.

📒 Files selected for processing (4)
  • packages/db/skills/db-core/collection-setup/SKILL.md
  • packages/db/skills/db-core/collection-setup/references/query-adapter.md
  • packages/db/skills/db-core/mutations-optimistic/SKILL.md
  • packages/db/skills/meta-framework/SKILL.md

@KyleAMathews
KyleAMathews merged commit 38682e4 into main Jul 24, 2026
11 checks passed
@KyleAMathews
KyleAMathews deleted the codex/pr-1692-review-evaluation branch July 24, 2026 21:49
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.

Document QueryFunctionContext signal and cancellation semantics

2 participants