| name | mongez-atomic-query-invalidation |
| description | How to invalidate cached queries (prefix and exact match), refetch on demand, seed from a server loader, and manage cache lifecycle (GC, destroy, stats).
TRIGGER when: code imports `invalidate`, `invalidateAll`, `invalidateBackground`, `invalidateBackgroundAll`, `refetchQuery`, `refetchMultipleQueries`, `refetchQueryBackground`, `refetchMultipleQueriesBackground`, `seedQuery`, `HydrateQueries`, `destroyQuery`, `clearCache`, `garbageCollect`, `limitCacheSize`, `getCacheStats`, `setupAutoGC`, `getQuery`, `getData`, or `isStale` from `@mongez/atomic-query`; user asks "how do I force a refetch after a mutation / invalidate a group of queries / seed cache from loader / configure GC"; typical import `import { queryAtom, invalidate } from "@mongez/atomic-query"`.
SKIP: pure cache-API reference (no invalidation framing) — use `mongez-atomic-query-cache`; defining the `useQuery` hook itself — use `mongez-atomic-query-basic-query` or `mongez-atomic-query-queries`; SSR boundary integration with framework loaders — use `mongez-atomic-query-ssr`; array helpers `push`/`remove`/`sort` — use `mongez-atomic-query-list-helpers`.
|
Cache invalidation and refetch patterns
When to use
Use this skill when:
- Someone wants to trigger a refetch after a mutation completes.
- Someone wants to invalidate a group of related queries by a shared key prefix.
- Someone wants only the exact-match entry invalidated.
- Someone asks about
invalidateBackground, invalidateAll, or invalidateBackgroundAll.
- Someone wants to manually refetch a specific query from outside a component.
- Someone asks about seeding the cache from a framework loader (
seedQuery, HydrateQueries).
- Someone asks about GC,
gcTime, destroyQuery, clearCache, getCacheStats, or setupAutoGC.
- Someone asks how query key matching works (segment-aware prefix matching).
How to use
invalidate — force a refetch
import { queryAtom } from "@mongez/atomic-query";
await queryAtom.invalidate({ queryKey: ["users"] });
await queryAtom.invalidate({ queryKey: ["users", 1], exact: true });
await queryAtom.invalidateAll();
Invalidation refetches in the background ("silent" mode) — the UI does not flash a loading state for data that is already present.
Background variants (fire-and-forget)
Use these when you do not need to await the refetch (e.g. in an analytics event handler or a non-critical side effect).
queryAtom.invalidateBackground({ queryKey: ["users"] });
queryAtom.invalidateBackground({ queryKey: ["users", 1], exact: true });
queryAtom.invalidateBackgroundAll();
Standalone exports are available for all four functions:
import { invalidate, invalidateAll, invalidateBackground, invalidateBackgroundAll } from "@mongez/atomic-query";
Common pattern: invalidate after mutation
const createPost = useMutation<Post, { title: string }>({
mutationFn: ({ title }, { signal }) =>
fetch("/api/posts", { method: "POST", body: JSON.stringify({ title }), signal })
.then(r => r.json()),
onSuccess: (post) => {
queryAtom.push(["posts"], post);
},
onSettled: () => {
queryAtom.invalidate({ queryKey: ["posts", "stats"] });
queryAtom.invalidate({ queryKey: ["dashboard"] });
},
});
Manual refetch outside React
Use refetchQuery / refetchMultipleQueries when you need to await the result:
import { refetchQuery, refetchMultipleQueries } from "@mongez/atomic-query";
await refetchQuery(["users"]);
await refetchMultipleQueries([["users"], ["posts"]]);
Fire-and-forget variants (from outside components):
queryAtom.refetchQueryBackground(["users"]);
queryAtom.refetchMultipleQueriesBackground([["users"], ["posts"]]);
How key matching works
Keys are serialised to canonical JSON with sorted object keys. Prefix matching is segment-aware: a prefix matches if the target key starts with the same sequence of complete JSON elements.
Invalidating ["users", 1] matches:
["users", 1] ✓ (exact)
["users", 1, "profile"] ✓ (extends the prefix)
["users", 1, { role: "admin" }] ✓
Does NOT match:
["users", 10] ✗ (10 ≠ 1 at that position)
["users", 100] ✗
["posts"] ✗
Object key order inside a key element does not affect matching:
["users", { role: "admin", active: true }]
["users", { active: true, role: "admin" }]
Seeding the cache from a server loader
seedQuery writes a pre-fetched value into the cache synchronously. A useQuery consumer that mounts afterwards will skip the on-mount refetch as long as the data is still within staleTime.
import { seedQuery } from "@mongez/atomic-query";
seedQuery({ queryKey: ["users"], data: usersFromServer });
seedQuery({ queryKey: ["users"], data: usersFromServer, freshFor: 60_000 });
<HydrateQueries> is the React wrapper (for Next.js App Router server components):
import { HydrateQueries } from "@mongez/atomic-query";
export default async function Page() {
const users = await db.users.findMany();
return (
<HydrateQueries entries={[{ queryKey: ["users"], data: users, freshFor: 60_000 }]}>
<UserListClient />
</HydrateQueries>
);
}
Cache lifecycle management
queryAtom.getQuery(["users"]);
queryAtom.getData(["users"]);
queryAtom.isStale(["users"], 60_000);
queryAtom.destroyQuery(["users"]);
queryAtom.clearCache();
queryAtom.garbageCollect(300_000);
queryAtom.limitCacheSize(50);
const stats = queryAtom.getCacheStats();
Auto-GC
Auto-GC starts automatically on the first useQuery call with these defaults:
- Interval: every 60 seconds.
- Evict entries unobserved for more than 5 minutes.
- Cap cache at 100 entries.
Override the defaults (call before or after the first useQuery):
const stopGC = queryAtom.setupAutoGC(
30_000,
120_000,
200,
);
stopGC();
Standalone export:
import { setupAutoGC, garbageCollect, limitCacheSize, getCacheStats, destroyQuery, clearCache } from "@mongez/atomic-query";
Key details / Pitfalls
-
invalidate refetches silently. Active consumers do not see isLoading: true during a background invalidation refetch — only isFetching: true. This preserves the current data on screen while the refresh runs.
-
refetchQuery throws if the key is not in the cache. This is intentional — it signals a programming error (refetching something that was never mounted). Use queryAtom.getQuery(key) to guard if the key may not exist.
-
Segment-aware matching is strict. ["users", 1] does NOT match ["users", 10] or ["users", 100]. The match requires each JSON element at every position to be identical, and the target key must start with (or equal) the full prefix array.
-
freshFor in seedQuery/HydrateQueries: sets staleTime on the seeded entry. A consumer's own staleTime option overrides this once the query is mounted. Use Infinity to completely suppress the on-mount refetch for seeded data.
-
destroyQuery vs clearing from GC: destroyQuery removes the entry immediately and aborts the in-flight fetch. GC removes entries that have had no observers for longer than gcTime. Use destroyQuery when you want instant removal (e.g. logout); let GC handle routine cleanup.
-
clearCache() aborts all in-flight fetches. Any components still mounted that own queries will re-enter the idle state. They will re-fetch on their next render cycle or when next observed.