@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
package/src/query.ts ADDED
@@ -0,0 +1,612 @@
1
+ import { getAbortController, noop, replaceData, timeUntilStale } from './utils'
2
+ import type {
3
+ InitialDataFunction,
4
+ QueryKey,
5
+ QueryOptions,
6
+ QueryStatus,
7
+ QueryFunctionContext,
8
+ QueryMeta,
9
+ CancelOptions,
10
+ SetDataOptions,
11
+ FetchStatus,
12
+ } from './types'
13
+ import type { QueryCache } from './queryCache'
14
+ import type { QueryObserver } from './queryObserver'
15
+ import { defaultLogger, Logger } from './logger'
16
+ import { notifyManager } from './notifyManager'
17
+ import { Retryer, isCancelledError, canFetch, createRetryer } from './retryer'
18
+ import { Removable } from './removable'
19
+
20
+ // TYPES
21
+
22
+ interface QueryConfig<
23
+ TQueryFnData,
24
+ TError,
25
+ TData,
26
+ TQueryKey extends QueryKey = QueryKey,
27
+ > {
28
+ cache: QueryCache
29
+ queryKey: TQueryKey
30
+ queryHash: string
31
+ logger?: Logger
32
+ options?: QueryOptions<TQueryFnData, TError, TData, TQueryKey>
33
+ defaultOptions?: QueryOptions<TQueryFnData, TError, TData, TQueryKey>
34
+ state?: QueryState<TData, TError>
35
+ meta: QueryMeta | undefined
36
+ }
37
+
38
+ export interface QueryState<TData = unknown, TError = unknown> {
39
+ data: TData | undefined
40
+ dataUpdateCount: number
41
+ dataUpdatedAt: number
42
+ error: TError | null
43
+ errorUpdateCount: number
44
+ errorUpdatedAt: number
45
+ fetchFailureCount: number
46
+ fetchMeta: any
47
+ isInvalidated: boolean
48
+ status: QueryStatus
49
+ fetchStatus: FetchStatus
50
+ }
51
+
52
+ export interface FetchContext<
53
+ TQueryFnData,
54
+ TError,
55
+ TData,
56
+ TQueryKey extends QueryKey = QueryKey,
57
+ > {
58
+ fetchFn: () => unknown | Promise<unknown>
59
+ fetchOptions?: FetchOptions
60
+ signal?: AbortSignal
61
+ options: QueryOptions<TQueryFnData, TError, TData, any>
62
+ queryKey: TQueryKey
63
+ state: QueryState<TData, TError>
64
+ meta: QueryMeta | undefined
65
+ }
66
+
67
+ export interface QueryBehavior<
68
+ TQueryFnData = unknown,
69
+ TError = unknown,
70
+ TData = TQueryFnData,
71
+ TQueryKey extends QueryKey = QueryKey,
72
+ > {
73
+ onFetch: (
74
+ context: FetchContext<TQueryFnData, TError, TData, TQueryKey>,
75
+ ) => void
76
+ }
77
+
78
+ export interface FetchOptions {
79
+ cancelRefetch?: boolean
80
+ meta?: any
81
+ }
82
+
83
+ interface FailedAction {
84
+ type: 'failed'
85
+ }
86
+
87
+ interface FetchAction {
88
+ type: 'fetch'
89
+ meta?: any
90
+ }
91
+
92
+ interface SuccessAction<TData> {
93
+ data: TData | undefined
94
+ type: 'success'
95
+ dataUpdatedAt?: number
96
+ manual?: boolean
97
+ }
98
+
99
+ interface ErrorAction<TError> {
100
+ type: 'error'
101
+ error: TError
102
+ }
103
+
104
+ interface InvalidateAction {
105
+ type: 'invalidate'
106
+ }
107
+
108
+ interface PauseAction {
109
+ type: 'pause'
110
+ }
111
+
112
+ interface ContinueAction {
113
+ type: 'continue'
114
+ }
115
+
116
+ interface SetStateAction<TData, TError> {
117
+ type: 'setState'
118
+ state: QueryState<TData, TError>
119
+ setStateOptions?: SetStateOptions
120
+ }
121
+
122
+ export type Action<TData, TError> =
123
+ | ContinueAction
124
+ | ErrorAction<TError>
125
+ | FailedAction
126
+ | FetchAction
127
+ | InvalidateAction
128
+ | PauseAction
129
+ | SetStateAction<TData, TError>
130
+ | SuccessAction<TData>
131
+
132
+ export interface SetStateOptions {
133
+ meta?: any
134
+ }
135
+
136
+ // CLASS
137
+
138
+ export class Query<
139
+ TQueryFnData = unknown,
140
+ TError = unknown,
141
+ TData = TQueryFnData,
142
+ TQueryKey extends QueryKey = QueryKey,
143
+ > extends Removable {
144
+ queryKey: TQueryKey
145
+ queryHash: string
146
+ options!: QueryOptions<TQueryFnData, TError, TData, TQueryKey>
147
+ initialState: QueryState<TData, TError>
148
+ revertState?: QueryState<TData, TError>
149
+ state: QueryState<TData, TError>
150
+ meta: QueryMeta | undefined
151
+ isFetchingOptimistic?: boolean
152
+
153
+ private cache: QueryCache
154
+ private logger: Logger
155
+ private promise?: Promise<TData>
156
+ private retryer?: Retryer<TData>
157
+ private observers: QueryObserver<any, any, any, any, any>[]
158
+ private defaultOptions?: QueryOptions<TQueryFnData, TError, TData, TQueryKey>
159
+ private abortSignalConsumed: boolean
160
+
161
+ constructor(config: QueryConfig<TQueryFnData, TError, TData, TQueryKey>) {
162
+ super()
163
+
164
+ this.abortSignalConsumed = false
165
+ this.defaultOptions = config.defaultOptions
166
+ this.setOptions(config.options)
167
+ this.observers = []
168
+ this.cache = config.cache
169
+ this.logger = config.logger || defaultLogger
170
+ this.queryKey = config.queryKey
171
+ this.queryHash = config.queryHash
172
+ this.initialState = config.state || getDefaultState(this.options)
173
+ this.state = this.initialState
174
+ this.meta = config.meta
175
+ }
176
+
177
+ private setOptions(
178
+ options?: QueryOptions<TQueryFnData, TError, TData, TQueryKey>,
179
+ ): void {
180
+ this.options = { ...this.defaultOptions, ...options }
181
+
182
+ this.meta = options?.meta
183
+
184
+ this.updateCacheTime(this.options.cacheTime)
185
+ }
186
+
187
+ protected optionalRemove() {
188
+ if (!this.observers.length && this.state.fetchStatus === 'idle') {
189
+ this.cache.remove(this)
190
+ }
191
+ }
192
+
193
+ setData(
194
+ newData: TData,
195
+ options?: SetDataOptions & { manual: boolean },
196
+ ): TData {
197
+ const data = replaceData(this.state.data, newData, this.options)
198
+
199
+ // Set data and mark it as cached
200
+ this.dispatch({
201
+ data,
202
+ type: 'success',
203
+ dataUpdatedAt: options?.updatedAt,
204
+ manual: options?.manual,
205
+ })
206
+
207
+ return data
208
+ }
209
+
210
+ setState(
211
+ state: QueryState<TData, TError>,
212
+ setStateOptions?: SetStateOptions,
213
+ ): void {
214
+ this.dispatch({ type: 'setState', state, setStateOptions })
215
+ }
216
+
217
+ cancel(options?: CancelOptions): Promise<void> {
218
+ const promise = this.promise
219
+ this.retryer?.cancel(options)
220
+ return promise ? promise.then(noop).catch(noop) : Promise.resolve()
221
+ }
222
+
223
+ destroy(): void {
224
+ super.destroy()
225
+
226
+ this.cancel({ silent: true })
227
+ }
228
+
229
+ reset(): void {
230
+ this.destroy()
231
+ this.setState(this.initialState)
232
+ }
233
+
234
+ isActive(): boolean {
235
+ return this.observers.some((observer) => observer.options.enabled !== false)
236
+ }
237
+
238
+ isDisabled(): boolean {
239
+ return this.getObserversCount() > 0 && !this.isActive()
240
+ }
241
+
242
+ isStale(): boolean {
243
+ return (
244
+ this.state.isInvalidated ||
245
+ !this.state.dataUpdatedAt ||
246
+ this.observers.some((observer) => observer.getCurrentResult().isStale)
247
+ )
248
+ }
249
+
250
+ isStaleByTime(staleTime = 0): boolean {
251
+ return (
252
+ this.state.isInvalidated ||
253
+ !this.state.dataUpdatedAt ||
254
+ !timeUntilStale(this.state.dataUpdatedAt, staleTime)
255
+ )
256
+ }
257
+
258
+ onFocus(): void {
259
+ const observer = this.observers.find((x) => x.shouldFetchOnWindowFocus())
260
+
261
+ if (observer) {
262
+ observer.refetch({ cancelRefetch: false })
263
+ }
264
+
265
+ // Continue fetch if currently paused
266
+ this.retryer?.continue()
267
+ }
268
+
269
+ onOnline(): void {
270
+ const observer = this.observers.find((x) => x.shouldFetchOnReconnect())
271
+
272
+ if (observer) {
273
+ observer.refetch({ cancelRefetch: false })
274
+ }
275
+
276
+ // Continue fetch if currently paused
277
+ this.retryer?.continue()
278
+ }
279
+
280
+ addObserver(observer: QueryObserver<any, any, any, any, any>): void {
281
+ if (this.observers.indexOf(observer) === -1) {
282
+ this.observers.push(observer)
283
+
284
+ // Stop the query from being garbage collected
285
+ this.clearGcTimeout()
286
+
287
+ this.cache.notify({ type: 'observerAdded', query: this, observer })
288
+ }
289
+ }
290
+
291
+ removeObserver(observer: QueryObserver<any, any, any, any, any>): void {
292
+ if (this.observers.indexOf(observer) !== -1) {
293
+ this.observers = this.observers.filter((x) => x !== observer)
294
+
295
+ if (!this.observers.length) {
296
+ // If the transport layer does not support cancellation
297
+ // we'll let the query continue so the result can be cached
298
+ if (this.retryer) {
299
+ if (this.abortSignalConsumed) {
300
+ this.retryer.cancel({ revert: true })
301
+ } else {
302
+ this.retryer.cancelRetry()
303
+ }
304
+ }
305
+
306
+ this.scheduleGc()
307
+ }
308
+
309
+ this.cache.notify({ type: 'observerRemoved', query: this, observer })
310
+ }
311
+ }
312
+
313
+ getObserversCount(): number {
314
+ return this.observers.length
315
+ }
316
+
317
+ invalidate(): void {
318
+ if (!this.state.isInvalidated) {
319
+ this.dispatch({ type: 'invalidate' })
320
+ }
321
+ }
322
+
323
+ fetch(
324
+ options?: QueryOptions<TQueryFnData, TError, TData, TQueryKey>,
325
+ fetchOptions?: FetchOptions,
326
+ ): Promise<TData> {
327
+ if (this.state.fetchStatus !== 'idle') {
328
+ if (this.state.dataUpdatedAt && fetchOptions?.cancelRefetch) {
329
+ // Silently cancel current fetch if the user wants to cancel refetches
330
+ this.cancel({ silent: true })
331
+ } else if (this.promise) {
332
+ // make sure that retries that were potentially cancelled due to unmounts can continue
333
+ this.retryer?.continueRetry()
334
+ // Return current promise if we are already fetching
335
+ return this.promise
336
+ }
337
+ }
338
+
339
+ // Update config if passed, otherwise the config from the last execution is used
340
+ if (options) {
341
+ this.setOptions(options)
342
+ }
343
+
344
+ // Use the options from the first observer with a query function if no function is found.
345
+ // This can happen when the query is hydrated or created with setQueryData.
346
+ if (!this.options.queryFn) {
347
+ const observer = this.observers.find((x) => x.options.queryFn)
348
+ if (observer) {
349
+ this.setOptions(observer.options)
350
+ }
351
+ }
352
+
353
+ if (!Array.isArray(this.options.queryKey)) {
354
+ if (process.env.NODE_ENV !== 'production') {
355
+ this.logger.error(
356
+ `As of v4, queryKey needs to be an Array. If you are using a string like 'repoData', please change it to an Array, e.g. ['repoData']`,
357
+ )
358
+ }
359
+ }
360
+
361
+ const abortController = getAbortController()
362
+
363
+ // Create query function context
364
+ const queryFnContext: QueryFunctionContext<TQueryKey> = {
365
+ queryKey: this.queryKey,
366
+ pageParam: undefined,
367
+ meta: this.meta,
368
+ }
369
+
370
+ // Adds an enumerable signal property to the object that
371
+ // which sets abortSignalConsumed to true when the signal
372
+ // is read.
373
+ const addSignalProperty = (object: unknown) => {
374
+ Object.defineProperty(object, 'signal', {
375
+ enumerable: true,
376
+ get: () => {
377
+ if (abortController) {
378
+ this.abortSignalConsumed = true
379
+ return abortController.signal
380
+ }
381
+ return undefined
382
+ },
383
+ })
384
+ }
385
+
386
+ addSignalProperty(queryFnContext)
387
+
388
+ // Create fetch function
389
+ const fetchFn = () => {
390
+ if (!this.options.queryFn) {
391
+ return Promise.reject('Missing queryFn')
392
+ }
393
+ this.abortSignalConsumed = false
394
+ return this.options.queryFn(queryFnContext)
395
+ }
396
+
397
+ // Trigger behavior hook
398
+ const context: FetchContext<TQueryFnData, TError, TData, TQueryKey> = {
399
+ fetchOptions,
400
+ options: this.options,
401
+ queryKey: this.queryKey,
402
+ state: this.state,
403
+ fetchFn,
404
+ meta: this.meta,
405
+ }
406
+
407
+ addSignalProperty(context)
408
+
409
+ this.options.behavior?.onFetch(context)
410
+
411
+ // Store state in case the current fetch needs to be reverted
412
+ this.revertState = this.state
413
+
414
+ // Set to fetching state if not already in it
415
+ if (
416
+ this.state.fetchStatus === 'idle' ||
417
+ this.state.fetchMeta !== context.fetchOptions?.meta
418
+ ) {
419
+ this.dispatch({ type: 'fetch', meta: context.fetchOptions?.meta })
420
+ }
421
+
422
+ const onError = (error: TError | { silent?: boolean }) => {
423
+ // Optimistically update state if needed
424
+ if (!(isCancelledError(error) && error.silent)) {
425
+ this.dispatch({
426
+ type: 'error',
427
+ error: error as TError,
428
+ })
429
+ }
430
+
431
+ if (!isCancelledError(error)) {
432
+ // Notify cache callback
433
+ this.cache.config.onError?.(error, this as Query<any, any, any, any>)
434
+
435
+ if (process.env.NODE_ENV !== 'production') {
436
+ this.logger.error(error)
437
+ }
438
+ }
439
+
440
+ if (!this.isFetchingOptimistic) {
441
+ // Schedule query gc after fetching
442
+ this.scheduleGc()
443
+ }
444
+ this.isFetchingOptimistic = false
445
+ }
446
+
447
+ // Try to fetch the data
448
+ this.retryer = createRetryer({
449
+ fn: context.fetchFn as () => TData,
450
+ abort: abortController?.abort.bind(abortController),
451
+ onSuccess: (data) => {
452
+ if (typeof data === 'undefined') {
453
+ onError(new Error('Query data cannot be undefined') as any)
454
+ return
455
+ }
456
+
457
+ this.setData(data as TData)
458
+
459
+ // Notify cache callback
460
+ this.cache.config.onSuccess?.(data, this as Query<any, any, any, any>)
461
+
462
+ if (!this.isFetchingOptimistic) {
463
+ // Schedule query gc after fetching
464
+ this.scheduleGc()
465
+ }
466
+ this.isFetchingOptimistic = false
467
+ },
468
+ onError,
469
+ onFail: () => {
470
+ this.dispatch({ type: 'failed' })
471
+ },
472
+ onPause: () => {
473
+ this.dispatch({ type: 'pause' })
474
+ },
475
+ onContinue: () => {
476
+ this.dispatch({ type: 'continue' })
477
+ },
478
+ retry: context.options.retry,
479
+ retryDelay: context.options.retryDelay,
480
+ networkMode: context.options.networkMode,
481
+ })
482
+
483
+ this.promise = this.retryer.promise
484
+
485
+ return this.promise
486
+ }
487
+
488
+ private dispatch(action: Action<TData, TError>): void {
489
+ const reducer = (
490
+ state: QueryState<TData, TError>,
491
+ ): QueryState<TData, TError> => {
492
+ switch (action.type) {
493
+ case 'failed':
494
+ return {
495
+ ...state,
496
+ fetchFailureCount: state.fetchFailureCount + 1,
497
+ }
498
+ case 'pause':
499
+ return {
500
+ ...state,
501
+ fetchStatus: 'paused',
502
+ }
503
+ case 'continue':
504
+ return {
505
+ ...state,
506
+ fetchStatus: 'fetching',
507
+ }
508
+ case 'fetch':
509
+ return {
510
+ ...state,
511
+ fetchFailureCount: 0,
512
+ fetchMeta: action.meta ?? null,
513
+ fetchStatus: canFetch(this.options.networkMode)
514
+ ? 'fetching'
515
+ : 'paused',
516
+ ...(!state.dataUpdatedAt && {
517
+ error: null,
518
+ status: 'loading',
519
+ }),
520
+ }
521
+ case 'success':
522
+ return {
523
+ ...state,
524
+ data: action.data,
525
+ dataUpdateCount: state.dataUpdateCount + 1,
526
+ dataUpdatedAt: action.dataUpdatedAt ?? Date.now(),
527
+ error: null,
528
+ isInvalidated: false,
529
+ status: 'success',
530
+ ...(!action.manual && {
531
+ fetchStatus: 'idle',
532
+ fetchFailureCount: 0,
533
+ }),
534
+ }
535
+ case 'error':
536
+ const error = action.error as unknown
537
+
538
+ if (isCancelledError(error) && error.revert && this.revertState) {
539
+ return { ...this.revertState }
540
+ }
541
+
542
+ return {
543
+ ...state,
544
+ error: error as TError,
545
+ errorUpdateCount: state.errorUpdateCount + 1,
546
+ errorUpdatedAt: Date.now(),
547
+ fetchFailureCount: state.fetchFailureCount + 1,
548
+ fetchStatus: 'idle',
549
+ status: 'error',
550
+ }
551
+ case 'invalidate':
552
+ return {
553
+ ...state,
554
+ isInvalidated: true,
555
+ }
556
+ case 'setState':
557
+ return {
558
+ ...state,
559
+ ...action.state,
560
+ }
561
+ }
562
+ }
563
+
564
+ this.state = reducer(this.state)
565
+
566
+ notifyManager.batch(() => {
567
+ this.observers.forEach((observer) => {
568
+ observer.onQueryUpdate(action)
569
+ })
570
+
571
+ this.cache.notify({ query: this, type: 'updated', action })
572
+ })
573
+ }
574
+ }
575
+
576
+ function getDefaultState<
577
+ TQueryFnData,
578
+ TError,
579
+ TData,
580
+ TQueryKey extends QueryKey,
581
+ >(
582
+ options: QueryOptions<TQueryFnData, TError, TData, TQueryKey>,
583
+ ): QueryState<TData, TError> {
584
+ const data =
585
+ typeof options.initialData === 'function'
586
+ ? (options.initialData as InitialDataFunction<TData>)()
587
+ : options.initialData
588
+
589
+ const hasInitialData = typeof options.initialData !== 'undefined'
590
+
591
+ const initialDataUpdatedAt = hasInitialData
592
+ ? typeof options.initialDataUpdatedAt === 'function'
593
+ ? (options.initialDataUpdatedAt as () => number | undefined)()
594
+ : options.initialDataUpdatedAt
595
+ : 0
596
+
597
+ const hasData = typeof data !== 'undefined'
598
+
599
+ return {
600
+ data,
601
+ dataUpdateCount: 0,
602
+ dataUpdatedAt: hasData ? initialDataUpdatedAt ?? Date.now() : 0,
603
+ error: null,
604
+ errorUpdateCount: 0,
605
+ errorUpdatedAt: 0,
606
+ fetchFailureCount: 0,
607
+ fetchMeta: null,
608
+ isInvalidated: false,
609
+ status: hasData ? 'success' : 'loading',
610
+ fetchStatus: 'idle',
611
+ }
612
+ }