ghcopilot-hub 1.0.0

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.
Files changed (109) hide show
  1. package/README.md +176 -0
  2. package/hub/agents/README.md +243 -0
  3. package/hub/agents/archiver.agent.md +231 -0
  4. package/hub/agents/explore.agent.md +49 -0
  5. package/hub/agents/implementador.agent.md +176 -0
  6. package/hub/agents/librarian.agent.md +34 -0
  7. package/hub/agents/momus.agent.md +130 -0
  8. package/hub/agents/oracle.agent.md +52 -0
  9. package/hub/agents/plan-guardian.agent.md +109 -0
  10. package/hub/agents/planificador.agent.md +295 -0
  11. package/hub/agents/test-sentinel.agent.md +106 -0
  12. package/hub/base/.github/copilot-instructions.md +10 -0
  13. package/hub/base/.github/instructions/ghcopilot-hub.instructions.md +6 -0
  14. package/hub/base/.github/prompts/ghcopilot-hub-maintenance.prompt.md +8 -0
  15. package/hub/base/.vscode/settings.json +1 -0
  16. package/hub/packs/base-web.json +4 -0
  17. package/hub/packs/nextjs-ssr.json +4 -0
  18. package/hub/packs/node-api.json +4 -0
  19. package/hub/packs/spa-tanstack.json +4 -0
  20. package/hub/skills/architecture-testing/SKILL.md +108 -0
  21. package/hub/skills/architecture-testing/references/archunitts.md +46 -0
  22. package/hub/skills/ghcopilot-hub-consumer/SKILL.md +115 -0
  23. package/hub/skills/ghcopilot-hub-consumer/references/workflow.md +39 -0
  24. package/hub/skills/mermaid-expert/SKILL.md +152 -0
  25. package/hub/skills/mermaid-expert/assets/examples/c4_model.md +121 -0
  26. package/hub/skills/mermaid-expert/assets/examples/flowchart.md +123 -0
  27. package/hub/skills/mermaid-expert/assets/examples/img/base_minimal.png +0 -0
  28. package/hub/skills/mermaid-expert/assets/examples/img/corporate.png +0 -0
  29. package/hub/skills/mermaid-expert/assets/examples/img/dark.png +0 -0
  30. package/hub/skills/mermaid-expert/assets/examples/img/dark_neo.png +0 -0
  31. package/hub/skills/mermaid-expert/assets/examples/img/default_neo.png +0 -0
  32. package/hub/skills/mermaid-expert/assets/examples/img/forest_corp.png +0 -0
  33. package/hub/skills/mermaid-expert/assets/examples/img/handdrawn.png +0 -0
  34. package/hub/skills/mermaid-expert/assets/examples/img/neo.png +0 -0
  35. package/hub/skills/mermaid-expert/assets/examples/img/neutral_sketch.png +0 -0
  36. package/hub/skills/mermaid-expert/assets/examples/img/retro.png +0 -0
  37. package/hub/skills/mermaid-expert/assets/examples/sequence.md +116 -0
  38. package/hub/skills/mermaid-expert/assets/examples/styles_and_looks.md +102 -0
  39. package/hub/skills/mermaid-expert/assets/examples/technical.md +130 -0
  40. package/hub/skills/mermaid-expert/assets/examples.md +57 -0
  41. package/hub/skills/mermaid-expert/references/cheatsheet.md +88 -0
  42. package/hub/skills/mermaid-expert/references/validation.md +66 -0
  43. package/hub/skills/react/SKILL.md +235 -0
  44. package/hub/skills/react/references/common-mistakes.md +518 -0
  45. package/hub/skills/react/references/composition-patterns.md +526 -0
  46. package/hub/skills/react/references/effects-patterns.md +396 -0
  47. package/hub/skills/react/references/react-compiler.md +268 -0
  48. package/hub/skills/react-hook-form/SKILL.md +291 -0
  49. package/hub/skills/react-hook-form/references/field-arrays.md +98 -0
  50. package/hub/skills/react-hook-form/references/integration.md +102 -0
  51. package/hub/skills/react-hook-form/references/performance.md +96 -0
  52. package/hub/skills/skill-creator/SKILL.md +152 -0
  53. package/hub/skills/skill-creator/assets/SKILL-TEMPLATE.md +84 -0
  54. package/hub/skills/skill-judge/README.md +261 -0
  55. package/hub/skills/skill-judge/SKILL.md +806 -0
  56. package/hub/skills/tailwind/SKILL.md +200 -0
  57. package/hub/skills/tanstack/SKILL.md +284 -0
  58. package/hub/skills/tanstack/references/loader-adapter-examples.md +79 -0
  59. package/hub/skills/tanstack/references/query-options-examples.md +115 -0
  60. package/hub/skills/tanstack/references/resilience-patterns.md +110 -0
  61. package/hub/skills/tanstack/references/suspense-consumption-examples.md +82 -0
  62. package/hub/skills/tanstack-query/SKILL.md +241 -0
  63. package/hub/skills/tanstack-query/references/advanced-hooks.md +126 -0
  64. package/hub/skills/tanstack-query/references/best-practices.md +241 -0
  65. package/hub/skills/tanstack-query/references/cache-strategies.md +474 -0
  66. package/hub/skills/tanstack-query/references/common-patterns.md +239 -0
  67. package/hub/skills/tanstack-query/references/migration-v5.md +93 -0
  68. package/hub/skills/tanstack-query/references/resilience-and-mutations.md +63 -0
  69. package/hub/skills/tanstack-query/references/testing.md +116 -0
  70. package/hub/skills/tanstack-query/references/top-errors.md +148 -0
  71. package/hub/skills/tanstack-query/references/typescript.md +176 -0
  72. package/hub/skills/tanstack-router/SKILL.md +145 -0
  73. package/hub/skills/tanstack-router/references/code-splitting.md +31 -0
  74. package/hub/skills/tanstack-router/references/errors-and-boundaries.md +44 -0
  75. package/hub/skills/tanstack-router/references/loaders-and-preload.md +51 -0
  76. package/hub/skills/tanstack-router/references/navigation.md +24 -0
  77. package/hub/skills/tanstack-router/references/private-routes.md +169 -0
  78. package/hub/skills/tanstack-router/references/router-context.md +35 -0
  79. package/hub/skills/tanstack-router/references/search-params.md +29 -0
  80. package/hub/skills/tanstack-router/references/typescript.md +24 -0
  81. package/hub/skills/testing/SKILL.md +187 -0
  82. package/hub/skills/testing/references/assertions.md +64 -0
  83. package/hub/skills/testing/references/async-testing.md +66 -0
  84. package/hub/skills/testing/references/e2e-strategy.md +69 -0
  85. package/hub/skills/testing/references/layer-matrix.md +67 -0
  86. package/hub/skills/testing/references/performance.md +49 -0
  87. package/hub/skills/testing/references/tooling-map.md +81 -0
  88. package/hub/skills/testing/references/zustand-mocking.md +84 -0
  89. package/hub/skills/typescript/SKILL.md +232 -0
  90. package/hub/skills/typescript/references/perf-additional-concerns.md +248 -0
  91. package/hub/skills/typescript/references/perf-execution-cache-locality.md +178 -0
  92. package/hub/skills/typescript/references/reduce-branching.md +147 -0
  93. package/hub/skills/typescript/references/reduce-looping.md +203 -0
  94. package/hub/skills/typescript/references/style-and-types.md +171 -0
  95. package/hub/skills/typescript/references/type-vs-interface.md +27 -0
  96. package/hub/skills/zod/SKILL.md +219 -0
  97. package/hub/skills/zustand/SKILL.md +273 -0
  98. package/package.json +59 -0
  99. package/tooling/cli/src/bin.js +11 -0
  100. package/tooling/cli/src/cli.js +409 -0
  101. package/tooling/cli/src/lib/catalog-loader.js +191 -0
  102. package/tooling/cli/src/lib/constants.js +39 -0
  103. package/tooling/cli/src/lib/errors.js +8 -0
  104. package/tooling/cli/src/lib/frontmatter.js +41 -0
  105. package/tooling/cli/src/lib/fs-utils.js +77 -0
  106. package/tooling/cli/src/lib/managed-header.js +74 -0
  107. package/tooling/cli/src/lib/manifest.js +105 -0
  108. package/tooling/cli/src/lib/resolver.js +53 -0
  109. package/tooling/cli/src/lib/sync-engine.js +262 -0
