@tanstack/query-core 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/build/cjs/focusManager.js +101 -0
  2. package/build/cjs/focusManager.js.map +1 -0
  3. package/build/cjs/hydration.js +112 -0
  4. package/build/cjs/hydration.js.map +1 -0
  5. package/build/cjs/index.js +51 -0
  6. package/build/cjs/index.js.map +1 -0
  7. package/build/cjs/infiniteQueryBehavior.js +160 -0
  8. package/build/cjs/infiniteQueryBehavior.js.map +1 -0
  9. package/build/cjs/infiniteQueryObserver.js +92 -0
  10. package/build/cjs/infiniteQueryObserver.js.map +1 -0
  11. package/build/cjs/logger.js +18 -0
  12. package/build/cjs/logger.js.map +1 -0
  13. package/build/cjs/mutation.js +258 -0
  14. package/build/cjs/mutation.js.map +1 -0
  15. package/build/cjs/mutationCache.js +99 -0
  16. package/build/cjs/mutationCache.js.map +1 -0
  17. package/build/cjs/mutationObserver.js +130 -0
  18. package/build/cjs/mutationObserver.js.map +1 -0
  19. package/build/cjs/notifyManager.js +114 -0
  20. package/build/cjs/notifyManager.js.map +1 -0
  21. package/build/cjs/onlineManager.js +100 -0
  22. package/build/cjs/onlineManager.js.map +1 -0
  23. package/build/cjs/queriesObserver.js +170 -0
  24. package/build/cjs/queriesObserver.js.map +1 -0
  25. package/build/cjs/query.js +474 -0
  26. package/build/cjs/query.js.map +1 -0
  27. package/build/cjs/queryCache.js +140 -0
  28. package/build/cjs/queryCache.js.map +1 -0
  29. package/build/cjs/queryClient.js +357 -0
  30. package/build/cjs/queryClient.js.map +1 -0
  31. package/build/cjs/queryObserver.js +521 -0
  32. package/build/cjs/queryObserver.js.map +1 -0
  33. package/build/cjs/removable.js +47 -0
  34. package/build/cjs/removable.js.map +1 -0
  35. package/build/cjs/retryer.js +177 -0
  36. package/build/cjs/retryer.js.map +1 -0
  37. package/build/cjs/subscribable.js +43 -0
  38. package/build/cjs/subscribable.js.map +1 -0
  39. package/build/cjs/utils.js +356 -0
  40. package/build/cjs/utils.js.map +1 -0
  41. package/build/esm/index.js +3077 -0
  42. package/build/esm/index.js.map +1 -0
  43. package/build/stats-html.html +2689 -0
  44. package/build/umd/index.development.js +3106 -0
  45. package/build/umd/index.development.js.map +1 -0
  46. package/build/umd/index.production.js +12 -0
  47. package/build/umd/index.production.js.map +1 -0
  48. package/package.json +25 -0
  49. package/src/focusManager.ts +89 -0
  50. package/src/hydration.ts +164 -0
  51. package/src/index.ts +35 -0
  52. package/src/infiniteQueryBehavior.ts +214 -0
  53. package/src/infiniteQueryObserver.ts +159 -0
  54. package/src/logger.native.ts +11 -0
  55. package/src/logger.ts +9 -0
  56. package/src/mutation.ts +349 -0
  57. package/src/mutationCache.ts +157 -0
  58. package/src/mutationObserver.ts +195 -0
  59. package/src/notifyManager.ts +96 -0
  60. package/src/onlineManager.ts +89 -0
  61. package/src/queriesObserver.ts +211 -0
  62. package/src/query.ts +612 -0
  63. package/src/queryCache.ts +206 -0
  64. package/src/queryClient.ts +716 -0
  65. package/src/queryObserver.ts +748 -0
  66. package/src/removable.ts +37 -0
  67. package/src/retryer.ts +215 -0
  68. package/src/subscribable.ts +33 -0
  69. package/src/tests/focusManager.test.tsx +155 -0
  70. package/src/tests/hydration.test.tsx +429 -0
  71. package/src/tests/infiniteQueryBehavior.test.tsx +124 -0
  72. package/src/tests/infiniteQueryObserver.test.tsx +64 -0
  73. package/src/tests/mutationCache.test.tsx +260 -0
  74. package/src/tests/mutationObserver.test.tsx +75 -0
  75. package/src/tests/mutations.test.tsx +363 -0
  76. package/src/tests/notifyManager.test.tsx +51 -0
  77. package/src/tests/onlineManager.test.tsx +148 -0
  78. package/src/tests/queriesObserver.test.tsx +330 -0
  79. package/src/tests/query.test.tsx +888 -0
  80. package/src/tests/queryCache.test.tsx +236 -0
  81. package/src/tests/queryClient.test.tsx +1435 -0
  82. package/src/tests/queryObserver.test.tsx +802 -0
  83. package/src/tests/utils.test.tsx +360 -0
  84. package/src/types.ts +705 -0
  85. package/src/utils.ts +435 -0
