Cache invalidation plugin for Spoosh - auto-invalidates related queries after mutations using wildcard patterns.
Documentation · Requirements: TypeScript >= 5.0 · Peer Dependencies: @spoosh/core
npm install @spoosh/plugin-invalidationTags are automatically generated from the API path:
useRead((api) => api("posts").GET());
// → tag: "posts"
useRead((api) => api("posts/:id").GET({ params: { id: 123 } }));
// → tag: "posts/123"
useRead((api) => api("posts/:id/comments").GET({ params: { id: 123 } }));
// → tag: "posts/123/comments"When a mutation succeeds, related queries are automatically invalidated using wildcard patterns:
const { trigger } = useWrite((api) => api("posts/:id/comments").POST());
await trigger({ params: { id: 123 }, body: { text: "Hello" } });
// Default behavior (autoInvalidate: true):
// Invalidates: ["posts", "posts/*"]
// ✓ Matches: "posts", "posts/123", "posts/123/comments", etc.import { Spoosh } from "@spoosh/core";
import { invalidationPlugin } from "@spoosh/plugin-invalidation";
const spoosh = new Spoosh<ApiSchema, Error>("/api").use([invalidationPlugin()]);
const { trigger } = useWrite((api) => api("posts").POST());
await trigger({ body: { title: "New Post" } });| Pattern | Matches | Does NOT Match |
|---|---|---|
"posts" |
"posts" (exact) |
"posts/1", "users" |
"posts/*" |
"posts/1", "posts/1/comments" |
"posts" (parent) |
["posts", "posts/*"] |
"posts" AND all children |
- |
// Exact match only
await trigger({
body: { title: "New Post" },
invalidate: "posts",
});
// Children only (not the parent)
await trigger({
body: { title: "New Post" },
invalidate: "posts/*",
});
// Parent AND all children
await trigger({
body: { title: "New Post" },
invalidate: ["posts", "posts/*"],
});
// Multiple patterns
await trigger({
body: { title: "New Post" },
invalidate: ["posts", "users/*", "dashboard"],
});
// Disable invalidation for this mutation
await trigger({
body: { title: "New Post" },
invalidate: false,
});
// Global refetch - triggers ALL queries to refetch
await trigger({
body: { title: "New Post" },
invalidate: "*",
});| Option | Type | Default | Description |
|---|---|---|---|
autoInvalidate |
boolean |
true |
Auto-generate invalidation patterns from path |
groups |
string[] |
[] |
Path prefixes that use deeper segment matching |
// Default: auto-invalidate using [firstSegment, firstSegment/*]
invalidationPlugin(); // same as { autoInvalidate: true }
// Disable auto-invalidation (manual only)
invalidationPlugin({ autoInvalidate: false });
// Groups: use deeper segment matching for grouped endpoints
invalidationPlugin({
groups: ["admin", "api/v1"],
});Use groups when you have path prefixes that should be treated as a namespace:
invalidationPlugin({
groups: ["admin", "api/v1"],
});
// Without groups:
// POST admin/posts → invalidates ["admin", "admin/*"]
// With groups: ["admin"]:
// POST admin/posts → invalidates ["admin/posts", "admin/posts/*"]
// POST admin/users → invalidates ["admin/users", "admin/users/*"]
// POST admin → invalidates ["admin", "admin/*"]
// With groups: ["api/v1"]:
// POST api/v1/users → invalidates ["api/v1/users", "api/v1/users/*"]| Option | Type | Description |
|---|---|---|
invalidate |
string | string[] | false | "*" |
Pattern(s) to invalidate, false to disable, or "*" for global refetch |
When autoInvalidate: true (default) and no invalidate option is provided:
// POST /posts/123/comments
// → Invalidates: ["posts", "posts/*"]
// The first path segment is used to generate patterns:
// - "posts" - exact match for the root
// - "posts/*" - all children under postsThe plugin exposes invalidate for manual cache invalidation:
import { create } from "@spoosh/react";
const { useRead, invalidate } = create(spoosh);
// Single pattern
invalidate("posts");
// Multiple patterns
invalidate(["posts", "users/*"]);
// Global refetch
invalidate("*");
// Useful for external events
socket.on("posts-updated", () => {
invalidate(["posts", "posts/*"]);
});
socket.on("full-sync", () => {
invalidate("*");
});For scenarios like logout, combine with clearCache from @spoosh/plugin-cache:
const { trigger } = useWrite((api) => api("auth/logout").POST());
await trigger({
clearCache: true, // Clear all cached data
invalidate: "*", // Trigger all queries to refetch
});