@rozenite/tanstack-query-plugin 1.7.0-rc.2 → 1.8.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.
@@ -0,0 +1,156 @@
1
+ import {
2
+ Query,
3
+ QueryClient,
4
+ QueryKey,
5
+ QueryState,
6
+ } from '@tanstack/react-query';
7
+
8
+ type QuerySetState = Query['setState'];
9
+ type QueryClientSetQueryData = QueryClient['setQueryData'];
10
+
11
+ type SyncQueryDataPayload = {
12
+ queryHash: string;
13
+ data: unknown;
14
+ };
15
+
16
+ type QueryDataSyncHandler = (payload: SyncQueryDataPayload) => void;
17
+
18
+ const originalQuerySetStateMap = new WeakMap<Query, QuerySetState>();
19
+ const originalSetQueryDataMap = new WeakMap<
20
+ QueryClient,
21
+ QueryClientSetQueryData
22
+ >();
23
+ const instrumentedQueryClients = new WeakSet<QueryClient>();
24
+ const instrumentedQueries = new WeakSet<Query>();
25
+ const queryClientHandlers = new WeakMap<
26
+ QueryClient,
27
+ QueryDataSyncHandler | undefined
28
+ >();
29
+ const queryHandlers = new WeakMap<Query, QueryDataSyncHandler | undefined>();
30
+
31
+ const getOriginalQuerySetState = (query: Query): QuerySetState => {
32
+ return originalQuerySetStateMap.get(query) ?? query.setState.bind(query);
33
+ };
34
+
35
+ const emitIfDataChanged = (
36
+ query: Query,
37
+ previousData: unknown,
38
+ handler?: QueryDataSyncHandler,
39
+ ) => {
40
+ if (!handler || Object.is(previousData, query.state.data)) {
41
+ return;
42
+ }
43
+
44
+ handler({
45
+ queryHash: query.queryHash,
46
+ data: query.state.data,
47
+ });
48
+ };
49
+
50
+ const isDataOnlyStateUpdate = (
51
+ previousState: QueryState,
52
+ nextState: Partial<QueryState>,
53
+ ) => {
54
+ if (!('data' in nextState)) {
55
+ return false;
56
+ }
57
+
58
+ const changedKeys = Object.keys(nextState).filter((key) => {
59
+ const typedKey = key as keyof QueryState;
60
+ return !Object.is(previousState[typedKey], nextState[typedKey]);
61
+ }) as Array<keyof QueryState>;
62
+
63
+ return changedKeys.every((key) => key === 'data');
64
+ };
65
+
66
+ export const applyRemoteQueryState = (
67
+ query: Query,
68
+ state: Partial<QueryState>,
69
+ ) => {
70
+ const originalSetState = getOriginalQuerySetState(query);
71
+ originalSetState(state);
72
+ };
73
+
74
+ export const instrumentQuery = (
75
+ query: Query,
76
+ handler?: QueryDataSyncHandler,
77
+ ) => {
78
+ queryHandlers.set(query, handler);
79
+
80
+ if (instrumentedQueries.has(query)) {
81
+ return query;
82
+ }
83
+
84
+ const originalSetState = query.setState.bind(query);
85
+ originalQuerySetStateMap.set(query, originalSetState);
86
+
87
+ query.setState = ((state) => {
88
+ const shouldEmit = isDataOnlyStateUpdate(query.state, state);
89
+ const previousData = query.state.data;
90
+ const result = originalSetState(state);
91
+
92
+ if (shouldEmit) {
93
+ emitIfDataChanged(query, previousData, queryHandlers.get(query));
94
+ }
95
+
96
+ return result;
97
+ }) as QuerySetState;
98
+
99
+ instrumentedQueries.add(query);
100
+ return query;
101
+ };
102
+
103
+ const resolveQueryHash = (queryClient: QueryClient, queryKey: QueryKey) => {
104
+ return queryClient.getQueryCache().find({ queryKey })?.queryHash;
105
+ };
106
+
107
+ export const instrumentQueryClient = (
108
+ queryClient: QueryClient,
109
+ handler?: QueryDataSyncHandler,
110
+ ) => {
111
+ queryClientHandlers.set(queryClient, handler);
112
+
113
+ if (instrumentedQueryClients.has(queryClient)) {
114
+ queryClient
115
+ .getQueryCache()
116
+ .getAll()
117
+ .forEach((query) => {
118
+ instrumentQuery(query, handler);
119
+ });
120
+ return queryClient;
121
+ }
122
+
123
+ const originalSetQueryData = queryClient.setQueryData.bind(queryClient);
124
+ originalSetQueryDataMap.set(queryClient, originalSetQueryData);
125
+
126
+ queryClient.setQueryData = ((queryKey, updater, options) => {
127
+ const result = originalSetQueryData(queryKey, updater, options);
128
+ const query = queryClient.getQueryCache().find({ queryKey });
129
+ const activeQueryClientHandler = queryClientHandlers.get(queryClient);
130
+
131
+ if (query) {
132
+ instrumentQuery(query, activeQueryClientHandler);
133
+ const queryHash = resolveQueryHash(queryClient, queryKey);
134
+ const activeHandler = queryHandlers.get(query);
135
+
136
+ if (activeHandler && queryHash) {
137
+ activeHandler({
138
+ queryHash,
139
+ data: query.state.data,
140
+ });
141
+ }
142
+ }
143
+
144
+ return result;
145
+ }) as QueryClientSetQueryData;
146
+
147
+ queryClient
148
+ .getQueryCache()
149
+ .getAll()
150
+ .forEach((query) => {
151
+ instrumentQuery(query, handler);
152
+ });
153
+
154
+ instrumentedQueryClients.add(queryClient);
155
+ return queryClient;
156
+ };
@@ -37,6 +37,7 @@ export type SerializableQueryClient = {
37
37
 
38
38
  export type DevToolsActionType =
39
39
  | 'REFETCH'
40
+ | 'SET_QUERY_DATA'
40
41
  | 'INVALIDATE'
41
42
  | 'RESET'
42
43
  | 'REMOVE'
@@ -1,3 +1,4 @@
1
+ import { useEffect } from 'react';
1
2
  import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
2
3
  import { ReactQueryDevtoolsPanel } from '@tanstack/react-query-devtools/production';
3
4
  import { useRozeniteDevToolsClient } from '@rozenite/plugin-bridge';
@@ -6,12 +7,23 @@ import { useSyncInitialData } from './useSyncInitialData';
6
7
  import { useSyncDevToolsEvents } from './useSyncDevToolsEvents';
7
8
  import { useSyncOnlineStatus } from '../shared/useSyncOnlineStatus';
8
9
  import { useHandleSyncMessages } from './useHandleSyncMessages';
10
+ import { instrumentQueryClient } from '../shared/query-data-sync';
9
11
 
10
12
  const App = () => {
11
13
  const client = useRozeniteDevToolsClient<TanStackQueryPluginEventMap>({
12
14
  pluginId: '@rozenite/tanstack-query-plugin',
13
15
  });
14
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
+
15
27
  useSyncInitialData(client);
16
28
 
17
29
  useSyncDevToolsEvents(client);
package/tsconfig.json CHANGED
@@ -11,12 +11,15 @@
11
11
  "noFallthroughCasesInSwitch": true,
12
12
  "module": "ESNext",
13
13
  "moduleResolution": "bundler",
14
+ "paths": {
15
+ "@rozenite/agent-shared": ["../agent-shared/src/index.ts"]
16
+ },
14
17
  "resolveJsonModule": true,
15
18
  "isolatedModules": true,
16
19
  "noEmit": true,
17
20
  "jsx": "react-jsx"
18
21
  },
19
- "include": ["src/**/*", "react-native.ts", "rozenite.config.ts"],
22
+ "include": ["src/**/*", "react-native.ts", "sdk.ts", "rozenite.config.ts"],
20
23
  "exclude": ["node_modules", "dist", "build"],
21
24
  "references": [
22
25
  {
package/vite.config.ts CHANGED
@@ -1,10 +1,17 @@
1
1
  /// <reference types='vitest' />
2
+ import { resolve } from 'node:path';
2
3
  import { defineConfig } from 'vite';
3
4
  import { rozenitePlugin } from '@rozenite/vite-plugin';
4
5
 
5
6
  export default defineConfig({
6
7
  root: __dirname,
7
8
  plugins: [rozenitePlugin()],
9
+ test: {
10
+ passWithNoTests: true,
11
+ alias: {
12
+ '@rozenite/agent-shared': resolve(__dirname, '../agent-shared/src/index.ts'),
13
+ },
14
+ },
8
15
  base: './',
9
16
  build: {
10
17
  outDir: './dist',