@tanstack/solid-query 5.0.0-alpha.2 → 5.0.0-alpha.20

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 (54) hide show
  1. package/build/cjs/index.js +73 -34
  2. package/build/cjs/index.js.map +1 -1
  3. package/build/esm/index.js +75 -37
  4. package/build/esm/index.js.map +1 -1
  5. package/build/source/QueryClient.js +6 -0
  6. package/build/source/__tests__/QueryClientProvider.test.jsx +2 -1
  7. package/build/source/__tests__/createInfiniteQuery.test.jsx +67 -20
  8. package/build/source/__tests__/createMutation.test.jsx +19 -18
  9. package/build/source/__tests__/createQueries.test.jsx +2 -18
  10. package/build/source/__tests__/createQuery.test.jsx +70 -28
  11. package/build/source/__tests__/suspense.test.jsx +6 -5
  12. package/build/source/__tests__/useIsFetching.test.jsx +2 -4
  13. package/build/source/__tests__/useIsMutating.test.jsx +25 -28
  14. package/build/source/__tests__/utils.jsx +4 -3
  15. package/build/source/createBaseQuery.js +45 -19
  16. package/build/source/createQueries.js +5 -5
  17. package/build/source/index.js +1 -0
  18. package/build/source/useIsFetching.js +5 -5
  19. package/build/source/useIsMutating.js +5 -5
  20. package/build/types/QueryClient.d.ts +29 -0
  21. package/build/types/QueryClientProvider.d.ts +1 -1
  22. package/build/types/__tests__/utils.d.ts +3 -4
  23. package/build/types/createBaseQuery.d.ts +3 -2
  24. package/build/types/createInfiniteQuery.d.ts +4 -2
  25. package/build/types/createMutation.d.ts +4 -2
  26. package/build/types/createQueries.d.ts +5 -4
  27. package/build/types/createQuery.d.ts +2 -1
  28. package/build/types/index.d.ts +2 -0
  29. package/build/types/types.d.ts +2 -1
  30. package/build/types/useIsFetching.d.ts +3 -7
  31. package/build/types/useIsMutating.d.ts +3 -7
  32. package/build/umd/index.js +1 -1
  33. package/build/umd/index.js.map +1 -1
  34. package/package.json +5 -5
  35. package/src/QueryClient.ts +84 -0
  36. package/src/QueryClientProvider.tsx +1 -1
  37. package/src/__tests__/QueryClientProvider.test.tsx +2 -1
  38. package/src/__tests__/createInfiniteQuery.test.tsx +95 -34
  39. package/src/__tests__/createMutation.test.tsx +19 -18
  40. package/src/__tests__/createQueries.test.tsx +2 -24
  41. package/src/__tests__/createQuery.test.tsx +86 -29
  42. package/src/__tests__/suspense.test.tsx +6 -5
  43. package/src/__tests__/useIsFetching.test.tsx +2 -4
  44. package/src/__tests__/useIsMutating.test.tsx +32 -40
  45. package/src/__tests__/utils.tsx +4 -3
  46. package/src/createBaseQuery.ts +70 -25
  47. package/src/createInfiniteQuery.ts +3 -2
  48. package/src/createMutation.ts +4 -2
  49. package/src/createQueries.ts +9 -8
  50. package/src/createQuery.ts +4 -2
  51. package/src/index.ts +7 -0
  52. package/src/types.ts +4 -2
  53. package/src/useIsFetching.ts +10 -13
  54. package/src/useIsMutating.ts +10 -11
@@ -33,6 +33,9 @@ import {
33
33
  setActTimeout,
34
34
  sleep,
35
35
  } from './utils'
36
+ import { vi } from 'vitest'
37
+ import type { Mock } from 'vitest'
38
+ import { reconcile } from 'solid-js/store'
36
39
 
