@girardmedia/bootspring 3.3.2 → 3.4.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 (171) hide show
  1. package/assets/agents/accessibility-auditor.md +39 -0
  2. package/assets/agents/api-designer.md +40 -0
  3. package/assets/agents/auth-implementer.md +64 -0
  4. package/assets/agents/bug-hunter.md +42 -0
  5. package/assets/agents/bundle-analyzer.md +40 -0
  6. package/assets/agents/cache-optimizer.md +55 -0
  7. package/assets/agents/changelog-writer.md +55 -0
  8. package/assets/agents/ci-cd-builder.md +40 -0
  9. package/assets/agents/code-explainer.md +39 -0
  10. package/assets/agents/code-reviewer.md +39 -0
  11. package/assets/agents/cost-optimizer.md +57 -0
  12. package/assets/agents/cron-scheduler.md +51 -0
  13. package/assets/agents/data-seeder.md +56 -0
  14. package/assets/agents/database-architect.md +40 -0
  15. package/assets/agents/dependency-updater.md +40 -0
  16. package/assets/agents/deploy-checker.md +40 -0
  17. package/assets/agents/docker-optimizer.md +40 -0
  18. package/assets/agents/documentation-writer.md +40 -0
  19. package/assets/agents/email-builder.md +55 -0
  20. package/assets/agents/env-setup.md +40 -0
  21. package/assets/agents/error-handler.md +40 -0
  22. package/assets/agents/eslint-fixer.md +46 -0
  23. package/assets/agents/feature-flagger.md +69 -0
  24. package/assets/agents/git-detective.md +39 -0
  25. package/assets/agents/graphql-builder.md +60 -0
  26. package/assets/agents/incident-responder.md +59 -0
  27. package/assets/agents/log-analyzer.md +39 -0
  28. package/assets/agents/migration-planner.md +41 -0
  29. package/assets/agents/monorepo-navigator.md +39 -0
  30. package/assets/agents/nextjs-expert.md +57 -0
  31. package/assets/agents/notification-builder.md +56 -0
  32. package/assets/agents/onboarding-guide.md +39 -0
  33. package/assets/agents/performance-profiler.md +40 -0
  34. package/assets/agents/prisma-expert.md +57 -0
  35. package/assets/agents/rate-limiter.md +58 -0
  36. package/assets/agents/react-expert.md +58 -0
  37. package/assets/agents/refactorer.md +42 -0
  38. package/assets/agents/regex-builder.md +46 -0
  39. package/assets/agents/release-manager.md +40 -0
  40. package/assets/agents/s3-manager.md +58 -0
  41. package/assets/agents/schema-validator.md +40 -0
  42. package/assets/agents/search-builder.md +62 -0
  43. package/assets/agents/security-auditor.md +39 -0
  44. package/assets/agents/sitemap-generator.md +53 -0
  45. package/assets/agents/stripe-integrator.md +59 -0
  46. package/assets/agents/tailwind-expert.md +55 -0
  47. package/assets/agents/tech-debt-tracker.md +39 -0
  48. package/assets/agents/test-writer.md +42 -0
  49. package/assets/agents/type-fixer.md +45 -0
  50. package/assets/agents/webhook-builder.md +54 -0
  51. package/assets/rules/cpp.md +53 -0
  52. package/assets/rules/css.md +52 -0
  53. package/assets/rules/go.md +50 -0
  54. package/assets/rules/html.md +52 -0
  55. package/assets/rules/java.md +51 -0
  56. package/assets/rules/kotlin.md +50 -0
  57. package/assets/rules/php.md +51 -0
  58. package/assets/rules/python.md +51 -0
  59. package/assets/rules/ruby.md +51 -0
  60. package/assets/rules/rust.md +49 -0
  61. package/assets/rules/shell.md +52 -0
  62. package/assets/rules/sql.md +49 -0
  63. package/assets/rules/swift.md +50 -0
  64. package/assets/rules/typescript.md +52 -0
  65. package/assets/rules/yaml-json.md +51 -0
  66. package/assets/skills/accessibility.md +210 -0
  67. package/assets/skills/agent-patterns.md +387 -0
  68. package/assets/skills/ai-integration.md +263 -0
  69. package/assets/skills/animation-patterns.md +224 -0
  70. package/assets/skills/api-design.md +218 -0
  71. package/assets/skills/api-gateway.md +341 -0
  72. package/assets/skills/api-versioning.md +226 -0
  73. package/assets/skills/astro-patterns.md +233 -0
  74. package/assets/skills/auth-patterns.md +248 -0
  75. package/assets/skills/aws-patterns.md +171 -0
  76. package/assets/skills/background-jobs.md +162 -0
  77. package/assets/skills/browser-extensions.md +309 -0
  78. package/assets/skills/caching-patterns.md +253 -0
  79. package/assets/skills/ci-cd.md +251 -0
  80. package/assets/skills/cli-development.md +296 -0
  81. package/assets/skills/code-review.md +185 -0
  82. package/assets/skills/cron-patterns.md +327 -0
  83. package/assets/skills/data-fetching.md +231 -0
  84. package/assets/skills/database-migrations.md +346 -0
  85. package/assets/skills/database-patterns.md +219 -0
  86. package/assets/skills/debugging.md +281 -0
  87. package/assets/skills/design-system.md +289 -0
  88. package/assets/skills/django-patterns.md +182 -0
  89. package/assets/skills/docker-patterns.md +235 -0
  90. package/assets/skills/e2e-testing.md +287 -0
  91. package/assets/skills/edge-computing.md +268 -0
  92. package/assets/skills/electron-patterns.md +266 -0
  93. package/assets/skills/email-templates.md +206 -0
  94. package/assets/skills/error-handling.md +265 -0
  95. package/assets/skills/event-driven.md +232 -0
  96. package/assets/skills/express-patterns.md +239 -0
  97. package/assets/skills/fastapi-patterns.md +198 -0
  98. package/assets/skills/feature-flags.md +212 -0
  99. package/assets/skills/figma-to-code.md +298 -0
  100. package/assets/skills/file-upload.md +228 -0
  101. package/assets/skills/forms-patterns.md +264 -0
  102. package/assets/skills/gcp-patterns.md +189 -0
  103. package/assets/skills/git-workflow.md +187 -0
  104. package/assets/skills/golang-patterns.md +185 -0
  105. package/assets/skills/graphql-patterns.md +244 -0
  106. package/assets/skills/i18n-patterns.md +172 -0
  107. package/assets/skills/image-processing.md +350 -0
  108. package/assets/skills/java-springboot.md +226 -0
  109. package/assets/skills/kotlin-patterns.md +207 -0
  110. package/assets/skills/kubernetes-patterns.md +326 -0
  111. package/assets/skills/laravel-patterns.md +261 -0
  112. package/assets/skills/llm-fine-tuning.md +335 -0
  113. package/assets/skills/load-testing.md +303 -0
  114. package/assets/skills/logging-observability.md +228 -0
  115. package/assets/skills/markdown-processing.md +318 -0
  116. package/assets/skills/mcp-server-patterns.md +292 -0
  117. package/assets/skills/microservices.md +272 -0
  118. package/assets/skills/migration-patterns.md +239 -0
  119. package/assets/skills/mongodb-patterns.md +189 -0
  120. package/assets/skills/monorepo-patterns.md +287 -0
  121. package/assets/skills/nextjs-app-router.md +237 -0
  122. package/assets/skills/notification-patterns.md +348 -0
  123. package/assets/skills/oauth-patterns.md +246 -0
  124. package/assets/skills/payment-integration.md +222 -0
  125. package/assets/skills/pdf-generation.md +307 -0
  126. package/assets/skills/performance-optimization.md +277 -0
  127. package/assets/skills/php-patterns.md +210 -0
  128. package/assets/skills/prisma-patterns.md +241 -0
  129. package/assets/skills/prompt-engineering.md +193 -0
  130. package/assets/skills/pwa-patterns.md +247 -0
  131. package/assets/skills/python-patterns.md +158 -0
  132. package/assets/skills/python-testing.md +172 -0
  133. package/assets/skills/queue-patterns.md +295 -0
  134. package/assets/skills/rag-patterns.md +159 -0
  135. package/assets/skills/rate-limiting.md +319 -0
  136. package/assets/skills/react-components.md +201 -0
  137. package/assets/skills/react-native-patterns.md +299 -0
  138. package/assets/skills/real-time-patterns.md +181 -0
  139. package/assets/skills/redis-patterns.md +188 -0
  140. package/assets/skills/refactoring.md +218 -0
  141. package/assets/skills/regex-patterns.md +191 -0
  142. package/assets/skills/remix-patterns.md +262 -0
  143. package/assets/skills/responsive-design.md +199 -0
  144. package/assets/skills/ruby-rails-patterns.md +178 -0
  145. package/assets/skills/rust-patterns.md +211 -0
  146. package/assets/skills/search-patterns.md +227 -0
  147. package/assets/skills/security-hardening.md +237 -0
  148. package/assets/skills/seo-patterns.md +179 -0
  149. package/assets/skills/serverless-patterns.md +223 -0
  150. package/assets/skills/sql-optimization.md +154 -0
  151. package/assets/skills/state-management.md +254 -0
  152. package/assets/skills/storybook-patterns.md +330 -0
  153. package/assets/skills/svelte-patterns.md +258 -0
  154. package/assets/skills/swift-patterns.md +227 -0
  155. package/assets/skills/tailwind-patterns.md +272 -0
  156. package/assets/skills/tdd-workflow.md +199 -0
  157. package/assets/skills/terraform-patterns.md +270 -0
  158. package/assets/skills/testing-react.md +240 -0
  159. package/assets/skills/testing-vitest.md +232 -0
  160. package/assets/skills/typescript-strict.md +159 -0
  161. package/assets/skills/video-processing.md +340 -0
  162. package/assets/skills/vue-patterns.md +247 -0
  163. package/assets/skills/web-workers.md +327 -0
  164. package/assets/skills/webhooks-patterns.md +283 -0
  165. package/assets/skills/websocket-patterns.md +306 -0
  166. package/dist/cli/index.js +941 -958
  167. package/dist/core/index.d.ts +341 -11
  168. package/dist/core.js +58 -95
  169. package/dist/mcp/index.d.ts +33 -1
  170. package/dist/mcp-server.js +177 -255
  171. package/package.json +4 -1