@@ -0,0 +1,96 @@
1
+ import { scheduleMicrotask } from './utils'
2
+
3
+ // TYPES
4
+
5
+ type NotifyCallback = () => void
6
+
7
+ type NotifyFunction = (callback: () => void) => void
8
+
9
+ type BatchNotifyFunction = (callback: () => void) => void
10
+
11
+ export function createNotifyManager() {
12
+ let queue: NotifyCallback[] = []
13
+ let transactions = 0
14
+ let notifyFn: NotifyFunction = (callback) => {
15
+ callback()
16
+ }
17
+ let batchNotifyFn: BatchNotifyFunction = (callback: () => void) => {
18
+ callback()
19
+ }
20
+
21
+ const batch = <T>(callback: () => T): T => {
22
+ let result
23
+ transactions++
24
+ try {
25
+ result = callback()
26
+ } finally {
27
+ transactions--
28
+ if (!transactions) {
29
+ flush()
30
+ }
31
+ }
32
+ return result
33
+ }
34
+
35
+ const schedule = (callback: NotifyCallback): void => {
36
+ if (transactions) {
37
+ queue.push(callback)
38
+ } else {
39
+ scheduleMicrotask(() => {
40
+ notifyFn(callback)
41
+ })
42
+ }
43
+ }
44
+
45
+ /**
46
+ * All calls to the wrapped function will be batched.
47
+ */
48
+ const batchCalls = <T extends Function>(callback: T): T => {
49
+ return ((...args: any[]) => {
50
+ schedule(() => {
51
+ callback(...args)
52
+ })
53
+ }) as any
54
+ }
55
+
56
+ const flush = (): void => {
57
+ const originalQueue = queue
58
+ queue = []
59
+ if (originalQueue.length) {
60
+ scheduleMicrotask(() => {
61
+ batchNotifyFn(() => {
62
+ originalQueue.forEach((callback) => {
63
+ notifyFn(callback)
64
+ })
65
+ })
66
+ })
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Use this method to set a custom notify function.
72
+ * This can be used to for example wrap notifications with `React.act` while running tests.
73
+ */
74
+ const setNotifyFunction = (fn: NotifyFunction) => {
75
+ notifyFn = fn
76
+ }
77
+
78
+ /**
79
+ * Use this method to set a custom function to batch notifications together into a single tick.
80
+ * By default React Query will use the batch function provided by ReactDOM or React Native.
81
+ */
82
+ const setBatchNotifyFunction = (fn: BatchNotifyFunction) => {
83
+ batchNotifyFn = fn
84
+ }
85
+
86
+ return {
87
+ batch,
88
+ batchCalls,
89
+ schedule,
90
+ setNotifyFunction,
91
+ setBatchNotifyFunction,
92
+ } as const
93
+ }
94
+
95
+ // SINGLETON
96
+ export const notifyManager = createNotifyManager()
@@ -0,0 +1,89 @@
1
+ import { Subscribable } from './subscribable'
2
+ import { isServer } from './utils'
3
+
4
+ type SetupFn = (
5
+ setOnline: (online?: boolean) => void,
6
+ ) => (() => void) | undefined
7
+
8
+ export class OnlineManager extends Subscribable {
9
+ private online?: boolean
10
+ private cleanup?: () => void
11
+
12
+ private setup: SetupFn
13
+
14
+ constructor() {
15
+ super()
16
+ this.setup = (onOnline) => {
17
+ // addEventListener does not exist in React Native, but window does
18
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
19
+ if (!isServer && window.addEventListener) {
20
+ const listener = () => onOnline()
21
+ // Listen to online
22
+ window.addEventListener('online', listener, false)
23
+ window.addEventListener('offline', listener, false)
24
+
25
+ return () => {
26
+ // Be sure to unsubscribe if a new handler is set
27
+ window.removeEventListener('online', listener)
28
+ window.removeEventListener('offline', listener)
29
+ }
30
+ }
31
+ }
32
+ }
33
+
34
+ protected onSubscribe(): void {
35
+ if (!this.cleanup) {
36
+ this.setEventListener(this.setup)
37
+ }
38
+ }
39
+
40
+ protected onUnsubscribe() {
41
+ if (!this.hasListeners()) {
42
+ this.cleanup?.()
43
+ this.cleanup = undefined
44
+ }
45
+ }
46
+
47
+ setEventListener(setup: SetupFn): void {
48
+ this.setup = setup
49
+ this.cleanup?.()
50
+ this.cleanup = setup((online?: boolean) => {
51
+ if (typeof online === 'boolean') {
52
+ this.setOnline(online)
53
+ } else {
54
+ this.onOnline()
55
+ }
56
+ })
57
+ }
58
+
59
+ setOnline(online?: boolean): void {
60
+ this.online = online
61
+
62
+ if (online) {
63
+ this.onOnline()
64
+ }
65
+ }
66
+
67
+ onOnline(): void {
68
+ this.listeners.forEach((listener) => {
69
+ listener()
70
+ })
71
+ }
72
+
73
+ isOnline(): boolean {
74
+ if (typeof this.online === 'boolean') {
75
+ return this.online
76
+ }
77
+
78
+ if (
79
+ typeof navigator === 'undefined' ||
80
+ typeof navigator.onLine === 'undefined'
81
+ ) {
82
+ return true
83
+ }
84
+
85
+ return navigator.onLine
86
+ }
87
+ }
88
+
89
+ export const onlineManager = new OnlineManager()
@@ -0,0 +1,211 @@
1
+ import { difference, replaceAt } from './utils'
2
+ import { notifyManager } from './notifyManager'
3
+ import type {
4
+ QueryObserverOptions,
5
+ QueryObserverResult,
6
+ DefaultedQueryObserverOptions,
7
+ } from './types'
8
+ import type { QueryClient } from './queryClient'
9
+ import { NotifyOptions, QueryObserver } from './queryObserver'
10
+ import { Subscribable } from './subscribable'
11
+
12
+ type QueriesObserverListener = (result: QueryObserverResult[]) => void
13
+
14
+ export class QueriesObserver extends Subscribable<QueriesObserverListener> {
15
+ private client: QueryClient
16
+ private result: QueryObserverResult[]
17
+ private queries: QueryObserverOptions[]
18
+ private observers: QueryObserver[]
19
+ private observersMap: Record<string, QueryObserver>
20
+
21
+ constructor(client: QueryClient, queries?: QueryObserverOptions[]) {
22
+ super()
23
+
24
+ this.client = client
25
+ this.queries = []
26
+ this.result = []
27
+ this.observers = []
28
+ this.observersMap = {}
29
+
30
+ if (queries) {
31
+ this.setQueries(queries)
32
+ }
33
+ }
34
+
35
+ protected onSubscribe(): void {
36
+ if (this.listeners.length === 1) {
37
+ this.observers.forEach((observer) => {
38
+ observer.subscribe((result) => {
39
+ this.onUpdate(observer, result)
40
+ })
41
+ })
42
+ }
43
+ }
44
+
45
+ protected onUnsubscribe(): void {
46
+ if (!this.listeners.length) {
47
+ this.destroy()
48
+ }
49
+ }
50
+
51
+ destroy(): void {
52
+ this.listeners = []
53
+ this.observers.forEach((observer) => {
54
+ observer.destroy()
55
+ })
56
+ }
57
+
58
+ setQueries(
59
+ queries: QueryObserverOptions[],
60
+ notifyOptions?: NotifyOptions,
61
+ ): void {
62
+ this.queries = queries
63
+
64
+ notifyManager.batch(() => {
65
+ const prevObservers = this.observers
66
+
67
+ const newObserverMatches = this.findMatchingObservers(this.queries)
68
+
69
+ // set options for the new observers to notify of changes
70
+ newObserverMatches.forEach((match) =>
71
+ match.observer.setOptions(match.defaultedQueryOptions, notifyOptions),
72
+ )
73
+
74
+ const newObservers = newObserverMatches.map((match) => match.observer)
75
+ const newObserversMap = Object.fromEntries(
76
+ newObservers.map((observer) => [observer.options.queryHash, observer]),
77
+ )
78
+ const newResult = newObservers.map((observer) =>
79
+ observer.getCurrentResult(),
80
+ )
81
+
82
+ const hasIndexChange = newObservers.some(
83
+ (observer, index) => observer !== prevObservers[index],
84
+ )
85
+ if (prevObservers.length === newObservers.length && !hasIndexChange) {
86
+ return
87
+ }
88
+
89
+ this.observers = newObservers
90
+ this.observersMap = newObserversMap
91
+ this.result = newResult
92
+
93
+ if (!this.hasListeners()) {
94
+ return
95
+ }
96
+
97
+ difference(prevObservers, newObservers).forEach((observer) => {
98
+ observer.destroy()
99
+ })
100
+
101
+ difference(newObservers, prevObservers).forEach((observer) => {
102
+ observer.subscribe((result) => {
103
+ this.onUpdate(observer, result)
104
+ })
105
+ })
106
+
107
+ this.notify()
108
+ })
109
+ }
110
+
111
+ getCurrentResult(): QueryObserverResult[] {
112
+ return this.result
113
+ }
114
+
115
+ getOptimisticResult(queries: QueryObserverOptions[]): QueryObserverResult[] {
116
+ return this.findMatchingObservers(queries).map((match) =>
117
+ match.observer.getOptimisticResult(match.defaultedQueryOptions),
118
+ )
119
+ }
120
+
121
+ private findMatchingObservers(
122
+ queries: QueryObserverOptions[],
123
+ ): QueryObserverMatch[] {
124
+ const prevObservers = this.observers
125
+ const defaultedQueryOptions = queries.map((options) =>
126
+ this.client.defaultQueryOptions(options),
127
+ )
128
+
129
+ const matchingObservers: QueryObserverMatch[] =
130
+ defaultedQueryOptions.flatMap((defaultedOptions) => {
131
+ const match = prevObservers.find(
132
+ (observer) =>
133
+ observer.options.queryHash === defaultedOptions.queryHash,
134
+ )
135
+ if (match != null) {
136
+ return [{ defaultedQueryOptions: defaultedOptions, observer: match }]
137
+ }
138
+ return []
139
+ })
140
+
141
+ const matchedQueryHashes = matchingObservers.map(
142
+ (match) => match.defaultedQueryOptions.queryHash,
143
+ )
144
+ const unmatchedQueries = defaultedQueryOptions.filter(
145
+ (defaultedOptions) =>
146
+ !matchedQueryHashes.includes(defaultedOptions.queryHash),
147
+ )
148
+
149
+ const unmatchedObservers = prevObservers.filter(
150
+ (prevObserver) =>
151
+ !matchingObservers.some((match) => match.observer === prevObserver),
152
+ )
153
+
154
+ const getObserver = (options: QueryObserverOptions): QueryObserver => {
155
+ const defaultedOptions = this.client.defaultQueryOptions(options)
156
+ const currentObserver = this.observersMap[defaultedOptions.queryHash!]
157
+ return currentObserver ?? new QueryObserver(this.client, defaultedOptions)
158
+ }
159
+
160
+ const newOrReusedObservers: QueryObserverMatch[] = unmatchedQueries.map(
161
+ (options, index) => {
162
+ if (options.keepPreviousData) {
163
+ // return previous data from one of the observers that no longer match
164
+ const previouslyUsedObserver = unmatchedObservers[index]
165
+ if (previouslyUsedObserver !== undefined) {
166
+ return {
167
+ defaultedQueryOptions: options,
168
+ observer: previouslyUsedObserver,
169
+ }
170
+ }
171
+ }
172
+ return {
173
+ defaultedQueryOptions: options,
174
+ observer: getObserver(options),
175
+ }
176
+ },
177
+ )
178
+
179
+ const sortMatchesByOrderOfQueries = (
180
+ a: QueryObserverMatch,
181
+ b: QueryObserverMatch,
182
+ ): number =>
183
+ defaultedQueryOptions.indexOf(a.defaultedQueryOptions) -
184
+ defaultedQueryOptions.indexOf(b.defaultedQueryOptions)
185
+
186
+ return matchingObservers
187
+ .concat(newOrReusedObservers)
188
+ .sort(sortMatchesByOrderOfQueries)
189
+ }
190
+
191
+ private onUpdate(observer: QueryObserver, result: QueryObserverResult): void {
192
+ const index = this.observers.indexOf(observer)
193
+ if (index !== -1) {
194
+ this.result = replaceAt(this.result, index, result)
195
+ this.notify()
196
+ }
197
+ }
198
+
199
+ private notify(): void {
200
+ notifyManager.batch(() => {
201
+ this.listeners.forEach((listener) => {
202
+ listener(this.result)
203
+ })
204
+ })
205
+ }
206
+ }
207
+
208
+ type QueryObserverMatch = {
209
+ defaultedQueryOptions: DefaultedQueryObserverOptions
210
+ observer: QueryObserver
211
+ }