Skip to content

Commit 6fa901b

Browse files
feat/environmentManager (#10199)
* environmentManager * ci: apply automated fixes * ref: environmentManager * fix: adapt tests to not using isServer anymore * ref: revert to main * make tests better * Create moody-mails-deliver.md --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent 5cd3896 commit 6fa901b

File tree

19 files changed

+170
-62
lines changed

19 files changed

+170
-62
lines changed

.changeset/moody-mails-deliver.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@tanstack/preact-query": minor
3+
"@tanstack/query-core": minor
4+
"@tanstack/react-query": minor
5+
---
6+
7+
feat: environmentManager

docs/config.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -913,6 +913,10 @@
913913
"label": "onlineManager",
914914
"to": "reference/onlineManager"
915915
},
916+
{
917+
"label": "environmentManager",
918+
"to": "reference/environmentManager"
919+
},
916920
{
917921
"label": "notifyManager",
918922
"to": "reference/notifyManager"
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
---
2+
id: EnvironmentManager
3+
title: environmentManager
4+
---
5+
6+
The `environmentManager` manages how TanStack Query detects whether the current runtime should be treated as server-side.
7+
8+
By default, it uses the same server detection as the exported `isServer` utility from query-core.
9+
10+
Use this manager to override server detection globally for runtimes that are not traditional browser/server environments (for example, extension workers).
11+
12+
Its available methods are:
13+
14+
- [`isServer`](#environmentmanagerisserver)
15+
- [`setIsServer`](#environmentmanagersetisserver)
16+
17+
## `environmentManager.isServer`
18+
19+
Returns whether the current runtime is treated as a server environment.
20+
21+
```tsx
22+
import { environmentManager } from '@tanstack/react-query'
23+
24+
const server = environmentManager.isServer()
25+
```
26+
27+
## `environmentManager.setIsServer`
28+
29+
Overrides the server check globally.
30+
31+
```tsx
32+
import { environmentManager } from '@tanstack/react-query'
33+
34+
// Override
35+
environmentManager.setIsServer(() => {
36+
return typeof window === 'undefined' && !('chrome' in globalThis)
37+
})
38+
```
39+
40+
**Options**
41+
42+
- `isServerValue: () => boolean`
43+
44+
To restore the default behavior, set the function back to query-core's `isServer` utility:
45+
46+
```tsx
47+
import { environmentManager, isServer } from '@tanstack/react-query'
48+
49+
environmentManager.setIsServer(() => isServer)
50+
```

packages/preact-query/src/__tests__/utils.tsx

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import * as utils from '@tanstack/query-core'
1+
import { environmentManager, isServer } from '@tanstack/query-core'
22
import { act, render } from '@testing-library/preact'
33
import type { ComponentChildren, VNode } from 'preact'
44
import { useEffect, useState } from 'preact/hooks'
@@ -58,17 +58,9 @@ export function setActTimeout(fn: () => void, ms?: number) {
5858
}, ms)
5959
}
6060

61-
// This monkey-patches the isServer-value from utils,
62-
// so that we can pretend to be in a server environment
63-
export function setIsServer(isServer: boolean) {
64-
const original = utils.isServer
65-
Object.defineProperty(utils, 'isServer', {
66-
get: () => isServer,
67-
})
68-
61+
export function setIsServer(value: boolean) {
62+
environmentManager.setIsServer(() => value)
6963
return () => {
70-
Object.defineProperty(utils, 'isServer', {
71-
get: () => original,
72-
})
64+
environmentManager.setIsServer(() => isServer)
7365
}
7466
}

packages/preact-query/src/useBaseQuery.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { isServer, noop, notifyManager } from '@tanstack/query-core'
1+
import { environmentManager, noop, notifyManager } from '@tanstack/query-core'
22
import type {
33
QueryClient,
44
QueryKey,
@@ -147,7 +147,7 @@ export function useBaseQuery<
147147

148148
if (
149149
defaultedOptions.experimental_prefetchInRender &&
150-
!isServer &&
150+
!environmentManager.isServer() &&
151151
willFetch(result, isRestoring)
152152
) {
153153
const promise = isNewCacheEntry
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { afterEach, describe, expect, test } from 'vitest'
2+
import { environmentManager, isServer } from '..'
3+
4+
describe('environmentManager', () => {
5+
afterEach(() => {
6+
environmentManager.setIsServer(() => isServer)
7+
})
8+
9+
test('should use the default isServer detection', () => {
10+
expect(environmentManager.isServer()).toBe(isServer)
11+
})
12+
13+
test('should allow overriding isServer globally', () => {
14+
environmentManager.setIsServer(() => true)
15+
expect(environmentManager.isServer()).toBe(true)
16+
17+
environmentManager.setIsServer(() => false)
18+
expect(environmentManager.isServer()).toBe(false)
19+
})
20+
21+
test('should allow overriding isServer with a function', () => {
22+
let server = true
23+
environmentManager.setIsServer(() => server)
24+
expect(environmentManager.isServer()).toBe(true)
25+
26+
server = false
27+
expect(environmentManager.isServer()).toBe(false)
28+
})
29+
})

packages/query-core/src/__tests__/focusManager.test.tsx

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { afterEach, beforeEach, describe, expect, it, test, vi } from 'vitest'
22
import { FocusManager } from '../focusManager'
3-
import { setIsServer } from './utils'
43

54
describe('focusManager', () => {
65
let focusManager: FocusManager
@@ -55,17 +54,26 @@ describe('focusManager', () => {
5554
})
5655

5756
test('cleanup (removeEventListener) should not be called if window is not defined', () => {
58-
const restoreIsServer = setIsServer(true)
59-
57+
const windowSpy = vi.spyOn(globalThis, 'window', 'get')
58+
windowSpy.mockImplementation(
59+
() => undefined as unknown as Window & typeof globalThis,
60+
)
6061
const removeEventListenerSpy = vi.spyOn(globalThis, 'removeEventListener')
6162

62-
const unsubscribe = focusManager.subscribe(() => undefined)
63+
const subscribe = () => focusManager.subscribe(() => undefined)
64+
let firstUnsubscribe: (() => void) | undefined
6365

64-
unsubscribe()
66+
expect(() => {
67+
firstUnsubscribe = subscribe()
68+
}).not.toThrow()
69+
const secondUnsubscribe = subscribe()
70+
71+
firstUnsubscribe?.()
72+
secondUnsubscribe()
6573

6674
expect(removeEventListenerSpy).not.toHaveBeenCalled()
6775

68-
restoreIsServer()
76+
windowSpy.mockRestore()
6977
})
7078

7179
test('cleanup (removeEventListener) should not be called if window.addEventListener is not defined', () => {

packages/query-core/src/__tests__/onlineManager.test.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
22
import { OnlineManager } from '../onlineManager'
3-
import { setIsServer } from './utils'
43

54
describe('onlineManager', () => {
65
let onlineManager: OnlineManager
@@ -64,17 +63,20 @@ describe('onlineManager', () => {
6463
})
6564

6665
test('cleanup (removeEventListener) should not be called if window is not defined', () => {
67-
const restoreIsServer = setIsServer(true)
68-
66+
const windowSpy = vi.spyOn(globalThis, 'window', 'get')
67+
windowSpy.mockImplementation(
68+
() => undefined as unknown as Window & typeof globalThis,
69+
)
6970
const removeEventListenerSpy = vi.spyOn(globalThis, 'removeEventListener')
7071

7172
const unsubscribe = onlineManager.subscribe(() => undefined)
73+
expect(unsubscribe).toBeInstanceOf(Function)
7274

7375
unsubscribe()
7476

7577
expect(removeEventListenerSpy).not.toHaveBeenCalled()
7678

77-
restoreIsServer()
79+
windowSpy.mockRestore()
7880
})
7981

8082
test('cleanup (removeEventListener) should not be called if window.addEventListener is not defined', () => {
Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { vi } from 'vitest'
2-
import { onlineManager } from '..'
3-
import * as utils from '../utils'
2+
import { environmentManager, isServer, onlineManager } from '..'
43
import type { MockInstance } from 'vitest'
54
import type { MutationOptions, QueryClient } from '..'
65

@@ -21,17 +20,9 @@ export function executeMutation<TVariables>(
2120
.execute(variables)
2221
}
2322

24-
// This monkey-patches the isServer-value from utils,
25-
// so that we can pretend to be in a server environment
26-
export function setIsServer(isServer: boolean) {
27-
const original = utils.isServer
28-
Object.defineProperty(utils, 'isServer', {
29-
get: () => isServer,
30-
})
31-
23+
export function setIsServer(value: boolean) {
24+
environmentManager.setIsServer(() => value)
3225
return () => {
33-
Object.defineProperty(utils, 'isServer', {
34-
get: () => original,
35-
})
26+
environmentManager.setIsServer(() => isServer)
3627
}
3728
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { isServer } from './utils'
2+
3+
export type IsServerValue = () => boolean
4+
5+
/**
6+
* Manages environment detection used by TanStack Query internals.
7+
*/
8+
export const environmentManager = (() => {
9+
let isServerFn: IsServerValue = () => isServer
10+
11+
return {
12+
/**
13+
* Returns whether the current runtime should be treated as a server environment.
14+
*/
15+
isServer(): boolean {
16+
return isServerFn()
17+
},
18+
/**
19+
* Overrides the server check globally.
20+
*/
21+
setIsServer(isServerValue: IsServerValue): void {
22+
isServerFn = isServerValue
23+
},
24+
}
25+
})()

0 commit comments

Comments
 (0)