@spoosh/plugin-invalidation 0.5.2 → 0.5.3

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.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Cache invalidation plugin for Spoosh - auto-invalidates related queries after mutations.
4
4
 
5
- **[Documentation](https://spoosh.dev/docs/plugins/invalidation)** · **Requirements:** TypeScript >= 5.0 · **Peer Dependencies:** `@spoosh/core`
5
+ **[Documentation](https://spoosh.dev/react/docs/plugins/invalidation)** · **Requirements:** TypeScript >= 5.0 · **Peer Dependencies:** `@spoosh/core`
6
6
 
7
7
  ## Installation
8
8
 
@@ -81,7 +81,13 @@ await trigger({
81
81
  invalidate: "none", // No invalidation
82
82
  });
83
83
 
84
- // Tags only (array without mode keyword)
84
+ // Single tag (string)
85
+ await trigger({
86
+ body: { title: "New Post" },
87
+ invalidate: "posts", // Invalidate only "posts" tag
88
+ });
89
+
90
+ // Multiple tags (array without mode keyword)
85
91
  await trigger({
86
92
  body: { title: "New Post" },
87
93
  invalidate: ["posts", "users", "custom-tag"],
@@ -106,6 +112,18 @@ await trigger({
106
112
  invalidate: ["dashboard", "stats", "all"],
107
113
  // → 'all' mode + explicit tags (mode can be anywhere)
108
114
  });
115
+
116
+ // Wildcard - global refetch
117
+ await trigger({
118
+ body: { title: "New Post" },
119
+ invalidate: "*", // Triggers ALL queries to refetch
120
+ });
121
+
122
+ // Combined with clearCache (from @spoosh/plugin-cache)
123
+ await trigger({
124
+ clearCache: true, // Clear all cached data
125
+ invalidate: "*", // Then refetch all queries
126
+ });
109
127
  ```
110
128
 
111
129
  ## Options
@@ -118,9 +136,9 @@ await trigger({
118
136
 
119
137
  ### Per-Request Options
120
138
 
121
- | Option | Type | Description |
122
- | ------------ | --------------------------------------- | ------------------------------------------------------------------------------------------------------- |
123
- | `invalidate` | `"all" \| "self" \| "none" \| string[]` | Mode only (string), tags only (array), or mode + tags (array with 'all'/'self' keyword at any position) |
139
+ | Option | Type | Description |
140
+ | ------------ | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
141
+ | `invalidate` | `"all" \| "self" \| "none" \| "*" \| string \| string[]` | Mode (`"all"`, `"self"`, `"none"`), wildcard (`"*"` for global refetch), single tag, or array of tags with optional mode keyword |
124
142
 
125
143
  ### Invalidation Modes
126
144
 
@@ -129,6 +147,25 @@ await trigger({
129
147
  | `"all"` | Invalidate all tags from path hierarchy | `users/123/posts` → `users`, `users/123`, `users/123/posts` |
130
148
  | `"self"` | Only invalidate the exact endpoint tag | `users/123/posts` → `users/123/posts` |
131
149
  | `"none"` | Disable auto-invalidation (manual only) | No automatic invalidation |
150
+ | `"*"` | Global refetch - triggers all queries | All active queries refetch |
151
+
152
+ ### Understanding `"all"` vs `"*"`
153
+
154
+ These two options serve different purposes:
155
+
156
+ - **`"all"`** - Invalidates all tags **from the current endpoint's path hierarchy**. If you're mutating `users/123/posts`, it invalidates `["users", "users/123", "users/123/posts"]`. It's scoped to the mutation's path.
157
+
158
+ - **`"*"`** - Triggers a **global refetch of every active query** in your app, regardless of tags. Use this sparingly for scenarios like "user logged out" or "full data sync from server".
159
+
160
+ ```typescript
161
+ // "all" - scoped to this mutation's path hierarchy
162
+ await trigger({ invalidate: "all" });
163
+ // If path is users/123/posts → invalidates: users, users/123, users/123/posts
164
+
165
+ // "*" - refetches ALL queries in the entire app
166
+ await trigger({ invalidate: "*" });
167
+ // Every active useRead/injectRead will refetch
168
+ ```
132
169
 
133
170
  ## Instance API
134
171
 
@@ -145,12 +182,38 @@ invalidate(["users", "posts"]);
145
182
  // Invalidate with single string
146
183
  invalidate("users");
147
184
 
185
+ // Global refetch - triggers ALL queries to refetch
186
+ invalidate("*");
187
+
148
188
  // Useful for external events like WebSocket messages
149
189
  socket.on("data-changed", (tags) => {
150
190
  invalidate(tags);
151
191
  });
192
+
193
+ // WebSocket: trigger global refetch
194
+ socket.on("full-sync", () => {
195
+ invalidate("*");
196
+ });
197
+ ```
198
+
199
+ | Method | Description |
200
+ | ------------ | ---------------------------------------------------------------------- |
201
+ | `invalidate` | Manually invalidate cache entries by tags, or use `"*"` to refetch all |
202
+
203
+ ## Combining with Cache Plugin
204
+
205
+ For scenarios like logout or user switching, combine `invalidate: "*"` with `clearCache` from `@spoosh/plugin-cache`:
206
+
207
+ ```typescript
208
+ const { trigger } = useWrite((api) => api("auth/logout").POST);
209
+
210
+ // Clear cache + trigger all queries to refetch
211
+ await trigger({
212
+ clearCache: true, // From cache plugin: clear all cached data
213
+ invalidate: "*", // From invalidation plugin: trigger all queries to refetch
214
+ });
152
215
  ```
153
216
 
154
- | Method | Description |
155
- | ------------ | ----------------------------------------- |
156
- | `invalidate` | Manually invalidate cache entries by tags |
217
+ This ensures both:
218
+ 1. All cached data is cleared (no stale data from previous session)
219
+ 2. All active queries refetch with fresh data
package/dist/index.d.mts CHANGED
@@ -15,7 +15,7 @@ type ReadPaths<TSchema> = {
15
15
  * - Otherwise, it's tags only with mode defaulting to 'none'
16
16
  * - 'none' keyword should NOT be used in arrays (use string 'none' instead)
17
17
  */
18
- type InvalidateOption<TSchema = unknown> = InvalidationMode | (ReadPaths<TSchema> | "all" | "self" | (string & {}))[];
18
+ type InvalidateOption<TSchema = unknown> = InvalidationMode | "*" | ReadPaths<TSchema> | (ReadPaths<TSchema> | "all" | "self" | (string & {}))[];
19
19
  interface InvalidationPluginConfig {
20
20
  /**
21
21
  * Default invalidation mode when invalidate option is not specified
@@ -32,13 +32,12 @@ type InvalidationInfiniteReadOptions = object;
32
32
  type InvalidationReadResult = object;
33
33
  type InvalidationWriteResult = object;
34
34
  /**
35
- * Manual invalidation - tags only
35
+ * Manual invalidation - tags only, or "*" for global refetch
36
36
  */
37
37
  type InvalidateFn<TSchema> = {
38
- (tag: ReadPaths<TSchema>): void;
39
- (tags: ReadPaths<TSchema>[]): void;
40
- (tag: string): void;
41
- (tags: string[]): void;
38
+ (tag: "*"): void;
39
+ (tag: ReadPaths<TSchema> | (string & {})): void;
40
+ (tags: (ReadPaths<TSchema> | (string & {}))[]): void;
42
41
  };
43
42
  interface InvalidationInstanceApi {
44
43
  /** Manually invalidate cache entries by tags. Useful for external events like WebSocket messages. */
@@ -85,12 +84,20 @@ declare module "@spoosh/core" {
85
84
  * });
86
85
  *
87
86
  * trigger({
88
- * invalidate: ["posts", "users"], // Tags only
87
+ * invalidate: "posts", // Single tag
88
+ * });
89
+ *
90
+ * trigger({
91
+ * invalidate: ["posts", "users"], // Multiple tags
89
92
  * });
90
93
  *
91
94
  * trigger({
92
95
  * invalidate: ["all", "posts", "custom-tag"], // Mode + tags
93
96
  * });
97
+ *
98
+ * trigger({
99
+ * invalidate: "*", // Global refetch - triggers all queries to refetch
100
+ * });
94
101
  * ```
95
102
  */
96
103
  declare function invalidationPlugin(config?: InvalidationPluginConfig): SpooshPlugin<{
package/dist/index.d.ts CHANGED
@@ -15,7 +15,7 @@ type ReadPaths<TSchema> = {
15
15
  * - Otherwise, it's tags only with mode defaulting to 'none'
16
16
  * - 'none' keyword should NOT be used in arrays (use string 'none' instead)
17
17
  */
18
- type InvalidateOption<TSchema = unknown> = InvalidationMode | (ReadPaths<TSchema> | "all" | "self" | (string & {}))[];
18
+ type InvalidateOption<TSchema = unknown> = InvalidationMode | "*" | ReadPaths<TSchema> | (ReadPaths<TSchema> | "all" | "self" | (string & {}))[];
19
19
  interface InvalidationPluginConfig {
20
20
  /**
21
21
  * Default invalidation mode when invalidate option is not specified
@@ -32,13 +32,12 @@ type InvalidationInfiniteReadOptions = object;
32
32
  type InvalidationReadResult = object;
33
33
  type InvalidationWriteResult = object;
34
34
  /**
35
- * Manual invalidation - tags only
35
+ * Manual invalidation - tags only, or "*" for global refetch
36
36
  */
37
37
  type InvalidateFn<TSchema> = {
38
- (tag: ReadPaths<TSchema>): void;
39
- (tags: ReadPaths<TSchema>[]): void;
40
- (tag: string): void;
41
- (tags: string[]): void;
38
+ (tag: "*"): void;
39
+ (tag: ReadPaths<TSchema> | (string & {})): void;
40
+ (tags: (ReadPaths<TSchema> | (string & {}))[]): void;
42
41
  };
43
42
  interface InvalidationInstanceApi {
44
43
  /** Manually invalidate cache entries by tags. Useful for external events like WebSocket messages. */
@@ -85,12 +84,20 @@ declare module "@spoosh/core" {
85
84
  * });
86
85
  *
87
86
  * trigger({
88
- * invalidate: ["posts", "users"], // Tags only
87
+ * invalidate: "posts", // Single tag
88
+ * });
89
+ *
90
+ * trigger({
91
+ * invalidate: ["posts", "users"], // Multiple tags
89
92
  * });
90
93
  *
91
94
  * trigger({
92
95
  * invalidate: ["all", "posts", "custom-tag"], // Mode + tags
93
96
  * });
97
+ *
98
+ * trigger({
99
+ * invalidate: "*", // Global refetch - triggers all queries to refetch
100
+ * });
94
101
  * ```
95
102
  */
96
103
  declare function invalidationPlugin(config?: InvalidationPluginConfig): SpooshPlugin<{
package/dist/index.js CHANGED
@@ -45,7 +45,10 @@ function resolveInvalidateTags(context, defaultMode) {
45
45
  return resolveModeTags(context, effectiveDefault);
46
46
  }
47
47
  if (typeof invalidateOption === "string") {
48
- return resolveModeTags(context, invalidateOption);
48
+ if (invalidateOption === "all" || invalidateOption === "self" || invalidateOption === "none") {
49
+ return resolveModeTags(context, invalidateOption);
50
+ }
51
+ return [invalidateOption];
49
52
  }
50
53
  if (Array.isArray(invalidateOption)) {
51
54
  const tags = [];
@@ -77,6 +80,10 @@ function invalidationPlugin(config = {}) {
77
80
  afterResponse(context, response) {
78
81
  if (!response.error) {
79
82
  const tags = resolveInvalidateTags(context, defaultMode);
83
+ if (tags.includes("*")) {
84
+ context.eventEmitter.emit("refetchAll", void 0);
85
+ return;
86
+ }
80
87
  if (tags.length > 0) {
81
88
  context.stateManager.markStale(tags);
82
89
  context.eventEmitter.emit("invalidate", tags);
@@ -87,6 +94,10 @@ function invalidationPlugin(config = {}) {
87
94
  const { stateManager, eventEmitter } = context;
88
95
  const invalidate = (input) => {
89
96
  const tags = Array.isArray(input) ? input : [input];
97
+ if (tags.includes("*")) {
98
+ eventEmitter.emit("refetchAll", void 0);
99
+ return;
100
+ }
90
101
  if (tags.length > 0) {
91
102
  stateManager.markStale(tags);
92
103
  eventEmitter.emit("invalidate", tags);
package/dist/index.mjs CHANGED
@@ -19,7 +19,10 @@ function resolveInvalidateTags(context, defaultMode) {
19
19
  return resolveModeTags(context, effectiveDefault);
20
20
  }
21
21
  if (typeof invalidateOption === "string") {
22
- return resolveModeTags(context, invalidateOption);
22
+ if (invalidateOption === "all" || invalidateOption === "self" || invalidateOption === "none") {
23
+ return resolveModeTags(context, invalidateOption);
24
+ }
25
+ return [invalidateOption];
23
26
  }
24
27
  if (Array.isArray(invalidateOption)) {
25
28
  const tags = [];
@@ -51,6 +54,10 @@ function invalidationPlugin(config = {}) {
51
54
  afterResponse(context, response) {
52
55
  if (!response.error) {
53
56
  const tags = resolveInvalidateTags(context, defaultMode);
57
+ if (tags.includes("*")) {
58
+ context.eventEmitter.emit("refetchAll", void 0);
59
+ return;
60
+ }
54
61
  if (tags.length > 0) {
55
62
  context.stateManager.markStale(tags);
56
63
  context.eventEmitter.emit("invalidate", tags);
@@ -61,6 +68,10 @@ function invalidationPlugin(config = {}) {
61
68
  const { stateManager, eventEmitter } = context;
62
69
  const invalidate = (input) => {
63
70
  const tags = Array.isArray(input) ? input : [input];
71
+ if (tags.includes("*")) {
72
+ eventEmitter.emit("refetchAll", void 0);
73
+ return;
74
+ }
64
75
  if (tags.length > 0) {
65
76
  stateManager.markStale(tags);
66
77
  eventEmitter.emit("invalidate", tags);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spoosh/plugin-invalidation",
3
- "version": "0.5.2",
3
+ "version": "0.5.3",
4
4
  "description": "Cache invalidation plugin for Spoosh - auto-invalidates after mutations",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -36,7 +36,7 @@
36
36
  "@spoosh/core": ">=0.8.0"
37
37
  },
38
38
  "devDependencies": {
39
- "@spoosh/core": "0.9.2",
39
+ "@spoosh/core": "0.9.3",
40
40
  "@spoosh/test-utils": "0.1.5"
41
41
  },
42
42
  "scripts": {