Skip to content

Conversation

@un33k
Copy link

@un33k un33k commented Feb 4, 2026

Summary

This PR addresses multiple issues identified through automated code analysis:

  • Performance: Convert observers from Array to Set for O(1) lookups, reduce unnecessary iterations
  • Testing: Add tests for packages with zero coverage (vue-query-devtools, svelte-query-devtools, react-query-next-experimental)
  • Documentation: Add JSDoc comments to QueryClient, QueryObserver, and core types
  • Architecture: Split monolithic types.ts into focused modules

Changes

Performance

  • Convert observers from Array to Set for O(1) add/remove/has operations
  • Add countMatching() to QueryCache to avoid creating intermediate arrays in isFetching()
  • Cache Object.keys() result in shallowEqualObjects() instead of calling twice

Testing

  • Add tests for vue-query-devtools (was 0% coverage)
  • Add tests for svelte-query-devtools (was 0% coverage)
  • Add tests for react-query-next-experimental (was 0% coverage)
  • Replace placeholder test in query-devtools with actual component tests
  • Update existing tests for Set-based observers API

Documentation

  • Add JSDoc comments to QueryClient class and public methods
  • Add JSDoc comments to QueryObserver class and key methods
  • Add JSDoc comments to core types (QueryKey, QueryFunction, MutationOptions, etc.)

Architecture

  • Split types.ts (1,391 lines) into focused modules:
    • types/query.ts - Query-related types
    • types/mutation.ts - Mutation-related types
    • types/observer.ts - Observer-related types
    • types/common.ts - Shared utility types
    • types/client.ts - Client config types
  • Maintain backwards compatibility via re-exports

Other

  • Improve input validation and error handling in persistence layer

Test plan

  • TypeScript compilation passes (pnpm run test:types)
  • ESLint passes (pnpm run test:eslint)
  • Prettier formatting applied (pnpm run format)
  • Full test suite passes locally (pnpm run test:lib - all 20 packages)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Expanded QueryClient imperative API, richer QueryObserver lifecycle, QueryCache.countMatching, and consolidated/expanded public type exports.
  • Bug Fixes
    • Prototype-pollution checks for persisted cache deserialization, safer hydration script injection, and sanitized persist-restore error reporting.
  • Performance
    • Observer tracking moved to Set semantics for more efficient management.
  • Tests
    • Added and expanded test suites across multiple packages and tooling configs.

Performance:
- Convert observers from Array to Set for O(1) lookups
- Add countMatching() to queryCache to avoid creating intermediate arrays
- Cache Object.keys result in shallowEqualObjects

Testing:
- Add tests for vue-query-devtools, svelte-query-devtools, react-query-next-experimental
- Replace placeholder test in query-devtools with actual component tests
- Update existing tests for Set-based observers API

Documentation:
- Add JSDoc comments to QueryClient, QueryObserver, and core types

Architecture:
- Split types.ts (1,391 lines) into focused modules (query, mutation, observer, common, client)
- Maintain backwards compatibility via re-exports

Other:
- Improve input validation and error handling in persistence layer

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

changeset-bot bot commented Feb 4, 2026

⚠️ No Changeset found

Latest commit: 3e3ea7c

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 4, 2026

📝 Walkthrough

Walkthrough

Refactors observer storage from arrays to Sets; expands QueryClient with many imperative cache/data methods; promotes QueryObserver to a subscribable class with lifecycle and optimistic tracking; reorganizes and centralizes query-core types; adds persister validation/protection and improves tests and test configs.

Changes

