Thanks to visit codestin.com
Credit goes to github.com

Skip to content

feat/environmentManager#10199

Open
TkDodo wants to merge 8 commits intomainfrom
feature/environmentManager
Open

feat/environmentManager#10199
TkDodo wants to merge 8 commits intomainfrom
feature/environmentManager

Conversation

@TkDodo
Copy link
Collaborator

@TkDodo TkDodo commented Feb 28, 2026

alternative fix for #10139

Summary by CodeRabbit

  • New Features

    • Added environmentManager as a public API to manage runtime server-detection with isServer() and setIsServer().
  • Documentation

    • Added detailed environmentManager docs with usage examples for overriding and restoring server detection.
  • Deprecations

    • Marked the prior direct isServer export as deprecated in favor of environmentManager.isServer().
  • Tests

    • Added and updated tests to cover default detection, overrides, and related runtime behaviors.

@changeset-bot
Copy link

changeset-bot bot commented Feb 28, 2026

🦋 Changeset detected

Latest commit: 2f82fa9

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

This PR includes changesets to release 22 packages
Name Type
@tanstack/preact-query Minor
@tanstack/query-core Minor
@tanstack/react-query Minor
@tanstack/preact-query-devtools Major
@tanstack/preact-query-persist-client Major
@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/solid-query Patch
@tanstack/svelte-query Patch
@tanstack/vue-query Patch
@tanstack/react-query-devtools Major
@tanstack/react-query-next-experimental Major
@tanstack/react-query-persist-client Major
@tanstack/angular-query-persist-client Patch
@tanstack/solid-query-persist-client Patch
@tanstack/svelte-query-persist-client 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
Contributor

coderabbitai bot commented Feb 28, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6c78f60 and 2f82fa9.

📒 Files selected for processing (1)
  • docs/config.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/config.json

📝 Walkthrough

Walkthrough

Introduces a new exported environmentManager singleton for centralized runtime server-detection; replaces direct isServer checks with environmentManager.isServer() across core and framework packages, adds docs and tests, and exposes setIsServer() for overriding detection in tests.

Changes

Cohort / File(s) Summary
Documentation
docs/config.json, docs/reference/environmentManager.md
Adds API reference entry and full docs for environmentManager, including usage and reset examples.
Core export & new module
packages/query-core/src/environmentManager.ts, packages/query-core/src/index.ts
Adds new environmentManager singleton and re-exports it from core index. Introduces IsServerValue type, isServer() and setIsServer() APIs.
Deprecation note
packages/query-core/src/utils.ts
Marks legacy isServer export as deprecated (JSDoc) in favor of environmentManager.isServer().
Runtime checks (core)
packages/query-core/src/focusManager.ts, packages/query-core/src/onlineManager.ts, packages/query-core/src/queryObserver.ts, packages/query-core/src/removable.ts, packages/query-core/src/retryer.ts
Replaces direct isServer usage with environmentManager.isServer() or plain window-existence checks; updates imports and gating logic affecting GC timing and event listener registration paths.
Framework integration
packages/react-query/src/useBaseQuery.ts, packages/preact-query/src/useBaseQuery.ts
Switches prefetch-in-render server detection to environmentManager.isServer() and updates imports.
Tests & test helpers
packages/query-core/src/__tests__/environmentManager.test.tsx, packages/query-core/src/__tests__/utils.ts, packages/query-core/src/__tests__/focusManager.test.tsx, packages/query-core/src/__tests__/onlineManager.test.tsx, packages/react-query/src/__tests__/utils.tsx, packages/preact-query/src/__tests__/utils.tsx
Adds tests for environmentManager; refactors test utilities to use environmentManager.setIsServer(() => value) (helper param renamed to value); replaces some monkey-patching with window getter spies; updates cleanup/restoration flows.
Changeset / releases
.changeset/moody-mails-deliver.md
Adds changeset declaring minor version bumps and a feature flag for the new manager.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • theVedanta

Poem