37
40
  describe('createQuery', () => {
38
41
  const queryCache = new QueryCache()
@@ -491,7 +494,7 @@ describe('createQuery', () => {
491
494
  it('should call onSuccess after a query has been fetched', async () => {
492
495
  const key = queryKey()
493
496
  const states: CreateQueryResult<string>[] = []
494
- const onSuccess = jest.fn()
497
+ const onSuccess = vi.fn()
495
498
 
496
499
  function Page() {
497
500
  const state = createQuery(() => ({
@@ -523,7 +526,7 @@ describe('createQuery', () => {
523
526
  it('should call onSuccess after a disabled query has been fetched', async () => {
524
527
  const key = queryKey()
525
528
  const states: CreateQueryResult<string>[] = []
526
- const onSuccess = jest.fn()
529
+ const onSuccess = vi.fn()
527
530
 
528
531
  function Page() {
529
532
  const state = createQuery(() => ({
@@ -561,7 +564,7 @@ describe('createQuery', () => {
561
564
  it('should not call onSuccess if a component has unmounted', async () => {
562
565
  const key = queryKey()
563
566
  const states: CreateQueryResult<string>[] = []
564
- const onSuccess = jest.fn()
567
+ const onSuccess = vi.fn()
565
568
 
566
569
  function Page() {
567
570
  const [show, setShow] = createSignal(true)
@@ -601,7 +604,7 @@ describe('createQuery', () => {
601
604
  it('should call onError after a query has been fetched with an error', async () => {
602
605
  const key = queryKey()
603
606
  const states: CreateQueryResult<unknown>[] = []
604
- const onError = jest.fn()
607
+ const onError = vi.fn()
605
608
 
606
609
  function Page() {
607
610
  const state = createQuery(() => ({
@@ -632,7 +635,7 @@ describe('createQuery', () => {
632
635
 
633
636
  it('should not call onError when receiving a CancelledError', async () => {
634
637
  const key = queryKey()
635
- const onError = jest.fn()
638
+ const onError = vi.fn()
636
639
 
637
640
  function Page() {
638
641
  const state = createQuery(() => ({
@@ -666,7 +669,7 @@ describe('createQuery', () => {
666
669
  it('should call onSettled after a query has been fetched', async () => {
667
670
  const key = queryKey()
668
671
  const states: CreateQueryResult<string>[] = []
669
- const onSettled = jest.fn()
672
+ const onSettled = vi.fn()
670
673
 
671
674
  function Page() {
672
675
  const state = createQuery(() => ({
@@ -696,7 +699,7 @@ describe('createQuery', () => {
696
699
  it('should call onSettled after a query has been fetched with an error', async () => {
697
700
  const key = queryKey()
698
701
  const states: CreateQueryResult<string>[] = []
699
- const onSettled = jest.fn()
702
+ const onSettled = vi.fn()
700
703
 
701
704
  function Page() {
702
705
  const state = createQuery(() => ({
@@ -1280,7 +1283,6 @@ describe('createQuery', () => {
1280
1283
  count++
1281
1284
  return count === 1 ? result1 : result2
1282
1285
  },
1283
- notifyOnChangeProps: 'all',
1284
1286
  }))
1285
1287
 
1286
1288
  createRenderEffect(() => {
@@ -1320,9 +1322,8 @@ describe('createQuery', () => {
1320
1322
 
1321
1323
  expect(todos).toEqual(result1)
1322
1324
  expect(newTodos).toEqual(result2)
1323
- expect(newTodos).not.toBe(todos)
1324
1325
  expect(newTodo1).toBe(todo1)
1325
- expect(newTodo2).not.toBe(todo2)
1326
+ expect(newTodo2).toBe(todo2)
1326
1327
 
1327
1328
  return null
1328
1329
  })
@@ -2452,7 +2453,7 @@ describe('createQuery', () => {
2452
2453
 
2453
2454
  it('should not refetch query on focus when `enabled` is set to `false`', async () => {
2454
2455
  const key = queryKey()
2455
- const queryFn = jest.fn<string, unknown[]>().mockReturnValue('data')
2456
+ const queryFn = vi.fn<unknown[], string>().mockReturnValue('data')
2456
2457
 
2457
2458
  function Page() {
2458
2459
  const { data = 'default' } = createQuery(() => ({
@@ -3255,7 +3256,7 @@ describe('createQuery', () => {
3255
3256
 
3256
3257
  it('should keep initial data when the query key changes', async () => {
3257
3258
  const key = queryKey()
3258
- const states: DefinedCreateQueryResult<{ count: number }>[] = []
3259
+ const states: Partial<DefinedCreateQueryResult<{ count: number }>>[] = []
3259
3260
 
3260
3261
  function Page() {
3261
3262
  const [count, setCount] = createSignal(0)
@@ -3264,6 +3265,7 @@ describe('createQuery', () => {
3264
3265
  queryFn: () => ({ count: 10 }),
3265
3266
  staleTime: Infinity,
3266
3267
  initialData: () => ({ count: count() }),
3268
+ reconcile: false,
3267
3269
  }))
3268
3270
  createRenderEffect(() => {
3269
3271
  states.push({ ...state })
@@ -3296,7 +3298,7 @@ describe('createQuery', () => {
3296
3298
  it('should retry specified number of times', async () => {
3297
3299
  const key = queryKey()
3298
3300
 
3299
- const queryFn = jest.fn<unknown, unknown[]>()
3301
+ const queryFn = vi.fn<unknown[], unknown>()
3300
3302
  queryFn.mockImplementation(() => {
3301
3303
  return Promise.reject(new Error('Error test Barrett'))
3302
3304
  })
@@ -3337,7 +3339,7 @@ describe('createQuery', () => {
3337
3339
  it('should not retry if retry function `false`', async () => {
3338
3340
  const key = queryKey()
3339
3341
 
3340
- const queryFn = jest.fn<unknown, unknown[]>()
3342
+ const queryFn = vi.fn<unknown[], unknown>()
3341
3343
 
3342
3344
  queryFn.mockImplementationOnce(() => {
3343
3345
  return Promise.reject(new Error('Error test Tanner'))
@@ -3385,7 +3387,7 @@ describe('createQuery', () => {
3385
3387
 
3386
3388
  type DelayError = { delay: number }
3387
3389
 
3388
- const queryFn = jest.fn<unknown, unknown[]>()
3390
+ const queryFn = vi.fn<unknown[], unknown>()
3389
3391
  queryFn.mockImplementation(() => {
3390
3392
  return Promise.reject({ delay: 50 })
3391
3393
  })
@@ -3596,10 +3598,10 @@ describe('createQuery', () => {
3596
3598
  const key = queryKey()
3597
3599
  const states: CreateQueryResult<string>[] = []
3598
3600
 
3599
- const queryFn = jest.fn<string, unknown[]>()
3601
+ const queryFn = vi.fn<unknown[], string>()
3600
3602
  queryFn.mockImplementation(() => 'data')
3601
3603
 
3602
- const prefetchQueryFn = jest.fn<string, unknown[]>()
3604
+ const prefetchQueryFn = vi.fn<unknown[], string>()
3603
3605
  prefetchQueryFn.mockImplementation(() => 'not yet...')
3604
3606
 
3605
3607
  await queryClient.prefetchQuery({
@@ -3633,10 +3635,10 @@ describe('createQuery', () => {
3633
3635
  it('should not refetch if not stale after a prefetch', async () => {
3634
3636
  const key = queryKey()
3635
3637
 
3636
- const queryFn = jest.fn<string, unknown[]>()
3638
+ const queryFn = vi.fn<unknown[], string>()
3637
3639
  queryFn.mockImplementation(() => 'data')
3638
3640
 
3639
- const prefetchQueryFn = jest.fn<Promise<string>, unknown[]>()
3641
+ const prefetchQueryFn = vi.fn<unknown[], Promise<string>>()
3640
3642
  prefetchQueryFn.mockImplementation(async () => {
3641
3643
  await sleep(10)
3642
3644
  return 'not yet...'
@@ -3909,7 +3911,7 @@ describe('createQuery', () => {
3909
3911
 
3910
3912
  it('it should support enabled:false in query object syntax', async () => {
3911
3913
  const key = queryKey()
3912
- const queryFn = jest.fn<string, unknown[]>()
3914
+ const queryFn = vi.fn<unknown[], string>()
3913
3915
  queryFn.mockImplementation(() => 'data')
3914
3916
 
3915
3917
  function Page() {
@@ -3981,7 +3983,7 @@ describe('createQuery', () => {
3981
3983
  ))
3982
3984
 
3983
3985
  await waitFor(() => screen.getByText('fetched data'))
3984
- const setTimeoutSpy = jest.spyOn(window, 'setTimeout')
3986
+ const setTimeoutSpy = vi.spyOn(window, 'setTimeout')
3985
3987
 
3986
3988
  result.unmount()
3987
3989
 
@@ -4007,7 +4009,7 @@ describe('createQuery', () => {
4007
4009
  ))
4008
4010
 
4009
4011
  await waitFor(() => screen.getByText('fetched data'))
4010
- const setTimeoutSpy = jest.spyOn(window, 'setTimeout')
4012
+ const setTimeoutSpy = vi.spyOn(window, 'setTimeout')
4011
4013
 
4012
4014
  result.unmount()
4013
4015
 
@@ -4019,8 +4021,8 @@ describe('createQuery', () => {
4019
4021
 
4020
4022
  it('should not cause memo churn when data does not change', async () => {
4021
4023
  const key = queryKey()
4022
- const queryFn = jest.fn<string, unknown[]>().mockReturnValue('data')
4023
- const memoFn = jest.fn()
4024
+ const queryFn = vi.fn<unknown[], string>().mockReturnValue('data')
4025
+ const memoFn = vi.fn()
4024
4026
 
4025
4027
  function Page() {
4026
4028
  const result = createQuery(() => ({
@@ -4255,7 +4257,7 @@ describe('createQuery', () => {
4255
4257
  it('should refetch if any query instance becomes enabled', async () => {
4256
4258
  const key = queryKey()
4257
4259
 
4258
- const queryFn = jest.fn<string, unknown[]>().mockReturnValue('data')
4260
+ const queryFn = vi.fn<unknown[], string>().mockReturnValue('data')
4259
4261
 
4260
4262
  function Disabled() {
4261
4263
  createQuery(() => ({ queryKey: key, queryFn, enabled: false }))
@@ -4608,13 +4610,68 @@ describe('createQuery', () => {
4608
4610
  expect(states).toHaveLength(1)
4609
4611
  })
4610
4612
 
4613
+ it('The reconcile fn callback should correctly maintain referential equality', async () => {
4614
+ const key1 = queryKey()
4615
+ const states: Array<Array<number>> = []
4616
+
4617
+ function Page() {
4618
+ const [forceValue, setForceValue] = createSignal(1)
4619
+
4620
+ const state = createQuery(() => ({
4621
+ queryKey: key1,
4622
+ queryFn: async () => {
4623
+ await sleep(10)
4624
+ return [1, 2]
4625
+ },
4626
+ select: (res) => res.map((x) => x + 1),
4627
+ reconcile(oldData, newData) {
4628
+ return reconcile(newData)(oldData)
4629
+ },
4630
+ }))
4631
+
4632
+ createEffect(() => {
4633
+ if (state.data) {
4634
+ states.push(state.data)
4635
+ }
4636
+ })
4637
+
4638
+ const forceUpdate = () => {
4639
+ setForceValue((prev) => prev + 1)
4640
+ }
4641
+
4642
+ return (
4643
+ <div>
4644
+ <h2>Data: {JSON.stringify(state.data)}</h2>
4645
+ <h2>forceValue: {forceValue}</h2>
4646
+ <button onClick={forceUpdate}>forceUpdate</button>
4647
+ </div>
4648
+ )
4649
+ }
4650
+
4651
+ render(() => (
4652
+ <QueryClientProvider client={queryClient}>
4653
+ <Page />
4654
+ </QueryClientProvider>
4655
+ ))
4656
+ await waitFor(() => screen.getByText('Data: [2,3]'))
4657
+ expect(states).toHaveLength(1)
4658
+
4659
+ fireEvent.click(screen.getByRole('button', { name: /forceUpdate/i }))
4660
+
4661
+ await waitFor(() => screen.getByText('forceValue: 2'))
4662
+ await waitFor(() => screen.getByText('Data: [2,3]'))
4663
+
4664
+ // effect should not be triggered again due to structural sharing
4665
+ expect(states).toHaveLength(1)
4666
+ })
4667
+
4611
4668
  it('should cancel the query function when there are no more subscriptions', async () => {
4612
4669
  const key = queryKey()
4613
- let cancelFn: jest.Mock = jest.fn()
4670
+ let cancelFn: Mock = vi.fn()
4614
4671
 
4615
4672
  const queryFn = ({ signal }: { signal?: AbortSignal }) => {
4616
4673
  const promise = new Promise<string>((resolve, reject) => {
4617
- cancelFn = jest.fn(() => reject('Cancelled'))
4674
+ cancelFn = vi.fn(() => reject('Cancelled'))
4618
4675
  signal?.addEventListener('abort', cancelFn)
4619
4676
  sleep(20).then(() => resolve('OK'))
4620
4677
  })
@@ -4958,7 +5015,7 @@ describe('createQuery', () => {
4958
5015
  })
4959
5016
 
4960
5017
  it('should refetch when changed enabled to true in error state', async () => {
4961
- const queryFn = jest.fn<unknown, unknown[]>()
5018
+ const queryFn = vi.fn<unknown[], unknown>()
4962
5019
  queryFn.mockImplementation(async () => {
4963
5020
  await sleep(10)
4964
5021
  return Promise.reject(new Error('Suspense Error Bingo'))
@@ -6052,7 +6109,7 @@ describe('createQuery', () => {
6052
6109
 
6053
6110
  it('setQueryData - should not call onSuccess callback of active observers', async () => {
6054
6111
  const key = queryKey()
6055
- const onSuccess = jest.fn()
6112
+ const onSuccess = vi.fn()
6056
6113
 
6057
6114
  function Page() {
6058
6115
  const state = createQuery(() => ({
@@ -20,6 +20,7 @@ import {
20
20
  QueryClientProvider,
21
21
  } from '..'
22
22
  import { createQueryClient, queryKey, sleep } from './utils'
23
+ import { vi } from 'vitest'
23
24
 
24
25
  describe("useQuery's in Suspense mode", () => {
25
26
  const queryCache = new QueryCache()
@@ -142,7 +143,7 @@ describe("useQuery's in Suspense mode", () => {
142
143
  it('should not call the queryFn twice when used in Suspense mode', async () => {
143
144
  const key = queryKey()
144
145
 
145
- const queryFn = jest.fn<string, unknown[]>()
146
+ const queryFn = vi.fn<unknown[], string>()
146
147
  queryFn.mockImplementation(() => {
147
148
  sleep(10)
148
149
  return 'data'
@@ -219,7 +220,7 @@ describe("useQuery's in Suspense mode", () => {
219
220
  it('should call onSuccess on the first successful call', async () => {
220
221
  const key = queryKey()
221
222
 
222
- const successFn = jest.fn()
223
+ const successFn = vi.fn()
223
224
 
224
225
  function Page() {
225
226
  createQuery(() => ({
@@ -254,8 +255,8 @@ describe("useQuery's in Suspense mode", () => {
254
255
  it('should call every onSuccess handler within a suspense boundary', async () => {
255
256
  const key = queryKey()
256
257
 
257
- const successFn1 = jest.fn()
258
- const successFn2 = jest.fn()
258
+ const successFn1 = vi.fn()
259
+ const successFn2 = vi.fn()
259
260
 
260
261
  function FirstComponent() {
261
262
  createQuery(() => ({
@@ -733,7 +734,7 @@ describe("useQuery's in Suspense mode", () => {
733
734
  it('should not call the queryFn when not enabled', async () => {
734
735
  const key = queryKey()
735
736
 
736
- const queryFn = jest.fn<Promise<string>, unknown[]>()
737
+ const queryFn = vi.fn<unknown[], Promise<string>>()
737
738
  queryFn.mockImplementation(async () => {
738
739
  await sleep(10)
739
740
  return '23'
@@ -152,9 +152,7 @@ describe('useIsFetching', () => {
152
152
  function Page() {
153
153
  const [started, setStarted] = createSignal(false)
154
154
  const isFetching = useIsFetching(() => ({
155
- filters: {
156
- queryKey: key1,
157
- },
155
+ queryKey: key1,
158
156
  }))
159
157
 
160
158
  createRenderEffect(() => {
@@ -237,7 +235,7 @@ describe('useIsFetching', () => {
237
235
  () => queryClient,
238
236
  )
239
237
 
240
- const isFetching = useIsFetching(() => ({ queryClient }))
238
+ const isFetching = useIsFetching(undefined, () => queryClient)
241
239
 
242
240
  return (
243
241
  <div>
@@ -6,6 +6,7 @@ import { createEffect, createRenderEffect, createSignal, Show } from 'solid-js'
6
6
  import { render } from 'solid-testing-library'
7
7
  import * as MutationCacheModule from '../../../query-core/src/mutationCache'
8
8
  import { setActTimeout } from './utils'
9
+ import { vi } from 'vitest'
9
10
 
10
11
  describe('useIsMutating', () => {
11
12
  it('should return the number of fetching mutations', async () => {
@@ -68,9 +69,7 @@ describe('useIsMutating', () => {
68
69
  const queryClient = createQueryClient()
69
70
 
70
71
  function IsMutating() {
71
- const isMutating = useIsMutating(() => ({
72
- filters: { mutationKey: ['mutation1'] },
73
- }))
72
+ const isMutating = useIsMutating(() => ({ mutationKey: ['mutation1'] }))
74
73
  createRenderEffect(() => {
75
74
  isMutatings.push(isMutating())
76
75
  })
@@ -116,10 +115,8 @@ describe('useIsMutating', () => {
116
115
 
117
116
  function IsMutating() {
118
117
  const isMutating = useIsMutating(() => ({
119
- filters: {
120
- predicate: (mutation) =>
121
- mutation.options.mutationKey?.[0] === 'mutation1',
122
- },
118
+ predicate: (mutation) =>
119
+ mutation.options.mutationKey?.[0] === 'mutation1',
123
120
  }))
124
121
  createRenderEffect(() => {
125
122
  isMutatings.push(isMutating())
@@ -161,6 +158,33 @@ describe('useIsMutating', () => {
161
158
  await waitFor(() => expect(isMutatings).toEqual([0, 1, 0]))
162
159
  })
163
160
 
161
+ it('should use provided custom queryClient', async () => {
162
+ const queryClient = createQueryClient()
163
+ function Page() {
164
+ const isMutating = useIsMutating(undefined, () => queryClient)
165
+ const { mutate } = createMutation(
166
+ () => ({
167
+ mutationKey: ['mutation1'],
168
+ mutationFn: async () => {
169
+ await sleep(10)
170
+ return 'data'
171
+ },
172
+ }),
173
+ () => queryClient,
174
+ )
175
+ createEffect(() => {
176
+ mutate()
177
+ })
178
+ return (
179
+ <div>
180
+ <div>mutating: {isMutating}</div>
181
+ </div>
182
+ )
183
+ }
184
+ render(() => <Page></Page>)
185
+ await waitFor(() => screen.findByText('mutating: 1'))
186
+ })
187
+
164
188
  it('should not change state if unmounted', async () => {
165
189
  // We have to mock the MutationCache to not unsubscribe
166
190
  // the listener when the component is unmounted
@@ -171,7 +195,7 @@ describe('useIsMutating', () => {
171
195
  }
172
196
  }
173
197
 
174
- const MutationCacheSpy = jest
198
+ const MutationCacheSpy = vi
175
199
  .spyOn(MutationCacheModule, 'MutationCache')
176
200
  .mockImplementation((fn) => {
177
201
  return new MutationCacheMock(fn)
@@ -221,36 +245,4 @@ describe('useIsMutating', () => {
221
245
  await sleep(20)
222
246
  MutationCacheSpy.mockRestore()
223
247
  })
224
-
225
- it('should use provided custom queryClient', async () => {
226
- const queryClient = createQueryClient()
227
-
228
- function Page() {
229
- const isMutating = useIsMutating(() => ({ queryClient }))
230
- const { mutate } = createMutation(
231
- () => ({
232
- mutationKey: ['mutation1'],
233
- mutationFn: async () => {
234
- await sleep(10)
235
- return 'data'
236
- },
237
- }),
238
- () => queryClient,
239
- )
240
-
241
- createEffect(() => {
242
- mutate()
243
- })
244
-
245
- return (
246
- <div>
247
- <div>mutating: {isMutating}</div>
248
- </div>
249
- )
250
- }
251
-
252
- render(() => <Page></Page>)
253
-
254
- await waitFor(() => screen.findByText('mutating: 1'))
255
- })
256
248
  })
@@ -1,7 +1,8 @@
1
1
  import type { QueryClientConfig } from '@tanstack/query-core'
2
- import { QueryClient } from '@tanstack/query-core'
2
+ import { QueryClient } from '../QueryClient'
3
3
  import type { ParentProps } from 'solid-js'
4
4
  import { createEffect, createSignal, onCleanup, Show } from 'solid-js'
5
+ import { vi } from 'vitest'
5
6
 
6
7
  let queryKeyCount = 0
7
8
  export function queryKey(): Array<string> {
@@ -34,11 +35,11 @@ export function createQueryClient(config?: QueryClientConfig): QueryClient {
34
35
  }
35
36
 
36
37
  export function mockVisibilityState(value: DocumentVisibilityState) {
37
- return jest.spyOn(document, 'visibilityState', 'get').mockReturnValue(value)
38
+ return vi.spyOn(document, 'visibilityState', 'get').mockReturnValue(value)
38
39
  }
39
40
 
40
41
  export function mockNavigatorOnLine(value: boolean) {
41
- return jest.spyOn(navigator, 'onLine', 'get').mockReturnValue(value)
42
+ return vi.spyOn(navigator, 'onLine', 'get').mockReturnValue(value)
42
43
  }
43
44
 
44
45
  export function sleep(timeout: number): Promise<void> {
@@ -3,11 +3,11 @@
3
3
  // in solid-js/web package. I'll create a GitHub issue with them to see
4
4
  // why that happens.
5
5
  import type {
6
- QueryClient,
7
6
  QueryKey,
8
7
  QueryObserver,
9
8
  QueryObserverResult,
10
9
  } from '@tanstack/query-core'
10
+ import type { QueryClient } from './QueryClient'
11
11
  import { hydrate } from '@tanstack/query-core'
12
12
  import { notifyManager } from '@tanstack/query-core'
13
13
  import type { Accessor } from 'solid-js'
@@ -18,13 +18,29 @@ import {
18
18
  createResource,
19
19
  on,
20
20
  onCleanup,
21
- onMount,
22
21
  } from 'solid-js'
23
- import { createStore, unwrap } from 'solid-js/store'
22
+ import { createStore, reconcile, unwrap } from 'solid-js/store'
24
23
  import { useQueryClient } from './QueryClientProvider'
25
24
  import type { CreateBaseQueryOptions } from './types'
26
25
  import { shouldThrowError } from './utils'
27
26
 
27
+ function reconcileFn<TData, TError>(
28
+ store: QueryObserverResult<TData, TError>,
29
+ result: QueryObserverResult<TData, TError>,
30
+ reconcileOption:
31
+ | string
32
+ | false
33
+ | ((oldData: TData | undefined, newData: TData) => TData),
34
+ ): QueryObserverResult<TData, TError> {
35
+ if (reconcileOption === false) return result
36
+ if (typeof reconcileOption === 'function') {
37
+ const newData = reconcileOption(store.data, result.data as TData)
38
+ return { ...result, data: newData } as typeof result
39
+ }
40
+ const newData = reconcile(result.data, { key: reconcileOption })(store.data)
41
+ return { ...result, data: newData } as typeof result
42
+ }
43
+
28
44
  // Base Query Function that is used to create the query.
29
45
  export function createBaseQuery<
30
46
  TQueryFnData,
@@ -37,12 +53,13 @@ export function createBaseQuery<
37
53
  CreateBaseQueryOptions<TQueryFnData, TError, TData, TQueryData, TQueryKey>
38
54
  >,
39
55
  Observer: typeof QueryObserver,
40
- queryClient?: () => QueryClient,
56
+ queryClient?: Accessor<QueryClient>,
41
57
  ) {
42
58
  const client = createMemo(() => useQueryClient(queryClient?.()))
43
59
 
44
60
  const defaultedOptions = client().defaultQueryOptions(options())
45
61
  defaultedOptions._optimisticResults = 'optimistic'
62
+ defaultedOptions.structuralSharing = false
46
63
  if (isServer) {
47
64
  defaultedOptions.retry = false
48
65
  defaultedOptions.throwErrors = true
@@ -64,7 +81,21 @@ export function createBaseQuery<
64
81
  ) => {
65
82
  return observer.subscribe((result) => {
66
83
  notifyManager.batchCalls(() => {
67
- const unwrappedResult = { ...unwrap(result) }
84
+ const query = observer.getCurrentQuery()
85
+ const { refetch, ...rest } = unwrap(result)
86
+ const unwrappedResult = {
87
+ ...rest,
88
+
89
+ // hydrate() expects a QueryState object, which is similar but not
90
+ // quite the same as a QueryObserverResult object. Thus, for now, we're
91
+ // copying over the missing properties from state in order to support hydration
92
+ dataUpdateCount: query.state.dataUpdateCount,
93
+ fetchFailureCount: query.state.fetchFailureCount,
94
+ fetchFailureReason: query.state.fetchFailureReason,
95
+ fetchMeta: query.state.fetchMeta,
96
+ isInvalidated: query.state.isInvalidated,
97
+ }
98
+
68
99
  if (unwrappedResult.isError) {
69
100
  if (process.env['NODE_ENV'] === 'development') {
70
101
  console.error(unwrappedResult.error)
@@ -72,7 +103,9 @@ export function createBaseQuery<
72
103
  reject(unwrappedResult.error)
73
104
  }
74
105
  if (unwrappedResult.isSuccess) {
75
- resolve(unwrappedResult)
106
+ // Use of any here is fine
107
+ // We cannot include refetch since it is not serializable
108
+ resolve(unwrappedResult as any)
76
109
  }
77
110
  })()
78
111
  })
@@ -81,18 +114,28 @@ export function createBaseQuery<
81
114
  const createClientSubscriber = () => {
82
115
  return observer.subscribe((result) => {
83
116
  notifyManager.batchCalls(() => {
84
- const unwrappedResult = { ...unwrap(result) }
117
+ // @ts-expect-error - This will error because the reconcile option does not
118
+ // exist on the query-core QueryObserverResult type
119
+ const reconcileOptions = observer.options.reconcile
85
120
  // If the query has data we dont suspend but instead mutate the resource
86
121
  // This could happen when placeholderData/initialData is defined
87
- if (
88
- queryResource()?.data &&
89
- unwrappedResult.data &&
90
- !queryResource.loading
91
- ) {
92
- setState(unwrappedResult)
122
+ if (queryResource()?.data && result.data && !queryResource.loading) {
123
+ setState((store) => {
124
+ return reconcileFn(
125
+ store,
126
+ result,
127
+ reconcileOptions === undefined ? 'id' : reconcileOptions,
128
+ )
129
+ })
93
130
  mutate(state)
94
131
  } else {
95
- setState(unwrappedResult)
132
+ setState((store) => {
133
+ return reconcileFn(
134
+ store,
135
+ result,
136
+ reconcileOptions === undefined ? 'id' : reconcileOptions,
137
+ )
138
+ })
96
139
  refetch()
97
140
  }
98
141
  })()
@@ -178,13 +221,17 @@ export function createBaseQuery<
178
221
  }
179
222
  })
180
223
 
181
- onMount(() => {
182
- observer.setOptions(defaultedOptions, { listeners: false })
183
- })
184
-
185
- createComputed(() => {
186
- observer.setOptions(client().defaultQueryOptions(options()))
187
- })
224
+ createComputed(
225
+ on(
226
+ () => client().defaultQueryOptions(options()),
227
+ () => observer.setOptions(client().defaultQueryOptions(options())),
228
+ {
229
+ // Defer because we don't need to trigger on first render
230
+ // This only cares about changes to options after the observer is created
231
+ defer: true,
232
+ },
233
+ ),
234
+ )
188
235
 
189
236
  createComputed(
190
237
  on(
@@ -209,10 +256,8 @@ export function createBaseQuery<
209
256
  target: QueryObserverResult<TData, TError>,
210
257
  prop: keyof QueryObserverResult<TData, TError>,
211
258
  ): any {
212
- if (prop === 'data') {
213
- return queryResource()?.data
214
- }
215
- return Reflect.get(target, prop)
259
+ const val = queryResource()?.[prop]
260
+ return val !== undefined ? val : Reflect.get(target, prop)
216
261
  },
217
262
  }
218
263