@devmunna/agent-skillkit 0.1.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 (132) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +147 -0
  3. package/bin/ai-skills.js +5 -0
  4. package/dist/cli/commands/add.d.ts +2 -0
  5. package/dist/cli/commands/add.d.ts.map +1 -0
  6. package/dist/cli/commands/add.js +66 -0
  7. package/dist/cli/commands/add.js.map +1 -0
  8. package/dist/cli/commands/doctor.d.ts +2 -0
  9. package/dist/cli/commands/doctor.d.ts.map +1 -0
  10. package/dist/cli/commands/doctor.js +33 -0
  11. package/dist/cli/commands/doctor.js.map +1 -0
  12. package/dist/cli/commands/init.d.ts +10 -0
  13. package/dist/cli/commands/init.d.ts.map +1 -0
  14. package/dist/cli/commands/init.js +145 -0
  15. package/dist/cli/commands/init.js.map +1 -0
  16. package/dist/cli/commands/list.d.ts +5 -0
  17. package/dist/cli/commands/list.d.ts.map +1 -0
  18. package/dist/cli/commands/list.js +55 -0
  19. package/dist/cli/commands/list.js.map +1 -0
  20. package/dist/cli/commands/update.d.ts +2 -0
  21. package/dist/cli/commands/update.d.ts.map +1 -0
  22. package/dist/cli/commands/update.js +49 -0
  23. package/dist/cli/commands/update.js.map +1 -0
  24. package/dist/cli/commands/validate.d.ts +2 -0
  25. package/dist/cli/commands/validate.d.ts.map +1 -0
  26. package/dist/cli/commands/validate.js +22 -0
  27. package/dist/cli/commands/validate.js.map +1 -0
  28. package/dist/cli/index.d.ts +2 -0
  29. package/dist/cli/index.d.ts.map +1 -0
  30. package/dist/cli/index.js +49 -0
  31. package/dist/cli/index.js.map +1 -0
  32. package/dist/cli/prompts/agent-selector.d.ts +3 -0
  33. package/dist/cli/prompts/agent-selector.d.ts.map +1 -0
  34. package/dist/cli/prompts/agent-selector.js +23 -0
  35. package/dist/cli/prompts/agent-selector.js.map +1 -0
  36. package/dist/cli/prompts/stack-selector.d.ts +3 -0
  37. package/dist/cli/prompts/stack-selector.d.ts.map +1 -0
  38. package/dist/cli/prompts/stack-selector.js +60 -0
  39. package/dist/cli/prompts/stack-selector.js.map +1 -0
  40. package/dist/core/config-manager.d.ts +20 -0
  41. package/dist/core/config-manager.d.ts.map +1 -0
  42. package/dist/core/config-manager.js +107 -0
  43. package/dist/core/config-manager.js.map +1 -0
  44. package/dist/core/detector.d.ts +3 -0
  45. package/dist/core/detector.d.ts.map +1 -0
  46. package/dist/core/detector.js +50 -0
  47. package/dist/core/detector.js.map +1 -0
  48. package/dist/core/doctor.d.ts +12 -0
  49. package/dist/core/doctor.d.ts.map +1 -0
  50. package/dist/core/doctor.js +102 -0
  51. package/dist/core/doctor.js.map +1 -0
  52. package/dist/core/skill-registry.d.ts +11 -0
  53. package/dist/core/skill-registry.d.ts.map +1 -0
  54. package/dist/core/skill-registry.js +174 -0
  55. package/dist/core/skill-registry.js.map +1 -0
  56. package/dist/core/skill-resolver.d.ts +3 -0
  57. package/dist/core/skill-resolver.d.ts.map +1 -0
  58. package/dist/core/skill-resolver.js +36 -0
  59. package/dist/core/skill-resolver.js.map +1 -0
  60. package/dist/core/validator.d.ts +13 -0
  61. package/dist/core/validator.d.ts.map +1 -0
  62. package/dist/core/validator.js +99 -0
  63. package/dist/core/validator.js.map +1 -0
  64. package/dist/generators/agent-installer.d.ts +5 -0
  65. package/dist/generators/agent-installer.d.ts.map +1 -0
  66. package/dist/generators/agent-installer.js +20 -0
  67. package/dist/generators/agent-installer.js.map +1 -0
  68. package/dist/generators/agents-md.d.ts +3 -0
  69. package/dist/generators/agents-md.d.ts.map +1 -0
  70. package/dist/generators/agents-md.js +70 -0
  71. package/dist/generators/agents-md.js.map +1 -0
  72. package/dist/generators/claude-md.d.ts +3 -0
  73. package/dist/generators/claude-md.d.ts.map +1 -0
  74. package/dist/generators/claude-md.js +47 -0
  75. package/dist/generators/claude-md.js.map +1 -0
  76. package/dist/generators/skill-generator.d.ts +5 -0
  77. package/dist/generators/skill-generator.d.ts.map +1 -0
  78. package/dist/generators/skill-generator.js +34 -0
  79. package/dist/generators/skill-generator.js.map +1 -0
  80. package/dist/generators/workflows.d.ts +3 -0
  81. package/dist/generators/workflows.d.ts.map +1 -0
  82. package/dist/generators/workflows.js +57 -0
  83. package/dist/generators/workflows.js.map +1 -0
  84. package/dist/index.d.ts +13 -0
  85. package/dist/index.d.ts.map +1 -0
  86. package/dist/index.js +13 -0
  87. package/dist/index.js.map +1 -0
  88. package/dist/types/index.d.ts +55 -0
  89. package/dist/types/index.d.ts.map +1 -0
  90. package/dist/types/index.js +2 -0
  91. package/dist/types/index.js.map +1 -0
  92. package/dist/utils/file-utils.d.ts +12 -0
  93. package/dist/utils/file-utils.d.ts.map +1 -0
  94. package/dist/utils/file-utils.js +39 -0
  95. package/dist/utils/file-utils.js.map +1 -0
  96. package/dist/utils/logger.d.ts +10 -0
  97. package/dist/utils/logger.d.ts.map +1 -0
  98. package/dist/utils/logger.js +11 -0
  99. package/dist/utils/logger.js.map +1 -0
  100. package/package.json +73 -0
  101. package/skills/clean-architecture/SKILL.md +324 -0
  102. package/skills/express-mvc-prisma/SKILL.md +168 -0
  103. package/skills/express-mvc-prisma/references/auth.md +190 -0
  104. package/skills/express-mvc-prisma/references/boilerplate.md +196 -0
  105. package/skills/express-mvc-prisma/references/error-handling.md +121 -0
  106. package/skills/express-mvc-prisma/references/module-scaffold.md +253 -0
  107. package/skills/express-mvc-prisma/references/prisma-setup.md +97 -0
  108. package/skills/express-mvc-prisma/references/response-helpers.md +157 -0
  109. package/skills/express-mvc-prisma/references/zod-validation.md +157 -0
  110. package/skills/fastify-rest/SKILL.md +287 -0
  111. package/skills/mongoose-odm/SKILL.md +281 -0
  112. package/skills/nextjs-fullstack/SKILL.md +328 -0
  113. package/skills/nextjs-fullstack/references/auth.md +270 -0
  114. package/skills/nextjs-fullstack/references/caching.md +157 -0
  115. package/skills/nextjs-fullstack/references/route-handlers.md +194 -0
  116. package/skills/nextjs-fullstack/references/server-actions.md +214 -0
  117. package/skills/nextjs-fullstack/references/server-components.md +190 -0
  118. package/skills/node-base/SKILL.md +139 -0
  119. package/skills/prisma-orm/SKILL.md +334 -0
  120. package/skills/react-feature-arch/SKILL.md +208 -0
  121. package/skills/react-feature-arch/references/api-layer.md +110 -0
  122. package/skills/react-feature-arch/references/components.md +192 -0
  123. package/skills/react-feature-arch/references/data-fetching.md +198 -0
  124. package/skills/react-feature-arch/references/forms.md +194 -0
  125. package/skills/react-feature-arch/references/routing.md +148 -0
  126. package/skills/react-feature-arch/references/state-management.md +107 -0
  127. package/skills/tailwind-css/SKILL.md +236 -0
  128. package/skills/tailwind-css/references/components.md +340 -0
  129. package/skills/tailwind-css/references/design-tokens.md +230 -0
  130. package/skills/tailwind-css/references/patterns.md +375 -0
  131. package/skills/tailwind-css/references/setup.md +165 -0
  132. package/skills/zod-validation/SKILL.md +267 -0