🐰 I nudged a tiny switch today,
A manager now shows the way.
Tests can flip and then reset,
No more getters causing fret.
Hoppity hops — the flags all play! 🥕✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is minimal, providing only 'alternative fix for #10139' without following the template structure. Required sections like '🎯 Changes', checklist items, and '🚀 Release Impact' are completely absent. Expand the description to include the required template sections: describe the environmentManager feature under '🎯 Changes', confirm checklist items, and note that the changeset file was properly generated for release impact.
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 'feat/environmentManager' directly describes the main feature being introduced - a new environmentManager utility for server-detection management. It aligns with the changeset declaring environmentManager as the key feature across multiple packages.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/environmentManager

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


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

@nx-cloud
Copy link

nx-cloud bot commented Feb 28, 2026

View your CI Pipeline Execution ↗ for commit 2f82fa9

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

☁️ Nx Cloud last updated this comment at 2026-03-03 10:50:07 UTC

@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 28, 2026

More templates

@tanstack/angular-query-experimental

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

@tanstack/eslint-plugin-query

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

@tanstack/preact-query

npm i https://pkg.pr.new/@tanstack/preact-query@10199

@tanstack/preact-query-devtools

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

@tanstack/preact-query-persist-client

npm i https://pkg.pr.new/@tanstack/preact-query-persist-client@10199

@tanstack/query-async-storage-persister

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

@tanstack/query-broadcast-client-experimental

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

@tanstack/query-core

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

@tanstack/query-devtools

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

@tanstack/query-persist-client-core

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

@tanstack/query-sync-storage-persister

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

@tanstack/react-query

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

@tanstack/react-query-devtools

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

@tanstack/react-query-next-experimental

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

@tanstack/react-query-persist-client

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

@tanstack/solid-query

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

@tanstack/solid-query-devtools

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

@tanstack/solid-query-persist-client

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

@tanstack/svelte-query

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

@tanstack/svelte-query-devtools

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

@tanstack/svelte-query-persist-client

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

@tanstack/vue-query

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

@tanstack/vue-query-devtools

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

commit: 2b2ed48

@github-actions
Copy link
Contributor

github-actions bot commented Feb 28, 2026

size-limit report 📦

Path Size
react full 11.96 KB (+0.37% 🔺)
react minimal 8.98 KB (+0.35% 🔺)

@TkDodo TkDodo marked this pull request as ready for review February 28, 2026 15:41
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

🧹 Nitpick comments (3)
packages/vue-query/src/__tests__/queryClient.test.ts (1)

23-23: This change appears unrelated to the environmentManager feature.

The type improvement for the filter predicate is a good change, but it doesn't seem related to the environmentManager feature being introduced in this PR. Consider whether this should be in a separate PR for cleaner history, or note in the PR description that this is an incidental type improvement.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/vue-query/src/__tests__/queryClient.test.ts` at line 23, The change
to the filter predicate type guard in queryClient.test.ts (the line casting to
QueryClientPrototypeMethod) is unrelated to the environmentManager feature;
either revert that type-only change from this branch and leave the
environmentManager PR focused, or extract the type improvement into its own
commit/branch and open a separate PR; update the current PR description to
mention the incidental type tweak if you intend to keep it here. Ensure you
reference the test file's filter predicate that narrows to
QueryClientPrototypeMethod when making the change.
packages/query-core/src/__tests__/environmentManager.test.tsx (1)

21-24: Consider consolidating or differentiating this test.

This test ("should allow overriding isServer with a function") is essentially identical to the first part of the test on lines 13-15. Both tests call setIsServer(() => true) and assert the result. The test name suggests testing "with a function" but that's already what the API requires—setIsServer only accepts a function per the type IsServerValue = () => boolean.

Consider either removing this test as it's redundant, or differentiating it by testing a more dynamic function (e.g., one that returns different values based on some condition).

