@buoy-gg/react-query 1.7.7 → 2.1.1

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 (42) hide show
  1. package/lib/commonjs/index.js +619 -24
  2. package/lib/commonjs/preset.js +1 -1
  3. package/lib/commonjs/react-query/components/ReactQueryDevToolsModal.js +12 -0
  4. package/lib/commonjs/react-query/components/modals/MutationBrowserModal.js +10 -1
  5. package/lib/commonjs/react-query/components/modals/QueryBrowserModal.js +17 -15
  6. package/lib/commonjs/react-query/components/modals/ReactQueryModalHeader.js +14 -1
  7. package/lib/commonjs/react-query/hooks/useAllMutations.js +65 -15
  8. package/lib/commonjs/react-query/hooks/useWifiState.js +3 -3
  9. package/lib/commonjs/react-query/index.js +11 -0
  10. package/lib/commonjs/react-query/stores/index.js +48 -0
  11. package/lib/commonjs/react-query/stores/reactQueryEventStore.js +311 -0
  12. package/lib/commonjs/react-query/utils/modalStorageOperations.js +2 -2
  13. package/lib/module/index.js +113 -8
  14. package/lib/module/preset.js +2 -2
  15. package/lib/module/react-query/components/ReactQueryDevToolsModal.js +13 -1
  16. package/lib/module/react-query/components/modals/MutationBrowserModal.js +10 -1
  17. package/lib/module/react-query/components/modals/QueryBrowserModal.js +18 -16
  18. package/lib/module/react-query/components/modals/ReactQueryModalHeader.js +15 -2
  19. package/lib/module/react-query/hooks/useAllMutations.js +66 -16
  20. package/lib/module/react-query/hooks/useWifiState.js +4 -4
  21. package/lib/module/react-query/index.js +2 -1
  22. package/lib/module/react-query/stores/index.js +3 -0
  23. package/lib/module/react-query/stores/reactQueryEventStore.js +302 -0
  24. package/lib/module/react-query/utils/modalStorageOperations.js +3 -3
  25. package/lib/typescript/index.d.ts +61 -5
  26. package/lib/typescript/index.d.ts.map +1 -1
  27. package/lib/typescript/react-query/components/ReactQueryDevToolsModal.d.ts.map +1 -1
  28. package/lib/typescript/react-query/components/modals/MutationBrowserModal.d.ts.map +1 -1
  29. package/lib/typescript/react-query/components/modals/QueryBrowserModal.d.ts.map +1 -1
  30. package/lib/typescript/react-query/components/modals/ReactQueryModalHeader.d.ts +2 -1
  31. package/lib/typescript/react-query/components/modals/ReactQueryModalHeader.d.ts.map +1 -1
  32. package/lib/typescript/react-query/hooks/useAllMutations.d.ts +2 -2
  33. package/lib/typescript/react-query/hooks/useAllMutations.d.ts.map +1 -1
  34. package/lib/typescript/react-query/hooks/useWifiState.d.ts +1 -1
  35. package/lib/typescript/react-query/hooks/useWifiState.d.ts.map +1 -1
  36. package/lib/typescript/react-query/index.d.ts +1 -0
  37. package/lib/typescript/react-query/index.d.ts.map +1 -1
  38. package/lib/typescript/react-query/stores/index.d.ts +2 -0
  39. package/lib/typescript/react-query/stores/index.d.ts.map +1 -0
  40. package/lib/typescript/react-query/stores/reactQueryEventStore.d.ts +99 -0
  41. package/lib/typescript/react-query/stores/reactQueryEventStore.d.ts.map +1 -0
  42. package/package.json +17 -3
