Skip to content

Commit e1bf3bb

Browse files
committed
Add bulk remove all queries functionality
Implement ability to remove all queries at once with new RemoveAllQueriesButton component. Add BULK_QUERY_ACTION and CLEAR_ARTIFICIAL_STATES message types to handle bulk operations and clean up artificial states when queries are cleared.
1 parent 33309e2 commit e1bf3bb

10 files changed

Lines changed: 196 additions & 17 deletions

File tree

memory-bank

src/App.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,14 @@ function App() {
2222
sendMessage,
2323
} = useConnection();
2424
const { handleQueryAction } = useUIState(sendMessage);
25+
26+
// Handler for removing all queries
27+
const handleRemoveAllQueries = () => {
28+
sendMessage({
29+
type: "BULK_QUERY_ACTION",
30+
action: "REMOVE_ALL_QUERIES",
31+
});
32+
};
2533
const {
2634
currentView,
2735
searchTerm,
@@ -82,6 +90,7 @@ function App() {
8290
selectedMutationIndex={selectedMutationIndex}
8391
onSelectQuery={setSelectedQueryIndex}
8492
onSelectMutation={setSelectedMutationIndex}
93+
onRemoveAllQueries={handleRemoveAllQueries}
8594
queryKeyboardNavigation={queryKeyboardNavigation}
8695
mutationKeyboardNavigation={mutationKeyboardNavigation}
8796
/>

src/background/background.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import type {
77
UpdateMessage,
88
QueryActionResult,
99
RequestImmediateUpdateMessage,
10+
BulkQueryActionMessage,
11+
ClearArtificialStatesMessage,
1012
} from "../types/messages";
1113

1214
// Zod schema for serialized payload validation
@@ -24,6 +26,8 @@ type BackgroundMessage =
2426
| UpdateMessage
2527
| QueryActionResult
2628
| (RequestImmediateUpdateMessage & { inspectedTabId?: number })
29+
| (BulkQueryActionMessage & { inspectedTabId?: number })
30+
| ClearArtificialStatesMessage
2731
| {
2832
type: "QUERY_ACTION";
2933
inspectedTabId?: number;
@@ -42,6 +46,7 @@ chrome.runtime.onMessage.addListener(
4246
// Forward DevTools actions to content script of the specified tab
4347
if (
4448
message.type === "QUERY_ACTION" ||
49+
message.type === "BULK_QUERY_ACTION" ||
4550
message.type === "REQUEST_IMMEDIATE_UPDATE"
4651
) {
4752
// Track preservation flag for REQUEST_IMMEDIATE_UPDATE messages
@@ -181,6 +186,30 @@ chrome.runtime.onMessage.addListener(
181186
return true;
182187
}
183188

189+
// Handle clear artificial states message from content script
190+
if (message.type === "CLEAR_ARTIFICIAL_STATES") {
191+
StorageManager.getState()
192+
.then((currentState) => {
193+
const artificialStates = {
194+
...(currentState.artificialStates || {}),
195+
};
196+
// Clear artificial states for this tab only
197+
delete artificialStates[tabId];
198+
return StorageManager.updatePartialState({
199+
artificialStates,
200+
lastUpdated: Date.now(),
201+
});
202+
})
203+
.then(() => {
204+
sendResponse({ received: true });
205+
})
206+
.catch((error) => {
207+
console.error("Failed to clear artificial states:", error);
208+
sendResponse({ received: false, error: error.message });
209+
});
210+
return true;
211+
}
212+
184213
// Handle action results from content scripts and forward to DevTools
185214
if (message.type === "QUERY_ACTION_RESULT") {
186215
// Update artificial states in storage for TRIGGER_LOADING and TRIGGER_ERROR
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { Trash2 } from "lucide-react";
2+
import { buttonVariants } from "../../lib/variants";
3+
4+
interface RemoveAllQueriesButtonProps {
5+
disabled: boolean;
6+
onRemoveAll: () => void;
7+
}
8+
9+
export function RemoveAllQueriesButton({
10+
disabled,
11+
onRemoveAll,
12+
}: RemoveAllQueriesButtonProps) {
13+
return (
14+
<button
15+
onClick={onRemoveAll}
16+
disabled={disabled}
17+
title="Remove all queries"
18+
className={buttonVariants({ variant: "pink", size: "sm" })}
19+
aria-label="Remove all queries"
20+
>
21+
<Trash2 size={14} className="shrink-0" />
22+
</button>
23+
);
24+
}

src/components/layout/ListView.tsx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useEffect } from "react";
22
import { QueryListItem } from "../query/QueryListItem";
33
import { MutationListItem } from "../mutation/MutationListItem";
4+
import { RemoveAllQueriesButton } from "../common/RemoveAllQueriesButton";
45
import type { QueryData, MutationData, ViewType } from "../../types/query";
56
import type { useKeyboardNavigation } from "../../hooks/useKeyboardNavigation";
67

@@ -13,6 +14,7 @@ interface ListViewProps {
1314
selectedMutationIndex: number | null;
1415
onSelectQuery: (index: number | null) => void;
1516
onSelectMutation: (index: number | null) => void;
17+
onRemoveAllQueries?: () => void;
1618
// Keyboard navigation props
1719
queryKeyboardNavigation?: ReturnType<typeof useKeyboardNavigation>;
1820
mutationKeyboardNavigation?: ReturnType<typeof useKeyboardNavigation>;
@@ -27,6 +29,7 @@ export function ListView({
2729
selectedMutationIndex,
2830
onSelectQuery,
2931
onSelectMutation,
32+
onRemoveAllQueries,
3033
queryKeyboardNavigation,
3134
mutationKeyboardNavigation,
3235
}: ListViewProps) {
@@ -90,9 +93,17 @@ export function ListView({
9093
tabIndex={0}
9194
>
9295
<div className="p-4 border-b border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-700 flex-shrink-0">
93-
<h4 className="text-sm font-medium text-gray-900 dark:text-gray-100">
94-
{currentView === "queries" ? "Query List" : "Mutation List"}
95-
</h4>
96+
<div className="flex items-center justify-between">
97+
<h4 className="text-sm font-medium text-gray-900 dark:text-gray-100">
98+
{currentView === "queries" ? "Query List" : "Mutation List"}
99+
</h4>
100+
{currentView === "queries" && onRemoveAllQueries && (
101+
<RemoveAllQueriesButton
102+
disabled={queries.length === 0}
103+
onRemoveAll={onRemoveAllQueries}
104+
/>
105+
)}
106+
</div>
96107
</div>
97108

98109
<div className="flex-1 overflow-y-auto min-h-0 p-1">

src/components/query/QueryActions.tsx

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import type { QueryData } from "../../types/query";
22
import type { QueryAction } from "../../types/messages";
3-
import { formatQueryKeyShort } from "../../utils/formatters";
43
import { buttonVariants } from "../../lib/variants";
54

65
interface QueryActionsProps {
@@ -67,15 +66,7 @@ export function QueryActions({
6766
</button>
6867

6968
<button
70-
onClick={() => {
71-
if (
72-
confirm(
73-
`Are you sure you want to remove this query from cache?\n\nQuery: ${formatQueryKeyShort(selectedQuery.queryKey)}`,
74-
)
75-
) {
76-
handleAction("REMOVE");
77-
}
78-
}}
69+
onClick={() => handleAction("REMOVE")}
7970
disabled={shouldDisableButtons}
8071
className={buttonVariants({ variant: "pink" })}
8172
>

src/content/content.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,16 @@ window.addEventListener("message", (event) => {
7272
if (event.data.type === "QUERY_ACTION_RESULT") {
7373
sendToBackground(event.data);
7474
}
75+
76+
// Forward clear artificial states message to background script
77+
if (event.data.type === "CLEAR_ARTIFICIAL_STATES") {
78+
chrome.runtime.sendMessage(event.data).catch((error) => {
79+
console.warn(
80+
"TanStack Query DevTools: Failed to send CLEAR_ARTIFICIAL_STATES to background:",
81+
error,
82+
);
83+
});
84+
}
7585
});
7686

7787
// Inject the injected script into the page context
@@ -95,7 +105,7 @@ if (document.readyState === "loading") {
95105
// Listen for messages from DevTools and background script
96106
chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
97107
// Handle query actions from DevTools
98-
if (message.type === "QUERY_ACTION") {
108+
if (message.type === "QUERY_ACTION" || message.type === "BULK_QUERY_ACTION") {
99109
sendActionToInjected(message);
100110
sendResponse({ received: true });
101111
return true;

src/injected/injected.ts

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import type {
55
TanStackQueryEvent,
66
QueryActionMessage,
77
QueryActionResult,
8+
BulkQueryActionMessage,
9+
BulkQueryActionResult,
810
} from "../types/messages";
911
import type { QueryData, MutationData } from "../types/query";
1012

@@ -421,8 +423,67 @@ async function handleQueryAction(
421423
}
422424
}
423425

426+
// Bulk query action handlers
427+
async function handleBulkQueryAction(
428+
action: BulkQueryActionMessage,
429+
): Promise<BulkQueryActionResult> {
430+
const queryClient = getQueryClient();
431+
432+
if (!queryClient) {
433+
return {
434+
type: "BULK_QUERY_ACTION_RESULT",
435+
action: action.action,
436+
success: false,
437+
error: "QueryClient not found",
438+
};
439+
}
440+
441+
try {
442+
switch (action.action) {
443+
case "REMOVE_ALL_QUERIES": {
444+
// Get all queries from the cache
445+
const queryCache = queryClient.getQueryCache();
446+
const allQueries = queryCache.getAll();
447+
const queryCount = allQueries.length;
448+
449+
// Remove all queries
450+
queryClient.removeQueries();
451+
452+
// Also clear artificial states for this tab
453+
window.postMessage(
454+
{
455+
source: "tanstack-query-devtools-injected",
456+
type: "CLEAR_ARTIFICIAL_STATES",
457+
},
458+
"*",
459+
);
460+
461+
return {
462+
type: "BULK_QUERY_ACTION_RESULT",
463+
action: action.action,
464+
success: true,
465+
affectedCount: queryCount,
466+
};
467+
}
468+
469+
default:
470+
throw new Error(`Unknown bulk action: ${action.action}`);
471+
}
472+
} catch (error) {
473+
const errorMessage = error instanceof Error ? error.message : String(error);
474+
console.error("TanStack Query DevTools: Bulk action failed:", error);
475+
476+
return {
477+
type: "BULK_QUERY_ACTION_RESULT",
478+
action: action.action,
479+
success: false,
480+
error: errorMessage,
481+
};
482+
}
483+
}
484+
424485
// Send action result to content script
425-
function sendActionResult(result: QueryActionResult) {
486+
function sendActionResult(result: QueryActionResult | BulkQueryActionResult) {
426487
window.postMessage(
427488
{
428489
source: "tanstack-query-devtools-injected",
@@ -446,6 +507,14 @@ window.addEventListener("message", async (event) => {
446507
setTimeout(sendQueryDataUpdate, 100);
447508
}
448509

510+
if (event.data.type === "BULK_QUERY_ACTION") {
511+
const result = await handleBulkQueryAction(event.data);
512+
sendActionResult(result);
513+
514+
// Trigger query data update after bulk action
515+
setTimeout(sendQueryDataUpdate, 100);
516+
}
517+
449518
// Handle immediate update requests from DevTools
450519
if (event.data.type === "REQUEST_IMMEDIATE_UPDATE") {
451520
if (performEnhancedDetection()) {

src/popup/PopupApp.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@ function PopupApp() {
2424
sendMessage,
2525
} = usePopupConnection();
2626
const { handleQueryAction } = useUIState(sendMessage);
27+
28+
// Handle remove all queries action
29+
const handleRemoveAllQueries = () => {
30+
sendMessage({
31+
type: "BULK_QUERY_ACTION",
32+
action: "REMOVE_ALL_QUERIES",
33+
});
34+
};
2735
const {
2836
currentView,
2937
searchTerm,
@@ -94,6 +102,7 @@ function PopupApp() {
94102
onSelectMutation={setSelectedMutationIndex}
95103
queryKeyboardNavigation={queryKeyboardNavigation}
96104
mutationKeyboardNavigation={mutationKeyboardNavigation}
105+
onRemoveAllQueries={handleRemoveAllQueries}
97106
/>
98107
</div>
99108
) : (
@@ -116,7 +125,11 @@ function PopupApp() {
116125
? queries[selectedQueryIndex]
117126
: null
118127
}
119-
onAction={handleQueryAction}
128+
onAction={(action, queryHash, newState) => {
129+
if (action === "REMOVE") setSelectedQueryIndex(null);
130+
131+
handleQueryAction(action, queryHash, newState);
132+
}}
120133
artificialStates={artificialStates}
121134
/>
122135
) : (

src/types/messages.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ export type QueryAction = { queryHash: string } & (
1414
| { type: "SET_QUERY_DATA"; newData: unknown }
1515
);
1616

17+
// Bulk Query Action Types - For operations affecting all queries
18+
export type BulkQueryAction = { type: "REMOVE_ALL_QUERIES" };
19+
1720
// Base interface for action results
1821
interface BaseQueryActionResult {
1922
type: "QUERY_ACTION_RESULT";
@@ -36,6 +39,15 @@ export type QueryActionResult = BaseQueryActionResult &
3639
| { action: "SET_QUERY_DATA"; newData?: unknown }
3740
);
3841

42+
// Bulk action result type
43+
export interface BulkQueryActionResult {
44+
type: "BULK_QUERY_ACTION_RESULT";
45+
action: BulkQueryAction["type"];
46+
success: boolean;
47+
error?: string;
48+
affectedCount?: number;
49+
}
50+
3951
// TanStack Query Event Types - Discriminated unions
4052
export type TanStackQueryEvent = { type: "QEVENT" } & (
4153
| { subtype: "QUERY_CLIENT_DETECTED" }
@@ -63,8 +75,19 @@ export interface QueryActionMessage {
6375
newData?: unknown;
6476
}
6577

78+
// Bulk Query Action Message Type
79+
export interface BulkQueryActionMessage {
80+
type: "BULK_QUERY_ACTION";
81+
action: BulkQueryAction["type"];
82+
}
83+
6684
// Request Immediate Update Message Type
6785
export interface RequestImmediateUpdateMessage {
6886
type: "REQUEST_IMMEDIATE_UPDATE";
6987
preserveArtificialStates?: boolean;
7088
}
89+
90+
// Clear Artificial States Message Type
91+
export interface ClearArtificialStatesMessage {
92+
type: "CLEAR_ARTIFICIAL_STATES";
93+
}

0 commit comments

Comments
 (0)