Example of a more meaningful test
  test('should allow overriding isServer with a function', () => {
-   environmentManager.setIsServer(() => true)
-   expect(environmentManager.isServer()).toBe(true)
+   let value = true
+   environmentManager.setIsServer(() => value)
+   expect(environmentManager.isServer()).toBe(true)
+   
+   value = false
+   expect(environmentManager.isServer()).toBe(false)
  })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/query-core/src/__tests__/environmentManager.test.tsx` around lines
21 - 24, The test is redundant because environmentManager.setIsServer(() =>
true) is already asserted earlier; either remove this duplicate test or change
it to exercise a dynamic function: replace the static () => true with a function
that flips or returns different values based on external state, then assert
environmentManager.isServer() reflects that changing state (use
environmentManager.setIsServer and environmentManager.isServer to locate code to
modify). Ensure the new test verifies the dynamic behavior across state changes
rather than repeating the same static assertion.
packages/query-core/src/onlineManager.ts (1)

17-17: Inconsistent with environmentManager pattern used elsewhere.

This change uses typeof window !== 'undefined' directly instead of environmentManager.isServer(). While this works for detecting the browser environment, it creates an inconsistency with the rest of the codebase migration:

  • If a user calls environmentManager.setIsServer(() => true) to simulate server-side behavior (e.g., for testing), the OnlineManager will still register window event listeners because window exists.
  • Other files (retryer.ts, queryObserver.ts, removable.ts) use environmentManager.isServer() consistently.

If the intent is to check specifically for window.addEventListener availability (as the comment suggests for React Native compatibility), the current approach is fine. However, if the goal is to respect the environmentManager override, consider:

Option: Use environmentManager for consistency
-      if (typeof window !== 'undefined' && window.addEventListener) {
+      if (!environmentManager.isServer() && typeof window !== 'undefined' && window.addEventListener) {

This would require adding the import:

+import { environmentManager } from './environmentManager'

Please clarify the intended behavior: should OnlineManager respect environmentManager.setIsServer() overrides, or is checking for window existence the correct approach here?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/query-core/src/onlineManager.ts` at line 17, Replace the direct
"typeof window !== 'undefined' && window.addEventListener" check in
OnlineManager with the environmentManager-based guard so the module respects
environmentManager.setIsServer() overrides; import environmentManager and change
the condition to "!environmentManager.isServer() && typeof window !==
'undefined' && window.addEventListener" (so you still guard for environments
like React Native while honoring test/server overrides) and update any
surrounding comment to reflect the combined intent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/query-core/src/__tests__/focusManager.test.tsx`:
- Around line 63-68: The test currently calls subscribe twice (once via
expect(subscribe).not.toThrow() which invokes subscribe, and again on the next
line) but only calls unsubscribe once, leaving a dangling listener; change the
test so both subscriptions are unsubscribed: capture the first unsubscribe by
invoking subscribe inside the expect (e.g. let firstUnsub; expect(() => {
firstUnsub = subscribe(); }).not.toThrow()), then call const secondUnsub =
subscribe() and finally call firstUnsub() and secondUnsub(); reference the
subscribe constant and focusManager.subscribe to locate the code and ensure
removeEventListener gets exercised.

In `@packages/query-core/src/__tests__/onlineManager.test.tsx`:
- Around line 72-77: The test currently calls onlineManager.subscribe twice
(once via expect(subscribe).not.toThrow() and again when assigning unsubscribe)
which leaves the first listener active and masks cleanup; fix by calling
subscribe only once: replace the expect + second call with a single call to
onlineManager.subscribe(() => undefined) assigned to unsubscribe, then assert
the returned unsubscribe is a function (e.g.,
expect(unsubscribe).toBeInstanceOf(Function)) and finally call unsubscribe() so
onUnsubscribe() can be exercised; refer to onlineManager.subscribe, unsubscribe,
and onUnsubscribe to locate the code to change.

---

Nitpick comments:
In `@packages/query-core/src/__tests__/environmentManager.test.tsx`:
- Around line 21-24: The test is redundant because
environmentManager.setIsServer(() => true) is already asserted earlier; either
remove this duplicate test or change it to exercise a dynamic function: replace
the static () => true with a function that flips or returns different values
based on external state, then assert environmentManager.isServer() reflects that
changing state (use environmentManager.setIsServer and
environmentManager.isServer to locate code to modify). Ensure the new test
verifies the dynamic behavior across state changes rather than repeating the
same static assertion.

In `@packages/query-core/src/onlineManager.ts`:
- Line 17: Replace the direct "typeof window !== 'undefined' &&
window.addEventListener" check in OnlineManager with the
environmentManager-based guard so the module respects
environmentManager.setIsServer() overrides; import environmentManager and change
the condition to "!environmentManager.isServer() && typeof window !==
'undefined' && window.addEventListener" (so you still guard for environments
like React Native while honoring test/server overrides) and update any
surrounding comment to reflect the combined intent.

In `@packages/vue-query/src/__tests__/queryClient.test.ts`:
- Line 23: The change to the filter predicate type guard in queryClient.test.ts
(the line casting to QueryClientPrototypeMethod) is unrelated to the
environmentManager feature; either revert that type-only change from this branch
and leave the environmentManager PR focused, or extract the type improvement
into its own commit/branch and open a separate PR; update the current PR
description to mention the incidental type tweak if you intend to keep it here.
Ensure you reference the test file's filter predicate that narrows to
QueryClientPrototypeMethod when making the change.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0da627b and 9ee18df.

📒 Files selected for processing (19)
  • docs/config.json
  • docs/reference/environmentManager.md
  • packages/preact-query/src/__tests__/utils.tsx
  • packages/preact-query/src/useBaseQuery.ts
  • packages/query-core/src/__tests__/environmentManager.test.tsx
  • packages/query-core/src/__tests__/focusManager.test.tsx
  • packages/query-core/src/__tests__/onlineManager.test.tsx
  • packages/query-core/src/__tests__/utils.ts
  • packages/query-core/src/environmentManager.ts
  • packages/query-core/src/focusManager.ts
  • packages/query-core/src/index.ts
  • packages/query-core/src/onlineManager.ts
  • packages/query-core/src/queryObserver.ts
  • packages/query-core/src/removable.ts
  • packages/query-core/src/retryer.ts
  • packages/query-core/src/utils.ts
  • packages/react-query/src/__tests__/utils.tsx
  • packages/react-query/src/useBaseQuery.ts
  • packages/vue-query/src/__tests__/queryClient.test.ts

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.

🧹 Nitpick comments (2)
packages/query-core/src/__tests__/focusManager.test.tsx (1)

57-60: Use try/finally for spy restoration to prevent cross-test leakage.

If an assertion fails before Line 76, the mocked globalThis.window getter remains active and can pollute following tests.

Suggested hardening
   test('cleanup (removeEventListener) should not be called if window is not defined', () => {
     const windowSpy = vi.spyOn(globalThis, 'window', 'get')
     windowSpy.mockImplementation(
       () => undefined as unknown as Window & typeof globalThis,
     )
     const removeEventListenerSpy = vi.spyOn(globalThis, 'removeEventListener')

-    const subscribe = () => focusManager.subscribe(() => undefined)
-    let firstUnsubscribe: (() => void) | undefined
-
-    expect(() => {
-      firstUnsubscribe = subscribe()
-    }).not.toThrow()
-    const secondUnsubscribe = subscribe()
-
-    firstUnsubscribe?.()
-    secondUnsubscribe()
-
-    expect(removeEventListenerSpy).not.toHaveBeenCalled()
-
-    windowSpy.mockRestore()
+    try {
+      const subscribe = () => focusManager.subscribe(() => undefined)
+      let firstUnsubscribe: (() => void) | undefined
+
+      expect(() => {
+        firstUnsubscribe = subscribe()
+      }).not.toThrow()
+      const secondUnsubscribe = subscribe()
+
+      firstUnsubscribe?.()
+      secondUnsubscribe()
+
+      expect(removeEventListenerSpy).not.toHaveBeenCalled()
+    } finally {
+      removeEventListenerSpy.mockRestore()
+      windowSpy.mockRestore()
+    }
   })

Also applies to: 76-76

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/query-core/src/__tests__/focusManager.test.tsx` around lines 57 -
60, Wrap the test code that mocks globalThis.window (the windowSpy created via
vi.spyOn(globalThis, 'window', 'get')) in a try/finally block so you always
restore the spy; move windowSpy.mockImplementation(...) into the try and call
windowSpy.mockRestore() in the finally to ensure the mocked getter is removed
even if assertions fail (apply the same pattern for the other instance around
line 76).
packages/query-core/src/__tests__/onlineManager.test.tsx (1)

