@rozenite/tanstack-query-plugin 1.0.0-alpha.0 → 1.0.0-alpha.10
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.
- package/README.md +2 -2
- package/dist/assets/CXEL7IU7-BTFA0aeQ.js +1172 -0
- package/dist/assets/tanstack-query-DWTo8DT_.js +48 -0
- package/dist/react-native.cjs +1 -1
- package/dist/react-native.d.ts +1 -5
- package/dist/react-native.js +3 -86
- package/dist/rozenite.config.d.ts +7 -0
- package/dist/rozenite.json +1 -1
- package/dist/src/react-native/useHandleDevToolsMessages.d.ts +3 -0
- package/dist/src/react-native/useHandleInitialData.d.ts +3 -0
- package/dist/src/react-native/useSyncTanStackCache.d.ts +3 -0
- package/dist/src/react-native/useTanStackQueryDevTools.d.ts +2 -0
- package/dist/src/shared/dehydrate.d.ts +6 -0
- package/dist/src/shared/hydrate.d.ts +10 -0
- package/dist/src/shared/messaging.d.ts +34 -0
- package/dist/src/shared/types.d.ts +25 -0
- package/dist/src/shared/useSyncOnlineStatus.d.ts +2 -0
- package/dist/src/ui/tanstack-query.d.ts +1 -0
- package/dist/src/ui/useHandleSyncMessages.d.ts +2 -0
- package/dist/src/ui/useSyncDevToolsEvents.d.ts +2 -0
- package/dist/src/ui/useSyncInitialData.d.ts +2 -0
- package/dist/tanstack-query.html +1 -1
- package/dist/useTanstackQueryDevTools.cjs +1 -0
- package/dist/useTanstackQueryDevTools.js +312 -0
- package/package.json +14 -9
- package/project.json +12 -0
- package/react-native.ts +2 -2
- package/src/react-native/useHandleDevToolsMessages.ts +119 -0
- package/src/react-native/useHandleInitialData.ts +24 -0
- package/src/react-native/useSyncTanStackCache.ts +246 -0
- package/src/react-native/useTanstackQueryDevTools.ts +21 -0
- package/src/shared/dehydrate.ts +42 -0
- package/src/shared/hydrate.ts +250 -0
- package/src/shared/messaging.ts +54 -0
- package/src/shared/types.ts +48 -0
- package/src/shared/useSyncOnlineStatus.ts +29 -0
- package/src/ui/tanstack-query.tsx +21 -184
- package/src/ui/useHandleSyncMessages.ts +66 -0
- package/src/ui/useSyncDevToolsEvents.ts +55 -0
- package/src/ui/useSyncInitialData.ts +14 -0
- package/tsconfig.json +13 -6
- package/tsconfig.tsbuildinfo +1 -0
- package/dist/assets/tanstack-query-CXp_Psxe.js +0 -48
- package/src/react-native/useTanStackQueryDevTools.ts +0 -214
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
import { QueryClient } from '@tanstack/react-query';
|
|
3
|
+
import { TanStackQueryPluginClient } from '../shared/messaging';
|
|
4
|
+
|
|
5
|
+
export const useHandleDevToolsMessages = (
|
|
6
|
+
queryClient: QueryClient,
|
|
7
|
+
client: TanStackQueryPluginClient | null
|
|
8
|
+
) => {
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
if (!client) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const subscription = client.onMessage(
|
|
15
|
+
'devtools-action',
|
|
16
|
+
({ type, queryHash }) => {
|
|
17
|
+
const activeQuery = queryClient.getQueryCache().get(queryHash);
|
|
18
|
+
|
|
19
|
+
if (!activeQuery) {
|
|
20
|
+
console.warn(`No active query found for hash: ${queryHash}`);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
switch (type) {
|
|
25
|
+
case 'TRIGGER_ERROR': {
|
|
26
|
+
const __previousQueryOptions = activeQuery.options;
|
|
27
|
+
const error = new Error('Unknown error from devtools');
|
|
28
|
+
|
|
29
|
+
activeQuery.setState({
|
|
30
|
+
status: 'error',
|
|
31
|
+
error,
|
|
32
|
+
fetchMeta: {
|
|
33
|
+
...activeQuery.state.fetchMeta,
|
|
34
|
+
// @ts-expect-error This does exist
|
|
35
|
+
__previousQueryOptions,
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
case 'RESTORE_ERROR': {
|
|
41
|
+
queryClient.resetQueries(activeQuery);
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
case 'TRIGGER_LOADING': {
|
|
45
|
+
if (!activeQuery) return;
|
|
46
|
+
const __previousQueryOptions = activeQuery.options;
|
|
47
|
+
// Trigger a fetch in order to trigger suspense as well.
|
|
48
|
+
activeQuery.fetch({
|
|
49
|
+
...__previousQueryOptions,
|
|
50
|
+
queryFn: () => {
|
|
51
|
+
return new Promise(() => {
|
|
52
|
+
// Never resolve - simulates perpetual loading
|
|
53
|
+
});
|
|
54
|
+
},
|
|
55
|
+
gcTime: -1,
|
|
56
|
+
});
|
|
57
|
+
activeQuery.setState({
|
|
58
|
+
data: undefined,
|
|
59
|
+
status: 'pending',
|
|
60
|
+
fetchMeta: {
|
|
61
|
+
...activeQuery.state.fetchMeta,
|
|
62
|
+
// @ts-expect-error This does exist
|
|
63
|
+
__previousQueryOptions,
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
case 'RESTORE_LOADING': {
|
|
69
|
+
const previousState = activeQuery.state;
|
|
70
|
+
const previousOptions = activeQuery.state.fetchMeta
|
|
71
|
+
? (
|
|
72
|
+
activeQuery.state.fetchMeta as unknown as {
|
|
73
|
+
__previousQueryOptions: unknown;
|
|
74
|
+
}
|
|
75
|
+
).__previousQueryOptions
|
|
76
|
+
: null;
|
|
77
|
+
|
|
78
|
+
activeQuery.cancel({ silent: true });
|
|
79
|
+
activeQuery.setState({
|
|
80
|
+
...previousState,
|
|
81
|
+
fetchStatus: 'idle',
|
|
82
|
+
fetchMeta: null,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
if (previousOptions) {
|
|
86
|
+
activeQuery.fetch(previousOptions);
|
|
87
|
+
}
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
case 'RESET': {
|
|
91
|
+
queryClient.resetQueries(activeQuery);
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
case 'REMOVE': {
|
|
95
|
+
queryClient.removeQueries(activeQuery);
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
case 'REFETCH': {
|
|
99
|
+
activeQuery.fetch().catch(() => {
|
|
100
|
+
// Ignore errors
|
|
101
|
+
});
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
case 'INVALIDATE': {
|
|
105
|
+
queryClient.invalidateQueries(activeQuery);
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
default: {
|
|
109
|
+
console.warn(`Unknown devtools action: ${type}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
return () => {
|
|
116
|
+
subscription.remove();
|
|
117
|
+
};
|
|
118
|
+
}, [client]);
|
|
119
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
import { TanStackQueryPluginClient } from '../shared/messaging';
|
|
3
|
+
import { QueryClient } from '@tanstack/react-query';
|
|
4
|
+
import { dehydrateQueryClient } from '../shared/dehydrate';
|
|
5
|
+
|
|
6
|
+
export const useHandleInitialData = (
|
|
7
|
+
queryClient: QueryClient,
|
|
8
|
+
client: TanStackQueryPluginClient | null
|
|
9
|
+
) => {
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
if (!client) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const subscription = client.onMessage('request-initial-data', () => {
|
|
16
|
+
const dehydratedState = dehydrateQueryClient(queryClient);
|
|
17
|
+
client.send('sync-data', { data: dehydratedState });
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
return () => {
|
|
21
|
+
subscription.remove();
|
|
22
|
+
};
|
|
23
|
+
}, [client, queryClient]);
|
|
24
|
+
};
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import {
|
|
2
|
+
QueryClient,
|
|
3
|
+
QueryCacheNotifyEvent,
|
|
4
|
+
MutationCacheNotifyEvent,
|
|
5
|
+
} from '@tanstack/react-query';
|
|
6
|
+
import { useEffect, useMemo, useRef } from 'react';
|
|
7
|
+
import equal from 'fast-deep-equal';
|
|
8
|
+
import { TanStackQueryPluginClient } from '../shared/messaging';
|
|
9
|
+
import {
|
|
10
|
+
dehydrateQuery,
|
|
11
|
+
dehydrateMutation,
|
|
12
|
+
dehydrateObservers,
|
|
13
|
+
} from '../shared/dehydrate';
|
|
14
|
+
import { SerializableObserver, PartialQueryState } from '../shared/types';
|
|
15
|
+
|
|
16
|
+
export const useSyncTanStackCache = (
|
|
17
|
+
queryClient: QueryClient,
|
|
18
|
+
client: TanStackQueryPluginClient | null
|
|
19
|
+
) => {
|
|
20
|
+
const previousObserversRef = useRef<Map<string, SerializableObserver[]>>(
|
|
21
|
+
new Map()
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const handler = useMemo(() => {
|
|
25
|
+
return (event: QueryCacheNotifyEvent | MutationCacheNotifyEvent): void => {
|
|
26
|
+
if (!client) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (event.type === 'observerResultsUpdated') {
|
|
31
|
+
// We don't need to sync this type of events.
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if ('query' in event) {
|
|
36
|
+
const { query, type } = event;
|
|
37
|
+
|
|
38
|
+
if (type === 'added' || type === 'removed') {
|
|
39
|
+
if (type === 'removed') {
|
|
40
|
+
// We don't need to store observers for removed queries.
|
|
41
|
+
previousObserversRef.current.delete(query.queryHash);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const dehydratedQuery = dehydrateQuery(query);
|
|
45
|
+
client.send('sync-query-event', { type, data: dehydratedQuery });
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (type === 'updated' && 'action' in event) {
|
|
50
|
+
const action = event.action;
|
|
51
|
+
|
|
52
|
+
switch (action.type) {
|
|
53
|
+
case 'fetch': {
|
|
54
|
+
// Only need loading state and fetch metadata
|
|
55
|
+
const fetchData: PartialQueryState = {
|
|
56
|
+
queryHash: query.queryHash,
|
|
57
|
+
state: {
|
|
58
|
+
status: query.state.status,
|
|
59
|
+
fetchStatus: query.state.fetchStatus,
|
|
60
|
+
fetchMeta: query.state.fetchMeta,
|
|
61
|
+
dataUpdatedAt: query.state.dataUpdatedAt,
|
|
62
|
+
errorUpdatedAt: query.state.errorUpdatedAt,
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
client.send('sync-query-event', {
|
|
66
|
+
type: 'updated',
|
|
67
|
+
action: 'fetch',
|
|
68
|
+
data: fetchData,
|
|
69
|
+
});
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
case 'success': {
|
|
74
|
+
// Only need new data and success state
|
|
75
|
+
const successData: PartialQueryState = {
|
|
76
|
+
queryHash: query.queryHash,
|
|
77
|
+
state: {
|
|
78
|
+
status: query.state.status,
|
|
79
|
+
data: query.state.data,
|
|
80
|
+
dataUpdatedAt: query.state.dataUpdatedAt,
|
|
81
|
+
error: query.state.error,
|
|
82
|
+
errorUpdatedAt: query.state.errorUpdatedAt,
|
|
83
|
+
fetchStatus: query.state.fetchStatus,
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
client.send('sync-query-event', {
|
|
87
|
+
type: 'updated',
|
|
88
|
+
action: 'success',
|
|
89
|
+
data: successData,
|
|
90
|
+
});
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
case 'error': {
|
|
95
|
+
// Only need error information
|
|
96
|
+
const errorData: PartialQueryState = {
|
|
97
|
+
queryHash: query.queryHash,
|
|
98
|
+
state: {
|
|
99
|
+
status: query.state.status,
|
|
100
|
+
error: query.state.error,
|
|
101
|
+
errorUpdatedAt: query.state.errorUpdatedAt,
|
|
102
|
+
fetchStatus: query.state.fetchStatus,
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
client.send('sync-query-event', {
|
|
106
|
+
type: 'updated',
|
|
107
|
+
action: 'error',
|
|
108
|
+
data: errorData,
|
|
109
|
+
});
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
case 'setState': {
|
|
114
|
+
// Only need the specific state changes
|
|
115
|
+
const setStateData: PartialQueryState = {
|
|
116
|
+
queryHash: query.queryHash,
|
|
117
|
+
state: action.state, // Only the changed state parts
|
|
118
|
+
};
|
|
119
|
+
client.send('sync-query-event', {
|
|
120
|
+
type: 'updated',
|
|
121
|
+
action: 'setState',
|
|
122
|
+
data: setStateData,
|
|
123
|
+
});
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
case 'invalidate': {
|
|
128
|
+
// Only need invalidation flag
|
|
129
|
+
const invalidateData: PartialQueryState = {
|
|
130
|
+
queryHash: query.queryHash,
|
|
131
|
+
state: {
|
|
132
|
+
isInvalidated: query.state.isInvalidated,
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
client.send('sync-query-event', {
|
|
136
|
+
type: 'updated',
|
|
137
|
+
action: 'invalidate',
|
|
138
|
+
data: invalidateData,
|
|
139
|
+
});
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
case 'pause': {
|
|
144
|
+
// Need to sync the paused fetch status
|
|
145
|
+
const pauseData: PartialQueryState = {
|
|
146
|
+
queryHash: query.queryHash,
|
|
147
|
+
state: {
|
|
148
|
+
fetchStatus: query.state.fetchStatus,
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
client.send('sync-query-event', {
|
|
152
|
+
type: 'updated',
|
|
153
|
+
action: 'pause',
|
|
154
|
+
data: pauseData,
|
|
155
|
+
});
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
case 'continue': {
|
|
160
|
+
// Need to sync the resumed fetch status
|
|
161
|
+
const continueData: PartialQueryState = {
|
|
162
|
+
queryHash: query.queryHash,
|
|
163
|
+
state: {
|
|
164
|
+
fetchStatus: query.state.fetchStatus,
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
client.send('sync-query-event', {
|
|
168
|
+
type: 'updated',
|
|
169
|
+
action: 'continue',
|
|
170
|
+
data: continueData,
|
|
171
|
+
});
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
default: {
|
|
176
|
+
// Fallback to full data for unknown actions
|
|
177
|
+
const dehydratedQuery = dehydrateQuery(query);
|
|
178
|
+
client.send('sync-query-event', { type, data: dehydratedQuery });
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (
|
|
185
|
+
type === 'observerAdded' ||
|
|
186
|
+
type === 'observerRemoved' ||
|
|
187
|
+
type === 'observerOptionsUpdated'
|
|
188
|
+
) {
|
|
189
|
+
const dehydratedObservers = dehydrateObservers(query);
|
|
190
|
+
const previousObservers = previousObserversRef.current.get(
|
|
191
|
+
query.queryHash
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
// For observerOptionsUpdated, only send if the observers actually changed
|
|
195
|
+
if (type === 'observerOptionsUpdated' && previousObservers) {
|
|
196
|
+
if (equal(previousObservers, dehydratedObservers)) {
|
|
197
|
+
// No meaningful change, skip sending
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
previousObserversRef.current.set(
|
|
203
|
+
query.queryHash,
|
|
204
|
+
dehydratedObservers
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
client.send('sync-query-event', {
|
|
208
|
+
type,
|
|
209
|
+
data: {
|
|
210
|
+
queryHash: query.queryHash,
|
|
211
|
+
observers: dehydratedObservers,
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const { mutation, type } = event;
|
|
219
|
+
if (mutation) {
|
|
220
|
+
const dehydratedMutation = dehydrateMutation(mutation);
|
|
221
|
+
client.send('sync-mutation-event', {
|
|
222
|
+
type,
|
|
223
|
+
data: dehydratedMutation,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
}, [client]);
|
|
228
|
+
|
|
229
|
+
useEffect(() => {
|
|
230
|
+
if (!client) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const mutationCacheSubscription = queryClient
|
|
235
|
+
.getMutationCache()
|
|
236
|
+
.subscribe(handler);
|
|
237
|
+
const queryCacheSubscription = queryClient
|
|
238
|
+
.getQueryCache()
|
|
239
|
+
.subscribe(handler);
|
|
240
|
+
|
|
241
|
+
return () => {
|
|
242
|
+
mutationCacheSubscription();
|
|
243
|
+
queryCacheSubscription();
|
|
244
|
+
};
|
|
245
|
+
}, [client, queryClient, handler]);
|
|
246
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { QueryClient } from '@tanstack/react-query';
|
|
2
|
+
import { useRozeniteDevToolsClient } from '@rozenite/plugin-bridge';
|
|
3
|
+
import { TanStackQueryPluginEventMap } from '../shared/messaging';
|
|
4
|
+
import { useSyncOnlineStatus } from '../shared/useSyncOnlineStatus';
|
|
5
|
+
import { useHandleDevToolsMessages } from './useHandleDevToolsMessages';
|
|
6
|
+
import { useSyncTanStackCache } from './useSyncTanStackCache';
|
|
7
|
+
import { useHandleInitialData } from './useHandleInitialData';
|
|
8
|
+
|
|
9
|
+
export const useTanStackQueryDevTools = (queryClient: QueryClient) => {
|
|
10
|
+
const client = useRozeniteDevToolsClient<TanStackQueryPluginEventMap>({
|
|
11
|
+
pluginId: '@rozenite/tanstack-query-plugin',
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
useSyncOnlineStatus(client);
|
|
15
|
+
|
|
16
|
+
useHandleDevToolsMessages(queryClient, client);
|
|
17
|
+
|
|
18
|
+
useSyncTanStackCache(queryClient, client);
|
|
19
|
+
|
|
20
|
+
useHandleInitialData(queryClient, client);
|
|
21
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Query, Mutation, QueryClient } from '@tanstack/react-query';
|
|
2
|
+
import type {
|
|
3
|
+
SerializableQuery,
|
|
4
|
+
SerializableMutation,
|
|
5
|
+
SerializableObserver,
|
|
6
|
+
SerializableQueryClient,
|
|
7
|
+
} from './types';
|
|
8
|
+
|
|
9
|
+
export const dehydrateObservers = (query: Query): SerializableObserver[] => {
|
|
10
|
+
return query.observers.map((observer) => ({
|
|
11
|
+
queryHash: query.queryHash,
|
|
12
|
+
options: observer.options,
|
|
13
|
+
}));
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const dehydrateQuery = (query: Query): SerializableQuery => {
|
|
17
|
+
const dehydratedObservers: SerializableObserver[] = dehydrateObservers(query);
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
state: query.state,
|
|
21
|
+
queryKey: query.queryKey,
|
|
22
|
+
queryHash: query.queryHash,
|
|
23
|
+
observers: dehydratedObservers,
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const dehydrateMutation = (mutation: Mutation): SerializableMutation => {
|
|
28
|
+
return {
|
|
29
|
+
mutationId: mutation.mutationId,
|
|
30
|
+
state: mutation.state,
|
|
31
|
+
options: mutation.options,
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const dehydrateQueryClient = (
|
|
36
|
+
queryClient: QueryClient
|
|
37
|
+
): SerializableQueryClient => {
|
|
38
|
+
return {
|
|
39
|
+
queries: queryClient.getQueryCache().getAll().map(dehydrateQuery),
|
|
40
|
+
mutations: queryClient.getMutationCache().getAll().map(dehydrateMutation),
|
|
41
|
+
};
|
|
42
|
+
};
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import {
|
|
2
|
+
InfiniteQueryObserverOptions,
|
|
3
|
+
MutationOptions,
|
|
4
|
+
MutationState,
|
|
5
|
+
QueryClient,
|
|
6
|
+
QueryObserver,
|
|
7
|
+
QueryObserverOptions,
|
|
8
|
+
QueryState,
|
|
9
|
+
} from '@tanstack/react-query';
|
|
10
|
+
import type {
|
|
11
|
+
SerializableQuery,
|
|
12
|
+
SerializableMutation,
|
|
13
|
+
SerializableQueryClient,
|
|
14
|
+
SerializableObserver,
|
|
15
|
+
PartialQueryState,
|
|
16
|
+
} from './types';
|
|
17
|
+
|
|
18
|
+
const mockQueryFn = () => {
|
|
19
|
+
return Promise.resolve(null);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const hydrateObservers = (
|
|
23
|
+
client: QueryClient,
|
|
24
|
+
queryHash: string,
|
|
25
|
+
dehydratedObservers: SerializableObserver[]
|
|
26
|
+
) => {
|
|
27
|
+
const query = client.getQueryCache().get(queryHash);
|
|
28
|
+
|
|
29
|
+
if (!query) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
query.observers.forEach((observer) => {
|
|
34
|
+
query.removeObserver(observer);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
dehydratedObservers.forEach((observerState) => {
|
|
38
|
+
const hydratedOptions: InfiniteQueryObserverOptions | QueryObserverOptions =
|
|
39
|
+
observerState.options;
|
|
40
|
+
|
|
41
|
+
if ('initialPageParam' in hydratedOptions) {
|
|
42
|
+
delete hydratedOptions.initialPageParam;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if ('behavior' in hydratedOptions) {
|
|
46
|
+
delete hydratedOptions.behavior;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
hydratedOptions.queryFn = mockQueryFn;
|
|
50
|
+
|
|
51
|
+
const observer = new QueryObserver(
|
|
52
|
+
client,
|
|
53
|
+
hydratedOptions as QueryObserverOptions
|
|
54
|
+
);
|
|
55
|
+
query.addObserver(observer);
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const hydrateMutation = (
|
|
60
|
+
client: QueryClient,
|
|
61
|
+
dehydratedMutation: SerializableMutation
|
|
62
|
+
) => {
|
|
63
|
+
const mutationCache = client.getMutationCache();
|
|
64
|
+
const { options, state } = dehydratedMutation;
|
|
65
|
+
|
|
66
|
+
const existingMutation = mutationCache.find({
|
|
67
|
+
mutationKey: options.mutationKey,
|
|
68
|
+
});
|
|
69
|
+
const hydratedState: MutationState<unknown, Error, void, unknown> =
|
|
70
|
+
state as MutationState<unknown, Error, void, unknown>;
|
|
71
|
+
const hydratedOptions: MutationOptions = options;
|
|
72
|
+
|
|
73
|
+
if (existingMutation) {
|
|
74
|
+
mutationCache.remove(existingMutation);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
mutationCache.build(client, hydratedOptions, hydratedState);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const hydrateQuery = (
|
|
81
|
+
client: QueryClient,
|
|
82
|
+
dehydratedQuery: SerializableQuery
|
|
83
|
+
) => {
|
|
84
|
+
const queryCache = client.getQueryCache();
|
|
85
|
+
const { queryKey, state, queryHash, observers } = dehydratedQuery;
|
|
86
|
+
|
|
87
|
+
let query = queryCache.get(queryHash);
|
|
88
|
+
const data = state.data;
|
|
89
|
+
const hydratedState: QueryState = state;
|
|
90
|
+
|
|
91
|
+
// Do not hydrate if an existing query exists with newer data
|
|
92
|
+
if (query) {
|
|
93
|
+
if (
|
|
94
|
+
query.state.dataUpdatedAt < state.dataUpdatedAt ||
|
|
95
|
+
query.state.fetchStatus !== state.fetchStatus
|
|
96
|
+
) {
|
|
97
|
+
query.setState({
|
|
98
|
+
...hydratedState,
|
|
99
|
+
data,
|
|
100
|
+
});
|
|
101
|
+
query.setOptions({
|
|
102
|
+
...query.options,
|
|
103
|
+
queryFn: mockQueryFn,
|
|
104
|
+
retry: 0,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
} else {
|
|
108
|
+
// Restore query
|
|
109
|
+
query = queryCache.build(
|
|
110
|
+
client,
|
|
111
|
+
{
|
|
112
|
+
...client.getDefaultOptions().hydrate?.queries,
|
|
113
|
+
queryKey,
|
|
114
|
+
queryHash,
|
|
115
|
+
queryFn: mockQueryFn,
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
...hydratedState,
|
|
119
|
+
data,
|
|
120
|
+
}
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
hydrateObservers(client, queryHash, observers);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export const hydrateQueryClient = (
|
|
128
|
+
client: QueryClient,
|
|
129
|
+
dehydratedState: SerializableQueryClient
|
|
130
|
+
): void => {
|
|
131
|
+
// Hydrate mutations
|
|
132
|
+
dehydratedState.mutations.forEach((mutation) =>
|
|
133
|
+
hydrateMutation(client, mutation)
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
// Hydrate queries
|
|
137
|
+
dehydratedState.queries.forEach((query) => hydrateQuery(client, query));
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
export const applyQueryEvent = (
|
|
141
|
+
queryClient: QueryClient,
|
|
142
|
+
type:
|
|
143
|
+
| 'added'
|
|
144
|
+
| 'updated'
|
|
145
|
+
| 'removed'
|
|
146
|
+
| 'observerAdded'
|
|
147
|
+
| 'observerRemoved'
|
|
148
|
+
| 'observerResultsUpdated'
|
|
149
|
+
| 'observerOptionsUpdated',
|
|
150
|
+
data: SerializableQuery | PartialQueryState,
|
|
151
|
+
action?: string
|
|
152
|
+
): void => {
|
|
153
|
+
const queryCache = queryClient.getQueryCache();
|
|
154
|
+
|
|
155
|
+
switch (type) {
|
|
156
|
+
case 'added': {
|
|
157
|
+
hydrateQuery(queryClient, data as SerializableQuery);
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
case 'updated': {
|
|
161
|
+
if (action && 'state' in data && !('queryKey' in data)) {
|
|
162
|
+
// Handle action-based partial state update
|
|
163
|
+
applyPartialQueryState(queryClient, data as PartialQueryState);
|
|
164
|
+
} else {
|
|
165
|
+
// Handle full query update (backward compatibility)
|
|
166
|
+
hydrateQuery(queryClient, data as SerializableQuery);
|
|
167
|
+
}
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
case 'observerAdded':
|
|
171
|
+
case 'observerRemoved':
|
|
172
|
+
case 'observerOptionsUpdated': {
|
|
173
|
+
hydrateObservers(queryClient, data.queryHash, (data as any).observers);
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
case 'removed': {
|
|
177
|
+
const query = queryCache.get(data.queryHash);
|
|
178
|
+
if (query) {
|
|
179
|
+
queryCache.remove(query);
|
|
180
|
+
}
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
// New function to handle action-based partial state updates
|
|
187
|
+
export const applyPartialQueryState = (
|
|
188
|
+
queryClient: QueryClient,
|
|
189
|
+
data: PartialQueryState
|
|
190
|
+
): void => {
|
|
191
|
+
const queryCache = queryClient.getQueryCache();
|
|
192
|
+
const query = queryCache.get(data.queryHash);
|
|
193
|
+
|
|
194
|
+
if (!query) {
|
|
195
|
+
// Query doesn't exist, we need to create it with minimal data
|
|
196
|
+
// This is a fallback for when we receive a partial update for a non-existent query
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Apply the partial state changes
|
|
201
|
+
if (data.state) {
|
|
202
|
+
query.setState(data.state);
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
export const applyMutationEvent = (
|
|
207
|
+
queryClient: QueryClient,
|
|
208
|
+
type:
|
|
209
|
+
| 'added'
|
|
210
|
+
| 'updated'
|
|
211
|
+
| 'removed'
|
|
212
|
+
| 'observerAdded'
|
|
213
|
+
| 'observerRemoved'
|
|
214
|
+
| 'observerResultsUpdated'
|
|
215
|
+
| 'observerOptionsUpdated',
|
|
216
|
+
data: SerializableMutation
|
|
217
|
+
): void => {
|
|
218
|
+
const mutationCache = queryClient.getMutationCache();
|
|
219
|
+
|
|
220
|
+
switch (type) {
|
|
221
|
+
case 'added':
|
|
222
|
+
case 'updated':
|
|
223
|
+
case 'observerAdded':
|
|
224
|
+
case 'observerRemoved':
|
|
225
|
+
case 'observerResultsUpdated':
|
|
226
|
+
case 'observerOptionsUpdated': {
|
|
227
|
+
hydrateMutation(queryClient, data);
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
case 'removed': {
|
|
231
|
+
const mutation = mutationCache.find({
|
|
232
|
+
mutationKey: data.options.mutationKey,
|
|
233
|
+
});
|
|
234
|
+
if (mutation) {
|
|
235
|
+
mutationCache.remove(mutation);
|
|
236
|
+
}
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
export const applyQueryObserverEvent = (
|
|
243
|
+
queryClient: QueryClient,
|
|
244
|
+
data: {
|
|
245
|
+
queryHash: string;
|
|
246
|
+
observers: SerializableObserver[];
|
|
247
|
+
}
|
|
248
|
+
): void => {
|
|
249
|
+
hydrateObservers(queryClient, data.queryHash, data.observers);
|
|
250
|
+
};
|