Skip to content

fix(query-core): replaceEqualDeep max depth#10032

Merged
TkDodo merged 4 commits intoTanStack:mainfrom
Sheraff:fix-query-core-replace-equal-deep-max-depth
Jan 13, 2026
Merged

fix(query-core): replaceEqualDeep max depth#10032
TkDodo merged 4 commits intoTanStack:mainfrom
Sheraff:fix-query-core-replace-equal-deep-max-depth

Conversation

@Sheraff
Copy link
Copy Markdown
Contributor

@Sheraff Sheraff commented Jan 13, 2026

🎯 Changes

Add max depth for recursive replaceEqualDeep to avoid insanely deep objects (500 nested levels) slowing/freezing the main thread. Beyond 500, we just return the new object.

The function is still callable w/ the same parameters as before. It should have now impact on users, except for those that have insanely deep objects.

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested this code locally with pnpm run test:pr.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

Summary by CodeRabbit

  • Bug Fixes

    • Added a recursion depth safeguard to improve reliability when processing very deeply nested data, preventing excessive recursion or long-running operations.
  • Tests

    • Simplified test data to better validate the new depth-safety behavior.
  • Chores

    • Added a changelog entry for a patch release.

✏️ Tip: You can customize this high-level summary in your review settings.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Jan 13, 2026

🦋 Changeset detected

Latest commit: 7c287aa

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

This PR includes changesets to release 19 packages
Name Type
@tanstack/query-core Patch
@tanstack/angular-query-experimental Patch
@tanstack/query-async-storage-persister Patch
@tanstack/query-broadcast-client-experimental Patch
@tanstack/query-persist-client-core Patch
@tanstack/query-sync-storage-persister Patch
@tanstack/react-query Patch
@tanstack/solid-query Patch
@tanstack/svelte-query Patch
@tanstack/vue-query Patch
@tanstack/angular-query-persist-client Patch
@tanstack/react-query-persist-client Patch
@tanstack/solid-query-persist-client Patch
@tanstack/svelte-query-persist-client Patch
@tanstack/react-query-devtools Patch
@tanstack/react-query-next-experimental Patch
@tanstack/solid-query-devtools Patch
@tanstack/svelte-query-devtools Patch
@tanstack/vue-query-devtools 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
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 13, 2026

📝 Walkthrough

Walkthrough

Adds an optional depth parameter to replaceEqualDeep with a recursion cap (returns b when depth > 500) and propagates depth + 1 in recursive calls. Tests were simplified to use a small initialData object and updated prefetchQuery calls.

Changes

Cohort / File(s) Change Summary
Recursion safety enhancement
packages/query-core/src/utils.ts
replaceEqualDeep signature now accepts depth?: number (default 0); adds guard if (depth > 500) return b; passes depth + 1 on recursive calls.
Tests updated
packages/query-core/src/__tests__/query.test.tsx
Replaced complex initial test fixtures with initialData = { foo: 'bar' }; updated prefetchQuery usages to pass the simplified initialData.
Changelog
.changeset/swift-zoos-applaud.md
Added changelog entry: "fix(query-core): replaceEqualDeep max depth" (patch).

Sequence Diagram(s)

(omitted)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰
I count my hops through tangled trees,
Five hundred bounds then rest with ease,
If burrows curl too deep and steep,
I pause my digging, softly sleep,
Safe tunnels kept — a gentle breeze.

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning The description addresses the motivation and implementation of the change, but marks the changeset checkbox as unchecked despite a changeset file being present in the PR. Update the checklist to check the changeset box, as .changeset/swift-zoos-applaud.md has been added to the PR.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: adding a maximum depth limit to the replaceEqualDeep function.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 746c3fd and 7c287aa.

📒 Files selected for processing (1)
  • .changeset/swift-zoos-applaud.md
✅ Files skipped from review due to trivial changes (1)
  • .changeset/swift-zoos-applaud.md
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Test
  • GitHub Check: Preview

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

@nx-cloud
Copy link
Copy Markdown

nx-cloud Bot commented Jan 13, 2026

View your CI Pipeline Execution ↗ for commit 7c287aa