66-69: Guard spy restoration with finally for deterministic test cleanup.

If anything fails before Line 79, the mocked window getter may leak into later tests.

Suggested hardening
   test('cleanup (removeEventListener) should not be called if window is not defined', () => {
     const windowSpy = vi.spyOn(globalThis, 'window', 'get')
     windowSpy.mockImplementation(
       () => undefined as unknown as Window & typeof globalThis,
     )
     const removeEventListenerSpy = vi.spyOn(globalThis, 'removeEventListener')

-    const unsubscribe = onlineManager.subscribe(() => undefined)
-    expect(unsubscribe).toBeInstanceOf(Function)
-
-    unsubscribe()
-
-    expect(removeEventListenerSpy).not.toHaveBeenCalled()
-
-    windowSpy.mockRestore()
+    try {
+      const unsubscribe = onlineManager.subscribe(() => undefined)
+      expect(unsubscribe).toBeInstanceOf(Function)
+      unsubscribe()
+      expect(removeEventListenerSpy).not.toHaveBeenCalled()
+    } finally {
+      removeEventListenerSpy.mockRestore()
+      windowSpy.mockRestore()
+    }
   })

Also applies to: 79-79

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/query-core/src/__tests__/onlineManager.test.tsx` around lines 66 -
69, The test creates a spy via vi.spyOn(globalThis, 'window', 'get') assigned to
windowSpy and mocks it with windowSpy.mockImplementation(...); ensure the spy is
always restored to avoid leaking into other tests by wrapping the test body so
that windowSpy.mockRestore() is called in a finally block (or register
windowSpy.mockRestore() in an afterEach/teardown hook) — update the test that
uses windowSpy, mockImplementation, and windowSpy.mockRestore() to guarantee
restoration even if assertions throw.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/query-core/src/__tests__/focusManager.test.tsx`:
- Around line 57-60: Wrap the test code that mocks globalThis.window (the
windowSpy created via vi.spyOn(globalThis, 'window', 'get')) in a try/finally
block so you always restore the spy; move windowSpy.mockImplementation(...) into
the try and call windowSpy.mockRestore() in the finally to ensure the mocked
getter is removed even if assertions fail (apply the same pattern for the other
instance around line 76).

In `@packages/query-core/src/__tests__/onlineManager.test.tsx`:
- Around line 66-69: The test creates a spy via vi.spyOn(globalThis, 'window',
'get') assigned to windowSpy and mocks it with
windowSpy.mockImplementation(...); ensure the spy is always restored to avoid
leaking into other tests by wrapping the test body so that
windowSpy.mockRestore() is called in a finally block (or register
windowSpy.mockRestore() in an afterEach/teardown hook) — update the test that
uses windowSpy, mockImplementation, and windowSpy.mockRestore() to guarantee
restoration even if assertions throw.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9ee18df and 4206f79.

📒 Files selected for processing (3)
  • packages/query-core/src/__tests__/environmentManager.test.tsx
  • packages/query-core/src/__tests__/focusManager.test.tsx
  • packages/query-core/src/__tests__/onlineManager.test.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/query-core/src/tests/environmentManager.test.tsx

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.

Tanstack Query refetch does not work in certain environments (vscode extension, chrome extension)

1 participant