@@ -0,0 +1,311 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.subscribeToReactQueryEvents = exports.setQueryClient = exports.reactQueryEventStore = exports.isReactQueryConnected = exports.getReactQueryEvents = exports.disconnectQueryClient = exports.clearReactQueryEvents = void 0;
7
+ var _storageQueryUtils = require("../utils/storageQueryUtils");
8
+ /**
9
+ * React Query Event Store
10
+ *
11
+ * Bridges React Query's cache subscription system to a simple pub/sub pattern
12
+ * that can be consumed by the unified Events DevTools.
13
+ *
14
+ * Usage:
15
+ * 1. In your app, call setQueryClient(queryClient) to connect
16
+ * 2. The store will emit events for query/mutation state changes
17
+ * 3. The Events DevTools subscribes to these events
18
+ */
19
+
20
+ /**
21
+ * React Query event types for the unified timeline
22
+ */
23
+
24
+ /**
25
+ * Unified React Query event for the timeline
26
+ */
27
+
28
+ let eventIdCounter = 0;
29
+ function generateEventId() {
30
+ return `rq-${Date.now()}-${++eventIdCounter}`;
31
+ }
32
+ class ReactQueryEventStore {
33
+ events = [];
34
+ listeners = new Set();
35
+ queryClient = null;
36
+ queryUnsubscribe = null;
37
+ mutationUnsubscribe = null;
38
+ maxEvents = 200;
39
+
40
+ // Track fetch start times for duration calculation
41
+ fetchStartTimes = new Map();
42
+ mutationStartTimes = new Map();
43
+
44
+ // Track last known states to detect transitions
45
+ lastQueryStates = new Map();
46
+ lastMutationStates = new Map();
47
+
48
+ /**
49
+ * Connect a QueryClient to the store
50
+ * Call this in your app after creating the QueryClient
51
+ */
52
+ setQueryClient(queryClient) {
53
+ // Cleanup previous connection
54
+ this.disconnect();
55
+ this.queryClient = queryClient;
56
+
57
+ // Subscribe to query cache
58
+ this.queryUnsubscribe = queryClient.getQueryCache().subscribe(event => {
59
+ if (event.type === "updated" && event.query) {
60
+ this.handleQueryUpdate(event.query);
61
+ }
62
+ });
63
+
64
+ // Subscribe to mutation cache
65
+ this.mutationUnsubscribe = queryClient.getMutationCache().subscribe(event => {
66
+ if (event.type === "updated" && event.mutation) {
67
+ this.handleMutationUpdate(event.mutation);
68
+ }
69
+ });
70
+ }
71
+
72
+ /**
73
+ * Disconnect from the QueryClient
74
+ */
75
+ disconnect() {
76
+ if (this.queryUnsubscribe) {
77
+ this.queryUnsubscribe();
78
+ this.queryUnsubscribe = null;
79
+ }
80
+ if (this.mutationUnsubscribe) {
81
+ this.mutationUnsubscribe();
82
+ this.mutationUnsubscribe = null;
83
+ }
84
+ this.queryClient = null;
85
+ this.lastQueryStates.clear();
86
+ this.lastMutationStates.clear();
87
+ this.fetchStartTimes.clear();
88
+ this.mutationStartTimes.clear();
89
+ }
90
+
91
+ /**
92
+ * Handle query state updates and emit appropriate events
93
+ */
94
+ handleQueryUpdate(query) {
95
+ // Skip storage queries (internal to the devtools)
96
+ if ((0, _storageQueryUtils.isStorageQuery)(query.queryKey)) {
97
+ return;
98
+ }
99
+ const queryHash = query.queryHash;
100
+ const currentState = {
101
+ fetchStatus: query.state.fetchStatus,
102
+ status: query.state.status
103
+ };
104
+ const lastState = this.lastQueryStates.get(queryHash);
105
+
106
+ // Detect state transitions
107
+ if (!lastState) {
108
+ // New query - if it's fetching, emit fetch start
109
+ if (currentState.fetchStatus === "fetching") {
110
+ this.fetchStartTimes.set(queryHash, Date.now());
111
+ this.addEvent({
112
+ id: generateEventId(),
113
+ type: "query-fetch-start",
114
+ timestamp: Date.now(),
115
+ queryKey: query.queryKey,
116
+ queryHash,
117
+ query
118
+ });
119
+ }
120
+ } else {
121
+ // Existing query - check for transitions
122
+ const wasFetching = lastState.fetchStatus === "fetching";
123
+ const isFetching = currentState.fetchStatus === "fetching";
124
+
125
+ // Started fetching
126
+ if (!wasFetching && isFetching) {
127
+ this.fetchStartTimes.set(queryHash, Date.now());
128
+ this.addEvent({
129
+ id: generateEventId(),
130
+ type: "query-fetch-start",
131
+ timestamp: Date.now(),
132
+ queryKey: query.queryKey,
133
+ queryHash,
134
+ query
135
+ });
136
+ }
137
+
138
+ // Finished fetching
139
+ if (wasFetching && !isFetching) {
140
+ const startTime = this.fetchStartTimes.get(queryHash);
141
+ const duration = startTime ? Date.now() - startTime : undefined;
142
+ this.fetchStartTimes.delete(queryHash);
143
+ if (currentState.status === "error") {
144
+ this.addEvent({
145
+ id: generateEventId(),
146
+ type: "query-fetch-error",
147
+ timestamp: Date.now(),
148
+ queryKey: query.queryKey,
149
+ queryHash,
150
+ queryError: query.state.error,
151
+ duration,
152
+ query
153
+ });
154
+ } else {
155
+ this.addEvent({
156
+ id: generateEventId(),
157
+ type: "query-fetch-success",
158
+ timestamp: Date.now(),
159
+ queryKey: query.queryKey,
160
+ queryHash,
161
+ queryData: query.state.data,
162
+ duration,
163
+ query
164
+ });
165
+ }
166
+ }
167
+
168
+ // Query was invalidated
169
+ if (!lastState.status && query.state.isInvalidated) {
170
+ this.addEvent({
171
+ id: generateEventId(),
172
+ type: "query-invalidated",
173
+ timestamp: Date.now(),
174
+ queryKey: query.queryKey,
175
+ queryHash,
176
+ query
177
+ });
178
+ }
179
+ }
180
+
181
+ // Update last known state
182
+ this.lastQueryStates.set(queryHash, currentState);
183
+ }
184
+
185
+ /**
186
+ * Handle mutation state updates and emit appropriate events
187
+ */
188
+ handleMutationUpdate(mutation) {
189
+ const mutationId = mutation.mutationId;
190
+ const currentStatus = mutation.state.status;
191
+ const lastStatus = this.lastMutationStates.get(mutationId);
192
+ if (lastStatus !== currentStatus) {
193
+ // Status changed
194
+ if (currentStatus === "pending" && lastStatus !== "pending") {
195
+ // Mutation started
196
+ this.mutationStartTimes.set(mutationId, Date.now());
197
+ this.addEvent({
198
+ id: generateEventId(),
199
+ type: "mutation-start",
200
+ timestamp: Date.now(),
201
+ mutationId,
202
+ mutationKey: mutation.options.mutationKey,
203
+ mutationVariables: mutation.state.variables,
204
+ mutation
205
+ });
206
+ } else if (currentStatus === "success" && lastStatus === "pending") {
207
+ // Mutation succeeded
208
+ const startTime = this.mutationStartTimes.get(mutationId);
209
+ const duration = startTime ? Date.now() - startTime : undefined;
210
+ this.mutationStartTimes.delete(mutationId);
211
+ this.addEvent({
212
+ id: generateEventId(),
213
+ type: "mutation-success",
214
+ timestamp: Date.now(),
215
+ mutationId,
216
+ mutationKey: mutation.options.mutationKey,
217
+ mutationVariables: mutation.state.variables,
218
+ mutationData: mutation.state.data,
219
+ duration,
220
+ mutation
221
+ });
222
+ } else if (currentStatus === "error" && lastStatus === "pending") {
223
+ // Mutation failed
224
+ const startTime = this.mutationStartTimes.get(mutationId);
225
+ const duration = startTime ? Date.now() - startTime : undefined;
226
+ this.mutationStartTimes.delete(mutationId);
227
+ this.addEvent({
228
+ id: generateEventId(),
229
+ type: "mutation-error",
230
+ timestamp: Date.now(),
231
+ mutationId,
232
+ mutationKey: mutation.options.mutationKey,
233
+ mutationVariables: mutation.state.variables,
234
+ mutationError: mutation.state.error,
235
+ duration,
236
+ mutation
237
+ });
238
+ }
239
+ }
240
+
241
+ // Update last known status
242
+ this.lastMutationStates.set(mutationId, currentStatus);
243
+ }
244
+
245
+ /**
246
+ * Add an event to the store
247
+ */
248
+ addEvent(event) {
249
+ this.events = [event, ...this.events].slice(0, this.maxEvents);
250
+ this.notifyListeners();
251
+ }
252
+
253
+ /**
254
+ * Get all events
255
+ */
256
+ getEvents() {
257
+ return this.events;
258
+ }
259
+
260
+ /**
261
+ * Subscribe to event changes
262
+ */
263
+ subscribe(listener) {
264
+ this.listeners.add(listener);
265
+ // Immediately call with current events
266
+ listener(this.events);
267
+ return () => {
268
+ this.listeners.delete(listener);
269
+ };
270
+ }
271
+
272
+ /**
273
+ * Notify all listeners
274
+ */
275
+ notifyListeners() {
276
+ const events = this.events;
277
+ this.listeners.forEach(listener => listener(events));
278
+ }
279
+
280
+ /**
281
+ * Clear all events
282
+ */
283
+ clearEvents() {
284
+ this.events = [];
285
+ this.notifyListeners();
286
+ }
287
+
288
+ /**
289
+ * Check if connected to a QueryClient
290
+ */
291
+ isConnected() {
292
+ return this.queryClient !== null;
293
+ }
294
+ }
295
+
296
+ // Singleton instance
297
+ const reactQueryEventStore = exports.reactQueryEventStore = new ReactQueryEventStore();
298
+
299
+ // Convenience exports
300
+ const setQueryClient = queryClient => reactQueryEventStore.setQueryClient(queryClient);
301
+ exports.setQueryClient = setQueryClient;
302
+ const disconnectQueryClient = () => reactQueryEventStore.disconnect();
303
+ exports.disconnectQueryClient = disconnectQueryClient;
304
+ const getReactQueryEvents = () => reactQueryEventStore.getEvents();
305
+ exports.getReactQueryEvents = getReactQueryEvents;
306
+ const subscribeToReactQueryEvents = listener => reactQueryEventStore.subscribe(listener);
307
+ exports.subscribeToReactQueryEvents = subscribeToReactQueryEvents;
308
+ const clearReactQueryEvents = () => reactQueryEventStore.clearEvents();
309
+ exports.clearReactQueryEvents = clearReactQueryEvents;
310
+ const isReactQueryConnected = () => reactQueryEventStore.isConnected();
311
+ exports.isReactQueryConnected = isReactQueryConnected;
@@ -7,10 +7,10 @@ exports.savePanelHeight = exports.savePanelDimensions = exports.saveModalVisibil
7
7
  var _sharedUi = require("@buoy-gg/shared-ui");