Command Status Duration Result
nx run-many --target=build --exclude=examples/*... ✅ Succeeded 2s View ↗
nx affected --targets=test:sherif,test:knip,tes... ✅ Succeeded 38s View ↗

☁️ Nx Cloud last updated this comment at 2026-01-13 17:46:40 UTC

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
packages/query-core/src/utils.ts (1)

273-274: Consider extracting the magic number to a named constant.

A named constant improves readability and makes the limit self-documenting and easier to adjust if needed.

♻️ Suggested refactor

Add near the top of the file (around line 89 with other constants):

const MAX_REPLACE_EQUAL_DEPTH = 500

Then update the guard:

-  if (depth > 500) return b
+  if (depth > MAX_REPLACE_EQUAL_DEPTH) return b
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 44c3cb9 and 367c2c4.

📒 Files selected for processing (1)
  • packages/query-core/src/utils.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Preview
  • GitHub Check: Test
🔇 Additional comments (3)
packages/query-core/src/utils.ts (3)

267-268: LGTM on the signature change.

Adding an optional parameter with a default value maintains backward compatibility. Existing callers will continue to work without modification.


308-308: LGTM on depth propagation.

Correctly increments depth on each recursive call to track nesting level.


267-314: Add tests for the depth limit behavior.

The replaceEqualDeep function includes a depth limit (depth > 500) to prevent stack overflow on deeply nested structures, but no tests exist to verify this critical safety feature. Add tests covering:

  • Behavior when depth reaches 500 vs exceeds 500
  • Deeply nested structures that trigger the limit
  • Structural sharing stops correctly at the boundary

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Jan 13, 2026

More templates

@tanstack/angular-query-experimental

npm i https://pkg.pr.new/@tanstack/angular-query-experimental@10032

@tanstack/eslint-plugin-query

npm i https://pkg.pr.new/@tanstack/eslint-plugin-query@10032

@tanstack/query-async-storage-persister

npm i https://pkg.pr.new/@tanstack/query-async-storage-persister@10032

@tanstack/query-broadcast-client-experimental

npm i https://pkg.pr.new/@tanstack/query-broadcast-client-experimental@10032

@tanstack/query-core

npm i https://pkg.pr.new/@tanstack/query-core@10032

@tanstack/query-devtools

npm i https://pkg.pr.new/@tanstack/query-devtools@10032

@tanstack/query-persist-client-core

npm i https://pkg.pr.new/@tanstack/query-persist-client-core@10032

@tanstack/query-sync-storage-persister

npm i https://pkg.pr.new/@tanstack/query-sync-storage-persister@10032

@tanstack/react-query

npm i https://pkg.pr.new/@tanstack/react-query@10032

@tanstack/react-query-devtools

npm i https://pkg.pr.new/@tanstack/react-query-devtools@10032

@tanstack/react-query-next-experimental

npm i https://pkg.pr.new/@tanstack/react-query-next-experimental@10032

@tanstack/react-query-persist-client

npm i https://pkg.pr.new/@tanstack/react-query-persist-client@10032

@tanstack/solid-query

npm i https://pkg.pr.new/@tanstack/solid-query@10032

@tanstack/solid-query-devtools

npm i https://pkg.pr.new/@tanstack/solid-query-devtools@10032

@tanstack/solid-query-persist-client

npm i https://pkg.pr.new/@tanstack/solid-query-persist-client@10032

@tanstack/svelte-query

npm i https://pkg.pr.new/@tanstack/svelte-query@10032

@tanstack/svelte-query-devtools

npm i https://pkg.pr.new/@tanstack/svelte-query-devtools@10032

@tanstack/svelte-query-persist-client

npm i https://pkg.pr.new/@tanstack/svelte-query-persist-client@10032

@tanstack/vue-query

npm i https://pkg.pr.new/@tanstack/vue-query@10032

@tanstack/vue-query-devtools

npm i https://pkg.pr.new/@tanstack/vue-query-devtools@10032

commit: 7c287aa

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @packages/query-core/src/__tests__/query.test.tsx:
- Around line 1061-1076: The getter in the test object `data` is declared as
`get foo(): void` which is incorrect — remove the explicit `: void` return type
(or change it to a correct type such as `string` or `any`) on the `get foo`
accessor in the `data` object so TypeScript won't flag a mismatch; keep the
getter body unchanged if you still want the recursion/serialization-failure
behavior used by `queryFn` and `queryClient.prefetchQuery`.
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 367c2c4 and 746c3fd.

📒 Files selected for processing (1)
  • packages/query-core/src/__tests__/query.test.tsx
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-11-22T09:06:05.219Z
Learnt from: sukvvon
Repo: TanStack/query PR: 9892
File: packages/solid-query-persist-client/src/__tests__/PersistQueryClientProvider.test.tsx:331-335
Timestamp: 2025-11-22T09:06:05.219Z
Learning: In TanStack/query test files, when a queryFn contains side effects (e.g., setting flags for test verification), prefer async/await syntax for clarity; when there are no side effects, prefer the .then() pattern for conciseness.

Applied to files:

  • packages/query-core/src/__tests__/query.test.tsx
📚 Learning: 2025-11-02T22:52:33.071Z
Learnt from: DogPawHat
Repo: TanStack/query PR: 9835
File: packages/query-core/src/__tests__/queryClient.test-d.tsx:242-256
Timestamp: 2025-11-02T22:52:33.071Z
Learning: In the TanStack Query codebase, the new `query` and `infiniteQuery` methods support the `select` option for data transformation, while the legacy `fetchQuery` and `fetchInfiniteQuery` methods do not support `select` and should reject it at the type level.

Applied to files:

  • packages/query-core/src/__tests__/query.test.tsx
🧬 Code graph analysis (1)
packages/query-core/src/__tests__/query.test.tsx (1)
packages/query-core/src/utils.ts (1)
  • sleep (376-380)
🪛 Biome (2.1.2)
packages/query-core/src/__tests__/query.test.tsx

[error] 1067-1067: The function should not return a value because its return type is void.

The function is here:

'void' signals the absence of value. The returned value is likely to be ignored by the caller.

(lint/correctness/noVoidTypeReturn)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Preview
  • GitHub Check: Test

Comment on lines +1061 to +1076
const initialData = {
foo: 'bar',
}

const data = {
get foo(): void {
return this.foo
},
}

queryFn.mockImplementation(() => sleep(10).then(() => data))

queryClient.prefetchQuery({
queryKey: key,
queryFn,
initialData: structuredClone(data),
initialData,
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.

⚠️ Potential issue | 🟡 Minor

Simplified test data looks good; fix the getter return type.

The initialData simplification is appropriate. However, the static analysis correctly flags that the getter at line 1067 has an incorrect return type—void indicates no return value, but the getter attempts to return this.foo.

Since this getter is designed to cause infinite recursion (triggering serialization failure), consider removing the type annotation or using a more accurate type.

Proposed fix
 const data = {
-  get foo(): void {
+  get foo(): string {
     return this.foo
   },
 }

Alternatively, remove the type annotation entirely and let TypeScript infer:

 const data = {
-  get foo(): void {
+  get foo() {
     return this.foo
   },
 }
📝 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
const initialData = {
foo: 'bar',
}
const data = {
get foo(): void {
return this.foo
},
}
queryFn.mockImplementation(() => sleep(10).then(() => data))
queryClient.prefetchQuery({
queryKey: key,
queryFn,
initialData: structuredClone(data),
initialData,
const initialData = {
foo: 'bar',
}
const data = {
get foo(): string {
return this.foo
},
}
queryFn.mockImplementation(() => sleep(10).then(() => data))
queryClient.prefetchQuery({
queryKey: key,
queryFn,
initialData,
🧰 Tools
🪛 Biome (2.1.2)

[error] 1067-1067: The function should not return a value because its return type is void.

The function is here:

'void' signals the absence of value. The returned value is likely to be ignored by the caller.

(lint/correctness/noVoidTypeReturn)

🤖 Prompt for AI Agents
In @packages/query-core/src/__tests__/query.test.tsx around lines 1061 - 1076,
The getter in the test object `data` is declared as `get foo(): void` which is
incorrect — remove the explicit `: void` return type (or change it to a correct
type such as `string` or `any`) on the `get foo` accessor in the `data` object
so TypeScript won't flag a mismatch; keep the getter body unchanged if you still
want the recursion/serialization-failure behavior used by `queryFn` and
`queryClient.prefetchQuery`.

@codecov
Copy link
Copy Markdown

codecov Bot commented Jan 13, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 59.63%. Comparing base (44c3cb9) to head (7c287aa).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##             main   #10032       +/-   ##
===========================================
+ Coverage   45.84%   59.63%   +13.79%     
===========================================
  Files         200      129       -71     
  Lines        8525     5740     -2785     
  Branches     1968     1587      -381     
===========================================
- Hits         3908     3423      -485     
+ Misses       4157     2000     -2157     
+ Partials      460      317      -143     
Components Coverage Δ
@tanstack/angular-query-experimental 93.85% <ø> (ø)
@tanstack/eslint-plugin-query ∅ <ø> (∅)
@tanstack/query-async-storage-persister 43.85% <ø> (ø)
@tanstack/query-broadcast-client-experimental 24.39% <ø> (ø)
@tanstack/query-codemods ∅ <ø> (∅)
@tanstack/query-core 97.38% <100.00%> (+<0.01%) ⬆️
@tanstack/query-devtools 3.38% <ø> (ø)
@tanstack/query-persist-client-core 80.00% <ø> (ø)
@tanstack/query-sync-storage-persister 84.61% <ø> (ø)
@tanstack/query-test-utils ∅ <ø> (∅)
@tanstack/react-query 96.73% <ø> (ø)
@tanstack/react-query-devtools 9.25% <ø> (ø)
@tanstack/react-query-next-experimental ∅ <ø> (∅)
@tanstack/react-query-persist-client 100.00% <ø> (ø)
@tanstack/solid-query 77.81% <ø> (ø)
@tanstack/solid-query-devtools 64.17% <ø> (ø)
@tanstack/solid-query-persist-client 100.00% <ø> (ø)
@tanstack/svelte-query ∅ <ø> (∅)
@tanstack/svelte-query-devtools ∅ <ø> (∅)
@tanstack/svelte-query-persist-client ∅ <ø> (∅)
@tanstack/vue-query 71.91% <ø> (ø)
@tanstack/vue-query-devtools ∅ <ø> (∅)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Fixes the max depth for replaceEqualDeep in query-core.
@TkDodo TkDodo merged commit 269351b into TanStack:main Jan 13, 2026
9 checks passed
@github-actions github-actions Bot mentioned this pull request Jan 13, 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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants