@smicolon/ai-kit 0.1.0 → 0.1.1

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 (162) hide show
  1. package/.claude-plugin/CLAUDE.md +7 -0
  2. package/.claude-plugin/marketplace.json +373 -0
  3. package/package.json +4 -3
  4. package/packs/architect/CHANGELOG.md +17 -0
  5. package/packs/architect/README.md +58 -0
  6. package/packs/architect/agents/system-architect.md +768 -0
  7. package/packs/architect/commands/diagram-create.md +300 -0
  8. package/packs/better-auth/.claude-plugin/plugin.json +14 -0
  9. package/packs/better-auth/.mcp.json +14 -0
  10. package/packs/better-auth/CHANGELOG.md +26 -0
  11. package/packs/better-auth/README.md +125 -0
  12. package/packs/better-auth/agents/auth-architect.md +278 -0
  13. package/packs/better-auth/commands/auth-provider-add.md +265 -0
  14. package/packs/better-auth/commands/auth-setup.md +298 -0
  15. package/packs/better-auth/skills/auth-security/SKILL.md +425 -0
  16. package/packs/better-auth/skills/better-auth-patterns/SKILL.md +455 -0
  17. package/packs/dev-loop/.claude-plugin/plugin.json +10 -0
  18. package/packs/dev-loop/CHANGELOG.md +69 -0
  19. package/packs/dev-loop/README.md +155 -0
  20. package/packs/dev-loop/commands/cancel-dev.md +21 -0
  21. package/packs/dev-loop/commands/dev-loop.md +72 -0
  22. package/packs/dev-loop/commands/dev-plan.md +351 -0
  23. package/packs/dev-loop/hooks/hooks.json +15 -0
  24. package/packs/dev-loop/hooks/stop-hook.sh +178 -0
  25. package/packs/dev-loop/scripts/setup-dev-loop.sh +194 -0
  26. package/packs/dev-loop/skills/tdd-planner/SKILL.md +249 -0
  27. package/packs/dev-loop/skills/tdd-planner/references/framework-patterns.md +874 -0
  28. package/packs/dev-loop/skills/tdd-planner/references/good-example.md +260 -0
  29. package/packs/dev-loop/skills/tdd-planner/references/plan-template.md +275 -0
  30. package/packs/django/CHANGELOG.md +39 -0
  31. package/packs/django/README.md +92 -0
  32. package/packs/django/agents/django-architect.md +182 -0
  33. package/packs/django/agents/django-builder.md +250 -0
  34. package/packs/django/agents/django-feature-based.md +420 -0
  35. package/packs/django/agents/django-reviewer.md +253 -0
  36. package/packs/django/agents/django-tester.md +230 -0
  37. package/packs/django/commands/api-endpoint.md +285 -0
  38. package/packs/django/commands/model-create.md +178 -0
  39. package/packs/django/commands/test-generate.md +325 -0
  40. package/packs/django/rules/migrations.md +138 -0
  41. package/packs/django/rules/models.md +167 -0
  42. package/packs/django/rules/serializers.md +126 -0
  43. package/packs/django/rules/services.md +131 -0
  44. package/packs/django/rules/tests.md +140 -0
  45. package/packs/django/rules/views.md +102 -0
  46. package/packs/django/skills/import-convention-enforcer/SKILL.md +226 -0
  47. package/packs/django/skills/import-convention-enforcer/patterns/django-imports.md +343 -0
  48. package/packs/django/skills/migration-safety-checker/SKILL.md +375 -0
  49. package/packs/django/skills/model-entity-validator/SKILL.md +298 -0
  50. package/packs/django/skills/performance-optimizer/SKILL.md +447 -0
  51. package/packs/django/skills/red-phase-verifier/SKILL.md +180 -0
  52. package/packs/django/skills/security-first-validator/SKILL.md +435 -0
  53. package/packs/django/skills/test-coverage-advisor/SKILL.md +394 -0
  54. package/packs/django/skills/test-validity-checker/SKILL.md +194 -0
  55. package/packs/failure-log/.claude-plugin/plugin.json +14 -0
  56. package/packs/failure-log/CHANGELOG.md +20 -0
  57. package/packs/failure-log/README.md +168 -0
  58. package/packs/failure-log/commands/failure-add.md +106 -0
  59. package/packs/failure-log/commands/failure-list.md +89 -0
  60. package/packs/failure-log/hooks/hooks.json +16 -0
  61. package/packs/failure-log/hooks/scripts/inject-failures.sh +64 -0
  62. package/packs/failure-log/skills/failure-log-manager/SKILL.md +164 -0
  63. package/packs/flutter/.claude-plugin/plugin.json +10 -0
  64. package/packs/flutter/CHANGELOG.md +19 -0
  65. package/packs/flutter/README.md +170 -0
  66. package/packs/flutter/agents/flutter-architect.md +166 -0
  67. package/packs/flutter/agents/flutter-builder.md +303 -0
  68. package/packs/flutter/agents/release-manager.md +355 -0
  69. package/packs/flutter/commands/fastlane-setup.md +188 -0
  70. package/packs/flutter/commands/flutter-build.md +90 -0
  71. package/packs/flutter/commands/flutter-deploy.md +133 -0
  72. package/packs/flutter/commands/flutter-test.md +117 -0
  73. package/packs/flutter/commands/signing-setup.md +209 -0
  74. package/packs/flutter/hooks/hooks.json +17 -0
  75. package/packs/flutter/skills/fastlane-knowledge/SKILL.md +193 -0
  76. package/packs/flutter/skills/flutter-architecture/SKILL.md +127 -0
  77. package/packs/flutter/skills/store-publishing/SKILL.md +163 -0
  78. package/packs/hono/.claude-plugin/plugin.json +19 -0
  79. package/packs/hono/CHANGELOG.md +19 -0
  80. package/packs/hono/README.md +143 -0
  81. package/packs/hono/agents/hono-architect.md +240 -0
  82. package/packs/hono/agents/hono-builder.md +285 -0
  83. package/packs/hono/agents/hono-reviewer.md +279 -0
  84. package/packs/hono/agents/hono-tester.md +346 -0
  85. package/packs/hono/commands/middleware-create.md +223 -0
  86. package/packs/hono/commands/project-init.md +306 -0
  87. package/packs/hono/commands/route-create.md +153 -0
  88. package/packs/hono/commands/rpc-client.md +263 -0
  89. package/packs/hono/hooks/hooks.json +4 -0
  90. package/packs/hono/skills/cloudflare-bindings/SKILL.md +408 -0
  91. package/packs/hono/skills/hono-patterns/SKILL.md +309 -0
  92. package/packs/hono/skills/rpc-typesafe/SKILL.md +388 -0
  93. package/packs/hono/skills/zod-validation/SKILL.md +332 -0
  94. package/packs/nestjs/CHANGELOG.md +29 -0
  95. package/packs/nestjs/README.md +75 -0
  96. package/packs/nestjs/agents/nestjs-architect.md +402 -0
  97. package/packs/nestjs/agents/nestjs-builder.md +301 -0
  98. package/packs/nestjs/agents/nestjs-tester.md +437 -0
  99. package/packs/nestjs/commands/module-create.md +369 -0
  100. package/packs/nestjs/rules/controllers.md +92 -0
  101. package/packs/nestjs/rules/dto.md +124 -0
  102. package/packs/nestjs/rules/entities.md +102 -0
  103. package/packs/nestjs/rules/services.md +106 -0
  104. package/packs/nestjs/skills/barrel-export-manager/SKILL.md +389 -0
  105. package/packs/nestjs/skills/import-convention-enforcer/SKILL.md +365 -0
  106. package/packs/nextjs/CHANGELOG.md +36 -0
  107. package/packs/nextjs/README.md +76 -0
  108. package/packs/nextjs/agents/frontend-tester.md +680 -0
  109. package/packs/nextjs/agents/frontend-visual.md +820 -0
  110. package/packs/nextjs/agents/nextjs-architect.md +331 -0
  111. package/packs/nextjs/agents/nextjs-modular.md +433 -0
  112. package/packs/nextjs/commands/component-create.md +398 -0
  113. package/packs/nextjs/rules/api-routes.md +129 -0
  114. package/packs/nextjs/rules/components.md +106 -0
  115. package/packs/nextjs/rules/hooks.md +132 -0
  116. package/packs/nextjs/skills/accessibility-validator/SKILL.md +445 -0
  117. package/packs/nextjs/skills/import-convention-enforcer/SKILL.md +399 -0
  118. package/packs/nextjs/skills/react-form-validator/SKILL.md +569 -0
  119. package/packs/nuxtjs/CHANGELOG.md +30 -0
  120. package/packs/nuxtjs/README.md +56 -0
  121. package/packs/nuxtjs/agents/frontend-tester.md +680 -0
  122. package/packs/nuxtjs/agents/frontend-visual.md +820 -0
  123. package/packs/nuxtjs/agents/nuxtjs-architect.md +537 -0
  124. package/packs/nuxtjs/commands/component-create.md +223 -0
  125. package/packs/nuxtjs/rules/components.md +101 -0
  126. package/packs/nuxtjs/rules/composables.md +118 -0
  127. package/packs/nuxtjs/rules/server-routes.md +127 -0
  128. package/packs/nuxtjs/skills/accessibility-validator/SKILL.md +183 -0
  129. package/packs/nuxtjs/skills/import-convention-enforcer/SKILL.md +196 -0
  130. package/packs/nuxtjs/skills/veevalidate-form-validator/SKILL.md +190 -0
  131. package/packs/onboard/CHANGELOG.md +22 -0
  132. package/packs/onboard/README.md +103 -0
  133. package/packs/onboard/agents/onboard-guide.md +118 -0
  134. package/packs/onboard/commands/onboard.md +313 -0
  135. package/packs/onboard/skills/onboard-context-provider/SKILL.md +98 -0
  136. package/packs/tanstack-router/.claude-plugin/plugin.json +14 -0
  137. package/packs/tanstack-router/CHANGELOG.md +30 -0
  138. package/packs/tanstack-router/README.md +113 -0
  139. package/packs/tanstack-router/agents/tanstack-architect.md +173 -0
  140. package/packs/tanstack-router/agents/tanstack-builder.md +360 -0
  141. package/packs/tanstack-router/agents/tanstack-tester.md +454 -0
  142. package/packs/tanstack-router/commands/form-create.md +313 -0
  143. package/packs/tanstack-router/commands/query-create.md +263 -0
  144. package/packs/tanstack-router/commands/route-create.md +190 -0
  145. package/packs/tanstack-router/commands/table-create.md +413 -0
  146. package/packs/tanstack-router/skills/ai-patterns/SKILL.md +370 -0
  147. package/packs/tanstack-router/skills/db-patterns/SKILL.md +346 -0
  148. package/packs/tanstack-router/skills/devtools-patterns/SKILL.md +415 -0
  149. package/packs/tanstack-router/skills/form-patterns/SKILL.md +425 -0
  150. package/packs/tanstack-router/skills/pacer-patterns/SKILL.md +341 -0
  151. package/packs/tanstack-router/skills/query-patterns/SKILL.md +359 -0
  152. package/packs/tanstack-router/skills/router-patterns/SKILL.md +285 -0
  153. package/packs/tanstack-router/skills/store-patterns/SKILL.md +351 -0
  154. package/packs/tanstack-router/skills/table-patterns/SKILL.md +531 -0
  155. package/packs/tanstack-router/skills/tanstack-conventions/SKILL.md +428 -0
  156. package/packs/tanstack-router/skills/virtual-patterns/SKILL.md +490 -0
  157. package/packs/worktree/.claude-plugin/plugin.json +19 -0
  158. package/packs/worktree/CHANGELOG.md +24 -0
  159. package/packs/worktree/README.md +110 -0
  160. package/packs/worktree/commands/wt.md +73 -0
  161. package/packs/worktree/scripts/wt.sh +396 -0
  162. package/packs/worktree/skills/worktree-manager/SKILL.md +68 -0