8
8
  // Helper functions for persisting panel state using shared storage wrapper
9
9
  const setItem = async (key, value) => {
10
- await (0, _sharedUi.safeSetItem)(key, value);
10
+ await _sharedUi.persistentStorage.setItem(key, value);
11
11
  };
12
12
  const getItem = async key => {
13
- return (0, _sharedUi.safeGetItem)(key);
13
+ return _sharedUi.persistentStorage.getItem(key);
14
14
  };
15
15
  // Storage operations
16
16
  /**
@@ -1,5 +1,15 @@
1
1
  "use strict";
2
2
 
3
+ /**
4
+ * @buoy-gg/react-query
5
+ *
6
+ * React Query DevTools for React Native.
7
+ *
8
+ * PUBLIC API - Only these exports are supported for external use.
9
+ * Internal stores and event subscription mechanisms are not exported
10
+ * to prevent bypassing the tool's intended usage patterns.
11
+ */
12
+
3
13
  // Check if @tanstack/react-query is installed
4
14
  try {
5
15
  require("@tanstack/react-query");
@@ -7,13 +17,108 @@ try {
7
17
  throw new Error("\n\n[@buoy-gg/react-query] ERROR: Missing required peer dependency\n\n" + "This package requires @tanstack/react-query to be installed.\n\n" + "Install it with:\n" + " npm install @tanstack/react-query\n" + " or\n" + " pnpm add @tanstack/react-query\n" + " or\n" + " yarn add @tanstack/react-query\n\n" + "For more information, visit: https://tanstack.com/query/latest\n");
8
18
  }
9
19
 
10
- // Export preset configuration (easiest way to add to FloatingDevTools!)
20
+ // =============================================================================
21
+ // PRESET (Primary entry point for users)
22
+ // =============================================================================
11
23
  export { reactQueryToolPreset, createReactQueryTool, wifiTogglePreset, createWifiToggleTool } from "./preset";
12
24
 
13
- // React Query dev tools entry point
14
- // Re-export the full dev tools surface so consumers can tree-shake as needed
15
- export * from "./react-query";
16
- export * from "./react-query/components";
17
- export * from "./react-query/hooks";
18
- export * from "./react-query/utils";
19
- export * from "./react-query/types";
25
+ // =============================================================================
26
+ // QUERY CLIENT CONNECTION (Required for users to connect their QueryClient)
27
+ // =============================================================================
28
+ export { setQueryClient, disconnectQueryClient } from "./react-query/stores/reactQueryEventStore";
29
+
30
+ // =============================================================================
31
+ // COMPONENTS (For custom UI implementations)
32
+ // =============================================================================
33
+
34
+ // Main modals
35
+ export { ReactQueryModal } from "./react-query/components/modals/ReactQueryModal";
36
+ export { ReactQueryModalHeader } from "./react-query/components/modals/ReactQueryModalHeader";
37
+ export { QueryBrowserModal } from "./react-query/components/modals/QueryBrowserModal";
38
+ export { MutationBrowserModal } from "./react-query/components/modals/MutationBrowserModal";
39
+ export { MutationEditorModal } from "./react-query/components/modals/MutationEditorModal";
40
+ export { DataEditorModal } from "./react-query/components/modals/DataEditorModal";
41
+ export { QueryBrowserFooter } from "./react-query/components/modals/QueryBrowserFooter";
42
+ export { MutationBrowserFooter } from "./react-query/components/modals/MutationBrowserFooter";
43
+ export { SwipeIndicator } from "./react-query/components/modals/SwipeIndicator";
44
+
45
+ // Query browser components
46
+ export { Explorer, QueryBrowser, QueryDetails, QueryInformation, QueryActions, QueryRow, QueryStatus, QueryStatusCount, QueryDetailsChip, MutationsList, MutationDetails, MutationInformation, MutationButton, MutationStatusCount, MutationDetailsChips, ActionButton, ClearCacheButton, NetworkToggleButton, StorageStatusCount } from "./react-query/components/query-browser";
47
+
48
+ // Mode components
49
+ export { QueryBrowserMode } from "./react-query/components/QueryBrowserMode";
50
+ export { MutationBrowserMode } from "./react-query/components/MutationBrowserMode";
51
+ export { MutationEditorMode } from "./react-query/components/MutationEditorMode";
52
+ export { DataEditorMode } from "./react-query/components/DataEditorMode";
53
+ export { QuerySelector } from "./react-query/components/QuerySelector";
54
+ export { QueryDebugInfo } from "./react-query/components/QueryDebugInfo";
55
+ export { WifiToggle } from "./react-query/components/WifiToggle";
56
+ export { ReactQuerySection } from "./react-query/components/ReactQuerySection";
57
+ export { ReactQueryDevToolsModal } from "./react-query/components/ReactQueryDevToolsModal";
58
+
59
+ // Shared data viewer components
60
+ export { VirtualizedDataExplorer, DataViewer, TypeLegend } from "@buoy-gg/shared-ui/dataViewer";
61
+
62
+ // =============================================================================
63
+ // HOOKS (For consuming React Query data)
64
+ // =============================================================================
65
+ export { default as useAllQueries } from "./react-query/hooks/useAllQueries";
66
+ export { default as useAllMutations } from "./react-query/hooks/useAllMutations";
67
+ export { useGetQueryByQueryKey, useGetQueryByQueryKeyWithVersion } from "./react-query/hooks/useSelectedQuery";
68
+ export { useGetMutationById } from "./react-query/hooks/useSelectedMutation";
69
+ export { default as useQueryStatusCounts } from "./react-query/hooks/useQueryStatusCounts";
70
+ export { useStorageQueryCounts } from "./react-query/hooks/useStorageQueryCounts";
71
+ export { useReactQueryState } from "./react-query/hooks/useReactQueryState";
72
+ export { useActionButtons } from "./react-query/hooks/useActionButtons";
73
+ export { useMutationActionButtons } from "./react-query/hooks/useMutationActionButtons";
74
+ export { useModalManager } from "./react-query/hooks/useModalManager";
75
+ export { useModalPersistence } from "./react-query/hooks/useModalPersistence";
76
+ export { useWifiState } from "./react-query/hooks/useWifiState";
77
+
78
+ // =============================================================================
79
+ // UTILITIES (Public helpers only)
80
+ // =============================================================================
81
+
82
+ // Action utilities
83
+ export { default as invalidate } from "./react-query/utils/actions/invalidate";
84
+ export { default as refetch } from "./react-query/utils/actions/refetch";
85
+ export { default as reset } from "./react-query/utils/actions/reset";
86
+ export { default as remove } from "./react-query/utils/actions/remove";
87
+ export { default as deleteItem } from "./react-query/utils/actions/deleteItem";
88
+ export { default as triggerError } from "./react-query/utils/actions/triggerError";
89
+ export { default as triggerLoading } from "./react-query/utils/actions/triggerLoading";
90
+
91
+ // Query status utilities
92
+ export * from "./react-query/utils/getQueryStatusLabel";
93
+ export * from "./react-query/utils/getQueryStatusColor";
94
+
95
+ // Storage utilities
96
+ export * from "./react-query/utils/getStorageQueryCounts";
97
+ export * from "./react-query/utils/storageQueryUtils";
98
+ export * from "./react-query/utils/modalStorageOperations";
99
+
100
+ // Data manipulation utilities
101
+ export * from "./react-query/utils/updateNestedDataByPath";
102
+ export * from "./react-query/utils/deleteNestedDataByPath";
103
+ export { safeStringify, displayValue, parseDisplayValue } from "@buoy-gg/shared-ui";
104
+
105
+ // =============================================================================
106
+ // TYPES
107
+ // =============================================================================
108
+
109
+ export { isPlainObject } from "./react-query/types/types";
110
+
111
+ // =============================================================================
112
+ // INTERNAL EXPORTS (For @buoy-gg/* packages only - not part of public API)
113
+ // =============================================================================
114
+ /** @internal */
115
+ export { reactQueryEventStore } from "./react-query/stores/reactQueryEventStore";
116
+
117
+ // =============================================================================
118
+ // NOT EXPORTED (Internal controls)
119
+ // =============================================================================
120
+ // The following are intentionally NOT exported to prevent bypassing:
121
+ // - getReactQueryEvents (internal store access)
122
+ // - subscribeToReactQueryEvents (internal subscription)
123
+ // - clearReactQueryEvents (internal store control)
124
+ // - isReactQueryConnected (internal state query)
@@ -21,7 +21,7 @@ import { QueryIcon } from "@buoy-gg/floating-tools-core";
21
21
  import { Wifi } from "@buoy-gg/shared-ui";
22
22
  import { ReactQueryDevToolsModal } from "./react-query/components/ReactQueryDevToolsModal";
23
23
  import { onlineManager } from "@tanstack/react-query";
24
- import { devToolsStorageKeys, safeSetItem } from "@buoy-gg/shared-ui";
24
+ import { devToolsStorageKeys, persistentStorage } from "@buoy-gg/shared-ui";
25
25
 
26
26
  // Empty component for toggle-only mode
27
27
  const EmptyComponent = () => null;
@@ -29,7 +29,7 @@ const EmptyComponent = () => null;
29
29
  // Save WiFi state to storage
30
30
  const saveWifiState = async enabled => {
31
31
  try {
32
- await safeSetItem(devToolsStorageKeys.settings.wifiEnabled(), enabled.toString());
32
+ await persistentStorage.setItem(devToolsStorageKeys.settings.wifiEnabled(), enabled.toString());
33
33
  } catch (error) {
34
34
  // Failed to save WiFi state
35
35
  }
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
2
 
3
- import { useCallback, useState } from "react";
3
+ import { useCallback, useState, useEffect } from "react";
4
+ import { useQueryClient } from "@tanstack/react-query";
4
5
  import { ReactQueryModal } from "./modals/ReactQueryModal";
6
+ import { reactQueryEventStore } from "../stores/reactQueryEventStore";
5
7
 
6
8
  /** Configuration options for the high-level React Query dev tools modal wrapper. */
7
9
  import { jsx as _jsx } from "react/jsx-runtime";
@@ -15,6 +17,16 @@ export function ReactQueryDevToolsModal({
15
17
  onMinimize,
16
18
  enableSharedModalDimensions = true
17
19
  }) {
20
+ // Get QueryClient and connect to event store for unified Events DevTools
21
+ const queryClient = useQueryClient();
22
+ useEffect(() => {
23
+ // Connect the QueryClient to the event store so events flow to unified Events DevTools
24
+ // Only connect if not already connected (to preserve connection across modal open/close)
25
+ if (!reactQueryEventStore.isConnected()) {
26
+ reactQueryEventStore.setQueryClient(queryClient);
27
+ }
28
+ // Don't disconnect on unmount - keep the connection for unified Events DevTools
29
+ }, [queryClient]);
18
30
  const [selectedQueryKey, setSelectedQueryKey] = useState(undefined);
19
31
  const [selectedMutationId, setSelectedMutationId] = useState(undefined);
20
32
  const [activeFilter, setActiveFilter] = useState(null);
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
 
3
+ import { useQueryClient } from "@tanstack/react-query";
3
4
  import { useCallback, useState, useRef } from "react";
4
5
  import { useGetMutationById } from "../../hooks/useSelectedMutation";
5
6
  import { MutationBrowserMode } from "../MutationBrowserMode";
@@ -27,11 +28,18 @@ export function MutationBrowserModal({
27
28
  searchText = "",
28
29
  onSearchChange
29
30
  }) {
31
+ const queryClient = useQueryClient();
30
32
  const selectedMutation = useGetMutationById(selectedMutationId);
31
33
  const [internalActiveFilter, setInternalActiveFilter] = useState(null);
32
34
  const activeFilter = externalActiveFilter ?? internalActiveFilter;
33
35
  const setActiveFilter = externalOnFilterChange ?? setInternalActiveFilter;
34
36
 
37
+ // Clear mutation cache handler
38
+ const handleClearCache = useCallback(() => {
39
+ queryClient.getMutationCache().clear();
40
+ onMutationSelect(undefined);
41
+ }, [queryClient, onMutationSelect]);
42
+
35
43
  // Track modal mode for conditional styling
36
44
  // Initialize with bottomSheet but it will be updated from persisted state if available
37
45
  const [modalMode, setModalMode] = useState("bottomSheet");
@@ -95,7 +103,8 @@ export function MutationBrowserModal({
95
103
  onTabChange: onTabChange,
96
104
  onBack: () => onMutationSelect(undefined),
97
105
  searchText: searchText,
98
- onSearchChange: onSearchChange
106
+ onSearchChange: onSearchChange,
107
+ onClearCache: handleClearCache
99
108
  });
100
109
  const footerNode = /*#__PURE__*/_jsx(MutationBrowserFooter, {
101
110
  activeFilter: activeFilter,
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
 
3
+ import { useQueryClient } from "@tanstack/react-query";
3
4
  import { JsModal, ModalHeader, TabSelector } from "@buoy-gg/shared-ui";
4
5
  import { useGetQueryByQueryKey } from "../../hooks/useSelectedQuery";
5
6
  import { ReactQueryModalHeader } from "./ReactQueryModalHeader";
@@ -8,7 +9,7 @@ import { QueryBrowserFooter } from "./QueryBrowserFooter";
8
9
  import { QueryFilterViewV3 } from "../QueryFilterViewV3";
9
10
  import { useState, useCallback, useEffect } from "react";
10
11
  import { View, StyleSheet } from "react-native";
11
- import { devToolsStorageKeys, useSafeAsyncStorage, buoyColors } from "@buoy-gg/shared-ui";
12
+ import { devToolsStorageKeys, persistentStorage, buoyColors } from "@buoy-gg/shared-ui";
12
13
  import useAllQueries from "../../hooks/useAllQueries";
13
14
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
14
15
  /**
@@ -28,9 +29,15 @@ export function QueryBrowserModal({
28
29
  searchText = "",
29
30
  onSearchChange
30
31
  }) {
32
+ const queryClient = useQueryClient();
31
33
  const selectedQuery = useGetQueryByQueryKey(selectedQueryKey);
32
34
  const allQueries = useAllQueries();
33
35
 
36
+ // Clear query cache handler
37
+ const handleClearCache = useCallback(() => {
38
+ queryClient.getQueryCache().clear();
39
+ }, [queryClient]);
40
+
34
41
  // Use external filter state if provided (for persistence), otherwise use internal state
35
42
  const [internalActiveFilter, setInternalActiveFilter] = useState(null);
36
43
  const activeFilter = externalActiveFilter ?? internalActiveFilter;
@@ -41,17 +48,11 @@ export function QueryBrowserModal({
41
48
  const [ignoredPatterns, setIgnoredPatterns] = useState(new Set());
42
49
  const [includedPatterns, setIncludedPatterns] = useState(new Set());
43
50
 
44
- // AsyncStorage for persisting ignored patterns
45
- const {
46
- getItem: safeGetItem,
47
- setItem: safeSetItem
48
- } = useSafeAsyncStorage();
49
-
50
51
  // Load ignored patterns from storage on mount
51
52
  useEffect(() => {
52
53
  const loadFilters = async () => {
53
54
  try {
54
- const stored = await safeGetItem(devToolsStorageKeys.reactQuery.ignoredPatterns());
55
+ const stored = await persistentStorage.getItem(devToolsStorageKeys.reactQuery.ignoredPatterns());
55
56
  if (stored) {
56
57
  const patterns = JSON.parse(stored);
57
58
  if (Array.isArray(patterns)) {
@@ -63,13 +64,13 @@ export function QueryBrowserModal({
63
64
  }
64
65
  };
65
66
  loadFilters();
66
- }, [safeGetItem]);
67
+ }, []);
67
68
 
68
69
  // Load included patterns from storage on mount
69
70
  useEffect(() => {
70
71
  const loadFilters = async () => {
71
72
  try {
72
- const stored = await safeGetItem(devToolsStorageKeys.reactQuery.includedPatterns());
73
+ const stored = await persistentStorage.getItem(devToolsStorageKeys.reactQuery.includedPatterns());
73
74
  if (stored) {
74
75
  const patterns = JSON.parse(stored);
75
76
  if (Array.isArray(patterns)) {
@@ -81,33 +82,33 @@ export function QueryBrowserModal({
81
82
  }
82
83
  };
83
84
  loadFilters();
84
- }, [safeGetItem]);
85
+ }, []);
85
86
 
86
87
  // Save ignored patterns to storage when they change
87
88
  useEffect(() => {
88
89
  const saveFilters = async () => {
89
90
  try {
90
91
  const patterns = Array.from(ignoredPatterns);
91
- await safeSetItem(devToolsStorageKeys.reactQuery.ignoredPatterns(), JSON.stringify(patterns));
92
+ await persistentStorage.setItem(devToolsStorageKeys.reactQuery.ignoredPatterns(), JSON.stringify(patterns));
92
93
  } catch (error) {
93
94
  console.error("Failed to save ignored patterns:", error);
94
95
  }
95
96
  };
96
97
  saveFilters();
97
- }, [ignoredPatterns, safeSetItem]);
98
+ }, [ignoredPatterns]);
98
99
 
99
100
  // Save included patterns to storage when they change
100
101
  useEffect(() => {
101
102
  const saveFilters = async () => {
102
103
  try {
103
104
  const patterns = Array.from(includedPatterns);
104
- await safeSetItem(devToolsStorageKeys.reactQuery.includedPatterns(), JSON.stringify(patterns));
105
+ await persistentStorage.setItem(devToolsStorageKeys.reactQuery.includedPatterns(), JSON.stringify(patterns));
105
106
  } catch (error) {
106
107
  console.error("Failed to save included patterns:", error);
107
108
  }
108
109
  };
109
110
  saveFilters();
110
- }, [includedPatterns, safeSetItem]);
111
+ }, [includedPatterns]);
111
112
 
112
113
  // Toggle pattern in ignored set
113
114
  const handlePatternToggle = useCallback(pattern => {
@@ -173,7 +174,8 @@ export function QueryBrowserModal({
173
174
  searchText: searchText,
174
175
  onSearchChange: onSearchChange,
175
176
  onFilterPress: () => setShowFilterView(true),
176
- hasActiveFilters: activeFilter !== null || ignoredPatterns.size > 0 || includedPatterns.size > 0
177
+ hasActiveFilters: activeFilter !== null || ignoredPatterns.size > 0 || includedPatterns.size > 0,
178
+ onClearCache: handleClearCache
177
179
  });
178
180
  };
179
181
  const footerNode = /*#__PURE__*/_jsx(QueryBrowserFooter, {