@smicolon/ai-kit 0.3.2 → 0.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 (155) hide show
  1. package/README.md +73 -40
  2. package/dist/index.js +260 -126
  3. package/package.json +5 -5
  4. package/.claude-plugin/marketplace.json +0 -369
  5. package/packs/architect/CHANGELOG.md +0 -17
  6. package/packs/architect/README.md +0 -58
  7. package/packs/architect/agents/system-architect.md +0 -768
  8. package/packs/architect/commands/diagram-create.md +0 -300
  9. package/packs/better-auth/.mcp.json +0 -14
  10. package/packs/better-auth/CHANGELOG.md +0 -26
  11. package/packs/better-auth/README.md +0 -125
  12. package/packs/better-auth/agents/auth-architect.md +0 -278
  13. package/packs/better-auth/commands/auth-provider-add.md +0 -265
  14. package/packs/better-auth/commands/auth-setup.md +0 -298
  15. package/packs/better-auth/skills/auth-security/SKILL.md +0 -425
  16. package/packs/better-auth/skills/better-auth-patterns/SKILL.md +0 -455
  17. package/packs/dev-loop/CHANGELOG.md +0 -69
  18. package/packs/dev-loop/README.md +0 -155
  19. package/packs/dev-loop/commands/cancel-dev.md +0 -21
  20. package/packs/dev-loop/commands/dev-loop.md +0 -72
  21. package/packs/dev-loop/commands/dev-plan.md +0 -351
  22. package/packs/dev-loop/hooks/hooks.json +0 -15
  23. package/packs/dev-loop/hooks/stop-hook.sh +0 -178
  24. package/packs/dev-loop/scripts/setup-dev-loop.sh +0 -194
  25. package/packs/dev-loop/skills/tdd-planner/SKILL.md +0 -249
  26. package/packs/dev-loop/skills/tdd-planner/references/framework-patterns.md +0 -874
  27. package/packs/dev-loop/skills/tdd-planner/references/good-example.md +0 -260
  28. package/packs/dev-loop/skills/tdd-planner/references/plan-template.md +0 -275
  29. package/packs/django/CHANGELOG.md +0 -39
  30. package/packs/django/README.md +0 -92
  31. package/packs/django/agents/django-architect.md +0 -182
  32. package/packs/django/agents/django-builder.md +0 -250
  33. package/packs/django/agents/django-feature-based.md +0 -420
  34. package/packs/django/agents/django-reviewer.md +0 -253
  35. package/packs/django/agents/django-tester.md +0 -230
  36. package/packs/django/commands/api-endpoint.md +0 -285
  37. package/packs/django/commands/model-create.md +0 -178
  38. package/packs/django/commands/test-generate.md +0 -325
  39. package/packs/django/rules/migrations.md +0 -138
  40. package/packs/django/rules/models.md +0 -167
  41. package/packs/django/rules/serializers.md +0 -126
  42. package/packs/django/rules/services.md +0 -131
  43. package/packs/django/rules/tests.md +0 -140
  44. package/packs/django/rules/views.md +0 -102
  45. package/packs/django/skills/import-convention-enforcer/SKILL.md +0 -226
  46. package/packs/django/skills/import-convention-enforcer/patterns/django-imports.md +0 -343
  47. package/packs/django/skills/migration-safety-checker/SKILL.md +0 -375
  48. package/packs/django/skills/model-entity-validator/SKILL.md +0 -298
  49. package/packs/django/skills/performance-optimizer/SKILL.md +0 -447
  50. package/packs/django/skills/red-phase-verifier/SKILL.md +0 -180
  51. package/packs/django/skills/security-first-validator/SKILL.md +0 -435
  52. package/packs/django/skills/test-coverage-advisor/SKILL.md +0 -394
  53. package/packs/django/skills/test-validity-checker/SKILL.md +0 -194
  54. package/packs/failure-log/CHANGELOG.md +0 -20
  55. package/packs/failure-log/README.md +0 -168
  56. package/packs/failure-log/commands/failure-add.md +0 -106
  57. package/packs/failure-log/commands/failure-list.md +0 -89
  58. package/packs/failure-log/hooks/hooks.json +0 -16
  59. package/packs/failure-log/hooks/scripts/inject-failures.sh +0 -64
  60. package/packs/failure-log/skills/failure-log-manager/SKILL.md +0 -164
  61. package/packs/flutter/CHANGELOG.md +0 -19
  62. package/packs/flutter/README.md +0 -170
  63. package/packs/flutter/agents/flutter-architect.md +0 -166
  64. package/packs/flutter/agents/flutter-builder.md +0 -303
  65. package/packs/flutter/agents/release-manager.md +0 -355
  66. package/packs/flutter/commands/fastlane-setup.md +0 -188
  67. package/packs/flutter/commands/flutter-build.md +0 -90
  68. package/packs/flutter/commands/flutter-deploy.md +0 -133
  69. package/packs/flutter/commands/flutter-test.md +0 -117
  70. package/packs/flutter/commands/signing-setup.md +0 -209
  71. package/packs/flutter/hooks/hooks.json +0 -17
  72. package/packs/flutter/skills/fastlane-knowledge/SKILL.md +0 -193
  73. package/packs/flutter/skills/flutter-architecture/SKILL.md +0 -127
  74. package/packs/flutter/skills/store-publishing/SKILL.md +0 -163
  75. package/packs/hono/CHANGELOG.md +0 -19
  76. package/packs/hono/README.md +0 -143
  77. package/packs/hono/agents/hono-architect.md +0 -240
  78. package/packs/hono/agents/hono-builder.md +0 -285
  79. package/packs/hono/agents/hono-reviewer.md +0 -279
  80. package/packs/hono/agents/hono-tester.md +0 -346
  81. package/packs/hono/commands/middleware-create.md +0 -223
  82. package/packs/hono/commands/project-init.md +0 -306
  83. package/packs/hono/commands/route-create.md +0 -153
  84. package/packs/hono/commands/rpc-client.md +0 -263
  85. package/packs/hono/skills/cloudflare-bindings/SKILL.md +0 -408
  86. package/packs/hono/skills/hono-patterns/SKILL.md +0 -309
  87. package/packs/hono/skills/rpc-typesafe/SKILL.md +0 -388
  88. package/packs/hono/skills/zod-validation/SKILL.md +0 -332
  89. package/packs/nestjs/CHANGELOG.md +0 -29
  90. package/packs/nestjs/README.md +0 -75
  91. package/packs/nestjs/agents/nestjs-architect.md +0 -402
  92. package/packs/nestjs/agents/nestjs-builder.md +0 -301
  93. package/packs/nestjs/agents/nestjs-tester.md +0 -437
  94. package/packs/nestjs/commands/module-create.md +0 -369
  95. package/packs/nestjs/rules/controllers.md +0 -92
  96. package/packs/nestjs/rules/dto.md +0 -124
  97. package/packs/nestjs/rules/entities.md +0 -102
  98. package/packs/nestjs/rules/services.md +0 -106
  99. package/packs/nestjs/skills/barrel-export-manager/SKILL.md +0 -389
  100. package/packs/nestjs/skills/import-convention-enforcer/SKILL.md +0 -365
  101. package/packs/nextjs/CHANGELOG.md +0 -36
  102. package/packs/nextjs/README.md +0 -76
  103. package/packs/nextjs/agents/frontend-tester.md +0 -680
  104. package/packs/nextjs/agents/frontend-visual.md +0 -820
  105. package/packs/nextjs/agents/nextjs-architect.md +0 -331
  106. package/packs/nextjs/agents/nextjs-modular.md +0 -433
  107. package/packs/nextjs/commands/component-create.md +0 -398
  108. package/packs/nextjs/rules/api-routes.md +0 -129
  109. package/packs/nextjs/rules/components.md +0 -106
  110. package/packs/nextjs/rules/hooks.md +0 -132
  111. package/packs/nextjs/skills/accessibility-validator/SKILL.md +0 -445
  112. package/packs/nextjs/skills/import-convention-enforcer/SKILL.md +0 -399
  113. package/packs/nextjs/skills/react-form-validator/SKILL.md +0 -569
  114. package/packs/nuxtjs/CHANGELOG.md +0 -30
  115. package/packs/nuxtjs/README.md +0 -56
  116. package/packs/nuxtjs/agents/frontend-tester.md +0 -680
  117. package/packs/nuxtjs/agents/frontend-visual.md +0 -820
  118. package/packs/nuxtjs/agents/nuxtjs-architect.md +0 -537
  119. package/packs/nuxtjs/commands/component-create.md +0 -223
  120. package/packs/nuxtjs/rules/components.md +0 -101
  121. package/packs/nuxtjs/rules/composables.md +0 -118
  122. package/packs/nuxtjs/rules/server-routes.md +0 -127
  123. package/packs/nuxtjs/skills/accessibility-validator/SKILL.md +0 -183
  124. package/packs/nuxtjs/skills/import-convention-enforcer/SKILL.md +0 -196
  125. package/packs/nuxtjs/skills/veevalidate-form-validator/SKILL.md +0 -190
  126. package/packs/onboard/CHANGELOG.md +0 -22
  127. package/packs/onboard/README.md +0 -103
  128. package/packs/onboard/agents/onboard-guide.md +0 -118
  129. package/packs/onboard/commands/onboard.md +0 -313
  130. package/packs/onboard/skills/onboard-context-provider/SKILL.md +0 -98
  131. package/packs/tanstack-router/CHANGELOG.md +0 -30
  132. package/packs/tanstack-router/README.md +0 -113
  133. package/packs/tanstack-router/agents/tanstack-architect.md +0 -173
  134. package/packs/tanstack-router/agents/tanstack-builder.md +0 -360
  135. package/packs/tanstack-router/agents/tanstack-tester.md +0 -454
  136. package/packs/tanstack-router/commands/form-create.md +0 -313
  137. package/packs/tanstack-router/commands/query-create.md +0 -263
  138. package/packs/tanstack-router/commands/route-create.md +0 -190
  139. package/packs/tanstack-router/commands/table-create.md +0 -413
  140. package/packs/tanstack-router/skills/ai-patterns/SKILL.md +0 -370
  141. package/packs/tanstack-router/skills/db-patterns/SKILL.md +0 -346
  142. package/packs/tanstack-router/skills/devtools-patterns/SKILL.md +0 -415
  143. package/packs/tanstack-router/skills/form-patterns/SKILL.md +0 -425
  144. package/packs/tanstack-router/skills/pacer-patterns/SKILL.md +0 -341
  145. package/packs/tanstack-router/skills/query-patterns/SKILL.md +0 -359
  146. package/packs/tanstack-router/skills/router-patterns/SKILL.md +0 -285
  147. package/packs/tanstack-router/skills/store-patterns/SKILL.md +0 -351
  148. package/packs/tanstack-router/skills/table-patterns/SKILL.md +0 -531
  149. package/packs/tanstack-router/skills/tanstack-conventions/SKILL.md +0 -428
  150. package/packs/tanstack-router/skills/virtual-patterns/SKILL.md +0 -490
  151. package/packs/worktree/CHANGELOG.md +0 -45
  152. package/packs/worktree/README.md +0 -219
  153. package/packs/worktree/commands/wt.md +0 -93
  154. package/packs/worktree/scripts/wt.sh +0 -957
  155. package/packs/worktree/skills/worktree-manager/SKILL.md +0 -113
@@ -1,341 +0,0 @@
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
- ```
@@ -1,359 +0,0 @@
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
- ```