@@ -0,0 +1,327 @@
1
+ ---
2
+ name: cron-patterns
3
+ description: Cron job patterns for node-cron scheduling, idempotent tasks, distributed locks, health monitoring, and graceful shutdown.
4
+ ---
5
+
6
+ # Cron Job Patterns
7
+
8
+ ## When to Use
9
+ Use cron patterns for recurring tasks: database cleanup, report generation, cache warming, health checks, subscription billing, and data synchronization. These patterns ensure jobs run reliably, do not overlap, handle failures gracefully, and work correctly in multi-instance deployments. Apply distributed locks when running multiple server instances to prevent duplicate execution.
10
+
11
+ ## How It Works
12
+
13
+ ### node-cron Scheduling
14
+
15
+ ```typescript
16
+ // src/cron/scheduler.ts
17
+ import cron from 'node-cron';
18
+
19
+ interface CronJob {
20
+ name: string;
21
+ schedule: string;
22
+ handler: () => Promise<void>;
23
+ enabled: boolean;
24
+ }
25
+
26
+ const jobs: CronJob[] = [
27
+ {
28
+ name: 'cleanup-expired-sessions',
29
+ schedule: '0 */6 * * *', // every 6 hours
30
+ handler: cleanupExpiredSessions,
31
+ enabled: true,
32
+ },
33
+ {
34
+ name: 'generate-daily-report',
35
+ schedule: '0 7 * * *', // daily at 7 AM
36
+ handler: generateDailyReport,
37
+ enabled: true,
38
+ },
39
+ {
40
+ name: 'sync-external-data',
41
+ schedule: '*/15 * * * *', // every 15 minutes
42
+ handler: syncExternalData,
43
+ enabled: true,
44
+ },
45
+ {
46
+ name: 'billing-check',
47
+ schedule: '0 0 1 * *', // first of every month
48
+ handler: processBilling,
49
+ enabled: true,
50
+ },
51
+ ];
52
+
53
+ const scheduledTasks: cron.ScheduledTask[] = [];
54
+
55
+ export function startScheduler() {
56
+ for (const job of jobs) {
57
+ if (!job.enabled) continue;
58
+
59
+ if (!cron.validate(job.schedule)) {
60
+ console.error(`Invalid cron schedule for ${job.name}: ${job.schedule}`);
61
+ continue;
62
+ }
63
+
64
+ const task = cron.schedule(job.schedule, async () => {
65
+ const start = Date.now();
66
+ console.log(`[cron] Starting: ${job.name}`);
67
+
68
+ try {
69
+ await job.handler();
70
+ console.log(`[cron] Completed: ${job.name} (${Date.now() - start}ms)`);
71
+ } catch (err) {
72
+ console.error(`[cron] Failed: ${job.name}`, err);
73
+ await reportCronFailure(job.name, err as Error);
74
+ }
75
+ }, {
76
+ timezone: 'UTC',
77
+ runOnInit: false,
78
+ });
79
+
80
+ scheduledTasks.push(task);
81
+ console.log(`[cron] Registered: ${job.name} [${job.schedule}]`);
82
+ }
83
+ }
84
+
85
+ export function stopScheduler() {
86
+ scheduledTasks.forEach((task) => task.stop());
87
+ console.log(`[cron] Stopped ${scheduledTasks.length} tasks`);
88
+ }
89
+ ```
90
+
91
+ ### Idempotent Job Execution
92
+
93
+ ```typescript
94
+ // src/cron/idempotent.ts
95
+ import { db } from '../lib/db';
96
+
97
+ interface JobRun {
98
+ jobName: string;
99
+ runId: string;
100
+ startedAt: Date;
101
+ completedAt?: Date;
102
+ status: 'running' | 'completed' | 'failed';
103
+ result?: string;
104
+ }
105
+
106
+ async function runIdempotent(jobName: string, handler: () => Promise<string>): Promise<void> {
107
+ const runId = `${jobName}-${new Date().toISOString().split('T')[0]}`;
108
+
109
+ // Check if already run today
110
+ const existing = await db.query(
111
+ 'SELECT status FROM cron_runs WHERE run_id = $1',
112
+ [runId]
113
+ );
114
+
115
+ if (existing.rows.length > 0 && existing.rows[0].status === 'completed') {
116
+ console.log(`[cron] ${jobName} already completed for this period, skipping`);
117
+ return;
118
+ }
119
+
120
+ // Record start
121
+ await db.query(
122
+ `INSERT INTO cron_runs (job_name, run_id, started_at, status)
123
+ VALUES ($1, $2, NOW(), 'running')
124
+ ON CONFLICT (run_id) DO UPDATE SET started_at = NOW(), status = 'running'`,
125
+ [jobName, runId]
126
+ );
127
+
128
+ try {
129
+ const result = await handler();
130
+ await db.query(
131
+ `UPDATE cron_runs SET completed_at = NOW(), status = 'completed', result = $1
132
+ WHERE run_id = $2`,
133
+ [result, runId]
134
+ );
135
+ } catch (err) {
136
+ await db.query(
137
+ `UPDATE cron_runs SET completed_at = NOW(), status = 'failed', result = $1
138
+ WHERE run_id = $2`,
139
+ [(err as Error).message, runId]
140
+ );
141
+ throw err;
142
+ }
143
+ }
144
+
145
+ // Usage
146
+ async function generateDailyReport() {
147
+ await runIdempotent('daily-report', async () => {
148
+ const data = await aggregateMetrics();
149
+ await saveReport(data);
150
+ return `Generated report with ${data.totalRecords} records`;
151
+ });
152
+ }
153
+ ```
154
+
155
+ ### Distributed Lock (Redis)
156
+
157
+ ```typescript
158
+ // src/cron/distributed-lock.ts
159
+ import Redis from 'ioredis';
160
+
161
+ const redis = new Redis(process.env.REDIS_URL!);
162
+
163
+ export async function withDistributedLock<T>(
164
+ lockName: string,
165
+ ttlMs: number,
166
+ fn: () => Promise<T>
167
+ ): Promise<T | null> {
168
+ const lockKey = `lock:cron:${lockName}`;
169
+ const lockValue = `${process.pid}:${Date.now()}`;
170
+
171
+ // Acquire lock (NX = only if not exists, PX = TTL in ms)
172
+ const acquired = await redis.set(lockKey, lockValue, 'PX', ttlMs, 'NX');
173
+
174
+ if (!acquired) {
175
+ console.log(`[cron] Lock "${lockName}" held by another instance, skipping`);
176
+ return null;
177
+ }
178
+
179
+ try {
180
+ const result = await fn();
181
+ return result;
182
+ } finally {
183
+ // Release lock only if we still own it (Lua script for atomicity)
184
+ await redis.eval(
185
+ `if redis.call("get", KEYS[1]) == ARGV[1] then
186
+ return redis.call("del", KEYS[1])
187
+ else
188
+ return 0
189
+ end`,
190
+ 1, lockKey, lockValue
191
+ );
192
+ }
193
+ }
194
+
195
+ // Usage: only one instance runs cleanup at a time
196
+ async function cleanupExpiredSessions() {
197
+ await withDistributedLock('cleanup-sessions', 300_000, async () => {
198
+ const deleted = await db.query(
199
+ 'DELETE FROM sessions WHERE expires_at < NOW() RETURNING id'
200
+ );
201
+ console.log(`Cleaned up ${deleted.rowCount} expired sessions`);
202
+ });
203
+ }
204
+ ```
205
+
206
+ ### Overlap Prevention
207
+
208
+ ```typescript
209
+ // src/cron/no-overlap.ts
210
+ const runningJobs = new Set<string>();
211
+
212
+ function withNoOverlap(jobName: string, handler: () => Promise<void>) {
213
+ return async () => {
214
+ if (runningJobs.has(jobName)) {
215
+ console.log(`[cron] ${jobName} still running, skipping this invocation`);
216
+ return;
217
+ }
218
+
219
+ runningJobs.add(jobName);
220
+ try {
221
+ await handler();
222
+ } finally {
223
+ runningJobs.delete(jobName);
224
+ }
225
+ };
226
+ }
227
+
228
+ // Register with overlap guard
229
+ cron.schedule('*/5 * * * *', withNoOverlap('sync-data', async () => {
230
+ // This might take 3-10 minutes
231
+ await syncAllRecords();
232
+ }));
233
+ ```
234
+
235
+ ### Health Monitoring
236
+
237
+ ```typescript
238
+ // src/cron/health.ts
239
+ interface CronHealth {
240
+ name: string;
241
+ lastRun: string | null;
242
+ lastStatus: 'completed' | 'failed' | null;
243
+ nextRun: string;
244
+ overdue: boolean;
245
+ }
246
+
247
+ export async function getCronHealth(): Promise<CronHealth[]> {
248
+ const results: CronHealth[] = [];
249
+
250
+ for (const job of jobs) {
251
+ const lastRun = await db.query(
252
+ 'SELECT started_at, status FROM cron_runs WHERE job_name = $1 ORDER BY started_at DESC LIMIT 1',
253
+ [job.name]
254
+ );
255
+
256
+ const expression = cron.validate(job.schedule) ? job.schedule : null;
257
+ const interval = expression ? cron.getTasks().get(job.name) : null;
258
+
259
+ results.push({
260
+ name: job.name,
261
+ lastRun: lastRun.rows[0]?.started_at?.toISOString() ?? null,
262
+ lastStatus: lastRun.rows[0]?.status ?? null,
263
+ nextRun: getNextCronDate(job.schedule).toISOString(),
264
+ overdue: isOverdue(job.schedule, lastRun.rows[0]?.started_at),
265
+ });
266
+ }
267
+
268
+ return results;
269
+ }
270
+
271
+ function isOverdue(schedule: string, lastRun?: Date): boolean {
272
+ if (!lastRun) return true;
273
+ const expected = getNextCronDate(schedule, lastRun);
274
+ return new Date() > new Date(expected.getTime() + 60_000); // 1min grace
275
+ }
276
+
277
+ // API endpoint
278
+ app.get('/admin/cron/health', async (_req, res) => {
279
+ const health = await getCronHealth();
280
+ const allHealthy = health.every((h) => !h.overdue && h.lastStatus !== 'failed');
281
+ res.status(allHealthy ? 200 : 503).json({ jobs: health });
282
+ });
283
+ ```
284
+
285
+ ### Graceful Shutdown
286
+
287
+ ```typescript
288
+ // src/index.ts
289
+ import { startScheduler, stopScheduler } from './cron/scheduler';
290
+
291
+ startScheduler();
292
+
293
+ process.on('SIGTERM', async () => {
294
+ console.log('SIGTERM received, shutting down gracefully...');
295
+ stopScheduler();
296
+ // Wait for running jobs to finish (max 30s)
297
+ const deadline = Date.now() + 30_000;
298
+ while (runningJobs.size > 0 && Date.now() < deadline) {
299
+ await new Promise((r) => setTimeout(r, 1000));
300
+ }
301
+ if (runningJobs.size > 0) {
302
+ console.warn(`Forcing shutdown with ${runningJobs.size} jobs still running`);
303
+ }
304
+ process.exit(0);
305
+ });
306
+ ```
307
+
308
+ ## Examples
309
+
310
+ | Schedule | Cron Expression | Description |
311
+ |----------|----------------|-------------|
312
+ | Every minute | `* * * * *` | Heartbeat, queue polling |
313
+ | Every 15 min | `*/15 * * * *` | Data sync, cache refresh |
314
+ | Hourly | `0 * * * *` | Metrics aggregation |
315
+ | Daily 7 AM | `0 7 * * *` | Reports, digests |
316
+ | Weekly Monday | `0 0 * * 1` | Cleanup, archival |
317
+ | Monthly 1st | `0 0 1 * *` | Billing, invoicing |
318
+
319
+ ## Checklist
320
+ - [ ] All cron expressions validated at startup with `cron.validate()`
321
+ - [ ] Jobs are idempotent (safe to re-run without side effects)
322
+ - [ ] Distributed lock prevents duplicate execution across instances
323
+ - [ ] Overlap guard prevents concurrent runs of the same job
324
+ - [ ] Job runs recorded in database with status, timing, and result
325
+ - [ ] Health endpoint reports last run, status, and overdue state
326
+ - [ ] Graceful shutdown waits for running jobs before exiting
327
+ - [ ] Failed jobs trigger alerts to on-call
@@ -0,0 +1,231 @@
1
+ ---
2
+ name: data-fetching
3
+ description: Data fetching patterns with SWR/TanStack Query, suspense, pagination, infinite scroll, and prefetching.
4
+ ---
5
+
6
+ # Data Fetching
7
+
8
+ ## When to Use
9
+ Apply when building React applications that consume API data. Use TanStack Query (React Query) or SWR for caching, deduplication, and background refetching. These patterns eliminate boilerplate, prevent waterfalls, and keep the UI in sync with server state.
10
+
11
+ ## How It Works
12
+
13
+ ### TanStack Query -- The Standard
14
+
15
+ ```typescript
16
+ import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
17
+
18
+ // Fetch with automatic caching, refetching, and error handling
19
+ function UserProfile({ userId }: { userId: string }) {
20
+ const { data: user, isLoading, error } = useQuery({
21
+ queryKey: ["user", userId],
22
+ queryFn: () => fetch(`/api/users/${userId}`).then((r) => r.json()),
23
+ staleTime: 5 * 60 * 1000, // consider fresh for 5 minutes
24
+ gcTime: 30 * 60 * 1000, // keep in cache for 30 minutes
25
+ });
26
+
27
+ if (isLoading) return <Skeleton />;
28
+ if (error) return <ErrorMessage error={error} />;
29
+ return <div>{user.name}</div>;
30
+ }
31
+
32
+ // Mutation with optimistic update and cache invalidation
33
+ function useUpdateUser() {
34
+ const queryClient = useQueryClient();
35
+
36
+ return useMutation({
37
+ mutationFn: (data: { id: string; name: string }) =>
38
+ fetch(`/api/users/${data.id}`, {
39
+ method: "PATCH",
40
+ headers: { "Content-Type": "application/json" },
41
+ body: JSON.stringify(data),
42
+ }).then((r) => r.json()),
43
+
44
+ onMutate: async (newData) => {
45
+ await queryClient.cancelQueries({ queryKey: ["user", newData.id] });
46
+ const previous = queryClient.getQueryData(["user", newData.id]);
47
+ queryClient.setQueryData(["user", newData.id], (old: any) => ({
48
+ ...old,
49
+ ...newData,
50
+ }));
51
+ return { previous };
52
+ },
53
+ onError: (_err, newData, context) => {
54
+ queryClient.setQueryData(["user", newData.id], context?.previous);
55
+ },
56
+ onSettled: (_data, _err, variables) => {
57
+ queryClient.invalidateQueries({ queryKey: ["user", variables.id] });
58
+ },
59
+ });
60
+ }
61
+ ```
62
+
63
+ ### SWR -- Lightweight Alternative
64
+
65
+ ```typescript
66
+ import useSWR from "swr";
67
+
68
+ const fetcher = (url: string) => fetch(url).then((r) => r.json());
69
+
70
+ function Dashboard() {
71
+ const { data, error, isLoading, mutate } = useSWR("/api/dashboard", fetcher, {
72
+ refreshInterval: 30_000, // poll every 30s
73
+ revalidateOnFocus: true, // refetch when tab regains focus
74
+ dedupingInterval: 2000, // dedupe requests within 2s
75
+ });
76
+
77
+ // Optimistic update
78
+ const handleRefresh = () => {
79
+ mutate(
80
+ fetch("/api/dashboard").then((r) => r.json()),
81
+ { optimisticData: { ...data, lastRefresh: new Date() } }
82
+ );
83
+ };
84
+
85
+ if (isLoading) return <Skeleton />;
86
+ if (error) return <ErrorMessage error={error} />;
87
+ return <DashboardView data={data} onRefresh={handleRefresh} />;
88
+ }
89
+ ```
90
+
91
+ ### Cursor-Based Pagination
92
+
93
+ ```typescript
94
+ function useOrders() {
95
+ return useInfiniteQuery({
96
+ queryKey: ["orders"],
97
+ queryFn: ({ pageParam }) =>
98
+ fetch(`/api/orders?cursor=${pageParam ?? ""}&limit=20`).then((r) => r.json()),
99
+ initialPageParam: undefined as string | undefined,
100
+ getNextPageParam: (lastPage) => lastPage.nextCursor ?? undefined,
101
+ });
102
+ }
103
+
104
+ function OrderList() {
105
+ const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useOrders();
106
+
107
+ return (
108
+ <div>
109
+ {data?.pages.flatMap((page) =>
110
+ page.orders.map((order: Order) => (
111
+ <OrderCard key={order.id} order={order} />
112
+ ))
113
+ )}
114
+ {hasNextPage && (
115
+ <button onClick={() => fetchNextPage()} disabled={isFetchingNextPage}>
116
+ {isFetchingNextPage ? "Loading..." : "Load More"}
117
+ </button>
118
+ )}
119
+ </div>
120
+ );
121
+ }
122
+ ```
123
+
124
+ ### Infinite Scroll with Intersection Observer
125
+
126
+ ```typescript
127
+ function InfiniteOrderList() {
128
+ const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useOrders();
129
+ const loadMoreRef = useRef<HTMLDivElement>(null);
130
+
131
+ useEffect(() => {
132
+ const observer = new IntersectionObserver(
133
+ (entries) => {
134
+ if (entries[0].isIntersecting && hasNextPage && !isFetchingNextPage) {
135
+ fetchNextPage();
136
+ }
137
+ },
138
+ { rootMargin: "200px" }
139
+ );
140
+
141
+ if (loadMoreRef.current) observer.observe(loadMoreRef.current);
142
+ return () => observer.disconnect();
143
+ }, [fetchNextPage, hasNextPage, isFetchingNextPage]);
144
+
145
+ return (
146
+ <div>
147
+ {data?.pages.flatMap((page) =>
148
+ page.orders.map((order: Order) => (
149
+ <OrderCard key={order.id} order={order} />
150
+ ))
151
+ )}
152
+ <div ref={loadMoreRef} />
153
+ {isFetchingNextPage && <Spinner />}
154
+ </div>
155
+ );
156
+ }
157
+ ```
158
+
159
+ ### Prefetching for Instant Navigation
160
+
161
+ ```typescript
162
+ function OrderRow({ order }: { order: Order }) {
163
+ const queryClient = useQueryClient();
164
+
165
+ // Prefetch on hover so detail page loads instantly
166
+ const handleMouseEnter = () => {
167
+ queryClient.prefetchQuery({
168
+ queryKey: ["order", order.id],
169
+ queryFn: () => fetch(`/api/orders/${order.id}`).then((r) => r.json()),
170
+ staleTime: 60_000,
171
+ });
172
+ };
173
+
174
+ return (
175
+ <Link
176
+ href={`/orders/${order.id}`}
177
+ onMouseEnter={handleMouseEnter}
178
+ >
179
+ {order.id} -- ${order.total}
180
+ </Link>
181
+ );
182
+ }
183
+ ```
184
+
185
+ ### React Suspense Integration
186
+
187
+ ```typescript
188
+ import { useSuspenseQuery } from "@tanstack/react-query";
189
+ import { Suspense } from "react";
190
+
191
+ function UserProfileSuspense({ userId }: { userId: string }) {
192
+ const { data: user } = useSuspenseQuery({
193
+ queryKey: ["user", userId],
194
+ queryFn: () => fetch(`/api/users/${userId}`).then((r) => r.json()),
195
+ });
196
+
197
+ return <div>{user.name}</div>;
198
+ }
199
+
200
+ // Parent handles loading and error states
201
+ function UserPage({ userId }: { userId: string }) {
202
+ return (
203
+ <ErrorBoundary fallback={<ErrorMessage />}>
204
+ <Suspense fallback={<Skeleton />}>
205
+ <UserProfileSuspense userId={userId} />
206
+ </Suspense>
207
+ </ErrorBoundary>
208
+ );
209
+ }
210
+ ```
211
+
212
+ ## Examples
213
+
214
+ | Pattern | When | Result |
215
+ |---------|------|--------|
216
+ | TanStack Query | Most React apps | Caching, dedup, background refetch |
217
+ | SWR | Simpler needs, smaller bundle | Stale-while-revalidate out of the box |
218
+ | Cursor pagination | Large lists | Consistent results, no offset drift |
219
+ | Infinite scroll | Social feeds, logs | Seamless loading as user scrolls |
220
+ | Prefetch on hover | Detail pages | Instant navigation, zero loading |
221
+ | Suspense | Streaming SSR | Declarative loading states |
222
+
223
+ ## Checklist
224
+ - [ ] All API data fetched through TanStack Query or SWR, not raw useEffect
225
+ - [ ] `staleTime` and `gcTime` configured per query based on data freshness needs
226
+ - [ ] Mutations invalidate related queries for cache consistency
227
+ - [ ] Pagination uses cursor-based approach for large datasets
228
+ - [ ] Infinite scroll uses Intersection Observer with rootMargin
229
+ - [ ] Detail pages prefetched on hover for instant navigation
230
+ - [ ] Error boundaries wrap Suspense-based data fetching
231
+ - [ ] Loading skeletons match the shape of the loaded content