@@ -0,0 +1,341 @@
1
+ ---
2
+ name: TanStack Pacer Patterns (Beta)
3
+ description: >-
4
+ TanStack Pacer patterns for rate limiting, debouncing, and throttling.
5
+ Activates when implementing search inputs, API rate limiting, or
6
+ performance optimization. NOTE: Beta library - API may change.
7
+ version: 1.0.0
8
+ ---
9
+
10
+ # TanStack Pacer Patterns (Beta)
11
+
12
+ > **Beta Library**: TanStack Pacer is in beta. APIs may change between versions.
13
+
14
+ TanStack Pacer provides utilities for rate limiting, debouncing, throttling, and async queuing.
15
+
16
+ ## Debounce
17
+
18
+ Delay execution until input stops for a specified time.
19
+
20
+ ### Basic Debounce
21
+ ```typescript
22
+ import { debounce } from '@tanstack/pacer'
23
+
24
+ const debouncedSearch = debounce((query: string) => {
25
+ console.log('Searching:', query)
26
+ return fetch(`/api/search?q=${query}`)
27
+ }, 300)
28
+
29
+ // Only executes after 300ms of no calls
30
+ debouncedSearch('h')
31
+ debouncedSearch('he')
32
+ debouncedSearch('hel')
33
+ debouncedSearch('hell')
34
+ debouncedSearch('hello') // Only this executes
35
+ ```
36
+
37
+ ### Debounce in React
38
+ ```typescript
39
+ import { useDebounce } from '@tanstack/pacer-react'
40
+ import { useState } from 'react'
41
+
42
+ function SearchInput() {
43
+ const [query, setQuery] = useState('')
44
+
45
+ const debouncedQuery = useDebounce(query, 300)
46
+
47
+ // Use debouncedQuery for API calls
48
+ const { data } = useQuery({
49
+ queryKey: ['search', debouncedQuery],
50
+ queryFn: () => searchApi.search(debouncedQuery),
51
+ enabled: debouncedQuery.length > 0,
52
+ })
53
+
54
+ return (
55
+ <div>
56
+ <input
57
+ value={query}
58
+ onChange={(e) => setQuery(e.target.value)}
59
+ placeholder="Search..."
60
+ />
61
+ {data && <SearchResults results={data} />}
62
+ </div>
63
+ )
64
+ }
65
+ ```
66
+
67
+ ### Debounced Callback
68
+ ```typescript
69
+ import { useDebouncedCallback } from '@tanstack/pacer-react'
70
+
71
+ function AutoSaveEditor({ postId }: { postId: string }) {
72
+ const [content, setContent] = useState('')
73
+ const updatePost = useUpdatePost()
74
+
75
+ const saveContent = useDebouncedCallback(
76
+ async (content: string) => {
77
+ await updatePost.mutateAsync({ id: postId, content })
78
+ },
79
+ 1000, // Save after 1s of no typing
80
+ { maxWait: 5000 } // But at least every 5s
81
+ )
82
+
83
+ const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
84
+ const newContent = e.target.value
85
+ setContent(newContent)
86
+ saveContent(newContent)
87
+ }
88
+
89
+ return (
90
+ <textarea value={content} onChange={handleChange} />
91
+ )
92
+ }
93
+ ```
94
+
95
+ ## Throttle
96
+
97
+ Limit execution to at most once per interval.
98
+
99
+ ### Basic Throttle
100
+ ```typescript
101
+ import { throttle } from '@tanstack/pacer'
102
+
103
+ const throttledScroll = throttle((position: number) => {
104
+ console.log('Scroll position:', position)
105
+ }, 100)
106
+
107
+ // Executes at most once every 100ms
108
+ window.addEventListener('scroll', () => {
109
+ throttledScroll(window.scrollY)
110
+ })
111
+ ```
112
+
113
+ ### Throttle in React
114
+ ```typescript
115
+ import { useThrottledCallback } from '@tanstack/pacer-react'
116
+ import { useEffect } from 'react'
117
+
118
+ function ScrollTracker() {
119
+ const trackScroll = useThrottledCallback(
120
+ (position: number) => {
121
+ analytics.track('scroll', { position })
122
+ },
123
+ 500
124
+ )
125
+
126
+ useEffect(() => {
127
+ const handleScroll = () => trackScroll(window.scrollY)
128
+ window.addEventListener('scroll', handleScroll)
129
+ return () => window.removeEventListener('scroll', handleScroll)
130
+ }, [trackScroll])
131
+
132
+ return null
133
+ }
134
+ ```
135
+
136
+ ### Throttled API Calls
137
+ ```typescript
138
+ import { useThrottledCallback } from '@tanstack/pacer-react'
139
+
140
+ function LiveSearch() {
141
+ const [results, setResults] = useState<Result[]>([])
142
+
143
+ const search = useThrottledCallback(
144
+ async (query: string) => {
145
+ const data = await searchApi.search(query)
146
+ setResults(data)
147
+ },
148
+ 200, // At most 5 requests per second
149
+ { leading: true, trailing: true }
150
+ )
151
+
152
+ return (
153
+ <input
154
+ onChange={(e) => search(e.target.value)}
155
+ placeholder="Search..."
156
+ />
157
+ )
158
+ }
159
+ ```
160
+
161
+ ## Rate Limiting
162
+
163
+ Control the rate of operations.
164
+
165
+ ### Rate Limiter
166
+ ```typescript
167
+ import { createRateLimiter } from '@tanstack/pacer'
168
+
169
+ const apiLimiter = createRateLimiter({
170
+ limit: 10, // 10 requests
171
+ interval: 1000, // per second
172
+ })
173
+
174
+ async function fetchData(id: string) {
175
+ await apiLimiter.acquire() // Wait if rate limited
176
+ return fetch(`/api/data/${id}`)
177
+ }
178
+
179
+ // Safe to call rapidly - will be rate limited
180
+ await Promise.all(
181
+ ids.map(id => fetchData(id))
182
+ )
183
+ ```
184
+
185
+ ### Per-Key Rate Limiting
186
+ ```typescript
187
+ import { createKeyedRateLimiter } from '@tanstack/pacer'
188
+
189
+ const userLimiter = createKeyedRateLimiter({
190
+ limit: 5,
191
+ interval: 60000, // 5 requests per minute per user
192
+ })
193
+
194
+ async function handleUserAction(userId: string, action: Action) {
195
+ const limiter = userLimiter.get(userId)
196
+ if (!limiter.tryAcquire()) {
197
+ throw new Error('Rate limited. Please try again later.')
198
+ }
199
+ return processAction(action)
200
+ }
201
+ ```
202
+
203
+ ## Async Queue
204
+
205
+ Process async operations sequentially or with concurrency limits.
206
+
207
+ ### Sequential Queue
208
+ ```typescript
209
+ import { createAsyncQueue } from '@tanstack/pacer'
210
+
211
+ const uploadQueue = createAsyncQueue({
212
+ concurrency: 1, // One at a time
213
+ })
214
+
215
+ async function uploadFiles(files: File[]) {
216
+ const results = await Promise.all(
217
+ files.map(file =>
218
+ uploadQueue.add(() => uploadFile(file))
219
+ )
220
+ )
221
+ return results
222
+ }
223
+ ```
224
+
225
+ ### Concurrent Queue with Limit
226
+ ```typescript
227
+ import { createAsyncQueue } from '@tanstack/pacer'
228
+
229
+ const processQueue = createAsyncQueue({
230
+ concurrency: 3, // Max 3 concurrent
231
+ })
232
+
233
+ function ProcessingStatus() {
234
+ const { pending, active, completed } = processQueue.status
235
+
236
+ return (
237
+ <div>
238
+ <span>Pending: {pending}</span>
239
+ <span>Active: {active}</span>
240
+ <span>Completed: {completed}</span>
241
+ </div>
242
+ )
243
+ }
244
+ ```
245
+
246
+ ### Queue with Priority
247
+ ```typescript
248
+ import { createAsyncQueue } from '@tanstack/pacer'
249
+
250
+ const taskQueue = createAsyncQueue({
251
+ concurrency: 2,
252
+ })
253
+
254
+ // Higher priority tasks run first
255
+ taskQueue.add(() => processTask('low'), { priority: 1 })
256
+ taskQueue.add(() => processTask('high'), { priority: 10 })
257
+ taskQueue.add(() => processTask('urgent'), { priority: 100 })
258
+ ```
259
+
260
+ ## Integration with TanStack Query
261
+
262
+ ```typescript
263
+ import { useDebounce } from '@tanstack/pacer-react'
264
+ import { useQuery } from '@tanstack/react-query'
265
+
266
+ function SearchWithDebounce() {
267
+ const [input, setInput] = useState('')
268
+ const debouncedInput = useDebounce(input, 300)
269
+
270
+ const { data, isLoading } = useQuery({
271
+ queryKey: ['search', debouncedInput],
272
+ queryFn: () => searchApi.search(debouncedInput),
273
+ enabled: debouncedInput.length >= 2,
274
+ })
275
+
276
+ return (
277
+ <div>
278
+ <input
279
+ value={input}
280
+ onChange={(e) => setInput(e.target.value)}
281
+ placeholder="Search (min 2 chars)..."
282
+ />
283
+ {isLoading && <Spinner />}
284
+ {data && <Results items={data} />}
285
+ </div>
286
+ )
287
+ }
288
+ ```
289
+
290
+ ## Common Use Cases
291
+
292
+ | Scenario | Solution |
293
+ |----------|----------|
294
+ | Search input | Debounce 300ms |
295
+ | Auto-save | Debounce 1s with maxWait |
296
+ | Scroll tracking | Throttle 100-200ms |
297
+ | API rate limits | Rate limiter |
298
+ | File uploads | Async queue with concurrency |
299
+ | Resize handler | Throttle 100ms |
300
+
301
+ ## Conventions
302
+
303
+ 1. **Debounce for inputs** - Always debounce search/filter inputs
304
+ 2. **Throttle for events** - Use for scroll, resize, mousemove
305
+ 3. **Rate limit APIs** - Protect against abuse
306
+ 4. **Queue heavy operations** - Use for file uploads, bulk operations
307
+ 5. **Cleanup** - Cancel pending operations on unmount
308
+
309
+ ## Anti-Patterns
310
+
311
+ ```typescript
312
+ // ❌ WRONG: No debounce on search
313
+ function Search() {
314
+ const [query, setQuery] = useState('')
315
+ const { data } = useQuery({
316
+ queryKey: ['search', query], // Fires on every keystroke!
317
+ queryFn: () => search(query),
318
+ })
319
+ }
320
+
321
+ // ✅ CORRECT: Debounced search
322
+ function Search() {
323
+ const [query, setQuery] = useState('')
324
+ const debouncedQuery = useDebounce(query, 300)
325
+ const { data } = useQuery({
326
+ queryKey: ['search', debouncedQuery],
327
+ queryFn: () => search(debouncedQuery),
328
+ enabled: debouncedQuery.length > 0,
329
+ })
330
+ }
331
+
332
+ // ❌ WRONG: Creating debounce inside render
333
+ function Component() {
334
+ const search = debounce(() => {}, 300) // New instance every render!
335
+ }
336
+
337
+ // ✅ CORRECT: Use hook
338
+ function Component() {
339
+ const search = useDebouncedCallback(() => {}, 300)
340
+ }
341
+ ```
@@ -0,0 +1,359 @@
1
+ ---
2
+ name: TanStack Query Patterns
3
+ description: >-
4
+ Auto-enforce TanStack Query best practices with factory key pattern. Activates
5
+ when creating queries, mutations, managing server state, or implementing data
6
+ fetching in React applications.
7
+ version: 1.0.0
8
+ ---
9
+
10
+ # TanStack Query Patterns
11
+
12
+ This skill enforces TanStack Query best practices for server state management in React applications.
13
+
14
+ ## Query Key Factory Pattern
15
+
16
+ The factory pattern provides type-safe, hierarchical query keys:
17
+
18
+ ```typescript
19
+ // lib/query-keys.ts
20
+ export const queryKeys = {
21
+ posts: {
22
+ all: () => ['posts'] as const,
23
+ lists: () => [...queryKeys.posts.all(), 'list'] as const,
24
+ list: (filters: PostFilters) => [...queryKeys.posts.lists(), filters] as const,
25
+ details: () => [...queryKeys.posts.all(), 'detail'] as const,
26
+ detail: (id: string) => [...queryKeys.posts.details(), id] as const,
27
+ comments: (id: string) => [...queryKeys.posts.detail(id), 'comments'] as const,
28
+ },
29
+ users: {
30
+ all: () => ['users'] as const,
31
+ detail: (id: string) => [...queryKeys.users.all(), id] as const,
32
+ profile: () => [...queryKeys.users.all(), 'profile'] as const,
33
+ },
34
+ auth: {
35
+ session: () => ['auth', 'session'] as const,
36
+ },
37
+ } as const
38
+ ```
39
+
40
+ ## Query Options Factory
41
+
42
+ Define reusable query options for consistency:
43
+
44
+ ```typescript
45
+ // features/posts/queries/postQueries.ts
46
+ import { queryOptions } from '@tanstack/react-query'
47
+ import { queryKeys } from '@/lib/query-keys'
48
+ import { postApi } from '@/features/posts/api'
49
+
50
+ export const postQueryOptions = (postId: string) =>
51
+ queryOptions({
52
+ queryKey: queryKeys.posts.detail(postId),
53
+ queryFn: () => postApi.getPost(postId),
54
+ staleTime: 5 * 60 * 1000, // 5 minutes
55
+ })
56
+
57
+ export const postsQueryOptions = (filters: PostFilters = {}) =>
58
+ queryOptions({
59
+ queryKey: queryKeys.posts.list(filters),
60
+ queryFn: () => postApi.getPosts(filters),
61
+ staleTime: 1 * 60 * 1000, // 1 minute
62
+ })
63
+
64
+ export const postCommentsQueryOptions = (postId: string) =>
65
+ queryOptions({
66
+ queryKey: queryKeys.posts.comments(postId),
67
+ queryFn: () => postApi.getPostComments(postId),
68
+ enabled: Boolean(postId),
69
+ })
70
+ ```
71
+
72
+ ## Query Client Setup
73
+
74
+ ```typescript
75
+ // lib/query-client.ts
76
+ import { QueryClient } from '@tanstack/react-query'
77
+
78
+ export function createQueryClient() {
79
+ return new QueryClient({
80
+ defaultOptions: {
81
+ queries: {
82
+ staleTime: 60 * 1000, // 1 minute
83
+ gcTime: 5 * 60 * 1000, // 5 minutes (formerly cacheTime)
84
+ retry: 1,
85
+ refetchOnWindowFocus: false,
86
+ },
87
+ mutations: {
88
+ retry: 0,
89
+ },
90
+ },
91
+ })
92
+ }
93
+ ```
94
+
95
+ ## Using Queries in Components
96
+
97
+ ### With useSuspenseQuery (Recommended with Router)
98
+ ```typescript
99
+ import { useSuspenseQuery } from '@tanstack/react-query'
100
+ import { postQueryOptions } from '@/features/posts/queries'
101
+
102
+ function PostDetail({ postId }: { postId: string }) {
103
+ // Data guaranteed by route loader, suspense handles loading
104
+ const { data: post } = useSuspenseQuery(postQueryOptions(postId))
105
+
106
+ return <article>{post.title}</article>
107
+ }
108
+ ```
109
+
110
+ ### With useQuery (Manual Loading States)
111
+ ```typescript
112
+ import { useQuery } from '@tanstack/react-query'
113
+ import { postsQueryOptions } from '@/features/posts/queries'
114
+
115
+ function PostList({ filters }: { filters: PostFilters }) {
116
+ const { data, isLoading, error } = useQuery(postsQueryOptions(filters))
117
+
118
+ if (isLoading) return <Skeleton />
119
+ if (error) return <Error error={error} />
120
+
121
+ return (
122
+ <ul>
123
+ {data.map(post => (
124
+ <PostCard key={post.id} post={post} />
125
+ ))}
126
+ </ul>
127
+ )
128
+ }
129
+ ```
130
+
131
+ ## Mutations
132
+
133
+ ### Basic Mutation Hook
134
+ ```typescript
135
+ // features/posts/hooks/useCreatePost.ts
136
+ import { useMutation, useQueryClient } from '@tanstack/react-query'
137
+ import { queryKeys } from '@/lib/query-keys'
138
+ import { postApi } from '@/features/posts/api'
139
+
140
+ export function useCreatePost() {
141
+ const queryClient = useQueryClient()
142
+
143
+ return useMutation({
144
+ mutationFn: postApi.createPost,
145
+ onSuccess: () => {
146
+ // Invalidate all post lists
147
+ queryClient.invalidateQueries({ queryKey: queryKeys.posts.lists() })
148
+ },
149
+ })
150
+ }
151
+ ```
152
+
153
+ ### Optimistic Updates
154
+ ```typescript
155
+ // features/posts/hooks/useUpdatePost.ts
156
+ import { useMutation, useQueryClient } from '@tanstack/react-query'
157
+ import { queryKeys } from '@/lib/query-keys'
158
+ import type { Post, UpdatePostInput } from '@/features/posts/types'
159
+
160
+ export function useUpdatePost() {
161
+ const queryClient = useQueryClient()
162
+
163
+ return useMutation({
164
+ mutationFn: postApi.updatePost,
165
+ onMutate: async (newPost: UpdatePostInput) => {
166
+ // Cancel outgoing refetches
167
+ await queryClient.cancelQueries({
168
+ queryKey: queryKeys.posts.detail(newPost.id)
169
+ })
170
+
171
+ // Snapshot previous value
172
+ const previous = queryClient.getQueryData<Post>(
173
+ queryKeys.posts.detail(newPost.id)
174
+ )
175
+
176
+ // Optimistically update
177
+ queryClient.setQueryData(
178
+ queryKeys.posts.detail(newPost.id),
179
+ (old: Post | undefined) => old ? { ...old, ...newPost } : undefined
180
+ )
181
+
182
+ return { previous }
183
+ },
184
+ onError: (err, newPost, context) => {
185
+ // Rollback on error
186
+ if (context?.previous) {
187
+ queryClient.setQueryData(
188
+ queryKeys.posts.detail(newPost.id),
189
+ context.previous
190
+ )
191
+ }
192
+ },
193
+ onSettled: (data, error, variables) => {
194
+ // Always refetch after error or success
195
+ queryClient.invalidateQueries({
196
+ queryKey: queryKeys.posts.detail(variables.id)
197
+ })
198
+ },
199
+ })
200
+ }
201
+ ```
202
+
203
+ ### Delete with Optimistic Update
204
+ ```typescript
205
+ export function useDeletePost() {
206
+ const queryClient = useQueryClient()
207
+
208
+ return useMutation({
209
+ mutationFn: postApi.deletePost,
210
+ onMutate: async (postId: string) => {
211
+ await queryClient.cancelQueries({ queryKey: queryKeys.posts.lists() })
212
+
213
+ const previousLists = queryClient.getQueriesData<Post[]>({
214
+ queryKey: queryKeys.posts.lists()
215
+ })
216
+
217
+ // Remove from all lists optimistically
218
+ queryClient.setQueriesData<Post[]>(
219
+ { queryKey: queryKeys.posts.lists() },
220
+ (old) => old?.filter(post => post.id !== postId)
221
+ )
222
+
223
+ return { previousLists }
224
+ },
225
+ onError: (err, postId, context) => {
226
+ context?.previousLists.forEach(([queryKey, data]) => {
227
+ queryClient.setQueryData(queryKey, data)
228
+ })
229
+ },
230
+ onSettled: () => {
231
+ queryClient.invalidateQueries({ queryKey: queryKeys.posts.all() })
232
+ },
233
+ })
234
+ }
235
+ ```
236
+
237
+ ## Prefetching
238
+
239
+ ### In Route Loaders
240
+ ```typescript
241
+ // routes/posts.tsx
242
+ export const Route = createFileRoute('/posts')({
243
+ loader: ({ context: { queryClient } }) =>
244
+ queryClient.ensureQueryData(postsQueryOptions()),
245
+ })
246
+ ```
247
+
248
+ ### On Hover/Focus
249
+ ```typescript
250
+ import { useQueryClient } from '@tanstack/react-query'
251
+ import { Link } from '@tanstack/react-router'
252
+ import { postQueryOptions } from '@/features/posts/queries'
253
+
254
+ function PostLink({ postId, title }: { postId: string; title: string }) {
255
+ const queryClient = useQueryClient()
256
+
257
+ const prefetch = () => {
258
+ queryClient.prefetchQuery(postQueryOptions(postId))
259
+ }
260
+
261
+ return (
262
+ <Link
263
+ to="/posts/$postId"
264
+ params={{ postId }}
265
+ onMouseEnter={prefetch}
266
+ onFocus={prefetch}
267
+ >
268
+ {title}
269
+ </Link>
270
+ )
271
+ }
272
+ ```
273
+
274
+ ## Infinite Queries
275
+
276
+ ```typescript
277
+ import { useInfiniteQuery } from '@tanstack/react-query'
278
+
279
+ export function useInfinitePosts(filters: PostFilters) {
280
+ return useInfiniteQuery({
281
+ queryKey: queryKeys.posts.list({ ...filters, infinite: true }),
282
+ queryFn: ({ pageParam = 1 }) =>
283
+ postApi.getPosts({ ...filters, page: pageParam }),
284
+ getNextPageParam: (lastPage, pages) =>
285
+ lastPage.hasMore ? pages.length + 1 : undefined,
286
+ initialPageParam: 1,
287
+ })
288
+ }
289
+ ```
290
+
291
+ ## Dependent Queries
292
+
293
+ ```typescript
294
+ function PostWithAuthor({ postId }: { postId: string }) {
295
+ const { data: post } = useQuery(postQueryOptions(postId))
296
+
297
+ const { data: author } = useQuery({
298
+ ...userQueryOptions(post?.authorId ?? ''),
299
+ enabled: Boolean(post?.authorId),
300
+ })
301
+
302
+ if (!post) return <Skeleton />
303
+
304
+ return (
305
+ <article>
306
+ <h1>{post.title}</h1>
307
+ {author && <p>By {author.name}</p>}
308
+ </article>
309
+ )
310
+ }
311
+ ```
312
+
313
+ ## Conventions to Enforce
314
+
315
+ 1. **Factory pattern for keys** - All query keys through `queryKeys` factory
316
+ 2. **Query options factories** - Define in `features/*/queries/` directories
317
+ 3. **Invalidate by hierarchy** - Use `queryKeys.posts.lists()` to invalidate all lists
318
+ 4. **Optimistic updates** - Always include rollback logic
319
+ 5. **Enable conditional queries** - Use `enabled` option for dependent queries
320
+ 6. **Proper staleTime** - Set based on data freshness requirements
321
+ 7. **Use gcTime not cacheTime** - Renamed in v5
322
+
323
+ ## Anti-Patterns to Block
324
+
325
+ ```typescript
326
+ // ❌ WRONG: String query keys
327
+ useQuery({ queryKey: ['posts', postId] })
328
+
329
+ // ✅ CORRECT: Factory pattern
330
+ useQuery(postQueryOptions(postId))
331
+
332
+ // ❌ WRONG: Inline query function
333
+ useQuery({
334
+ queryKey: queryKeys.posts.detail(postId),
335
+ queryFn: async () => {
336
+ const res = await fetch(`/api/posts/${postId}`)
337
+ return res.json()
338
+ }
339
+ })
340
+
341
+ // ✅ CORRECT: Extracted to API layer
342
+ useQuery(postQueryOptions(postId))
343
+
344
+ // ❌ WRONG: Manual cache updates without invalidation
345
+ onSuccess: (newPost) => {
346
+ queryClient.setQueryData(['posts', newPost.id], newPost)
347
+ }
348
+
349
+ // ✅ CORRECT: Invalidate to refetch
350
+ onSuccess: () => {
351
+ queryClient.invalidateQueries({ queryKey: queryKeys.posts.all() })
352
+ }
353
+
354
+ // ❌ WRONG: Using cacheTime (deprecated)
355
+ useQuery({ cacheTime: 5000 })
356
+
357
+ // ✅ CORRECT: Use gcTime
358
+ useQuery({ gcTime: 5000 })
359
+ ```