Cohort / File(s) Summary
Observer storage & observer API
packages/query-core/src/query.ts, packages/query-broadcast-client-experimental/src/index.ts, packages/react-query/src/__tests__/useQuery*.test.tsx
Switched Query.observers from Array to Set (init, add/remove, iteration, size checks). Updated related tests and broadcast client to use size and Set iteration.
QueryObserver surface & behavior
packages/query-core/src/queryObserver.ts, packages/query-core/src/types/observer.ts
Converted QueryObserver into a subscribable class with generics and added lifecycle/public methods (destroy, setOptions, getOptimisticResult, getCurrentResult, trackResult/trackProp, refetch, fetchOptimistic) and supporting internal adjustments.
QueryClient & QueryCache additions
packages/query-core/src/queryClient.ts, packages/query-core/src/queryCache.ts
Added many new public QueryClient APIs for imperative cache access/manipulation, fetching, defaults management, lifecycle (mount/unmount), and countMatching on QueryCache.
Type system reorganization
packages/query-core/src/types.ts, packages/query-core/src/types/*.ts, packages/query-core/src/types/index.ts
Split monolithic types into modular files (common, query, observer, mutation, client) and created an index re-export aggregating 100+ types and a few runtime symbols (dataTagSymbol, dataTagErrorSymbol, unsetMarker).
Persistence safety & error handling
packages/query-persist-client-core/src/createPersister.ts, packages/query-persist-client-core/src/persist.ts, packages/query-persist-client-core/src/__tests__/persist.test.ts
Added storage-key validation and prototype-pollution checks on deserialized data; normalize and throw a PersistQueryClientError when restore fails; updated tests accordingly.
Hydration & DOM safety
packages/react-query-next-experimental/src/HydrationStreamProvider.tsx, packages/react-query-next-experimental/src/__tests__/HydrationStreamProvider.test.tsx
Replaced dangerouslySetInnerHTML script injection with textContent-based script content; added tests for the hydration provider.
Test infra & configs
packages/*-devtools/*/vite.config.ts, packages/*-devtools/*/package.json, packages/*-devtools/*/src/__tests__/*
Added vitest test configs and test:lib / test:lib:dev scripts across devtools packages; added/expanded unit tests for devtools components with mocked dependencies.
Utility tweaks
packages/query-core/src/utils.ts
Refactored shallowEqualObjects to use explicit key-array iteration and explicit key-count comparison; behavior preserved.
Docs/analysis
ANALYSIS_REPORT.md
Added a new analysis report document summarizing architecture, testing, security, and recommendations.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

package: vue-query

Suggested reviewers

  • TkDodo
  • ArturKustyaev

Poem

🐰
Hopping through Sets with a twitch and a cheer,
Observers rearranged — now tidy and clear.
QueryClient grown with tools bold and new,
Types stacked like carrots in a neat little queue.
Tests clap their paws — the rabbit says, "Woo!"

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 46.15% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main changes: addressing performance, testing, and documentation issues. It accurately reflects the primary objectives of the PR.
Description check ✅ Passed The description comprehensively covers all major changes with organized sections (Performance, Testing, Documentation, Architecture, Other) and includes a test plan with verification steps, aligning well with the template structure.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Important

Action Needed: IP Allowlist Update

If your organization protects your Git platform with IP whitelisting, please add the new CodeRabbit IP address to your allowlist:

  • 136.113.208.247/32 (new)
  • 34.170.211.100/32
  • 35.222.179.152/32

Reviews will stop working after February 8, 2026 if the new IP is not added to your allowlist.


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 and usage tips.

Copy link
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: 2

🤖 Fix all issues with AI agents
In `@packages/query-core/src/types/common.ts`:
- Around line 44-50: Add a Biome ignore comment immediately above the empty
Register interface to silence the no-empty-interface rule used for module
augmentation; e.g. place a line like "// biome-ignore-next-line
no-empty-interface" directly above the "export interface Register { }"
declaration so the lint rule is skipped for the Register interface while
preserving the empty interface for augmentation.

In `@packages/query-persist-client-core/src/createPersister.ts`:
- Around line 104-129: The validateDeserializedData function uses Object.hasOwn
(ES2022) which breaks ES2020 targets; update the forbidden-key checks in
validateDeserializedData to use Object.prototype.hasOwnProperty.call(obj,
'<key>') for '__proto__', 'constructor', and 'prototype' (preserving the
existing error throw) so the check is compatible with ES2020 environments and
still prevents prototype pollution.
🧹 Nitpick comments (2)
packages/vue-query-devtools/src/__tests__/devtools.test.ts (1)

4-24: Consider expanding tests to exercise the mocks.

The mocks for TanstackQueryDevtools lifecycle methods (mount, unmount, setButtonPosition, etc.) are comprehensive but currently unused. If you plan to expand test coverage later, consider adding tests that actually render the components and verify these methods are called appropriately.

packages/query-core/src/queryClient.ts (1)

154-158: Consider adding countMatching() to MutationCache for consistency.

The isFetching() method was optimized to use countMatching(), but isMutating() still uses findAll().length. While mutations are typically fewer than queries (making this less impactful), adding a similar optimization to MutationCache would maintain consistency.

un33k and others added 2 commits February 3, 2026 20:57
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The Register interface is intentionally empty for module augmentation.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/query-persist-client-core/src/createPersister.ts (2)

299-317: ⚠️ Potential issue | 🟡 Minor

Consider wrapping validation in try-catch for resilience.

If validateDeserializedData throws on one corrupted entry, the entire GC loop stops and remaining stale entries won't be cleaned up. Unlike retrieveQuery, this function lacks error handling around the deserialization/validation step.

🛡️ Proposed fix to continue GC on corrupted entries
       for (const [key, value] of entries) {
         if (key.startsWith(prefix)) {
-          const persistedQuery = await deserialize(value)
-          validateDeserializedData(persistedQuery)
-
-          if (isExpiredOrBusted(persistedQuery)) {
+          try {
+            const persistedQuery = await deserialize(value)
+            validateDeserializedData(persistedQuery)
+
+            if (isExpiredOrBusted(persistedQuery)) {
+              await storage.removeItem(key)
+            }
+          } catch {
+            // Remove corrupted entries during GC
             await storage.removeItem(key)
           }
         }
       }

319-361: ⚠️ Potential issue | 🟡 Minor

Same resilience concern as persisterGc.

Similar to the GC function, a single corrupted entry will abort the entire restoration loop. Consider whether partial restoration with logged warnings would provide better user experience than failing entirely.

The parameter rename from filters to queryFilters improves clarity.

🛡️ Proposed fix to continue restoration on corrupted entries
       for (const [key, value] of entries) {
         if (key.startsWith(prefix)) {
-          const persistedQuery = await deserialize(value)
-          validateDeserializedData(persistedQuery)
+          let persistedQuery: PersistedQuery
+          try {
+            persistedQuery = await deserialize(value)
+            validateDeserializedData(persistedQuery)
+          } catch (err) {
+            if (process.env.NODE_ENV === 'development') {
+              console.error(err)
+              console.warn('Skipping corrupted persisted query:', key)
+            }
+            await storage.removeItem(key)
+            continue
+          }
 
           if (isExpiredOrBusted(persistedQuery)) {
🧹 Nitpick comments (2)
ANALYSIS_REPORT.md (2)

1-10: Clarify which issues are addressed by this PR.

The report presents findings as open issues requiring action, but the PR objectives indicate several items are being fixed in this PR (observer arrays → Sets, QueryCache.countMatching(), types.ts splitting, JSDoc additions, test coverage). This creates confusion about what's already resolved versus what remains outstanding.

Consider adding a note in the Executive Summary or a new section indicating which recommendations are implemented by the accompanying PR changes.

📝 Suggested addition to Executive Summary
 Five specialized agents reviewed the TanStack Query codebase from different perspectives. This report consolidates findings across architecture, testing, performance, security, and documentation. The project is well-engineered overall but has accumulated technical debt that warrants attention.
+
+**Note:** Several high-priority items identified in this analysis are addressed by the accompanying PR changes, including:
+- Performance: Observer collections converted from Array to Set (Section 3.4)
+- Performance: QueryCache.countMatching() added to reduce N+1 patterns (Section 3.3)
+- Architecture: types.ts split into focused modules (Section 1.1)
+- Testing: Coverage added for previously untested packages (Section 2.1)
+- Documentation: JSDoc comments added to core types (Section 5.1)
+
+Remaining recommendations require follow-up work as outlined in Section 7.

378-385: Add language specification to fenced code block.

The fenced code block is missing a language identifier, which triggers a linting warning.

🔧 Proposed fix
 **Location:** `examples/react/basic/README.md`
 
-```
+```markdown
 # Basic
 
 To run this example:

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.

1 participant