@@ -0,0 +1,241 @@
1
+ # TanStack Query Best Practices
2
+
3
+ **Performance, caching strategies, and common patterns**
4
+
5
+ > Alignment note: For required route data, follow the tanstack render-as-you-fetch flow
6
+ > (Application `queryOptions` → Loader `ensureQueryData` → Presentation `useSuspenseQuery`).
7
+ > Examples below use `useQuery` for brevity and are intended for non-route or optional data.
8
+
9
+ ---
10
+
11
+ ## 1. Avoid Request Waterfalls
12
+
13
+ ### ❌ Bad: Sequential Dependencies
14
+
15
+ ```tsx
16
+ function BadUserProfile({ userId }) {
17
+ const { data: user } = useQuery({
18
+ queryKey: ["users", userId],
19
+ queryFn: () => fetchUser(userId),
20
+ });
21
+
22
+ const { data: posts } = useQuery({
23
+ queryKey: ["posts", user?.id],
24
+ queryFn: () => fetchPosts(user!.id),
25
+ enabled: !!user,
26
+ });
27
+
28
+ const { data: comments } = useQuery({
29
+ queryKey: ["comments", posts?.[0]?.id],
30
+ queryFn: () => fetchComments(posts![0].id),
31
+ enabled: !!posts && posts.length > 0,
32
+ });
33
+ }
34
+ ```
35
+
36
+ ### ✅ Good: Parallel Queries
37
+
38
+ ```tsx
39
+ function GoodUserProfile({ userId }) {
40
+ const { data: user } = useQuery({
41
+ queryKey: ["users", userId],
42
+ queryFn: () => fetchUser(userId),
43
+ });
44
+
45
+ const { data: posts } = useQuery({
46
+ queryKey: ["posts", userId],
47
+ queryFn: () => fetchPosts(userId),
48
+ });
49
+
50
+ const { data: comments } = useQuery({
51
+ queryKey: ["comments", userId],
52
+ queryFn: () => fetchUserComments(userId),
53
+ });
54
+ }
55
+ ```
56
+
57
+ > For route data, prefetch in parallel inside the loader (e.g., `Promise.all`).
58
+
59
+ ---
60
+
61
+ ## 2. Query Key Strategy
62
+
63
+ ### Hierarchical Structure
64
+
65
+ ```tsx
66
+ ["todos"][("todos", { status: "done" })][("todos", 123)];
67
+
68
+ queryClient.invalidateQueries({ queryKey: ["todos"] });
69
+ queryClient.invalidateQueries({ queryKey: ["todos", { status: "done" }] });
70
+ ```
71
+
72
+ ### Best Practices
73
+
74
+ ```tsx
75
+ // ✅ Good: Stable, serializable keys
76
+ ["users", userId, { sort: "name", filter: "active" }][
77
+ // ❌ Bad: Functions in keys (not serializable)
78
+ ("users", () => userId)
79
+ ][
80
+ // ❌ Bad: Changing order
81
+ ("users", { filter: "active", sort: "name" })
82
+ ];
83
+
84
+ // ✅ Good: Consistent ordering
85
+ const userFilters = { filter: "active", sort: "name" };
86
+ ```
87
+
88
+ ---
89
+
90
+ ## 3. Caching Configuration
91
+
92
+ ### staleTime vs gcTime
93
+
94
+ ```tsx
95
+ // Real-time data
96
+ staleTime: 0;
97
+ // Stable data
98
+ staleTime: 1000 * 60 * 60;
99
+ // Static data
100
+ staleTime: Infinity;
101
+
102
+ // Keep frequently revisited data longer
103
+ gcTime: 1000 * 60 * 60;
104
+ ```
105
+
106
+ ### Per-Query vs Global
107
+
108
+ ```tsx
109
+ const queryClient = new QueryClient({
110
+ defaultOptions: {
111
+ queries: {
112
+ staleTime: 1000 * 60 * 5,
113
+ gcTime: 1000 * 60 * 60,
114
+ },
115
+ },
116
+ });
117
+
118
+ useQuery({
119
+ queryKey: ["stock-price"],
120
+ queryFn: fetchStockPrice,
121
+ staleTime: 0,
122
+ refetchInterval: 1000 * 30,
123
+ });
124
+ ```
125
+
126
+ ---
127
+
128
+ ## 4. Use queryOptions Factory
129
+
130
+ ```tsx
131
+ export const todosQueryOptions = queryOptions({
132
+ queryKey: ["todos"],
133
+ queryFn: fetchTodos,
134
+ staleTime: 1000 * 60,
135
+ });
136
+
137
+ useQuery(todosQueryOptions);
138
+ useSuspenseQuery(todosQueryOptions);
139
+ queryClient.prefetchQuery(todosQueryOptions);
140
+ ```
141
+
142
+ ---
143
+
144
+ ## 5. Data Transformations
145
+
146
+ ```tsx
147
+ function TodoCount() {
148
+ const { data: count } = useQuery({
149
+ queryKey: ["todos"],
150
+ queryFn: fetchTodos,
151
+ select: (data) => data.length,
152
+ });
153
+ }
154
+ ```
155
+
156
+ ---
157
+
158
+ ## 6. Prefetching (Optional Navigation)
159
+
160
+ ```tsx
161
+ const prefetch = (id: number) => {
162
+ queryClient.prefetchQuery({
163
+ queryKey: ["todos", id],
164
+ queryFn: () => fetchTodo(id),
165
+ staleTime: 1000 * 60 * 5,
166
+ });
167
+ };
168
+ ```
169
+
170
+ > Do not use `prefetchQuery` for required route data; use loader `ensureQueryData` instead.
171
+
172
+ ---
173
+
174
+ ## 7. Optimistic Updates
175
+
176
+ ```tsx
177
+ useMutation({
178
+ mutationFn: updateTodo,
179
+ onMutate: async (newTodo) => {
180
+ await queryClient.cancelQueries({ queryKey: ["todos"] });
181
+ const previous = queryClient.getQueryData(["todos"]);
182
+ queryClient.setQueryData(["todos"], (old) => [...(old ?? []), newTodo]);
183
+ return { previous };
184
+ },
185
+ onError: (err, newTodo, context) => {
186
+ queryClient.setQueryData(["todos"], context?.previous);
187
+ },
188
+ onSettled: () => {
189
+ queryClient.invalidateQueries({ queryKey: ["todos"] });
190
+ },
191
+ });
192
+ ```
193
+
194
+ ---
195
+
196
+ ## 8. Error Handling Strategy
197
+
198
+ ```tsx
199
+ useQuery({
200
+ queryKey: ["todos"],
201
+ queryFn: fetchTodos,
202
+ throwOnError: true,
203
+ });
204
+
205
+ useQuery({
206
+ queryKey: ["todos"],
207
+ queryFn: fetchTodos,
208
+ throwOnError: (error) => error.status >= 500,
209
+ });
210
+ ```
211
+
212
+ ---
213
+
214
+ ## 9. Server State vs Client State
215
+
216
+ ```tsx
217
+ // ✅ Server state
218
+ const { data: todos } = useQuery({ queryKey: ["todos"], queryFn: fetchTodos });
219
+
220
+ // ✅ Client state
221
+ const [isModalOpen, setIsModalOpen] = useState(false);
222
+ ```
223
+
224
+ ---
225
+
226
+ ## 10. Performance Monitoring
227
+
228
+ Use DevTools to:
229
+
230
+ - Verify cache hits
231
+ - Track refetch frequency
232
+ - Inspect query states
233
+ - Export cache for debugging
234
+
235
+ ---
236
+
237
+ ## 11. Mutation Resilience Ownership
238
+
239
+ - Mutation callbacks are valid in v5 and should be used for rollback and invalidation.
240
+ - Keep UI feedback adapters in Presentation; prefer callback injection in Application hooks.
241
+ - For complete mutation resilience patterns, see `references/resilience-and-mutations.md`.
@@ -0,0 +1,474 @@
1
+ # Cache Strategies & Invalidation Patterns
2
+
3
+ Comprehensive guide to TanStack Query v5 caching, invalidation, and data synchronization.
4
+
5
+ > Alignment note: For required route data, follow the tanstack render-as-you-fetch flow
6
+ > (Application `queryOptions` → Loader `ensureQueryData` → Presentation `useSuspenseQuery`).
7
+ > The `useQuery` examples below are conceptual and should be adapted to that flow when used in routes.
8
+
9
+ ## Cache Lifecycle
10
+
11
+ ```
12
+ ┌─────────────────────────────────────────────────────────────────────────────┐
13
+ │ Query Cache Lifecycle │
14
+ ├─────────────────────────────────────────────────────────────────────────────┤
15
+ │ │
16
+ │ ┌──────────┐ staleTime ┌──────────┐ gcTime ┌──────────────┐ │
17
+ │ │ FRESH │ ──────────────► │ STALE │ ────────────► │ GARBAGE │ │
18
+ │ │ │ (no refetch) │ │ (if unused) │ COLLECTED │ │
19
+ │ └──────────┘ └──────────┘ └──────────────┘ │
20
+ │ │ │ │
21
+ │ │ Component mounts │ Component mounts │
22
+ │ ▼ ▼ │
23
+ │ Return cached data Return cached data │
24
+ │ (no network request) + background refetch │
25
+ │ │
26
+ └─────────────────────────────────────────────────────────────────────────────┘
27
+ ```
28
+
29
+ ## staleTime vs gcTime
30
+
31
+ | Setting | Purpose | Default | When to Adjust |
32
+ | ----------- | ------------------------------------ | ---------------- | --------------------------------------- |
33
+ | `staleTime` | How long data is considered "fresh" | 0 (always stale) | Increase for rarely-changing data |
34
+ | `gcTime` | How long unused data stays in memory | 5 minutes | Increase for frequently revisited pages |
35
+
36
+ ### staleTime Configuration
37
+
38
+ ```typescript
39
+ // Real-time data (stock prices, notifications)
40
+ useQuery({
41
+ queryKey: ["stock", symbol],
42
+ queryFn: () => fetchStock(symbol),
43
+ staleTime: 0, // Always refetch
44
+ refetchInterval: 5000, // Poll every 5s
45
+ });
46
+
47
+ // User profile (changes occasionally)
48
+ useQuery({
49
+ queryKey: ["user", userId],
50
+ queryFn: () => fetchUser(userId),
51
+ staleTime: 5 * 60 * 1000, // Fresh for 5 minutes
52
+ });
53
+
54
+ // Static configuration (rarely changes)
55
+ useQuery({
56
+ queryKey: ["config"],
57
+ queryFn: fetchConfig,
58
+ staleTime: 60 * 60 * 1000, // Fresh for 1 hour
59
+ });
60
+
61
+ // Truly static data (never changes)
62
+ useQuery({
63
+ queryKey: ["countries"],
64
+ queryFn: fetchCountries,
65
+ staleTime: Infinity, // Never refetch
66
+ });
67
+ ```
68
+
69
+ ### gcTime Configuration
70
+
71
+ ```typescript
72
+ // Frequently revisited pages (keep in memory longer)
73
+ useQuery({
74
+ queryKey: ["dashboard"],
75
+ queryFn: fetchDashboard,
76
+ gcTime: 30 * 60 * 1000, // Keep 30 minutes after unmount
77
+ });
78
+
79
+ // Large data that shouldn't linger
80
+ useQuery({
81
+ queryKey: ["reports", year],
82
+ queryFn: () => fetchReports(year),
83
+ gcTime: 60 * 1000, // Clear 1 minute after unmount
84
+ });
85
+
86
+ // Critical data (keep indefinitely)
87
+ useQuery({
88
+ queryKey: ["currentUser"],
89
+ queryFn: fetchCurrentUser,
90
+ gcTime: Infinity, // Never garbage collect
91
+ });
92
+ ```
93
+
94
+ ## Query Key Hierarchy
95
+
96
+ Query keys form a hierarchy. Invalidating a parent invalidates all children.
97
+
98
+ ```typescript
99
+ // Key hierarchy:
100
+ // ['todos']
101
+ // └── ['todos', 'list']
102
+ // └── ['todos', 'list', { filter: 'active' }]
103
+ // └── ['todos', 'list', { filter: 'completed' }]
104
+ // └── ['todos', 'detail', '1']
105
+ // └── ['todos', 'detail', '2']
106
+
107
+ // Invalidate ALL todo queries (list + all details)
108
+ queryClient.invalidateQueries({ queryKey: ["todos"] });
109
+
110
+ // Invalidate only list queries (not details)
111
+ queryClient.invalidateQueries({ queryKey: ["todos", "list"] });
112
+
113
+ // Invalidate specific filter
114
+ queryClient.invalidateQueries({
115
+ queryKey: ["todos", "list", { filter: "active" }],
116
+ });
117
+
118
+ // Invalidate exact key only (not children)
119
+ queryClient.invalidateQueries({
120
+ queryKey: ["todos", "list"],
121
+ exact: true,
122
+ });
123
+ ```
124
+
125
+ ## Invalidation Strategies
126
+
127
+ ### 1. Mutation-Based Invalidation
128
+
129
+ ```typescript
130
+ // ✅ RECOMMENDED: Invalidate related queries after mutation
131
+ const createTodo = useMutation({
132
+ mutationFn: api.createTodo,
133
+ onSuccess: () => {
134
+ // Invalidate list to include new item
135
+ queryClient.invalidateQueries({ queryKey: ["todos", "list"] });
136
+ },
137
+ });
138
+
139
+ const updateTodo = useMutation({
140
+ mutationFn: api.updateTodo,
141
+ onSuccess: (data, variables) => {
142
+ // Invalidate specific item + list
143
+ queryClient.invalidateQueries({
144
+ queryKey: ["todos", "detail", variables.id],
145
+ });
146
+ queryClient.invalidateQueries({ queryKey: ["todos", "list"] });
147
+ },
148
+ });
149
+
150
+ const deleteTodo = useMutation({
151
+ mutationFn: api.deleteTodo,
152
+ onSuccess: (_, id) => {
153
+ // Remove from cache entirely
154
+ queryClient.removeQueries({ queryKey: ["todos", "detail", id] });
155
+ // Invalidate list
156
+ queryClient.invalidateQueries({ queryKey: ["todos", "list"] });
157
+ },
158
+ });
159
+ ```
160
+
161
+ ### 2. Optimistic Updates with Reconciliation
162
+
163
+ ```typescript
164
+ const updateTodo = useMutation({
165
+ mutationFn: ({ id, ...data }) => api.updateTodo(id, data),
166
+
167
+ // Step 1: Cancel any outgoing refetches
168
+ onMutate: async ({ id, ...updates }) => {
169
+ await queryClient.cancelQueries({ queryKey: ["todos", "detail", id] });
170
+ await queryClient.cancelQueries({ queryKey: ["todos", "list"] });
171
+
172
+ // Step 2: Snapshot current state
173
+ const previousTodo = queryClient.getQueryData(["todos", "detail", id]);
174
+ const previousList = queryClient.getQueryData(["todos", "list"]);
175
+
176
+ // Step 3: Optimistically update both caches
177
+ queryClient.setQueryData(["todos", "detail", id], (old) =>
178
+ old ? { ...old, ...updates } : undefined
179
+ );
180
+
181
+ queryClient.setQueryData(["todos", "list"], (old) =>
182
+ old?.map((todo) => (todo.id === id ? { ...todo, ...updates } : todo))
183
+ );
184
+
185
+ // Step 4: Return context for rollback
186
+ return { previousTodo, previousList, id };
187
+ },
188
+
189
+ // Step 5: Rollback on error
190
+ onError: (err, variables, context) => {
191
+ if (context) {
192
+ queryClient.setQueryData(["todos", "detail", context.id], context.previousTodo);
193
+ queryClient.setQueryData(["todos", "list"], context.previousList);
194
+ }
195
+ },
196
+
197
+ // Step 6: Always reconcile with server
198
+ onSettled: (data, error, { id }) => {
199
+ queryClient.invalidateQueries({ queryKey: ["todos", "detail", id] });
200
+ queryClient.invalidateQueries({ queryKey: ["todos", "list"] });
201
+ },
202
+ });
203
+ ```
204
+
205
+ ### 3. Predicate-Based Invalidation
206
+
207
+ ```typescript
208
+ // Invalidate todos by status
209
+ queryClient.invalidateQueries({
210
+ predicate: (query) => {
211
+ const key = query.queryKey;
212
+ return (
213
+ key[0] === "todos" &&
214
+ key[1] === "list" &&
215
+ (key[2] as { filter?: string })?.filter === "completed"
216
+ );
217
+ },
218
+ });
219
+
220
+ // Invalidate all queries older than 10 minutes
221
+ queryClient.invalidateQueries({
222
+ predicate: (query) => {
223
+ const dataUpdatedAt = query.state.dataUpdatedAt;
224
+ return Date.now() - dataUpdatedAt > 10 * 60 * 1000;
225
+ },
226
+ });
227
+
228
+ // Invalidate queries with errors
229
+ queryClient.invalidateQueries({
230
+ predicate: (query) => query.state.status === "error",
231
+ });
232
+ ```
233
+
234
+ ### 4. Type-Based Invalidation
235
+
236
+ ```typescript
237
+ // Invalidate only active queries (currently rendered)
238
+ queryClient.invalidateQueries({ refetchType: "active" });
239
+
240
+ // Invalidate inactive queries (not rendered but in cache)
241
+ queryClient.invalidateQueries({ refetchType: "inactive" });
242
+
243
+ // Invalidate all queries
244
+ queryClient.invalidateQueries({ refetchType: "all" });
245
+ ```
246
+
247
+ ### 5. Refetch vs Invalidate
248
+
249
+ ```typescript
250
+ // invalidateQueries: Mark as stale, refetch if active
251
+ queryClient.invalidateQueries({ queryKey: ["todos"] });
252
+ // - Marks all matching queries as stale
253
+ // - Active queries refetch immediately
254
+ // - Inactive queries refetch on next mount
255
+
256
+ // refetchQueries: Force immediate refetch
257
+ queryClient.refetchQueries({ queryKey: ["todos"], type: "active" });
258
+ // - Forces refetch regardless of stale state
259
+ // - Only refetches specified type
260
+
261
+ // When to use each:
262
+ // invalidateQueries: After mutations (let React Query decide when to refetch)
263
+ // refetchQueries: When you need guaranteed fresh data NOW
264
+ ```
265
+
266
+ ## Direct Cache Manipulation
267
+
268
+ ### setQueryData
269
+
270
+ ```typescript
271
+ // Update single item
272
+ queryClient.setQueryData(["user", userId], (old) =>
273
+ old ? { ...old, name: "New Name" } : undefined
274
+ );
275
+
276
+ // Add item to list
277
+ queryClient.setQueryData(["todos", "list"], (old) => (old ? [...old, newTodo] : [newTodo]));
278
+
279
+ // Update item in list
280
+ queryClient.setQueryData(["todos", "list"], (old) =>
281
+ old?.map((todo) => (todo.id === updatedTodo.id ? updatedTodo : todo))
282
+ );
283
+
284
+ // Remove item from list
285
+ queryClient.setQueryData(["todos", "list"], (old) => old?.filter((todo) => todo.id !== deletedId));
286
+
287
+ // ⚠️ IMPORTANT: Always return new reference
288
+ // ❌ BAD: Mutating existing data
289
+ queryClient.setQueryData(["todos"], (old) => {
290
+ old?.push(newTodo); // Mutation!
291
+ return old;
292
+ });
293
+
294
+ // ✅ GOOD: Return new array
295
+ queryClient.setQueryData(["todos"], (old) => (old ? [...old, newTodo] : [newTodo]));
296
+ ```
297
+
298
+ ### getQueryData & getQueriesData
299
+
300
+ ```typescript
301
+ // Get single query data
302
+ const user = queryClient.getQueryData<User>(["user", userId]);
303
+
304
+ // Get all matching queries
305
+ const allTodoQueries = queryClient.getQueriesData<Todo[]>({
306
+ queryKey: ["todos"],
307
+ });
308
+ // Returns: Array<[queryKey, data]>
309
+
310
+ // Check if data exists
311
+ const hasUser = queryClient.getQueryData(["user", userId]) !== undefined;
312
+
313
+ // Get query state (includes status, error, etc.)
314
+ const state = queryClient.getQueryState(["user", userId]);
315
+ if (state?.status === "error") {
316
+ console.log("Query failed:", state.error);
317
+ }
318
+ ```
319
+
320
+ ### removeQueries
321
+
322
+ ```typescript
323
+ // Remove specific query
324
+ queryClient.removeQueries({ queryKey: ["todos", "detail", deletedId] });
325
+
326
+ // Remove all queries matching prefix
327
+ queryClient.removeQueries({ queryKey: ["todos"] });
328
+
329
+ // Remove inactive queries only
330
+ queryClient.removeQueries({ queryKey: ["todos"], type: "inactive" });
331
+ ```
332
+
333
+ ## Cache Persistence
334
+
335
+ ### persist with localStorage
336
+
337
+ ```typescript
338
+ import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client';
339
+ import { createSyncStoragePersister } from '@tanstack/query-sync-storage-persister';
340
+
341
+ const queryClient = new QueryClient({
342
+ defaultOptions: {
343
+ queries: {
344
+ gcTime: 1000 * 60 * 60 * 24, // 24 hours (must be >= maxAge)
345
+ },
346
+ },
347
+ });
348
+
349
+ const persister = createSyncStoragePersister({
350
+ storage: window.localStorage,
351
+ key: 'REACT_QUERY_CACHE',
352
+ throttleTime: 1000,
353
+ });
354
+
355
+ function App() {
356
+ return (
357
+ <PersistQueryClientProvider
358
+ client={queryClient}
359
+ persistOptions={{
360
+ persister,
361
+ maxAge: 1000 * 60 * 60 * 24, // 24 hours
362
+ dehydrateOptions: {
363
+ shouldDehydrateQuery: (query) => {
364
+ // Only persist successful queries
365
+ return query.state.status === 'success';
366
+ },
367
+ },
368
+ }}
369
+ >
370
+ <YourApp />
371
+ </PersistQueryClientProvider>
372
+ );
373
+ }
374
+ ```
375
+
376
+ ### Async Persistence (IndexedDB)
377
+
378
+ ```typescript
379
+ import { createAsyncStoragePersister } from "@tanstack/query-async-storage-persister";
380
+ import { del, get, set } from "idb-keyval";
381
+
382
+ const persister = createAsyncStoragePersister({
383
+ storage: {
384
+ getItem: async (key) => await get(key),
385
+ setItem: async (key, value) => await set(key, value),
386
+ removeItem: async (key) => await del(key),
387
+ },
388
+ key: "REACT_QUERY_CACHE",
389
+ });
390
+ ```
391
+
392
+ ## Real-Time Data Strategies
393
+
394
+ ### Polling
395
+
396
+ ```typescript
397
+ useQuery({
398
+ queryKey: ["notifications"],
399
+ queryFn: fetchNotifications,
400
+ refetchInterval: 30000, // Poll every 30s
401
+ refetchIntervalInBackground: false, // Pause when tab hidden
402
+ });
403
+ ```
404
+
405
+ ### WebSocket Integration
406
+
407
+ ```typescript
408
+ // Subscribe to WebSocket updates
409
+ useEffect(() => {
410
+ const ws = new WebSocket("wss://api.example.com/ws");
411
+
412
+ ws.onmessage = (event) => {
413
+ const data = JSON.parse(event.data);
414
+
415
+ if (data.type === "TODO_UPDATED") {
416
+ // Update cache directly
417
+ queryClient.setQueryData(["todos", data.todo.id], data.todo);
418
+ // Invalidate list to ensure consistency
419
+ queryClient.invalidateQueries({ queryKey: ["todos", "list"] });
420
+ }
421
+ };
422
+
423
+ return () => ws.close();
424
+ }, [queryClient]);
425
+ ```
426
+
427
+ ### Server-Sent Events (SSE)
428
+
429
+ ```typescript
430
+ useEffect(() => {
431
+ const eventSource = new EventSource("/api/events");
432
+
433
+ eventSource.addEventListener("cache-invalidation", (event) => {
434
+ const { queryKey } = JSON.parse(event.data);
435
+ queryClient.invalidateQueries({ queryKey });
436
+ });
437
+
438
+ return () => eventSource.close();
439
+ }, [queryClient]);
440
+ ```
441
+
442
+ ## Common Patterns Matrix
443
+
444
+ | Scenario | staleTime | gcTime | Refetch Strategy |
445
+ | ------------------------ | --------- | -------- | ------------------- |
446
+ | Real-time (stocks, chat) | 0 | 5min | Poll or WebSocket |
447
+ | User data | 5min | 30min | Window focus |
448
+ | Product catalog | 1min | 10min | On navigation |
449
+ | Static config | Infinity | Infinity | Manual/deploy |
450
+ | Search results | 0 | 1min | On input change |
451
+ | Dashboard | 30s | 5min | Poll + window focus |
452
+
453
+ ## Debugging Tips
454
+
455
+ ```typescript
456
+ // Log all query activity in development
457
+ if (process.env.NODE_ENV === "development") {
458
+ queryClient.getQueryCache().subscribe((event) => {
459
+ console.log("Query event:", event.type, event.query.queryKey);
460
+ });
461
+ }
462
+
463
+ // Inspect query state
464
+ const queryCache = queryClient.getQueryCache();
465
+ const queries = queryCache.getAll();
466
+ queries.forEach((query) => {
467
+ console.log({
468
+ key: query.queryKey,
469
+ state: query.state.status,
470
+ dataUpdatedAt: new Date(query.state.dataUpdatedAt),
471
+ isStale: query.isStale(),
472
+ });
473
+ });
474
+ ```