@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
@@ -3,6 +3,8 @@ import { createEffect, createMemo, createRenderEffect, createSignal, ErrorBounda
3
3
  import { fireEvent, render, screen, waitFor } from 'solid-testing-library';
4
4
  import { createQuery, QueryCache, QueryClientProvider, keepPreviousData, } from '..';
5
5
  import { Blink, createQueryClient, expectType, mockNavigatorOnLine, mockVisibilityState, queryKey, setActTimeout, sleep, } from './utils';
6
+ import { vi } from 'vitest';
7
+ import { reconcile } from 'solid-js/store';
6
8
  describe('createQuery', () => {
7
9
  const queryCache = new QueryCache();
8
10
  const queryClient = createQueryClient({ queryCache });
@@ -361,7 +363,7 @@ describe('createQuery', () => {
361
363
  it('should call onSuccess after a query has been fetched', async () => {
362
364
  const key = queryKey();
363
365
  const states = [];
364
- const onSuccess = jest.fn();
366
+ const onSuccess = vi.fn();
365
367
  function Page() {
366
368
  const state = createQuery(() => ({
367
369
  queryKey: key,
@@ -387,7 +389,7 @@ describe('createQuery', () => {
387
389
  it('should call onSuccess after a disabled query has been fetched', async () => {
388
390
  const key = queryKey();
389
391
  const states = [];
390
- const onSuccess = jest.fn();
392
+ const onSuccess = vi.fn();
391
393
  function Page() {
392
394
  const state = createQuery(() => ({
393
395
  queryKey: key,
@@ -416,7 +418,7 @@ describe('createQuery', () => {
416
418
  it('should not call onSuccess if a component has unmounted', async () => {
417
419
  const key = queryKey();
418
420
  const states = [];
419
- const onSuccess = jest.fn();
421
+ const onSuccess = vi.fn();
420
422
  function Page() {
421
423
  const [show, setShow] = createSignal(true);
422
424
  createEffect(() => {
@@ -448,7 +450,7 @@ describe('createQuery', () => {
448
450
  it('should call onError after a query has been fetched with an error', async () => {
449
451
  const key = queryKey();
450
452
  const states = [];
451
- const onError = jest.fn();
453
+ const onError = vi.fn();
452
454
  function Page() {
453
455
  const state = createQuery(() => ({
454
456
  queryKey: key,
@@ -471,7 +473,7 @@ describe('createQuery', () => {
471
473
  });
472
474
  it('should not call onError when receiving a CancelledError', async () => {
473
475
  const key = queryKey();
474
- const onError = jest.fn();
476
+ const onError = vi.fn();
475
477
  function Page() {
476
478
  const state = createQuery(() => ({
477
479
  queryKey: key,
@@ -497,7 +499,7 @@ describe('createQuery', () => {
497
499
  it('should call onSettled after a query has been fetched', async () => {
498
500
  const key = queryKey();
499
501
  const states = [];
500
- const onSettled = jest.fn();
502
+ const onSettled = vi.fn();
501
503
  function Page() {
502
504
  const state = createQuery(() => ({
503
505
  queryKey: key,
@@ -520,7 +522,7 @@ describe('createQuery', () => {
520
522
  it('should call onSettled after a query has been fetched with an error', async () => {
521
523
  const key = queryKey();
522
524
  const states = [];
523
- const onSettled = jest.fn();
525
+ const onSettled = vi.fn();
524
526
  function Page() {
525
527
  const state = createQuery(() => ({
526
528
  queryKey: key,
@@ -964,7 +966,6 @@ describe('createQuery', () => {
964
966
  count++;
965
967
  return count === 1 ? result1 : result2;
966
968
  },
967
- notifyOnChangeProps: 'all',
968
969
  }));
969
970
  createRenderEffect(() => {
970
971
  states.push({ ...state });
@@ -991,9 +992,8 @@ describe('createQuery', () => {
991
992
  const newTodo2 = newTodos?.[1];
992
993
  expect(todos).toEqual(result1);
993
994
  expect(newTodos).toEqual(result2);
994
- expect(newTodos).not.toBe(todos);
995
995
  expect(newTodo1).toBe(todo1);
996
- expect(newTodo2).not.toBe(todo2);
996
+ expect(newTodo2).toBe(todo2);
997
997
  return null;
998
998
  });
999
999
  it('should use query function from hook when the existing query does not have a query function', async () => {
@@ -1887,7 +1887,7 @@ describe('createQuery', () => {
1887
1887
  });
1888
1888
  it('should not refetch query on focus when `enabled` is set to `false`', async () => {
1889
1889
  const key = queryKey();
1890
- const queryFn = jest.fn().mockReturnValue('data');
1890
+ const queryFn = vi.fn().mockReturnValue('data');
1891
1891
  function Page() {
1892
1892
  const { data = 'default' } = createQuery(() => ({
1893
1893
  queryKey: key,
@@ -2500,6 +2500,7 @@ describe('createQuery', () => {
2500
2500
  queryFn: () => ({ count: 10 }),
2501
2501
  staleTime: Infinity,
2502
2502
  initialData: () => ({ count: count() }),
2503
+ reconcile: false,
2503
2504
  }));
2504
2505
  createRenderEffect(() => {
2505
2506
  states.push({ ...state });
@@ -2523,7 +2524,7 @@ describe('createQuery', () => {
2523
2524
  });
2524
2525
  it('should retry specified number of times', async () => {
2525
2526
  const key = queryKey();
2526
- const queryFn = jest.fn();
2527
+ const queryFn = vi.fn();
2527
2528
  queryFn.mockImplementation(() => {
2528
2529
  return Promise.reject(new Error('Error test Barrett'));
2529
2530
  });
@@ -2552,7 +2553,7 @@ describe('createQuery', () => {
2552
2553
  });
2553
2554
  it('should not retry if retry function `false`', async () => {
2554
2555
  const key = queryKey();
2555
- const queryFn = jest.fn();
2556
+ const queryFn = vi.fn();
2556
2557
  queryFn.mockImplementationOnce(() => {
2557
2558
  return Promise.reject(new Error('Error test Tanner'));
2558
2559
  });
@@ -2585,7 +2586,7 @@ describe('createQuery', () => {
2585
2586
  });
2586
2587
  it('should extract retryDelay from error', async () => {
2587
2588
  const key = queryKey();
2588
- const queryFn = jest.fn();
2589
+ const queryFn = vi.fn();
2589
2590
  queryFn.mockImplementation(() => {
2590
2591
  return Promise.reject({ delay: 50 });
2591
2592
  });
@@ -2747,9 +2748,9 @@ describe('createQuery', () => {
2747
2748
  it('should refetch if stale after a prefetch', async () => {
2748
2749
  const key = queryKey();
2749
2750
  const states = [];
2750
- const queryFn = jest.fn();
2751
+ const queryFn = vi.fn();
2751
2752
  queryFn.mockImplementation(() => 'data');
2752
- const prefetchQueryFn = jest.fn();
2753
+ const prefetchQueryFn = vi.fn();
2753
2754
  prefetchQueryFn.mockImplementation(() => 'not yet...');
2754
2755
  await queryClient.prefetchQuery({
2755
2756
  queryKey: key,
@@ -2773,9 +2774,9 @@ describe('createQuery', () => {
2773
2774
  });
2774
2775
  it('should not refetch if not stale after a prefetch', async () => {
2775
2776
  const key = queryKey();
2776
- const queryFn = jest.fn();
2777
+ const queryFn = vi.fn();
2777
2778
  queryFn.mockImplementation(() => 'data');
2778
- const prefetchQueryFn = jest.fn();
2779
+ const prefetchQueryFn = vi.fn();
2779
2780
  prefetchQueryFn.mockImplementation(async () => {
2780
2781
  await sleep(10);
2781
2782
  return 'not yet...';
@@ -2977,7 +2978,7 @@ describe('createQuery', () => {
2977
2978
  });
2978
2979
  it('it should support enabled:false in query object syntax', async () => {
2979
2980
  const key = queryKey();
2980
- const queryFn = jest.fn();
2981
+ const queryFn = vi.fn();
2981
2982
  queryFn.mockImplementation(() => 'data');
2982
2983
  function Page() {
2983
2984
  const { fetchStatus } = createQuery(() => ({
@@ -3028,7 +3029,7 @@ describe('createQuery', () => {
3028
3029
  <Page />
3029
3030
  </QueryClientProvider>));
3030
3031
  await waitFor(() => screen.getByText('fetched data'));
3031
- const setTimeoutSpy = jest.spyOn(window, 'setTimeout');
3032
+ const setTimeoutSpy = vi.spyOn(window, 'setTimeout');
3032
3033
  result.unmount();
3033
3034
  expect(setTimeoutSpy).not.toHaveBeenCalled();
3034
3035
  });
@@ -3046,14 +3047,14 @@ describe('createQuery', () => {
3046
3047
  <Page />
3047
3048
  </QueryClientProvider>));
3048
3049
  await waitFor(() => screen.getByText('fetched data'));
3049
- const setTimeoutSpy = jest.spyOn(window, 'setTimeout');
3050
+ const setTimeoutSpy = vi.spyOn(window, 'setTimeout');
3050
3051
  result.unmount();
3051
3052
  expect(setTimeoutSpy).toHaveBeenLastCalledWith(expect.any(Function), 1000 * 60 * 10);
3052
3053
  });
3053
3054
  it('should not cause memo churn when data does not change', async () => {
3054
3055
  const key = queryKey();
3055
- const queryFn = jest.fn().mockReturnValue('data');
3056
- const memoFn = jest.fn();
3056
+ const queryFn = vi.fn().mockReturnValue('data');
3057
+ const memoFn = vi.fn();
3057
3058
  function Page() {
3058
3059
  const result = createQuery(() => ({
3059
3060
  queryKey: key,
@@ -3234,7 +3235,7 @@ describe('createQuery', () => {
3234
3235
  });
3235
3236
  it('should refetch if any query instance becomes enabled', async () => {
3236
3237
  const key = queryKey();
3237
- const queryFn = jest.fn().mockReturnValue('data');
3238
+ const queryFn = vi.fn().mockReturnValue('data');
3238
3239
  function Disabled() {
3239
3240
  createQuery(() => ({ queryKey: key, queryFn, enabled: false }));
3240
3241
  return null;
@@ -3502,12 +3503,53 @@ describe('createQuery', () => {
3502
3503
  // effect should not be triggered again due to structural sharing
3503
3504
  expect(states).toHaveLength(1);
3504
3505
  });
3506
+ it('The reconcile fn callback should correctly maintain referential equality', async () => {
3507
+ const key1 = queryKey();
3508
+ const states = [];
3509
+ function Page() {
3510
+ const [forceValue, setForceValue] = createSignal(1);
3511
+ const state = createQuery(() => ({
3512
+ queryKey: key1,
3513
+ queryFn: async () => {
3514
+ await sleep(10);
3515
+ return [1, 2];
3516
+ },
3517
+ select: (res) => res.map((x) => x + 1),
3518
+ reconcile(oldData, newData) {
3519
+ return reconcile(newData)(oldData);
3520
+ },
3521
+ }));
3522
+ createEffect(() => {
3523
+ if (state.data) {
3524
+ states.push(state.data);
3525
+ }
3526
+ });
3527
+ const forceUpdate = () => {
3528
+ setForceValue((prev) => prev + 1);
3529
+ };
3530
+ return (<div>
3531
+ <h2>Data: {JSON.stringify(state.data)}</h2>
3532
+ <h2>forceValue: {forceValue}</h2>
3533
+ <button onClick={forceUpdate}>forceUpdate</button>
3534
+ </div>);
3535
+ }
3536
+ render(() => (<QueryClientProvider client={queryClient}>
3537
+ <Page />
3538
+ </QueryClientProvider>));
3539
+ await waitFor(() => screen.getByText('Data: [2,3]'));
3540
+ expect(states).toHaveLength(1);
3541
+ fireEvent.click(screen.getByRole('button', { name: /forceUpdate/i }));
3542
+ await waitFor(() => screen.getByText('forceValue: 2'));
3543
+ await waitFor(() => screen.getByText('Data: [2,3]'));
3544
+ // effect should not be triggered again due to structural sharing
3545
+ expect(states).toHaveLength(1);
3546
+ });
3505
3547
  it('should cancel the query function when there are no more subscriptions', async () => {
3506
3548
  const key = queryKey();
3507
- let cancelFn = jest.fn();
3549
+ let cancelFn = vi.fn();
3508
3550
  const queryFn = ({ signal }) => {
3509
3551
  const promise = new Promise((resolve, reject) => {
3510
- cancelFn = jest.fn(() => reject('Cancelled'));
3552
+ cancelFn = vi.fn(() => reject('Cancelled'));
3511
3553
  signal?.addEventListener('abort', cancelFn);
3512
3554
  sleep(20).then(() => resolve('OK'));
3513
3555
  });
@@ -3767,7 +3809,7 @@ describe('createQuery', () => {
3767
3809
  expect(renders).toBe(hashes);
3768
3810
  });
3769
3811
  it('should refetch when changed enabled to true in error state', async () => {
3770
- const queryFn = jest.fn();
3812
+ const queryFn = vi.fn();
3771
3813
  queryFn.mockImplementation(async () => {
3772
3814
  await sleep(10);
3773
3815
  return Promise.reject(new Error('Suspense Error Bingo'));
@@ -4514,7 +4556,7 @@ describe('createQuery', () => {
4514
4556
  });
4515
4557
  it('setQueryData - should not call onSuccess callback of active observers', async () => {
4516
4558
  const key = queryKey();
4517
- const onSuccess = jest.fn();
4559
+ const onSuccess = vi.fn();
4518
4560
  function Page() {
4519
4561
  const state = createQuery(() => ({
4520
4562
  queryKey: key,
@@ -2,6 +2,7 @@ import { fireEvent, render, screen, waitFor } from 'solid-testing-library';
2
2
  import { createRenderEffect, createSignal, ErrorBoundary, on, Show, Suspense, } from 'solid-js';
3
3
  import { createInfiniteQuery, createQuery, QueryCache, QueryClientProvider, } from '..';
4
4
  import { createQueryClient, queryKey, sleep } from './utils';
5
+ import { vi } from 'vitest';
5
6
  describe("useQuery's in Suspense mode", () => {
6
7
  const queryCache = new QueryCache();
7
8
  const queryClient = createQueryClient({ queryCache });
@@ -91,7 +92,7 @@ describe("useQuery's in Suspense mode", () => {
91
92
  });
92
93
  it('should not call the queryFn twice when used in Suspense mode', async () => {
93
94
  const key = queryKey();
94
- const queryFn = jest.fn();
95
+ const queryFn = vi.fn();
95
96
  queryFn.mockImplementation(() => {
96
97
  sleep(10);
97
98
  return 'data';
@@ -141,7 +142,7 @@ describe("useQuery's in Suspense mode", () => {
141
142
  });
142
143
  it('should call onSuccess on the first successful call', async () => {
143
144
  const key = queryKey();
144
- const successFn = jest.fn();
145
+ const successFn = vi.fn();
145
146
  function Page() {
146
147
  createQuery(() => ({
147
148
  queryKey: [key],
@@ -166,8 +167,8 @@ describe("useQuery's in Suspense mode", () => {
166
167
  });
167
168
  it('should call every onSuccess handler within a suspense boundary', async () => {
168
169
  const key = queryKey();
169
- const successFn1 = jest.fn();
170
- const successFn2 = jest.fn();
170
+ const successFn1 = vi.fn();
171
+ const successFn2 = vi.fn();
171
172
  function FirstComponent() {
172
173
  createQuery(() => ({
173
174
  queryKey: key,
@@ -514,7 +515,7 @@ describe("useQuery's in Suspense mode", () => {
514
515
  });
515
516
  it('should not call the queryFn when not enabled', async () => {
516
517
  const key = queryKey();
517
- const queryFn = jest.fn();
518
+ const queryFn = vi.fn();
518
519
  queryFn.mockImplementation(async () => {
519
520
  await sleep(10);
520
521
  return '23';
@@ -120,9 +120,7 @@ describe('useIsFetching', () => {
120
120
  function Page() {
121
121
  const [started, setStarted] = createSignal(false);
122
122
  const isFetching = useIsFetching(() => ({
123
- filters: {
124
- queryKey: key1,
125
- },
123
+ queryKey: key1,
126
124
  }));
127
125
  createRenderEffect(() => {
128
126
  isFetchings.push(isFetching());
@@ -181,7 +179,7 @@ describe('useIsFetching', () => {
181
179
  return 'test';
182
180
  },
183
181
  }), () => queryClient);
184
- const isFetching = useIsFetching(() => ({ queryClient }));
182
+ const isFetching = useIsFetching(undefined, () => queryClient);
185
183
  return (<div>
186
184
  <div>isFetching: {isFetching}</div>
187
185
  </div>);
@@ -5,6 +5,7 @@ import { createEffect, createRenderEffect, createSignal, Show } from 'solid-js';
5
5
  import { render } from 'solid-testing-library';
6
6
  import * as MutationCacheModule from '../../../query-core/src/mutationCache';
7
7
  import { setActTimeout } from './utils';
8
+ import { vi } from 'vitest';
8
9
  describe('useIsMutating', () => {
9
10
  it('should return the number of fetching mutations', async () => {
10
11
  const isMutatings = [];
@@ -54,9 +55,7 @@ describe('useIsMutating', () => {
54
55
  const isMutatings = [];
55
56
  const queryClient = createQueryClient();
56
57
  function IsMutating() {
57
- const isMutating = useIsMutating(() => ({
58
- filters: { mutationKey: ['mutation1'] },
59
- }));
58
+ const isMutating = useIsMutating(() => ({ mutationKey: ['mutation1'] }));
60
59
  createRenderEffect(() => {
61
60
  isMutatings.push(isMutating());
62
61
  });
@@ -94,9 +93,7 @@ describe('useIsMutating', () => {
94
93
  const queryClient = createQueryClient();
95
94
  function IsMutating() {
96
95
  const isMutating = useIsMutating(() => ({
97
- filters: {
98
- predicate: (mutation) => mutation.options.mutationKey?.[0] === 'mutation1',
99
- },
96
+ predicate: (mutation) => mutation.options.mutationKey?.[0] === 'mutation1',
100
97
  }));
101
98
  createRenderEffect(() => {
102
99
  isMutatings.push(isMutating());
@@ -130,6 +127,27 @@ describe('useIsMutating', () => {
130
127
  // Again, No unnecessary re-renders like React
131
128
  await waitFor(() => expect(isMutatings).toEqual([0, 1, 0]));
132
129
  });
130
+ it('should use provided custom queryClient', async () => {
131
+ const queryClient = createQueryClient();
132
+ function Page() {
133
+ const isMutating = useIsMutating(undefined, () => queryClient);
134
+ const { mutate } = createMutation(() => ({
135
+ mutationKey: ['mutation1'],
136
+ mutationFn: async () => {
137
+ await sleep(10);
138
+ return 'data';
139
+ },
140
+ }), () => queryClient);
141
+ createEffect(() => {
142
+ mutate();
143
+ });
144
+ return (<div>
145
+ <div>mutating: {isMutating}</div>
146
+ </div>);
147
+ }
148
+ render(() => <Page></Page>);
149
+ await waitFor(() => screen.findByText('mutating: 1'));
150
+ });
133
151
  it('should not change state if unmounted', async () => {
134
152
  // We have to mock the MutationCache to not unsubscribe
135
153
  // the listener when the component is unmounted
@@ -139,7 +157,7 @@ describe('useIsMutating', () => {
139
157
  return () => void 0;
140
158
  }
141
159
  }
142
- const MutationCacheSpy = jest
160
+ const MutationCacheSpy = vi
143
161
  .spyOn(MutationCacheModule, 'MutationCache')
144
162
  .mockImplementation((fn) => {
145
163
  return new MutationCacheMock(fn);
@@ -177,25 +195,4 @@ describe('useIsMutating', () => {
177
195
  await sleep(20);
178
196
  MutationCacheSpy.mockRestore();
179
197
  });
180
- it('should use provided custom queryClient', async () => {
181
- const queryClient = createQueryClient();
182
- function Page() {
183
- const isMutating = useIsMutating(() => ({ queryClient }));
184
- const { mutate } = createMutation(() => ({
185
- mutationKey: ['mutation1'],
186
- mutationFn: async () => {
187
- await sleep(10);
188
- return 'data';
189
- },
190
- }), () => queryClient);
191
- createEffect(() => {
192
- mutate();
193
- });
194
- return (<div>
195
- <div>mutating: {isMutating}</div>
196
- </div>);
197
- }
198
- render(() => <Page></Page>);
199
- await waitFor(() => screen.findByText('mutating: 1'));
200
- });
201
198
  });
@@ -1,5 +1,6 @@
1
- import { QueryClient } from '@tanstack/query-core';
1
+ import { QueryClient } from '../QueryClient';
2
2
  import { createEffect, createSignal, onCleanup, Show } from 'solid-js';
3
+ import { vi } from 'vitest';
3
4
  let queryKeyCount = 0;
4
5
  export function queryKey() {
5
6
  queryKeyCount++;
@@ -20,10 +21,10 @@ export function createQueryClient(config) {
20
21
  return new QueryClient(config);
21
22
  }
22
23
  export function mockVisibilityState(value) {
23
- return jest.spyOn(document, 'visibilityState', 'get').mockReturnValue(value);
24
+ return vi.spyOn(document, 'visibilityState', 'get').mockReturnValue(value);
24
25
  }
25
26
  export function mockNavigatorOnLine(value) {
26
- return jest.spyOn(navigator, 'onLine', 'get').mockReturnValue(value);
27
+ return vi.spyOn(navigator, 'onLine', 'get').mockReturnValue(value);
27
28
  }
28
29
  export function sleep(timeout) {
29
30
  return new Promise((resolve, _reject) => {
@@ -1,15 +1,26 @@
1
1
  import { hydrate } from '@tanstack/query-core';
2
2
  import { notifyManager } from '@tanstack/query-core';
3
3
  import { isServer } from 'solid-js/web';
4
- import { createComputed, createMemo, createResource, on, onCleanup, onMount, } from 'solid-js';
5
- import { createStore, unwrap } from 'solid-js/store';
4
+ import { createComputed, createMemo, createResource, on, onCleanup, } from 'solid-js';
5
+ import { createStore, reconcile, unwrap } from 'solid-js/store';
6
6
  import { useQueryClient } from './QueryClientProvider';
7
7
  import { shouldThrowError } from './utils';
8
+ function reconcileFn(store, result, reconcileOption) {
9
+ if (reconcileOption === false)
10
+ return result;
11
+ if (typeof reconcileOption === 'function') {
12
+ const newData = reconcileOption(store.data, result.data);
13
+ return { ...result, data: newData };
14
+ }
15
+ const newData = reconcile(result.data, { key: reconcileOption })(store.data);
16
+ return { ...result, data: newData };
17
+ }
8
18
  // Base Query Function that is used to create the query.
9
19
  export function createBaseQuery(options, Observer, queryClient) {
10
20
  const client = createMemo(() => useQueryClient(queryClient?.()));
11
21
  const defaultedOptions = client().defaultQueryOptions(options());
12
22
  defaultedOptions._optimisticResults = 'optimistic';
23
+ defaultedOptions.structuralSharing = false;
13
24
  if (isServer) {
14
25
  defaultedOptions.retry = false;
15
26
  defaultedOptions.throwErrors = true;
@@ -19,7 +30,19 @@ export function createBaseQuery(options, Observer, queryClient) {
19
30
  const createServerSubscriber = (resolve, reject) => {
20
31
  return observer.subscribe((result) => {
21
32
  notifyManager.batchCalls(() => {
22
- const unwrappedResult = { ...unwrap(result) };
33
+ const query = observer.getCurrentQuery();
34
+ const { refetch, ...rest } = unwrap(result);
35
+ const unwrappedResult = {
36
+ ...rest,
37
+ // hydrate() expects a QueryState object, which is similar but not
38
+ // quite the same as a QueryObserverResult object. Thus, for now, we're
39
+ // copying over the missing properties from state in order to support hydration
40
+ dataUpdateCount: query.state.dataUpdateCount,
41
+ fetchFailureCount: query.state.fetchFailureCount,
42
+ fetchFailureReason: query.state.fetchFailureReason,
43
+ fetchMeta: query.state.fetchMeta,
44
+ isInvalidated: query.state.isInvalidated,
45
+ };
23
46
  if (unwrappedResult.isError) {
24
47
  if (process.env['NODE_ENV'] === 'development') {
25
48
  console.error(unwrappedResult.error);
@@ -27,6 +50,8 @@ export function createBaseQuery(options, Observer, queryClient) {
27
50
  reject(unwrappedResult.error);
28
51
  }
29
52
  if (unwrappedResult.isSuccess) {
53
+ // Use of any here is fine
54
+ // We cannot include refetch since it is not serializable
30
55
  resolve(unwrappedResult);
31
56
  }
32
57
  })();
@@ -35,17 +60,21 @@ export function createBaseQuery(options, Observer, queryClient) {
35
60
  const createClientSubscriber = () => {
36
61
  return observer.subscribe((result) => {
37
62
  notifyManager.batchCalls(() => {
38
- const unwrappedResult = { ...unwrap(result) };
63
+ // @ts-expect-error - This will error because the reconcile option does not
64
+ // exist on the query-core QueryObserverResult type
65
+ const reconcileOptions = observer.options.reconcile;
39
66
  // If the query has data we dont suspend but instead mutate the resource
40
67
  // This could happen when placeholderData/initialData is defined
41
- if (queryResource()?.data &&
42
- unwrappedResult.data &&
43
- !queryResource.loading) {
44
- setState(unwrappedResult);
68
+ if (queryResource()?.data && result.data && !queryResource.loading) {
69
+ setState((store) => {
70
+ return reconcileFn(store, result, reconcileOptions === undefined ? 'id' : reconcileOptions);
71
+ });
45
72
  mutate(state);
46
73
  }
47
74
  else {
48
- setState(unwrappedResult);
75
+ setState((store) => {
76
+ return reconcileFn(store, result, reconcileOptions === undefined ? 'id' : reconcileOptions);
77
+ });
49
78
  refetch();
50
79
  }
51
80
  })();
@@ -119,12 +148,11 @@ export function createBaseQuery(options, Observer, queryClient) {
119
148
  unsubscribe = null;
120
149
  }
121
150
  });
122
- onMount(() => {
123
- observer.setOptions(defaultedOptions, { listeners: false });
124
- });
125
- createComputed(() => {
126
- observer.setOptions(client().defaultQueryOptions(options()));
127
- });
151
+ createComputed(on(() => client().defaultQueryOptions(options()), () => observer.setOptions(client().defaultQueryOptions(options())), {
152
+ // Defer because we don't need to trigger on first render
153
+ // This only cares about changes to options after the observer is created
154
+ defer: true,
155
+ }));
128
156
  createComputed(on(() => state.status, () => {
129
157
  if (state.isError &&
130
158
  !state.isFetching &&
@@ -137,10 +165,8 @@ export function createBaseQuery(options, Observer, queryClient) {
137
165
  }));
138
166
  const handler = {
139
167
  get(target, prop) {
140
- if (prop === 'data') {
141
- return queryResource()?.data;
142
- }
143
- return Reflect.get(target, prop);
168
+ const val = queryResource()?.[prop];
169
+ return val !== undefined ? val : Reflect.get(target, prop);
144
170
  },
145
171
  };
146
172
  return new Proxy(state, handler);
@@ -2,14 +2,14 @@ import { notifyManager, QueriesObserver } from '@tanstack/query-core';
2
2
  import { createComputed, onCleanup, onMount } from 'solid-js';
3
3
  import { createStore, unwrap } from 'solid-js/store';
4
4
  import { useQueryClient } from './QueryClientProvider';
5
- export function createQueries(queriesOptions) {
6
- const queryClient = useQueryClient(queriesOptions().queryClient);
5
+ export function createQueries(queriesOptions, queryClient) {
6
+ const client = useQueryClient(queryClient?.());
7
7
  const defaultedQueries = queriesOptions().queries.map((options) => {
8
- const defaultedOptions = queryClient.defaultQueryOptions(options);
8
+ const defaultedOptions = client.defaultQueryOptions(options);
9
9
  defaultedOptions._optimisticResults = 'optimistic';
10
10
  return defaultedOptions;
11
11
  });
12
- const observer = new QueriesObserver(queryClient, defaultedQueries);
12
+ const observer = new QueriesObserver(client, defaultedQueries);
13
13
  const [state, setState] = createStore(observer.getOptimisticResult(defaultedQueries));
14
14
  const unsubscribe = observer.subscribe((result) => {
15
15
  notifyManager.batchCalls(() => {
@@ -22,7 +22,7 @@ export function createQueries(queriesOptions) {
22
22
  });
23
23
  createComputed(() => {
24
24
  const updatedQueries = queriesOptions().queries.map((options) => {
25
- const defaultedOptions = queryClient.defaultQueryOptions(options);
25
+ const defaultedOptions = client.defaultQueryOptions(options);
26
26
  defaultedOptions._optimisticResults = 'optimistic';
27
27
  return defaultedOptions;
28
28
  });
@@ -5,6 +5,7 @@ import './setBatchUpdatesFn';
5
5
  export * from '@tanstack/query-core';
6
6
  // Solid Query
7
7
  export * from './types';
8
+ export { QueryClient } from './QueryClient';
8
9
  export { createQuery } from './createQuery';
9
10
  export { QueryClientContext, QueryClientProvider, useQueryClient, } from './QueryClientProvider';
10
11
  export { useIsFetching } from './useIsFetching';
@@ -1,11 +1,11 @@
1
1
  import { createMemo, createSignal, onCleanup } from 'solid-js';
2
2
  import { useQueryClient } from './QueryClientProvider';
3
- export function useIsFetching(options = () => ({})) {
4
- const queryClient = createMemo(() => useQueryClient(options().queryClient));
5
- const queryCache = createMemo(() => queryClient().getQueryCache());
6
- const [fetches, setFetches] = createSignal(queryClient().isFetching(options().filters));
3
+ export function useIsFetching(filters, queryClient) {
4
+ const client = createMemo(() => useQueryClient(queryClient?.()));
5
+ const queryCache = createMemo(() => client().getQueryCache());
6
+ const [fetches, setFetches] = createSignal(client().isFetching(filters?.()));
7
7
  const unsubscribe = queryCache().subscribe(() => {
8
- setFetches(queryClient().isFetching(options().filters));
8
+ setFetches(client().isFetching(filters?.()));
9
9
  });
10
10
  onCleanup(unsubscribe);
11
11
  return fetches;
@@ -1,11 +1,11 @@
1
1
  import { useQueryClient } from './QueryClientProvider';
2
2
  import { createSignal, onCleanup, createMemo } from 'solid-js';
3
- export function useIsMutating(options = () => ({})) {
4
- const queryClient = createMemo(() => useQueryClient(options().queryClient));
5
- const mutationCache = createMemo(() => queryClient().getMutationCache());
6
- const [mutations, setMutations] = createSignal(queryClient().isMutating(options().filters));
3
+ export function useIsMutating(filters, queryClient) {
4
+ const client = createMemo(() => useQueryClient(queryClient?.()));
5
+ const mutationCache = createMemo(() => client().getMutationCache());
6
+ const [mutations, setMutations] = createSignal(client().isMutating(filters?.()));
7
7
  const unsubscribe = mutationCache().subscribe((_result) => {
8
- setMutations(queryClient().isMutating(options().filters));
8
+ setMutations(client().isMutating(filters?.()));
9
9
  });
10
10
  onCleanup(unsubscribe);
11
11
  return mutations;