@rozenite/tanstack-query-plugin 2.3.0 → 2.5.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.
- package/CHANGELOG.md +60 -0
- package/dist/react-native/cjs/src/react-native/agent/tanstack-query-agent.js +60 -68
- package/dist/react-native/cjs/src/react-native/agent/useTanStackQueryAgentTools.js +0 -12
- package/dist/react-native/cjs/src/shared/agent-tools.js +36 -6
- package/dist/react-native/src/react-native/agent/tanstack-query-agent.d.ts +16 -19
- package/dist/react-native/src/react-native/agent/tanstack-query-agent.js +60 -68
- package/dist/react-native/src/react-native/agent/useTanStackQueryAgentTools.js +1 -13
- package/dist/react-native/src/shared/agent-tools.d.ts +17 -5
- package/dist/react-native/src/shared/agent-tools.js +36 -6
- package/dist/rozenite.json +1 -1
- package/dist/sdk/sdk.d.ts +2 -2
- package/dist/sdk/src/shared/agent-tools.d.ts +17 -5
- package/dist/sdk/src/shared/agent-tools.js +36 -6
- package/package.json +12 -8
- package/react-native.ts +0 -18
- package/rozenite.config.ts +0 -9
- package/sdk.ts +0 -52
- package/src/__tests__/release-bundle.test.ts +0 -32
- package/src/react-native/agent/__tests__/tanstack-query-agent.test.ts +0 -553
- package/src/react-native/agent/tanstack-query-agent.ts +0 -484
- package/src/react-native/agent/useTanStackQueryAgentTools.ts +0 -116
- package/src/react-native/devtools-actions.ts +0 -204
- package/src/react-native/useHandleDevToolsMessages.ts +0 -32
- package/src/react-native/useHandleInitialData.ts +0 -31
- package/src/react-native/useSyncTanStackCache.ts +0 -231
- package/src/react-native/useTanStackQueryDevTools.ts +0 -24
- package/src/shared/agent-tools.ts +0 -418
- package/src/shared/dehydrate.ts +0 -40
- package/src/shared/hydrate.ts +0 -243
- package/src/shared/messaging.ts +0 -63
- package/src/shared/query-data-sync.test.ts +0 -89
- package/src/shared/query-data-sync.ts +0 -129
- package/src/shared/types.ts +0 -49
- package/src/shared/useSyncOnlineStatus.ts +0 -24
- package/src/ui/tanstack-query.tsx +0 -46
- package/src/ui/useHandleSyncMessages.ts +0 -58
- package/src/ui/useSyncDevToolsEvents.ts +0 -46
- package/src/ui/useSyncInitialData.test.tsx +0 -76
- package/src/ui/useSyncInitialData.ts +0 -27
- package/tsconfig.json +0 -36
- package/vite.config.ts +0 -27
package/src/shared/messaging.ts
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import type { RozeniteDevToolsClient } from '@rozenite/plugin-bridge';
|
|
2
|
-
import {
|
|
3
|
-
DevToolsActionType,
|
|
4
|
-
SerializableQueryClient,
|
|
5
|
-
SerializableQuery,
|
|
6
|
-
SerializableMutation,
|
|
7
|
-
SerializableObserver,
|
|
8
|
-
PartialQueryState,
|
|
9
|
-
} from './types';
|
|
10
|
-
|
|
11
|
-
export type TanStackQueryPluginEventMap = {
|
|
12
|
-
'online-status-changed': {
|
|
13
|
-
online: boolean;
|
|
14
|
-
};
|
|
15
|
-
'devtools-action': {
|
|
16
|
-
type: DevToolsActionType;
|
|
17
|
-
queryHash: string;
|
|
18
|
-
metadata?: {
|
|
19
|
-
data?: unknown;
|
|
20
|
-
};
|
|
21
|
-
};
|
|
22
|
-
/**
|
|
23
|
-
* Announced by the device once it is listening for panel messages. The panel
|
|
24
|
-
* is recreated on every app reload and asks for the cache immediately, which
|
|
25
|
-
* can land before the app's React tree has mounted the plugin; this lets the
|
|
26
|
-
* panel ask again instead of staying empty until the app is restarted.
|
|
27
|
-
*/
|
|
28
|
-
'device-ready': unknown;
|
|
29
|
-
'request-initial-data': unknown;
|
|
30
|
-
'sync-data': {
|
|
31
|
-
data: SerializableQueryClient;
|
|
32
|
-
};
|
|
33
|
-
'sync-query-event':
|
|
34
|
-
| {
|
|
35
|
-
type: 'added' | 'removed';
|
|
36
|
-
data: SerializableQuery;
|
|
37
|
-
}
|
|
38
|
-
| {
|
|
39
|
-
type: 'updated';
|
|
40
|
-
action?: string; // Add action type for optimization
|
|
41
|
-
data: SerializableQuery | PartialQueryState; // Support both full and partial state
|
|
42
|
-
}
|
|
43
|
-
| {
|
|
44
|
-
type: 'observerAdded' | 'observerRemoved' | 'observerOptionsUpdated';
|
|
45
|
-
data: {
|
|
46
|
-
queryHash: string;
|
|
47
|
-
observers: SerializableObserver[];
|
|
48
|
-
};
|
|
49
|
-
};
|
|
50
|
-
'sync-mutation-event': {
|
|
51
|
-
type:
|
|
52
|
-
| 'added'
|
|
53
|
-
| 'updated'
|
|
54
|
-
| 'removed'
|
|
55
|
-
| 'observerAdded'
|
|
56
|
-
| 'observerRemoved'
|
|
57
|
-
| 'observerResultsUpdated'
|
|
58
|
-
| 'observerOptionsUpdated';
|
|
59
|
-
data: SerializableMutation;
|
|
60
|
-
};
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
export type TanStackQueryPluginClient = RozeniteDevToolsClient<TanStackQueryPluginEventMap>;
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import { QueryClient } from '@tanstack/react-query';
|
|
3
|
-
import { applyRemoteQueryState, instrumentQuery, instrumentQueryClient } from './query-data-sync';
|
|
4
|
-
|
|
5
|
-
const createQueryClient = () =>
|
|
6
|
-
new QueryClient({
|
|
7
|
-
defaultOptions: {
|
|
8
|
-
queries: {
|
|
9
|
-
retry: false,
|
|
10
|
-
gcTime: Infinity,
|
|
11
|
-
},
|
|
12
|
-
},
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
describe('query data sync instrumentation', () => {
|
|
16
|
-
it('emits query data updates for data-only setState edits', () => {
|
|
17
|
-
const queryClient = createQueryClient();
|
|
18
|
-
const handler = vi.fn();
|
|
19
|
-
|
|
20
|
-
queryClient.setQueryData(['edited'], { value: 1 });
|
|
21
|
-
const query = queryClient.getQueryCache().find({ queryKey: ['edited'] })!;
|
|
22
|
-
|
|
23
|
-
instrumentQuery(query, handler);
|
|
24
|
-
|
|
25
|
-
query.setState({
|
|
26
|
-
...query.state,
|
|
27
|
-
data: { value: 2 },
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
expect(handler).toHaveBeenCalledTimes(1);
|
|
31
|
-
expect(handler).toHaveBeenCalledWith(
|
|
32
|
-
expect.objectContaining({
|
|
33
|
-
data: { value: 2 },
|
|
34
|
-
}),
|
|
35
|
-
);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('emits query data updates for setQueryData user edits', () => {
|
|
39
|
-
const queryClient = createQueryClient();
|
|
40
|
-
const handler = vi.fn();
|
|
41
|
-
|
|
42
|
-
instrumentQueryClient(queryClient, handler);
|
|
43
|
-
|
|
44
|
-
queryClient.setQueryData(['edited'], { value: 1 });
|
|
45
|
-
|
|
46
|
-
expect(handler).toHaveBeenCalledTimes(1);
|
|
47
|
-
expect(handler).toHaveBeenCalledWith(
|
|
48
|
-
expect.objectContaining({
|
|
49
|
-
data: { value: 1 },
|
|
50
|
-
}),
|
|
51
|
-
);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it('does not emit query data updates for non-data state changes', () => {
|
|
55
|
-
const queryClient = createQueryClient();
|
|
56
|
-
const handler = vi.fn();
|
|
57
|
-
|
|
58
|
-
queryClient.setQueryData(['edited'], { value: 1 });
|
|
59
|
-
const query = queryClient.getQueryCache().find({ queryKey: ['edited'] })!;
|
|
60
|
-
|
|
61
|
-
instrumentQuery(query, handler);
|
|
62
|
-
|
|
63
|
-
query.setState({
|
|
64
|
-
data: undefined,
|
|
65
|
-
status: 'pending',
|
|
66
|
-
fetchStatus: 'fetching',
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
expect(handler).not.toHaveBeenCalled();
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('does not emit query data updates for remote state application', () => {
|
|
73
|
-
const queryClient = createQueryClient();
|
|
74
|
-
const handler = vi.fn();
|
|
75
|
-
|
|
76
|
-
queryClient.setQueryData(['edited'], { value: 1 });
|
|
77
|
-
const query = queryClient.getQueryCache().find({ queryKey: ['edited'] })!;
|
|
78
|
-
|
|
79
|
-
instrumentQuery(query, handler);
|
|
80
|
-
|
|
81
|
-
applyRemoteQueryState(query, {
|
|
82
|
-
...query.state,
|
|
83
|
-
data: { value: 2 },
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
expect(handler).not.toHaveBeenCalled();
|
|
87
|
-
expect(query.state.data).toEqual({ value: 2 });
|
|
88
|
-
});
|
|
89
|
-
});
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import { Query, QueryClient, QueryKey, QueryState } from '@tanstack/react-query';
|
|
2
|
-
|
|
3
|
-
type QuerySetState = Query['setState'];
|
|
4
|
-
type QueryClientSetQueryData = QueryClient['setQueryData'];
|
|
5
|
-
|
|
6
|
-
type SyncQueryDataPayload = {
|
|
7
|
-
queryHash: string;
|
|
8
|
-
data: unknown;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
type QueryDataSyncHandler = (payload: SyncQueryDataPayload) => void;
|
|
12
|
-
|
|
13
|
-
const originalQuerySetStateMap = new WeakMap<Query, QuerySetState>();
|
|
14
|
-
const originalSetQueryDataMap = new WeakMap<QueryClient, QueryClientSetQueryData>();
|
|
15
|
-
const instrumentedQueryClients = new WeakSet<QueryClient>();
|
|
16
|
-
const instrumentedQueries = new WeakSet<Query>();
|
|
17
|
-
const queryClientHandlers = new WeakMap<QueryClient, QueryDataSyncHandler | undefined>();
|
|
18
|
-
const queryHandlers = new WeakMap<Query, QueryDataSyncHandler | undefined>();
|
|
19
|
-
|
|
20
|
-
const getOriginalQuerySetState = (query: Query): QuerySetState => {
|
|
21
|
-
return originalQuerySetStateMap.get(query) ?? query.setState.bind(query);
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
const emitIfDataChanged = (query: Query, previousData: unknown, handler?: QueryDataSyncHandler) => {
|
|
25
|
-
if (!handler || Object.is(previousData, query.state.data)) {
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
handler({
|
|
30
|
-
queryHash: query.queryHash,
|
|
31
|
-
data: query.state.data,
|
|
32
|
-
});
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
const isDataOnlyStateUpdate = (previousState: QueryState, nextState: Partial<QueryState>) => {
|
|
36
|
-
if (!('data' in nextState)) {
|
|
37
|
-
return false;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const changedKeys = Object.keys(nextState).filter((key) => {
|
|
41
|
-
const typedKey = key as keyof QueryState;
|
|
42
|
-
return !Object.is(previousState[typedKey], nextState[typedKey]);
|
|
43
|
-
}) as Array<keyof QueryState>;
|
|
44
|
-
|
|
45
|
-
return changedKeys.every((key) => key === 'data');
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
export const applyRemoteQueryState = (query: Query, state: Partial<QueryState>) => {
|
|
49
|
-
const originalSetState = getOriginalQuerySetState(query);
|
|
50
|
-
originalSetState(state);
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
export const instrumentQuery = (query: Query, handler?: QueryDataSyncHandler) => {
|
|
54
|
-
queryHandlers.set(query, handler);
|
|
55
|
-
|
|
56
|
-
if (instrumentedQueries.has(query)) {
|
|
57
|
-
return query;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const originalSetState = query.setState.bind(query);
|
|
61
|
-
originalQuerySetStateMap.set(query, originalSetState);
|
|
62
|
-
|
|
63
|
-
query.setState = ((state) => {
|
|
64
|
-
const shouldEmit = isDataOnlyStateUpdate(query.state, state);
|
|
65
|
-
const previousData = query.state.data;
|
|
66
|
-
const result = originalSetState(state);
|
|
67
|
-
|
|
68
|
-
if (shouldEmit) {
|
|
69
|
-
emitIfDataChanged(query, previousData, queryHandlers.get(query));
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return result;
|
|
73
|
-
}) as QuerySetState;
|
|
74
|
-
|
|
75
|
-
instrumentedQueries.add(query);
|
|
76
|
-
return query;
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
const resolveQueryHash = (queryClient: QueryClient, queryKey: QueryKey) => {
|
|
80
|
-
return queryClient.getQueryCache().find({ queryKey })?.queryHash;
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
export const instrumentQueryClient = (queryClient: QueryClient, handler?: QueryDataSyncHandler) => {
|
|
84
|
-
queryClientHandlers.set(queryClient, handler);
|
|
85
|
-
|
|
86
|
-
if (instrumentedQueryClients.has(queryClient)) {
|
|
87
|
-
queryClient
|
|
88
|
-
.getQueryCache()
|
|
89
|
-
.getAll()
|
|
90
|
-
.forEach((query) => {
|
|
91
|
-
instrumentQuery(query, handler);
|
|
92
|
-
});
|
|
93
|
-
return queryClient;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const originalSetQueryData = queryClient.setQueryData.bind(queryClient);
|
|
97
|
-
originalSetQueryDataMap.set(queryClient, originalSetQueryData);
|
|
98
|
-
|
|
99
|
-
queryClient.setQueryData = ((queryKey, updater, options) => {
|
|
100
|
-
const result = originalSetQueryData(queryKey, updater, options);
|
|
101
|
-
const query = queryClient.getQueryCache().find({ queryKey });
|
|
102
|
-
const activeQueryClientHandler = queryClientHandlers.get(queryClient);
|
|
103
|
-
|
|
104
|
-
if (query) {
|
|
105
|
-
instrumentQuery(query, activeQueryClientHandler);
|
|
106
|
-
const queryHash = resolveQueryHash(queryClient, queryKey);
|
|
107
|
-
const activeHandler = queryHandlers.get(query);
|
|
108
|
-
|
|
109
|
-
if (activeHandler && queryHash) {
|
|
110
|
-
activeHandler({
|
|
111
|
-
queryHash,
|
|
112
|
-
data: query.state.data,
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return result;
|
|
118
|
-
}) as QueryClientSetQueryData;
|
|
119
|
-
|
|
120
|
-
queryClient
|
|
121
|
-
.getQueryCache()
|
|
122
|
-
.getAll()
|
|
123
|
-
.forEach((query) => {
|
|
124
|
-
instrumentQuery(query, handler);
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
instrumentedQueryClients.add(queryClient);
|
|
128
|
-
return queryClient;
|
|
129
|
-
};
|
package/src/shared/types.ts
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
QueryKey,
|
|
3
|
-
MutationState,
|
|
4
|
-
QueryState,
|
|
5
|
-
QueryObserverOptions,
|
|
6
|
-
InfiniteQueryObserverOptions,
|
|
7
|
-
MutationOptions,
|
|
8
|
-
} from '@tanstack/react-query';
|
|
9
|
-
|
|
10
|
-
export type SerializableQuery = {
|
|
11
|
-
queryHash: string;
|
|
12
|
-
state: QueryState;
|
|
13
|
-
queryKey: QueryKey;
|
|
14
|
-
observers: SerializableObserver[];
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export type PartialQueryState = {
|
|
18
|
-
queryHash: string;
|
|
19
|
-
state: Partial<QueryState>;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
export type SerializableMutation = {
|
|
23
|
-
mutationId: number;
|
|
24
|
-
options: MutationOptions;
|
|
25
|
-
state: MutationState;
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
export type SerializableObserver = {
|
|
29
|
-
queryHash: string;
|
|
30
|
-
options: QueryObserverOptions | InfiniteQueryObserverOptions;
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
export type SerializableQueryClient = {
|
|
34
|
-
queries: SerializableQuery[];
|
|
35
|
-
mutations: SerializableMutation[];
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
export type DevToolsActionType =
|
|
39
|
-
| 'REFETCH'
|
|
40
|
-
| 'SET_QUERY_DATA'
|
|
41
|
-
| 'INVALIDATE'
|
|
42
|
-
| 'RESET'
|
|
43
|
-
| 'REMOVE'
|
|
44
|
-
| 'TRIGGER_ERROR'
|
|
45
|
-
| 'RESTORE_ERROR'
|
|
46
|
-
| 'TRIGGER_LOADING'
|
|
47
|
-
| 'RESTORE_LOADING'
|
|
48
|
-
| 'CLEAR_MUTATION_CACHE'
|
|
49
|
-
| 'CLEAR_QUERY_CACHE';
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { useEffect } from 'react';
|
|
2
|
-
import { onlineManager } from '@tanstack/react-query';
|
|
3
|
-
import { TanStackQueryPluginClient } from './messaging';
|
|
4
|
-
|
|
5
|
-
export const useSyncOnlineStatus = (client: TanStackQueryPluginClient | null) => {
|
|
6
|
-
useEffect(() => {
|
|
7
|
-
if (!client) {
|
|
8
|
-
return;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const onlineManagerSubscription = onlineManager.subscribe((online) => {
|
|
12
|
-
client.send('online-status-changed', { online });
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
const onlineMessageSubscription = client.onMessage('online-status-changed', ({ online }) => {
|
|
16
|
-
onlineManager.setOnline(online);
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
return () => {
|
|
20
|
-
onlineManagerSubscription();
|
|
21
|
-
onlineMessageSubscription.remove();
|
|
22
|
-
};
|
|
23
|
-
}, [client]);
|
|
24
|
-
};
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { useEffect } from 'react';
|
|
2
|
-
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
3
|
-
import { ReactQueryDevtoolsPanel } from '@tanstack/react-query-devtools/production';
|
|
4
|
-
import { useRozeniteDevToolsClient } from '@rozenite/plugin-bridge';
|
|
5
|
-
import { TanStackQueryPluginEventMap } from '../shared/messaging';
|
|
6
|
-
import { useSyncInitialData } from './useSyncInitialData';
|
|
7
|
-
import { useSyncDevToolsEvents } from './useSyncDevToolsEvents';
|
|
8
|
-
import { useSyncOnlineStatus } from '../shared/useSyncOnlineStatus';
|
|
9
|
-
import { useHandleSyncMessages } from './useHandleSyncMessages';
|
|
10
|
-
import { instrumentQueryClient } from '../shared/query-data-sync';
|
|
11
|
-
|
|
12
|
-
const App = () => {
|
|
13
|
-
const client = useRozeniteDevToolsClient<TanStackQueryPluginEventMap>({
|
|
14
|
-
pluginId: '@rozenite/tanstack-query-plugin',
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
useEffect(() => {
|
|
18
|
-
instrumentQueryClient(queryClient, ({ queryHash, data }) => {
|
|
19
|
-
client?.send('devtools-action', {
|
|
20
|
-
type: 'SET_QUERY_DATA',
|
|
21
|
-
queryHash,
|
|
22
|
-
metadata: { data },
|
|
23
|
-
});
|
|
24
|
-
});
|
|
25
|
-
}, [client]);
|
|
26
|
-
|
|
27
|
-
useSyncInitialData(client);
|
|
28
|
-
|
|
29
|
-
useSyncDevToolsEvents(client);
|
|
30
|
-
|
|
31
|
-
useSyncOnlineStatus(client);
|
|
32
|
-
|
|
33
|
-
useHandleSyncMessages(client);
|
|
34
|
-
|
|
35
|
-
return <ReactQueryDevtoolsPanel style={{ height: '100%', width: '100%' }} />;
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
const queryClient = new QueryClient();
|
|
39
|
-
|
|
40
|
-
export default function TanStackQueryPanel() {
|
|
41
|
-
return (
|
|
42
|
-
<QueryClientProvider client={queryClient}>
|
|
43
|
-
<App />
|
|
44
|
-
</QueryClientProvider>
|
|
45
|
-
);
|
|
46
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { useEffect } from 'react';
|
|
2
|
-
import { TanStackQueryPluginClient } from '../shared/messaging';
|
|
3
|
-
import { useQueryClient } from '@tanstack/react-query';
|
|
4
|
-
import {
|
|
5
|
-
hydrateQueryClient,
|
|
6
|
-
applyQueryEvent,
|
|
7
|
-
applyMutationEvent,
|
|
8
|
-
applyQueryObserverEvent,
|
|
9
|
-
} from '../shared/hydrate';
|
|
10
|
-
|
|
11
|
-
export const useHandleSyncMessages = (client: TanStackQueryPluginClient | null) => {
|
|
12
|
-
const queryClient = useQueryClient();
|
|
13
|
-
|
|
14
|
-
useEffect(() => {
|
|
15
|
-
if (!client) {
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const querySubscription = client.onMessage('sync-query-event', (message) => {
|
|
20
|
-
const { type, data } = message;
|
|
21
|
-
|
|
22
|
-
if (type === 'added' || type === 'removed') {
|
|
23
|
-
applyQueryEvent(queryClient, type, data);
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
if (type === 'updated') {
|
|
28
|
-
const action = 'action' in message ? message.action : undefined;
|
|
29
|
-
applyQueryEvent(queryClient, type, data, action);
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (
|
|
34
|
-
type === 'observerAdded' ||
|
|
35
|
-
type === 'observerRemoved' ||
|
|
36
|
-
type === 'observerOptionsUpdated'
|
|
37
|
-
) {
|
|
38
|
-
applyQueryObserverEvent(queryClient, data);
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
const mutationSubscription = client.onMessage('sync-mutation-event', ({ type, data }) => {
|
|
44
|
-
applyMutationEvent(queryClient, type, data);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
// Keep the full sync for initial data requests
|
|
48
|
-
const fullSyncSubscription = client.onMessage('sync-data', ({ data }) => {
|
|
49
|
-
hydrateQueryClient(queryClient, data);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
return () => {
|
|
53
|
-
querySubscription.remove();
|
|
54
|
-
mutationSubscription.remove();
|
|
55
|
-
fullSyncSubscription.remove();
|
|
56
|
-
};
|
|
57
|
-
}, [client, queryClient]);
|
|
58
|
-
};
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { useQueryClient } from '@tanstack/react-query';
|
|
2
|
-
import { useEffect } from 'react';
|
|
3
|
-
import { TanStackQueryPluginClient } from '../shared/messaging';
|
|
4
|
-
|
|
5
|
-
export const useSyncDevToolsEvents = (client: TanStackQueryPluginClient | null) => {
|
|
6
|
-
const queryClient = useQueryClient();
|
|
7
|
-
|
|
8
|
-
useEffect(() => {
|
|
9
|
-
if (!client) {
|
|
10
|
-
return;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const handleEvent = (event: Event) => {
|
|
14
|
-
const detail = (event as CustomEvent).detail;
|
|
15
|
-
const { type, queryHash } = detail;
|
|
16
|
-
|
|
17
|
-
// Get the query from cache if we have a hash
|
|
18
|
-
const query = queryHash ? queryClient.getQueryCache().get(queryHash) : undefined;
|
|
19
|
-
if (!query && type !== 'CLEAR_QUERY_CACHE' && type !== 'CLEAR_MUTATION_CACHE') {
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// Special handling for cache clear actions which don't require a query
|
|
24
|
-
if (type === 'CLEAR_QUERY_CACHE' || type === 'CLEAR_MUTATION_CACHE') {
|
|
25
|
-
client.send('devtools-action', {
|
|
26
|
-
type,
|
|
27
|
-
queryHash: '',
|
|
28
|
-
});
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// For all other actions, ensure we have a valid query
|
|
33
|
-
if (!query || !queryHash) {
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
client.send('devtools-action', {
|
|
38
|
-
type,
|
|
39
|
-
queryHash: query.queryHash,
|
|
40
|
-
});
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
window.addEventListener('@tanstack/query-devtools-event', handleEvent);
|
|
44
|
-
return () => window.removeEventListener('@tanstack/query-devtools-event', handleEvent);
|
|
45
|
-
}, [client]);
|
|
46
|
-
};
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
// @vitest-environment jsdom
|
|
2
|
-
|
|
3
|
-
import { act, type ReactNode } from 'react';
|
|
4
|
-
import { createRoot } from 'react-dom/client';
|
|
5
|
-
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
6
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
7
|
-
import { TanStackQueryPluginClient } from '../shared/messaging';
|
|
8
|
-
import { useSyncInitialData } from './useSyncInitialData';
|
|
9
|
-
|
|
10
|
-
declare global {
|
|
11
|
-
var IS_REACT_ACT_ENVIRONMENT: boolean | undefined;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
type Listener = (payload: unknown) => void;
|
|
15
|
-
|
|
16
|
-
const createClient = () => {
|
|
17
|
-
const listeners = new Map<string, Set<Listener>>();
|
|
18
|
-
|
|
19
|
-
return {
|
|
20
|
-
send: vi.fn(),
|
|
21
|
-
onMessage: vi.fn((type: string, listener: Listener) => {
|
|
22
|
-
const typeListeners = listeners.get(type) ?? new Set<Listener>();
|
|
23
|
-
typeListeners.add(listener);
|
|
24
|
-
listeners.set(type, typeListeners);
|
|
25
|
-
return { remove: () => typeListeners.delete(listener) };
|
|
26
|
-
}),
|
|
27
|
-
close: vi.fn(),
|
|
28
|
-
emit: (type: string, payload: unknown) =>
|
|
29
|
-
listeners.get(type)?.forEach((listener) => listener(payload)),
|
|
30
|
-
};
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
const requests = (client: ReturnType<typeof createClient>) =>
|
|
34
|
-
client.send.mock.calls.filter(([type]) => type === 'request-initial-data');
|
|
35
|
-
|
|
36
|
-
describe('useSyncInitialData', () => {
|
|
37
|
-
beforeEach(() => {
|
|
38
|
-
globalThis.IS_REACT_ACT_ENVIRONMENT = true;
|
|
39
|
-
});
|
|
40
|
-
afterEach(() => {
|
|
41
|
-
document.body.innerHTML = '';
|
|
42
|
-
delete globalThis.IS_REACT_ACT_ENVIRONMENT;
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
// The panel is recreated on every app reload and asks for the cache before the
|
|
46
|
-
// app's React tree has mounted the plugin, so its first request is dropped.
|
|
47
|
-
it('asks for the cache again when the device announces itself', async () => {
|
|
48
|
-
const client = createClient();
|
|
49
|
-
const queryClient = new QueryClient();
|
|
50
|
-
queryClient.setQueryData(['stale-from-previous-context'], 'value');
|
|
51
|
-
|
|
52
|
-
const Harness = ({ children }: { children?: ReactNode }) => {
|
|
53
|
-
useSyncInitialData(client as unknown as TanStackQueryPluginClient);
|
|
54
|
-
return <>{children}</>;
|
|
55
|
-
};
|
|
56
|
-
const container = document.createElement('div');
|
|
57
|
-
document.body.append(container);
|
|
58
|
-
const root = createRoot(container);
|
|
59
|
-
await act(async () =>
|
|
60
|
-
root.render(
|
|
61
|
-
<QueryClientProvider client={queryClient}>
|
|
62
|
-
<Harness />
|
|
63
|
-
</QueryClientProvider>,
|
|
64
|
-
),
|
|
65
|
-
);
|
|
66
|
-
|
|
67
|
-
expect(requests(client)).toHaveLength(1);
|
|
68
|
-
|
|
69
|
-
await act(async () => client.emit('device-ready', {}));
|
|
70
|
-
|
|
71
|
-
expect(requests(client)).toHaveLength(2);
|
|
72
|
-
expect(queryClient.getQueryData(['stale-from-previous-context'])).toBeUndefined();
|
|
73
|
-
|
|
74
|
-
await act(async () => root.unmount());
|
|
75
|
-
});
|
|
76
|
-
});
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { useEffect } from 'react';
|
|
2
|
-
import { useQueryClient } from '@tanstack/react-query';
|
|
3
|
-
import { TanStackQueryPluginClient } from '../shared/messaging';
|
|
4
|
-
|
|
5
|
-
export const useSyncInitialData = (client: TanStackQueryPluginClient | null) => {
|
|
6
|
-
const queryClient = useQueryClient();
|
|
7
|
-
|
|
8
|
-
useEffect(() => {
|
|
9
|
-
if (!client) {
|
|
10
|
-
return;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
// The device announces itself once it is listening. It reconnects on every
|
|
14
|
-
// app reload, so the cache has to be pulled again — what the panel holds
|
|
15
|
-
// belongs to the previous JS context.
|
|
16
|
-
const subscription = client.onMessage('device-ready', () => {
|
|
17
|
-
queryClient.clear();
|
|
18
|
-
client.send('request-initial-data', {});
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
client.send('request-initial-data', {});
|
|
22
|
-
|
|
23
|
-
return () => {
|
|
24
|
-
subscription.remove();
|
|
25
|
-
};
|
|
26
|
-
}, [client, queryClient]);
|
|
27
|
-
};
|
package/tsconfig.json
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2020",
|
|
4
|
-
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
5
|
-
"allowJs": true,
|
|
6
|
-
"skipLibCheck": true,
|
|
7
|
-
"esModuleInterop": true,
|
|
8
|
-
"allowSyntheticDefaultImports": true,
|
|
9
|
-
"strict": true,
|
|
10
|
-
"forceConsistentCasingInFileNames": true,
|
|
11
|
-
"noFallthroughCasesInSwitch": true,
|
|
12
|
-
"module": "ESNext",
|
|
13
|
-
"moduleResolution": "bundler",
|
|
14
|
-
"customConditions": ["development"],
|
|
15
|
-
"resolveJsonModule": true,
|
|
16
|
-
"isolatedModules": true,
|
|
17
|
-
"noEmit": true,
|
|
18
|
-
"jsx": "react-jsx"
|
|
19
|
-
},
|
|
20
|
-
"include": ["src/**/*", "react-native.ts", "sdk.ts", "rozenite.config.ts"],
|
|
21
|
-
"exclude": ["node_modules", "dist", "build"],
|
|
22
|
-
"references": [
|
|
23
|
-
{
|
|
24
|
-
"path": "../agent-bridge"
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
"path": "../plugin-bridge"
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
"path": "../cli"
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
"path": "../vite-plugin"
|
|
34
|
-
}
|
|
35
|
-
]
|
|
36
|
-
}
|
package/vite.config.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
/// <reference types='vitest' />
|
|
2
|
-
import { resolve } from 'node:path';
|
|
3
|
-
import { defineConfig } from 'vite';
|
|
4
|
-
import { rozenitePlugin } from '@rozenite/vite-plugin';
|
|
5
|
-
|
|
6
|
-
export default defineConfig({
|
|
7
|
-
root: __dirname,
|
|
8
|
-
plugins: [rozenitePlugin()],
|
|
9
|
-
test: {
|
|
10
|
-
passWithNoTests: true,
|
|
11
|
-
alias: {
|
|
12
|
-
'@rozenite/agent-shared': resolve(__dirname, '../agent-shared/src/index.ts'),
|
|
13
|
-
},
|
|
14
|
-
},
|
|
15
|
-
base: './',
|
|
16
|
-
build: {
|
|
17
|
-
outDir: './dist',
|
|
18
|
-
emptyOutDir: false,
|
|
19
|
-
reportCompressedSize: false,
|
|
20
|
-
minify: true,
|
|
21
|
-
sourcemap: false,
|
|
22
|
-
},
|
|
23
|
-
server: {
|
|
24
|
-
port: 3000,
|
|
25
|
-
open: true,
|
|
26
|
-
},
|
|
27
|
-
});
|