Documentation
API Reference
Every entry point, function, hook, and option in @tanstack-tools/query-tags — documented with copy-pasteable examples.
Root — shared primitives
Isomorphic@tanstack-tools/query-tagsSafe to import anywhere — no Node.js or browser APIs. Provides tag definition, normalization, introspection helpers, and type augmentations.
defineTags
Builds a typed, callable tag tree from a factory object. Each leaf factory is stamped with its path for group matching; each branch node is callable as a TagGroup that expands to all descendant zero-arg leaves.
import { defineTags } from "@tanstack-tools/query-tags";
const appTags = defineTags({
todos: {
list: () => ["todos"],
byId: (id: string) => ["todo", id],
},
users: {
list: () => ["users"],
byId: (id: string) => ["user", id],
},
});
// Leaf tags
appTags.todos.list() // ["todos"]
appTags.todos.byId("abc") // ["todo", "abc"]
// Group — expands all zero-arg leaves when passed to invalidateTags
appTags.todos() // TagGroup { leaves: [["todos"]] }
appTags() // TagGroup { leaves: [["todos"], ["users"]] }as const — it widens leaf return types to readonly arrays which are not assignable to TagValue. Define inline or use a plain variable.withTags
Merges tags into query options, setting meta.tags and deriving a stable queryKey from the tag values when no explicit key is provided. This is the canonical zero-boilerplate form; the meta.tags form is fully equivalent.
import { withTags } from "@tanstack-tools/query-tags";
import { useQuery } from "@tanstack/react-query";
// Form 1 — wrap existing options (canonical)
const query = useQuery(
withTags({ queryFn: fetchTodos }, [appTags.todos.list()]),
);
// → { queryKey: ['["todos"]'], meta: { tags: [["todos"]] }, queryFn: fetchTodos }
// Form 2 — meta.tags directly (equivalent, no helper)
const query2 = useQuery({
queryKey: ["todos"],
queryFn: fetchTodos,
meta: { tags: [appTags.todos.list()] },
});Introspection helpers
Inspect the QueryClient cache without firing any invalidation. Useful for debugging and test assertions.
import {
matchTags,
findQueriesByTag,
listAllTags,
EVERY_INVALIDATION_TAG,
} from "@tanstack-tools/query-tags";
// Which queries would be invalidated by these tags?
const hits = matchTags({ queryClient, tags: [appTags.todos.list()] });
// → [{ queryKey: ["todos"], queryHash: '["todos"]' }, …]
// Queries tagged with a single specific tag
const queries = findQueriesByTag({ queryClient, tag: appTags.todos.list() });
// All tags currently registered in the cache
const allTags = listAllTags({ queryClient });
// → ['"__every_invalidation__"', '["todos"]', '["users"]']
// Tag that matches on every invalidation event
meta: { tags: [EVERY_INVALIDATION_TAG] }| Name | Type | Default | Description |
|---|---|---|---|
| matchTags | ({ queryClient, tags }) => Array<{ queryKey, queryHash }> | — | Returns every cached query that would be invalidated by the given tags. |
| findQueriesByTag | ({ queryClient, tag }) => Array<{ queryKey, queryHash }> | — | Returns queries tagged with a single specific tag. |
| listAllTags | ({ queryClient }) => string[] | — | Enumerates all distinct normalized tags across all cached queries. |
| EVERY_INVALIDATION_TAG | string — "__every_invalidation__" | — | Attach to a query's meta.tags to refetch it on every invalidation event. |
Server — tag invalidation system
Server-only@tanstack-tools/query-tags/serverThe single entry point for all server-side invalidation: SSE streaming, control API, batching, scopes, and broadcast. Uses node:events and keyv — do not import in client bundles.
import { createTagInvalidationSystem } from "@tanstack-tools/query-tags/server";
// Minimal — defaults are sensible
const tagInvalidation = createTagInvalidationSystem();
// Full options
const tagInvalidation = createTagInvalidationSystem<MyContext>({
batchWindow: 50, // ms — merge rapid-fire events (default: 50)
heartbeatInterval: 30000,// ms — keep proxies alive (default: 30 000)
eventLogSize: 1000, // ring-buffer per scope (default: 1 000)
maxConnections: 10000, // global SSE cap (default: 10 000)
maxConnectionsPerScope: 1000, // per-scope SSE cap (default: 1 000)
maxScopeKeyLength: 128, // max scope key chars (default: 128)
broadcast: myBroadcast, // multi-instance adapter (default: in-process)
resolveScope: (ctx) => ({ kind: "user", id: ctx.userId }),
rateLimit: false, // disable rate limiting (or pass tiers)
resolveClientIp: (req) => req.headers.get("CF-Connecting-IP") ?? "",
onDevtoolsEvent: (event) => devtoolsSink.emit(event),
});
// Invalidate from a server action
await tagInvalidation.invalidateTags({ tags: [appTags.todos.list()] });createTagInvalidationSystem options
| Name | Type | Default | Description |
|---|---|---|---|
| batchWindow | number | 50 | Merge invalidations within this window (ms) into one SSE event. Set 0 to disable. |
| heartbeatInterval | number | 30000 | SSE heartbeat comment interval (ms) to prevent proxy idle timeouts. Set 0 to disable. |
| eventLogSize | number | 1000 | Per-scope ring-buffer size for Last-Event-ID replay on reconnect. |
| maxConnections | number | 10000 | Total SSE connection cap across all scopes. Set 0 to disable. |
| maxConnectionsPerScope | number | 1000 | Per-scope SSE connection cap. Set 0 to disable. |
| maxScopeKeyLength | number | 128 | Maximum character length for scope key strings. |
| broadcast | BroadcastAdapter | createLocalBroadcast() | Multi-instance broadcast adapter. Pass createRedisBroadcast() or createWebBroadcast() for multi-process deployments. |
| resolveScope | (context) => InvalidationScope | () => { kind: 'global' } | Derive per-request scope from server context. Used by oRPC/tRPC middleware. |
| rateLimit | false | RateLimiterTier | RateLimiterTier[] | tiered: 30/min, 300/10min, 1000/hr | Rate limiting for control API POST endpoints. Pass false to disable. |
| resolveClientIp | (request: Request) => string | X-Forwarded-For header | Extract client IP for rate limiting. |
| onDevtoolsEvent | (event: DevtoolsEvent) => void | undefined | Receive invalidation and connection events for the DevTools adapter. |
Return value
| Name | Type | Default | Description |
|---|---|---|---|
| invalidateTags | ({ tags, context?, scope? }) => Promise<InvalidationMessage | null> | — | Fire tag invalidation. Resolves after the batch window with the emitted message, or null if no clients are connected. |
| createAPIHandler | ({ basePath?, authenticate? }?) => (req: Request) => Promise<Response> | — | Returns a fetch-standard handler for the SSE + control API. Used internally by all framework adapters. |
| getStatus | () => InvalidationStatus | — | Current system state: paused, disabledTags, connectedClients, recentInvalidations. |
| getInvalidationHistory | () => InvalidationHistoryEntry[] | — | Rolling log of all invalidation events. |
| disableTags / enableTags | (tags: string[]) => void | — | Suppress or re-enable specific tags without restarting the system. |
| pauseAll / resumeAll | () => void | — | Pause or resume the entire invalidation pipeline. |
| dispose | () => void | — | Close all SSE connections and release resources. |
React — provider and hooks
Client-only@tanstack-tools/query-tags/reactReact context provider and all hooks. The provider opens the SSE connection and drives cache invalidation. Hooks provide access to status, history, and control.
TagInvalidationProvider
import { TagInvalidationProvider } from "@tanstack-tools/query-tags/react";
import { createInvalidatorClient } from "@tanstack-tools/query-tags/client";
const client = createInvalidatorClient();
// Minimal (required props only)
<TagInvalidationProvider queryClient={queryClient} router={router}>
<App />
</TagInvalidationProvider>
// With all options
<TagInvalidationProvider
queryClient={queryClient}
router={router}
client={client} // enables status/history/control hooks
scope={{ kind: "user", id: userId }}
eventSourceUrl="/api/invalidator/sse"
onDisconnect="invalidate-all" // or "silent" | { strategy, tags? }
onError={(err, ctx) => console.error(ctx, err)}
dedupeWindow={50} // ms client-side dedup (default: 50)
conditionalInvalidation={true} // skip fresh queries (default: false)
tabLeader={true} // WS1 tab leader election
>
<App />
</TagInvalidationProvider>| Name | Type | Default | Description |
|---|---|---|---|
| queryClient | QueryClient | — | Required. The TanStack QueryClient to invalidate against. |
| router | RouterLike | — | Required. The TanStack Router instance. Used to invalidate loader data. |
| client | InvalidatorClient | undefined | Enables useInvalidationStatus, useInvalidationHistory, and useInvalidationControl hooks. |
| scope | InvalidationScope | { kind: 'global' } | Which SSE scope this client subscribes to. |
| eventSourceUrl | string | /api/invalidator/sse | Override the SSE endpoint URL. |
| onDisconnect | "invalidate-all" | "silent" | object | "invalidate-all" | What to do on SSE disconnect. "invalidate-all" refetches everything; "silent" does nothing. |
| dedupeWindow | number | 50 | Client-side dedup window (ms). SSE messages within the window are merged before processing. |
| conditionalInvalidation | boolean | ConditionalInvalidationOptions | false | Skip refetching queries whose cached data is fresher than the invalidation timestamp. |
| tabLeader | boolean | TabLeaderOptions | false | Enable tab leader election (WS1). Only one tab opens SSE; others receive via BroadcastChannel. |
Hooks
import {
useTagInvalidationRuntime,
useTagInvalidationDebugState,
useInvalidationClient,
useInvalidationStatus,
useInvalidationHistory,
useInvalidationControl,
useInvalidationActions,
useOptimisticInvalidation,
useOptimisticMutation,
} from "@tanstack-tools/query-tags/react";
// Debug state — connected, paused, event log
const { isConnected, isPaused, messages } = useTagInvalidationDebugState();
// Control panel (requires client prop on provider)
const { pauseAll, resumeAll, disableTags, enableTags, isPausing } =
useInvalidationControl();
// Polling queries (require client prop)
const { data: status } = useInvalidationStatus({ refetchInterval: 5000 });
const { data: history } = useInvalidationHistory();
// Optimistic update with rollback
const [optimisticTodos, addOptimistic] = useOptimisticInvalidation(todos);| Name | Type | Default | Description |
|---|---|---|---|
| useTagInvalidationRuntime | () => runtime | — | Raw runtime — processMessage, getDebugState, subscribe. For advanced/devtools use. |
| useTagInvalidationDebugState | () => DebugState | — | Reactive debug state: isConnected, isPaused, messages, eventCount. |
| useHasInvalidationClient | () => boolean | — | Whether an InvalidatorClient was provided to the nearest provider. |
| useInvalidationClient | () => InvalidatorClient | — | Returns the client; throws if none was provided. |
| useInvalidationStatus | ({ refetchInterval? }) => QueryResult<InvalidationStatus> | — | Polls server status. Requires client on provider. |
| useInvalidationHistory | () => QueryResult<InvalidationHistoryEntry[]> | — | Fetches history; auto-refetches on every invalidation via EVERY_INVALIDATION_TAG. Requires client. |
| useInvalidationControl | () => ControlActions | — | Memoized actions: disableTags, enableTags, pauseAll, resumeAll, plus isPausing/isResuming flags. |
| useOptimisticInvalidation | (state, reducer?) => [optimisticState, dispatch] | — | Like useOptimistic — shows temporary state while a server action is in-flight. |
Client — TanStack Start server functions
Isomorphic@tanstack-tools/query-tags/clientControl API client backed by TanStack Start server functions. Use this in TanStack Start apps. For plain SPAs with a separate backend, use /http-client instead.
import { createInvalidatorClient } from "@tanstack-tools/query-tags/client";
// TanStack Start — uses server functions under the hood
const client = createInvalidatorClient({ basePath: "/api/invalidator" });
// Attach to provider
<TagInvalidationProvider client={client} queryClient={queryClient} router={router}>
<App />
</TagInvalidationProvider>| Name | Type | Default | Description |
|---|---|---|---|
| createInvalidatorClient | ({ basePath? }) => InvalidatorClient | — | Creates a typed client using TanStack Start server functions as the transport. basePath defaults to /api/invalidator. |
HTTP Client — framework-agnostic
Isomorphic@tanstack-tools/query-tags/http-clientFetch-based control API client. Use in SPA + separate backend setups (Express, Hono, Fastify, etc.). Also available as @tanstack-tools/query-tags/standalone.
import { createHTTPInvalidatorClient } from "@tanstack-tools/query-tags/http-client";
// alias: @tanstack-tools/query-tags/standalone
const client = createHTTPInvalidatorClient({
basePath: "/api/invalidator", // default
fetch: customFetch, // optional
headers: { Authorization: "Bearer …" }, // or () => ({ … })
});| Name | Type | Default | Description |
|---|---|---|---|
| basePath | string | "/api/invalidator" | Base URL for all control API requests. |
| fetch | typeof fetch | globalThis.fetch | Custom fetch implementation (e.g., for test mocking or cross-fetch). |
| headers | Record<string, string> | (() => Record<string, string>) | undefined | Extra headers added to every request. Useful for auth tokens. |
Framework adapters
Server-only@tanstack-tools/query-tags/adapters/*Zero-dependency thin wrappers over createAPIHandler(). All adapters share the same CreateInvalidatorHandlerOptions interface.
// Hono
import { createHonoHandler } from "@tanstack-tools/query-tags/adapters/hono";
app.all("/api/invalidator/*", createHonoHandler(system));
// Express
import { createExpressHandler } from "@tanstack-tools/query-tags/adapters/express";
app.all("/api/invalidator/*", createExpressHandler(system));
// Fastify
import { createFastifyHandler } from "@tanstack-tools/query-tags/adapters/fastify";
fastify.all("/api/invalidator/*", createFastifyHandler(system));
// Elysia
import { createElysiaHandler } from "@tanstack-tools/query-tags/adapters/elysia";
app.all("/api/invalidator/*", createElysiaHandler(system));
// Koa
import { createKoaHandler } from "@tanstack-tools/query-tags/adapters/koa";
router.all("/api/invalidator/(.*)", createKoaHandler(system));
// Node.js HTTP / https.createServer
import { createNodeHandler } from "@tanstack-tools/query-tags/adapters/node";
http.createServer(createNodeHandler(system));
// Fetch-standard (Bun, Deno, Cloudflare Workers)
import { createFetchHandler } from "@tanstack-tools/query-tags/adapters/fetch";
export default { fetch: createFetchHandler(system) };
// Shared options (all adapters accept these)
const handler = createHonoHandler(system, {
basePath: "/api/invalidator", // strip prefix from incoming URLs
authenticate: async (req) => {
const token = req.headers.get("Authorization");
return token === "Bearer secret"; // false → 401, Response → forwarded
},
});/adapters/honocreateHonoHandler(system, options?)
Structural duck typing — zero Hono peer dep
/adapters/expresscreateExpressHandler(system, options?)
Node.js IncomingMessage / ServerResponse bridge
/adapters/fastifycreateFastifyHandler(system, options?)
Fastify request / reply bridge
/adapters/elysiacreateElysiaHandler(system, options?)
Bun / web-standard fetch bridge
/adapters/koacreateKoaHandler(system, options?)
Koa context bridge
/adapters/nodecreateNodeHandler(system, options?)
Plain Node.js http.createServer
/adapters/fetchcreateFetchHandler(system, options?)
Fetch-standard — Bun, Deno, Workers
CreateInvalidatorHandlerOptions (shared by all adapters)
| Name | Type | Default | Description |
|---|---|---|---|
| basePath | string | "/api/invalidator" | Strip this prefix from incoming request URLs before routing. |
| authenticate | (request: Request) => boolean | Response | Promise<boolean | Response> | undefined | Return true to allow, false for 401, or a custom Response to forward. |
Broadcast adapters — multi-instance
Server-only@tanstack-tools/query-tags/redis · /web-broadcastBy default the server uses an in-process EventEmitter. For multi-instance deployments, pass a broadcast adapter so SSE events reach clients connected to different server processes.
Redis — @tanstack-tools/query-tags/redis
import { createRedisBroadcast } from "@tanstack-tools/query-tags/redis";
import Redis from "ioredis";
const pub = new Redis();
const sub = new Redis();
const broadcast = createRedisBroadcast({
channelPrefix: "query-tags:", // default
publish: (channel, msg) => pub.publish(channel, msg),
subscribe: (channel, handler) => {
sub.subscribe(channel);
sub.on("message", (ch, msg) => ch === channel && handler(ch, msg));
return () => sub.unsubscribe(channel);
},
});
const system = createTagInvalidationSystem({ broadcast });Web Broadcast — @tanstack-tools/query-tags/web-broadcast
In-process adapter using Map + Set. Works on all runtimes without node:events. Equivalent to the default createLocalBroadcast() (also re-exported from /server) but safe on edge and non-Node runtimes.
import { createWebBroadcast } from "@tanstack-tools/query-tags/web-broadcast";
// also re-exported from @tanstack-tools/query-tags/server
// In-process — works on all JS runtimes (Node, Bun, Deno, Workers)
// Equivalent to createLocalBroadcast() but without node:events dependency
const broadcast = createWebBroadcast();
const system = createTagInvalidationSystem({ broadcast });/broadcast re-exports the BroadcastAdapter interface if you need to implement your own adapter (e.g., NATS, Kafka).oRPC middleware
Server-only@tanstack-tools/query-tags/orpcMiddleware adapter for oRPC procedures. Injects context.invalidateTags into every procedure and automatically resolves scope from request context.
import { createTagsMiddleware } from "@tanstack-tools/query-tags/orpc";
const tagMiddleware = createTagsMiddleware({ system: tagInvalidation });
// Attach to any oRPC procedure
export const updateTodo = base
.use(tagMiddleware)
.handler(async ({ input, context }) => {
const todo = await db.todos.update(input);
context.invalidateTags([appTags.todos.byId(input.id)]);
return todo;
});tRPC middleware
Server-only@tanstack-tools/query-tags/trpcMiddleware adapter for tRPC procedures. Injects ctx.invalidateTags into every procedure.
import { createTagsMiddleware } from "@tanstack-tools/query-tags/trpc";
const withTags = createTagsMiddleware({ system: tagInvalidation });
export const updateTodo = t.procedure
.use(withTags)
.mutation(async ({ input, ctx }) => {
const todo = await db.todos.update(input);
ctx.invalidateTags([appTags.todos.byId(input.id)]);
return todo;
});Testing utilities
Test-only@tanstack-tools/query-tags/testingFour helpers covering the full test surface: server assertions, client mocks, provider wrappers, and invalidation simulation. Requires vi.fn from Vitest.
import { vi } from "vitest";
import {
createMockInvalidationSystem,
createMockInvalidatorClient,
createTestProvider,
simulateInvalidation,
} from "@tanstack-tools/query-tags/testing";
// 1. Server-side assertions
const system = createMockInvalidationSystem();
await system.invalidateTags({ tags: [appTags.todos.list()] });
system.expectTagsInvalidated(['"todos"']); // normalized string
system.expectTagsNotInvalidated(['"users"']);
system.getInvalidationCount('"todos"'); // → 1
system.reset();
// 2. Mock client for provider
const client = createMockInvalidatorClient({ vi });
client.mocks.getStatus.mockResolvedValue({ paused: true, … });
// 3. Test provider wrapper
const { TestProvider, queryClient, runtime } = createTestProvider({ client });
render(<MyComponent />, { wrapper: TestProvider });
// 4. Trigger invalidation without SSE
await simulateInvalidation(runtime, {
tags: [appTags.todos.list()],
scopeKey: "global",
});| Name | Type | Default | Description |
|---|---|---|---|
| createMockInvalidationSystem | () => MockSystem | — | In-memory mock with expectTagsInvalidated(), expectTagsNotInvalidated(), getInvalidationCount(), and reset(). |
| createMockInvalidatorClient | ({ vi? }) => MockClient | — | Mock client where every method is a vi.fn() spy with sensible defaults. Exposes .mocks and .reset(). |
| createTestProvider | ({ client?, scope? }) => { TestProvider, queryClient, router, runtime } | — | Pre-configured provider wrapper for @testing-library/react render(). |
| simulateInvalidation | (runtime, { tags, scopeKey?, groupPaths? }) => Promise<void> | — | Feeds an InvalidationMessage directly to the runtime without an SSE connection. |
DevTools
Client-only@tanstack-tools/query-tags/devtoolsFull observability panel: SSE event log, tag tree, query matching, connection health. Integrates as a TanStack Query DevTools plugin or runs standalone.
import {
QueryTagsDevtoolsPanel,
QueryTagsDevtools,
queryTagsPlugin,
useDevtoolsStore,
createDevtoolsAdapter,
} from "@tanstack-tools/query-tags/devtools";
// As a TanStack Query DevTools plugin
<TanStackQueryDevtools plugins={[queryTagsPlugin]} />
// As a standalone floating widget
<QueryTagsDevtools position="bottom-right" initialOpen={false} />
// As an embedded panel
<QueryTagsDevtoolsPanel theme="dark" />
// Access store state programmatically
const { debugState, setActiveTab, setTheme } = useDevtoolsStore();| Name | Type | Default | Description |
|---|---|---|---|
| queryTagsPlugin | TanStackDevtoolsPlugin | — | Drop-in plugin for <TanStackQueryDevtools plugins={[queryTagsPlugin]} />. |
| QueryTagsDevtools | React component | — | Floating draggable widget. Props: position ('bottom-right' default), initialOpen (false default). |
| QueryTagsDevtoolsPanel | React component | — | Embedded panel for custom layouts. Props: theme ('light' | 'dark'). |
| useDevtoolsStore | () => DevtoolsStore | — | Access active tab, selected tag, search query, and theme state programmatically. |
| createDevtoolsAdapter | () => EventClientAdapter | — | Adapter that bridges the server onDevtoolsEvent callback to the TanStack DevTools event bus. |
Core types
| Name | Type | Default | Description |
|---|---|---|---|
| InvalidationScope | { kind: "global" } | { kind: "user"; id: string } | { kind: "tenant"; id: string } | { kind: "custom"; key: string } | — | Discriminated union for SSE channel routing. Global is the default. |
| TagInput | TagValue | TagGroup | — | Anything you can pass to invalidateTags() or meta.tags. TagGroups are expanded to their leaf values. |
| TagValue | string | number | boolean | null | TagValue[] | { [key: string]: TagValue } | — | Any JSON-serializable value. Normalized to a stable string for matching. |
| TagGroup | { [TAG_GROUP]: true; leaves: TagValue[]; path: string } | — | Returned by calling a branch node in the defineTags tree, e.g. appTags.todos(). |
| InvalidatorClient | interface | — | Shared interface implemented by both createInvalidatorClient (/client) and createHTTPInvalidatorClient (/http-client). Methods: getStatus, getHistory, disableTags, enableTags, pause, resume, sseUrl. |
| BroadcastAdapter | interface | — | Interface for custom multi-instance broadcast adapters. Methods: publish(scopeKey, message, eventId), subscribe(scopeKey, handler) → () => void. |
| EVERY_INVALIDATION_TAG | "__every_invalidation__" | — | Constant. Any query with this tag in meta.tags refetches on every invalidation event. |
Entry point summary
| Import path | Environment | Main exports |
|---|---|---|
| @tanstack-tools/query-tags | Universal | defineTags, withTags, matchTags, findQueriesByTag, listAllTags, EVERY_INVALIDATION_TAG |
| .../server | Server-only | createTagInvalidationSystem, createLocalBroadcast, createWebBroadcast |
| .../react | Client-only | TagInvalidationProvider, createTagInvalidationProvider, useTagInvalidationDebugState, useInvalidationControl, useInvalidationStatus, useInvalidationHistory, useOptimisticInvalidation |
| .../client | Isomorphic | createInvalidatorClient (TanStack Start server fns) |
| .../http-client | Isomorphic | createHTTPInvalidatorClient (fetch-based) |
| .../standalone | Isomorphic | alias for /http-client |
| .../adapters/hono | Server-only | createHonoHandler |
| .../adapters/express | Server-only | createExpressHandler |
| .../adapters/fastify | Server-only | createFastifyHandler |
| .../adapters/elysia | Server-only | createElysiaHandler |
| .../adapters/koa | Server-only | createKoaHandler |
| .../adapters/node | Server-only | createNodeHandler |
| .../adapters/fetch | Server-only | createFetchHandler |
| .../redis | Server-only | createRedisBroadcast |
| .../web-broadcast | Universal | createWebBroadcast |
| .../broadcast | Server-only | BroadcastAdapter interface, createLocalBroadcast |
| .../orpc | Server-only | createTagsMiddleware (oRPC) |
| .../trpc | Server-only | createTagsMiddleware (tRPC) |
| .../testing | Test-only | createMockInvalidationSystem, createMockInvalidatorClient, createTestProvider, simulateInvalidation |
| .../devtools | Client-only | queryTagsPlugin, QueryTagsDevtools, QueryTagsDevtoolsPanel, useDevtoolsStore, createDevtoolsAdapter |