@@ -0,0 +1,208 @@
1
+ ---
2
+ name: react-feature-arch
3
+ version: 2.0.0
4
+ description: >
5
+ Use this skill for any React project task: component design, hooks, state management, API integration, routing, forms, or folder structure. Triggers: "React component", "custom hook", "manage state", "fetch data", "React Router", "Zustand store", "TanStack Query", "form validation", "feature architecture", "React 19", "useActionState", "useOptimistic".
6
+ stack: [react, typescript, vite]
7
+ depends: []
8
+ ---
9
+
10
+ # React 19 — Production Feature Architecture
11
+
12
+ **Version target:** React 19 · React Router v7 · TanStack Query v5 · Zustand v5 · Vite 6 · TypeScript 5 · Zod v4
13
+
14
+ ---
15
+
16
+ ## Tech Stack
17
+
18
+ | Layer | Package |
19
+ |---|---|
20
+ | UI | react v19 + react-dom v19 |
21
+ | Routing | react-router v7 |
22
+ | Server state | @tanstack/react-query v5 |
23
+ | Global UI state | zustand v5 |
24
+ | Forms | react-hook-form v7 + @hookform/resolvers |
25
+ | Validation | zod v4 |
26
+ | HTTP | axios v1 |
27
+ | Build | vite v6 + @vitejs/plugin-react |
28
+ | Language | TypeScript 5 |
29
+
30
+ ---
31
+
32
+ ## Feature Folder Structure
33
+
34
+ ```
35
+ src/
36
+ ├── app/
37
+ │ ├── App.tsx
38
+ │ ├── router.tsx # createBrowserRouter — all routes
39
+ │ └── providers.tsx # QueryClientProvider, Suspense, ErrorBoundary
40
+ ├── features/ # One folder per domain
41
+ │ └── {feature}/
42
+ │ ├── {feature}.page.tsx # Route component — layout + composition only
43
+ │ ├── {feature}.api.ts # Axios API calls (all HTTP here)
44
+ │ ├── {feature}.hooks.ts # TanStack Query hooks (useQuery, useMutation)
45
+ │ ├── {feature}.schema.ts # Zod schemas + derived TypeScript types
46
+ │ ├── components/ # Feature-specific UI components
47
+ │ │ ├── {Feature}List.tsx
48
+ │ │ └── {Feature}Form.tsx
49
+ │ └── index.ts # Public barrel export
50
+ ├── components/
51
+ │ ├── ui/ # Shared primitive components
52
+ │ └── layout/ # Layout + ProtectedRoute
53
+ ├── lib/
54
+ │ ├── axios.ts # Axios instance + interceptors
55
+ │ └── query-client.ts # TanStack QueryClient singleton
56
+ ├── store/ # Zustand stores (UI state only)
57
+ │ ├── auth.store.ts
58
+ │ └── ui.store.ts
59
+ ├── types/ # Global TypeScript types
60
+ └── utils/ # Pure utility functions
61
+ ```
62
+
63
+ **Rules:**
64
+ - Import from feature barrels (`@/features/users`), never deep into another feature's internals
65
+ - Page components own data-fetching via hooks; they compose child components and pass data down
66
+ - All HTTP calls live in `{feature}.api.ts` — never call axios directly in hooks or components
67
+ - Zod schemas in `{feature}.schema.ts` — TypeScript types derived with `z.infer<>`, shared by forms and API
68
+
69
+ ---
70
+
71
+ ## React 19 — New Hooks
72
+
73
+ ### useActionState — form actions with pending state
74
+ ```tsx
75
+ import { useActionState } from 'react';
76
+ import { createUserSchema } from './users.schema';
77
+ import { usersApi } from './users.api';
78
+
79
+ async function submitAction(_prev: any, formData: FormData) {
80
+ const result = createUserSchema.safeParse({
81
+ name: formData.get('name'),
82
+ email: formData.get('email'),
83
+ });
84
+ if (!result.success) return { error: result.error.issues[0].message };
85
+ try {
86
+ await usersApi.create(result.data);
87
+ return { success: true };
88
+ } catch (err: any) {
89
+ return { error: err.message ?? 'Failed' };
90
+ }
91
+ }
92
+
93
+ export function CreateUserForm() {
94
+ const [state, formAction, isPending] = useActionState(submitAction, null);
95
+
96
+ return (
97
+ <form action={formAction}>
98
+ <input name="name" required />
99
+ <input name="email" type="email" required />
100
+ {state?.error && <p className="error">{state.error}</p>}
101
+ <button type="submit" disabled={isPending}>
102
+ {isPending ? 'Creating...' : 'Create'}
103
+ </button>
104
+ </form>
105
+ );
106
+ }
107
+ ```
108
+
109
+ ### useOptimistic — optimistic UI
110
+ ```tsx
111
+ import { useOptimistic, useTransition } from 'react';
112
+
113
+ export function TodoList({ todos, onAdd }: { todos: Todo[]; onAdd: (t: Todo) => Promise<void> }) {
114
+ const [optimisticTodos, addOptimistic] = useOptimistic(
115
+ todos,
116
+ (state, newTodo: Todo) => [...state, { ...newTodo, pending: true }],
117
+ );
118
+ const [, startTransition] = useTransition();
119
+
120
+ const handleAdd = (title: string) => {
121
+ startTransition(async () => {
122
+ addOptimistic({ id: 'temp', title, done: false });
123
+ await onAdd({ id: 'temp', title, done: false });
124
+ });
125
+ };
126
+
127
+ return <ul>{optimisticTodos.map(t => <li key={t.id}>{t.title}</li>)}</ul>;
128
+ }
129
+ ```
130
+
131
+ ### use() — read Context without useContext
132
+ ```tsx
133
+ import { use } from 'react';
134
+ import { ThemeContext } from '@/app/providers';
135
+
136
+ export function ThemedCard({ children }: { children: React.ReactNode }) {
137
+ const theme = use(ThemeContext); // replaces useContext(ThemeContext)
138
+ return <div className={`card-${theme}`}>{children}</div>;
139
+ }
140
+ ```
141
+
142
+ ---
143
+
144
+ ## Component Rules
145
+
146
+ - Function components only — no class components
147
+ - Named exports preferred over default (better refactoring + tree-shaking)
148
+ - Single responsibility: one component = one visual concern
149
+ - Presentation components receive data via props — no direct API calls or store access
150
+ - If a component exceeds ~100 lines, extract sub-components
151
+
152
+ ```tsx
153
+ interface UserCardProps {
154
+ user: User;
155
+ onDelete: (id: string) => void;
156
+ isDeleting?: boolean;
157
+ }
158
+
159
+ export function UserCard({ user, onDelete, isDeleting = false }: UserCardProps) {
160
+ return (
161
+ <div className="card">
162
+ <h3>{user.name}</h3>
163
+ <p>{user.email}</p>
164
+ <button onClick={() => onDelete(user.id)} disabled={isDeleting}>
165
+ {isDeleting ? 'Deleting...' : 'Delete'}
166
+ </button>
167
+ </div>
168
+ );
169
+ }
170
+ ```
171
+
172
+ ---
173
+
174
+ ## State Management Strategy
175
+
176
+ | State type | Tool |
177
+ |---|---|
178
+ | Server data (API responses) | TanStack Query v5 |
179
+ | Global UI (auth session, theme, sidebar) | Zustand v5 |
180
+ | Form state | React Hook Form v7 |
181
+ | URL state (filters, pagination, tabs) | URL search params |
182
+ | Component-local state | useState / useReducer |
183
+
184
+ **Rule:** Never put API response data in Zustand. Zustand is for UI-only global state. TanStack Query owns all server state including caching, invalidation, and background refetching.
185
+
186
+ ---
187
+
188
+ ## Key Patterns
189
+
190
+ - `queryOptions()` helper for reusable, type-safe query definitions
191
+ - Mutations call `queryClient.invalidateQueries` on success to refresh stale data
192
+ - Axios interceptors set auth header from Zustand store + handle 401 globally
193
+ - `useShallow` from Zustand when selecting multiple fields to prevent extra re-renders
194
+ - Always `select` in `useQuery` to narrow returned shape when only part of data is needed
195
+ - Lazy-load route components: `const UsersPage = lazy(() => import('./users.page'))`
196
+
197
+ ---
198
+
199
+ ## Core References
200
+
201
+ | Topic | File |
202
+ |---|---|
203
+ | Axios instance + feature API files | `references/api-layer.md` |
204
+ | TanStack Query v5 — queryOptions, hooks, mutations | `references/data-fetching.md` |
205
+ | Zustand v5 — auth store, UI store | `references/state-management.md` |
206
+ | React Hook Form + Zod v4 forms | `references/forms.md` |
207
+ | React Router v7 — createBrowserRouter, ProtectedRoute | `references/routing.md` |
208
+ | Component patterns + React 19 hooks | `references/components.md` |
@@ -0,0 +1,110 @@
1
+ # API Layer Reference
2
+
3
+ All HTTP calls live in `{feature}.api.ts`. Never import axios directly in hooks or components.
4
+
5
+ ---
6
+
7
+ ## src/lib/axios.ts — Axios Instance
8
+
9
+ ```ts
10
+ import axios from 'axios';
11
+ import { useAuthStore } from '@/store/auth.store';
12
+
13
+ export const apiClient = axios.create({
14
+ baseURL: import.meta.env.VITE_API_URL,
15
+ headers: { 'Content-Type': 'application/json' },
16
+ timeout: 10_000,
17
+ });
18
+
19
+ // Attach Bearer token to every request
20
+ apiClient.interceptors.request.use((config) => {
21
+ const token = useAuthStore.getState().token;
22
+ if (token) config.headers.Authorization = `Bearer ${token}`;
23
+ return config;
24
+ });
25
+
26
+ // Unwrap data.data + handle 401 globally
27
+ apiClient.interceptors.response.use(
28
+ (res) => res.data, // callers receive the payload directly (no res.data wrapper)
29
+ (err) => {
30
+ if (err.response?.status === 401) {
31
+ useAuthStore.getState().logout();
32
+ }
33
+ return Promise.reject(err.response?.data ?? err);
34
+ },
35
+ );
36
+ ```
37
+
38
+ ---
39
+
40
+ ## .env
41
+
42
+ ```
43
+ VITE_API_URL=http://localhost:3000/api/v1
44
+ ```
45
+
46
+ ---
47
+
48
+ ## Feature API File — {feature}.api.ts
49
+
50
+ ```ts
51
+ // features/users/users.api.ts
52
+ import { apiClient } from '@/lib/axios';
53
+ import type { CreateUserDto, UpdateUserDto, User } from './users.schema';
54
+
55
+ export interface PaginatedResponse<T> {
56
+ data: T[];
57
+ meta: {
58
+ total: number;
59
+ page: number;
60
+ limit: number;
61
+ totalPages: number;
62
+ hasNext: boolean;
63
+ hasPrev: boolean;
64
+ };
65
+ }
66
+
67
+ export interface ListParams {
68
+ page?: number;
69
+ limit?: number;
70
+ search?: string;
71
+ }
72
+
73
+ export const usersApi = {
74
+ getAll: (params: ListParams) =>
75
+ apiClient.get<never, PaginatedResponse<User>>('/users', { params }),
76
+
77
+ getById: (id: string) =>
78
+ apiClient.get<never, User>(`/users/${id}`),
79
+
80
+ create: (data: CreateUserDto) =>
81
+ apiClient.post<never, User>('/users', data),
82
+
83
+ update: (id: string, data: UpdateUserDto) =>
84
+ apiClient.patch<never, User>(`/users/${id}`, data),
85
+
86
+ remove: (id: string) =>
87
+ apiClient.delete<never, void>(`/users/${id}`),
88
+ };
89
+ ```
90
+
91
+ ---
92
+
93
+ ## Auth API
94
+
95
+ ```ts
96
+ // features/auth/auth.api.ts
97
+ import { apiClient } from '@/lib/axios';
98
+ import type { LoginDto, RegisterDto, User } from './auth.schema';
99
+
100
+ export const authApi = {
101
+ login: (data: LoginDto) =>
102
+ apiClient.post<never, { user: User; token: string }>('/auth/login', data),
103
+
104
+ register: (data: RegisterDto) =>
105
+ apiClient.post<never, { user: User; token: string }>('/auth/register', data),
106
+
107
+ me: () =>
108
+ apiClient.get<never, User>('/auth/me'),
109
+ };
110
+ ```
@@ -0,0 +1,192 @@
1
+ # Component Patterns Reference — React 19
2
+
3
+ ---
4
+
5
+ ## Feature Component Structure
6
+
7
+ ```tsx
8
+ // features/users/components/UserList.tsx
9
+ import type { User } from '../users.schema';
10
+ import { UserCard } from './UserCard';
11
+
12
+ interface UserListProps {
13
+ users: User[];
14
+ onDelete?: (id: string) => void;
15
+ isDeleting?: boolean;
16
+ }
17
+
18
+ export function UserList({ users, onDelete, isDeleting }: UserListProps) {
19
+ if (users.length === 0) return <p className="empty">No users found.</p>;
20
+
21
+ return (
22
+ <ul className="user-list">
23
+ {users.map((user) => (
24
+ <UserCard
25
+ key={user.id}
26
+ user={user}
27
+ onDelete={onDelete}
28
+ isDeleting={isDeleting}
29
+ />
30
+ ))}
31
+ </ul>
32
+ );
33
+ }
34
+ ```
35
+
36
+ ---
37
+
38
+ ## Smart + Dumb Composition Pattern
39
+
40
+ ```tsx
41
+ // SMART — page-level, owns data fetching
42
+ export function UsersPage() {
43
+ const [page, setPage] = useState(1);
44
+ const { data, isLoading } = useUsers({ page, limit: 10 });
45
+ const { mutate: deleteUser, isPending } = useDeleteUser();
46
+
47
+ return (
48
+ <UserList
49
+ users={data?.data ?? []}
50
+ isLoading={isLoading}
51
+ onDelete={deleteUser}
52
+ isDeleting={isPending}
53
+ />
54
+ );
55
+ }
56
+
57
+ // DUMB — purely presentational, receives all data via props
58
+ export function UserList({ users, isLoading, onDelete, isDeleting }: Props) {
59
+ if (isLoading) return <Skeleton count={5} />;
60
+ return users.map((u) => (
61
+ <UserCard key={u.id} user={u} onDelete={onDelete} isDeleting={isDeleting} />
62
+ ));
63
+ }
64
+ ```
65
+
66
+ ---
67
+
68
+ ## Performance Optimization
69
+
70
+ ```tsx
71
+ import { memo, useMemo, useCallback } from 'react';
72
+
73
+ // Memoize expensive computation
74
+ const sortedUsers = useMemo(
75
+ () => [...users].sort((a, b) => a.name.localeCompare(b.name)),
76
+ [users],
77
+ );
78
+
79
+ // Stabilize callback passed to memoized children
80
+ const handleDelete = useCallback((id: string) => deleteUser(id), [deleteUser]);
81
+
82
+ // Memoize leaf component that gets stable props
83
+ export const UserCard = memo(function UserCard({ user, onDelete }: UserCardProps) {
84
+ return (
85
+ <div className="card">
86
+ <h3>{user.name}</h3>
87
+ <button onClick={() => onDelete(user.id)}>Delete</button>
88
+ </div>
89
+ );
90
+ });
91
+ ```
92
+
93
+ ---
94
+
95
+ ## Custom Hooks
96
+
97
+ ```ts
98
+ // features/auth/auth.hooks.ts
99
+ import { useMutation } from '@tanstack/react-query';
100
+ import { useNavigate } from 'react-router';
101
+ import { useAuthStore } from '@/store/auth.store';
102
+ import { authApi } from './auth.api';
103
+ import { queryClient } from '@/lib/query-client';
104
+
105
+ export function useLogin() {
106
+ const login = useAuthStore((s) => s.login);
107
+ const navigate = useNavigate();
108
+
109
+ return useMutation({
110
+ mutationFn: authApi.login,
111
+ onSuccess: ({ user, token }) => {
112
+ login(user, token);
113
+ navigate('/dashboard');
114
+ },
115
+ });
116
+ }
117
+
118
+ export function useLogout() {
119
+ const logout = useAuthStore((s) => s.logout);
120
+ const navigate = useNavigate();
121
+
122
+ return () => {
123
+ logout();
124
+ queryClient.clear(); // clear all cached server data on logout
125
+ navigate('/auth/login');
126
+ };
127
+ }
128
+
129
+ // Shared utility hook
130
+ export function useDebounce<T>(value: T, delay: number): T {
131
+ const [debounced, setDebounced] = useState(value);
132
+
133
+ useEffect(() => {
134
+ const timer = setTimeout(() => setDebounced(value), delay);
135
+ return () => clearTimeout(timer);
136
+ }, [value, delay]);
137
+
138
+ return debounced;
139
+ }
140
+ ```
141
+
142
+ ---
143
+
144
+ ## Error Boundary
145
+
146
+ ```tsx
147
+ // components/ui/ErrorBoundary.tsx
148
+ import { Component, type ReactNode } from 'react';
149
+
150
+ interface State { hasError: boolean; message: string }
151
+
152
+ export class ErrorBoundary extends Component<
153
+ { children: ReactNode; fallback?: ReactNode },
154
+ State
155
+ > {
156
+ state: State = { hasError: false, message: '' };
157
+
158
+ static getDerivedStateFromError(err: Error): State {
159
+ return { hasError: true, message: err.message };
160
+ }
161
+
162
+ render() {
163
+ if (this.state.hasError) {
164
+ return this.props.fallback ?? (
165
+ <div className="error-boundary">
166
+ <h2>Something went wrong</h2>
167
+ <p>{this.state.message}</p>
168
+ <button onClick={() => this.setState({ hasError: false, message: '' })}>
169
+ Try again
170
+ </button>
171
+ </div>
172
+ );
173
+ }
174
+ return this.props.children;
175
+ }
176
+ }
177
+ ```
178
+
179
+ ---
180
+
181
+ ## Feature Barrel Export (index.ts)
182
+
183
+ Export only the public surface. Never export internal helpers.
184
+
185
+ ```ts
186
+ // features/users/index.ts
187
+ export { UsersPage } from './users.page';
188
+ export { UserList } from './components/UserList';
189
+ export { CreateUserForm } from './components/CreateUserForm';
190
+ export { useUsers, useUser, useCreateUser, useDeleteUser } from './users.hooks';
191
+ export type { User, CreateUserDto, UpdateUserDto } from './users.schema';
192
+ ```
@@ -0,0 +1,198 @@
1
+ # TanStack Query v5 Reference
2
+
3
+ ---
4
+
5
+ ## src/lib/query-client.ts
6
+
7
+ ```ts
8
+ import { QueryClient } from '@tanstack/react-query';
9
+
10
+ export const queryClient = new QueryClient({
11
+ defaultOptions: {
12
+ queries: {
13
+ staleTime: 1000 * 60 * 5, // 5 min before background refetch
14
+ retry: 1,
15
+ refetchOnWindowFocus: false,
16
+ },
17
+ },
18
+ });
19
+ ```
20
+
21
+ ---
22
+
23
+ ## src/app/providers.tsx
24
+
25
+ ```tsx
26
+ import { QueryClientProvider } from '@tanstack/react-query';
27
+ import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
28
+ import { queryClient } from '@/lib/query-client';
29
+
30
+ export function Providers({ children }: { children: React.ReactNode }) {
31
+ return (
32
+ <QueryClientProvider client={queryClient}>
33
+ {children}
34
+ <ReactQueryDevtools initialIsOpen={false} />
35
+ </QueryClientProvider>
36
+ );
37
+ }
38
+ ```
39
+
40
+ ---
41
+
42
+ ## queryOptions() — Reusable Query Definitions (v5)
43
+
44
+ Define query options once, reuse in `useQuery`, `useSuspenseQuery`, and `queryClient.prefetchQuery`.
45
+
46
+ ```ts
47
+ // features/users/users.hooks.ts
48
+ import {
49
+ queryOptions,
50
+ useQuery,
51
+ useSuspenseQuery,
52
+ useMutation,
53
+ useQueryClient,
54
+ } from '@tanstack/react-query';
55
+ import { usersApi, type ListParams } from './users.api';
56
+ import type { CreateUserDto, UpdateUserDto } from './users.schema';
57
+
58
+ export const userQueries = {
59
+ list: (params: ListParams) =>
60
+ queryOptions({
61
+ queryKey: ['users', 'list', params],
62
+ queryFn: () => usersApi.getAll(params),
63
+ }),
64
+
65
+ detail: (id: string) =>
66
+ queryOptions({
67
+ queryKey: ['users', id],
68
+ queryFn: () => usersApi.getById(id),
69
+ enabled: !!id,
70
+ }),
71
+ };
72
+ ```
73
+
74
+ ---
75
+
76
+ ## Hooks — List, Detail, Mutations
77
+
78
+ ```ts
79
+ // List with pagination
80
+ export function useUsers(params: ListParams) {
81
+ return useQuery(userQueries.list(params));
82
+ }
83
+
84
+ // Detail
85
+ export function useUser(id: string) {
86
+ return useQuery(userQueries.detail(id));
87
+ }
88
+
89
+ // Suspense variant — throws while loading, use inside <Suspense>
90
+ export function useUserSuspense(id: string) {
91
+ return useSuspenseQuery(userQueries.detail(id));
92
+ }
93
+
94
+ // Create
95
+ export function useCreateUser() {
96
+ const queryClient = useQueryClient();
97
+
98
+ return useMutation({
99
+ mutationFn: (data: CreateUserDto) => usersApi.create(data),
100
+ onSuccess: () => {
101
+ queryClient.invalidateQueries({ queryKey: ['users'] });
102
+ },
103
+ });
104
+ }
105
+
106
+ // Update
107
+ export function useUpdateUser() {
108
+ const queryClient = useQueryClient();
109
+
110
+ return useMutation({
111
+ mutationFn: ({ id, data }: { id: string; data: UpdateUserDto }) =>
112
+ usersApi.update(id, data),
113
+ onSuccess: (_res, { id }) => {
114
+ queryClient.invalidateQueries({ queryKey: ['users'] });
115
+ queryClient.invalidateQueries({ queryKey: ['users', id] });
116
+ },
117
+ });
118
+ }
119
+
120
+ // Delete with optimistic update
121
+ export function useDeleteUser() {
122
+ const queryClient = useQueryClient();
123
+
124
+ return useMutation({
125
+ mutationFn: (id: string) => usersApi.remove(id),
126
+ onMutate: async (id) => {
127
+ await queryClient.cancelQueries({ queryKey: ['users'] });
128
+ const snapshot = queryClient.getQueryData(userQueries.list({ page: 1, limit: 10 }).queryKey);
129
+ // Optimistically remove from list
130
+ queryClient.setQueriesData({ queryKey: ['users', 'list'] }, (old: any) =>
131
+ old ? { ...old, data: old.data.filter((u: any) => u.id !== id) } : old,
132
+ );
133
+ return { snapshot };
134
+ },
135
+ onError: (_err, _id, context) => {
136
+ if (context?.snapshot) {
137
+ queryClient.setQueryData(
138
+ userQueries.list({ page: 1, limit: 10 }).queryKey,
139
+ context.snapshot,
140
+ );
141
+ }
142
+ },
143
+ onSettled: () => {
144
+ queryClient.invalidateQueries({ queryKey: ['users'] });
145
+ },
146
+ });
147
+ }
148
+ ```
149
+
150
+ ---
151
+
152
+ ## Usage in Page Component
153
+
154
+ ```tsx
155
+ // features/users/users.page.tsx
156
+ import { useState } from 'react';
157
+ import { useUsers, useDeleteUser } from './users.hooks';
158
+ import { UserList } from './components/UserList';
159
+ import { Pagination } from '@/components/ui/Pagination';
160
+
161
+ export function UsersPage() {
162
+ const [page, setPage] = useState(1);
163
+ const { data, isLoading, error } = useUsers({ page, limit: 10 });
164
+ const { mutate: deleteUser, isPending: isDeleting } = useDeleteUser();
165
+
166
+ if (isLoading) return <div>Loading...</div>;
167
+ if (error) return <div>Error: {(error as any).message}</div>;
168
+
169
+ return (
170
+ <>
171
+ <UserList
172
+ users={data!.data}
173
+ onDelete={deleteUser}
174
+ isDeleting={isDeleting}
175
+ />
176
+ <Pagination meta={data!.meta} onPageChange={setPage} />
177
+ </>
178
+ );
179
+ }
180
+ ```
181
+
182
+ ---
183
+
184
+ ## Prefetching (router loaders)
185
+
186
+ ```ts
187
+ // Prefetch in router loader for instant navigation
188
+ import { queryClient } from '@/lib/query-client';
189
+
190
+ const router = createBrowserRouter([
191
+ {
192
+ path: '/users/:id',
193
+ loader: ({ params }) =>
194
+ queryClient.ensureQueryData(userQueries.detail(params.id!)),
195
+ element: <UserDetailPage />,
196
+ },
197
+ ]);
198
+ ```