autoworkflow 3.1.5 → 3.6.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.
- package/.claude/commands/analyze.md +19 -0
- package/.claude/commands/audit.md +26 -0
- package/.claude/commands/build.md +39 -0
- package/.claude/commands/commit.md +25 -0
- package/.claude/commands/fix.md +23 -0
- package/.claude/commands/plan.md +18 -0
- package/.claude/commands/suggest.md +23 -0
- package/.claude/commands/verify.md +18 -0
- package/.claude/hooks/post-bash-router.sh +20 -0
- package/.claude/hooks/post-commit.sh +140 -0
- package/.claude/hooks/post-edit.sh +190 -17
- package/.claude/hooks/pre-edit.sh +221 -0
- package/.claude/hooks/session-check.sh +90 -0
- package/.claude/settings.json +56 -6
- package/.claude/settings.local.json +5 -1
- package/.claude/skills/actix.md +337 -0
- package/.claude/skills/alembic.md +504 -0
- package/.claude/skills/angular.md +237 -0
- package/.claude/skills/api-design.md +187 -0
- package/.claude/skills/aspnet-core.md +377 -0
- package/.claude/skills/astro.md +245 -0
- package/.claude/skills/auth-clerk.md +327 -0
- package/.claude/skills/auth-firebase.md +367 -0
- package/.claude/skills/auth-nextauth.md +359 -0
- package/.claude/skills/auth-supabase.md +368 -0
- package/.claude/skills/axum.md +386 -0
- package/.claude/skills/blazor.md +456 -0
- package/.claude/skills/chi.md +348 -0
- package/.claude/skills/code-review.md +133 -0
- package/.claude/skills/csharp.md +296 -0
- package/.claude/skills/css-modules.md +325 -0
- package/.claude/skills/cypress.md +343 -0
- package/.claude/skills/debugging.md +133 -0
- package/.claude/skills/diesel.md +392 -0
- package/.claude/skills/django.md +301 -0
- package/.claude/skills/docker.md +319 -0
- package/.claude/skills/doctrine.md +473 -0
- package/.claude/skills/documentation.md +182 -0
- package/.claude/skills/dotnet.md +409 -0
- package/.claude/skills/drizzle.md +293 -0
- package/.claude/skills/echo.md +321 -0
- package/.claude/skills/eloquent.md +256 -0
- package/.claude/skills/emotion.md +426 -0
- package/.claude/skills/entity-framework.md +370 -0
- package/.claude/skills/express.md +316 -0
- package/.claude/skills/fastapi.md +329 -0
- package/.claude/skills/fastify.md +299 -0
- package/.claude/skills/fiber.md +315 -0
- package/.claude/skills/flask.md +322 -0
- package/.claude/skills/gin.md +342 -0
- package/.claude/skills/git.md +116 -0
- package/.claude/skills/github-actions.md +353 -0
- package/.claude/skills/go.md +377 -0
- package/.claude/skills/gorm.md +409 -0
- package/.claude/skills/graphql.md +478 -0
- package/.claude/skills/hibernate.md +379 -0
- package/.claude/skills/hono.md +306 -0
- package/.claude/skills/java.md +400 -0
- package/.claude/skills/jest.md +313 -0
- package/.claude/skills/jpa.md +282 -0
- package/.claude/skills/kotlin.md +347 -0
- package/.claude/skills/kubernetes.md +363 -0
- package/.claude/skills/laravel.md +414 -0
- package/.claude/skills/mcp-browser.md +320 -0
- package/.claude/skills/mcp-database.md +219 -0
- package/.claude/skills/mcp-fetch.md +241 -0
- package/.claude/skills/mcp-filesystem.md +204 -0
- package/.claude/skills/mcp-github.md +217 -0
- package/.claude/skills/mcp-memory.md +240 -0
- package/.claude/skills/mcp-search.md +218 -0
- package/.claude/skills/mcp-slack.md +262 -0
- package/.claude/skills/micronaut.md +388 -0
- package/.claude/skills/mongodb.md +319 -0
- package/.claude/skills/mongoose.md +355 -0
- package/.claude/skills/mysql.md +281 -0
- package/.claude/skills/nestjs.md +335 -0
- package/.claude/skills/nextjs-app-router.md +260 -0
- package/.claude/skills/nextjs-pages.md +172 -0
- package/.claude/skills/nuxt.md +202 -0
- package/.claude/skills/openapi.md +489 -0
- package/.claude/skills/performance.md +199 -0
- package/.claude/skills/php.md +398 -0
- package/.claude/skills/playwright.md +371 -0
- package/.claude/skills/postgresql.md +257 -0
- package/.claude/skills/prisma.md +293 -0
- package/.claude/skills/pydantic.md +304 -0
- package/.claude/skills/pytest.md +313 -0
- package/.claude/skills/python.md +272 -0
- package/.claude/skills/quarkus.md +377 -0
- package/.claude/skills/react.md +230 -0
- package/.claude/skills/redis.md +391 -0
- package/.claude/skills/refactoring.md +143 -0
- package/.claude/skills/remix.md +246 -0
- package/.claude/skills/rest-api.md +490 -0
- package/.claude/skills/rocket.md +366 -0
- package/.claude/skills/rust.md +341 -0
- package/.claude/skills/sass.md +380 -0
- package/.claude/skills/sea-orm.md +382 -0
- package/.claude/skills/security.md +167 -0
- package/.claude/skills/sequelize.md +395 -0
- package/.claude/skills/spring-boot.md +416 -0
- package/.claude/skills/sqlalchemy.md +269 -0
- package/.claude/skills/sqlx-rust.md +408 -0
- package/.claude/skills/state-jotai.md +346 -0
- package/.claude/skills/state-mobx.md +353 -0
- package/.claude/skills/state-pinia.md +431 -0
- package/.claude/skills/state-redux.md +337 -0
- package/.claude/skills/state-tanstack-query.md +434 -0
- package/.claude/skills/state-zustand.md +340 -0
- package/.claude/skills/styled-components.md +403 -0
- package/.claude/skills/svelte.md +238 -0
- package/.claude/skills/sveltekit.md +207 -0
- package/.claude/skills/symfony.md +437 -0
- package/.claude/skills/tailwind.md +279 -0
- package/.claude/skills/terraform.md +394 -0
- package/.claude/skills/testing-library.md +371 -0
- package/.claude/skills/trpc.md +426 -0
- package/.claude/skills/typeorm.md +368 -0
- package/.claude/skills/vitest.md +330 -0
- package/.claude/skills/vue.md +202 -0
- package/.claude/skills/warp.md +365 -0
- package/README.md +163 -52
- package/package.json +1 -1
- package/system/triggers.md +256 -17
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
# Redux Toolkit Skill
|
|
2
|
+
|
|
3
|
+
## Store Setup
|
|
4
|
+
\`\`\`typescript
|
|
5
|
+
// store/index.ts
|
|
6
|
+
import { configureStore } from '@reduxjs/toolkit';
|
|
7
|
+
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
|
|
8
|
+
import userReducer from './userSlice';
|
|
9
|
+
import postsReducer from './postsSlice';
|
|
10
|
+
|
|
11
|
+
export const store = configureStore({
|
|
12
|
+
reducer: {
|
|
13
|
+
user: userReducer,
|
|
14
|
+
posts: postsReducer,
|
|
15
|
+
},
|
|
16
|
+
middleware: (getDefaultMiddleware) =>
|
|
17
|
+
getDefaultMiddleware({
|
|
18
|
+
serializableCheck: {
|
|
19
|
+
// Ignore these paths in the state
|
|
20
|
+
ignoredPaths: ['user.lastLogin'],
|
|
21
|
+
},
|
|
22
|
+
}),
|
|
23
|
+
devTools: process.env.NODE_ENV !== 'production',
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Infer types from store
|
|
27
|
+
export type RootState = ReturnType<typeof store.getState>;
|
|
28
|
+
export type AppDispatch = typeof store.dispatch;
|
|
29
|
+
|
|
30
|
+
// Typed hooks
|
|
31
|
+
export const useAppDispatch: () => AppDispatch = useDispatch;
|
|
32
|
+
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
|
|
33
|
+
\`\`\`
|
|
34
|
+
|
|
35
|
+
## Slice Pattern
|
|
36
|
+
\`\`\`typescript
|
|
37
|
+
// store/userSlice.ts
|
|
38
|
+
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
|
|
39
|
+
import type { RootState } from './index';
|
|
40
|
+
|
|
41
|
+
interface User {
|
|
42
|
+
id: string;
|
|
43
|
+
email: string;
|
|
44
|
+
name: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface UserState {
|
|
48
|
+
user: User | null;
|
|
49
|
+
loading: boolean;
|
|
50
|
+
error: string | null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const initialState: UserState = {
|
|
54
|
+
user: null,
|
|
55
|
+
loading: false,
|
|
56
|
+
error: null,
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// Async thunk
|
|
60
|
+
export const fetchUser = createAsyncThunk(
|
|
61
|
+
'user/fetchUser',
|
|
62
|
+
async (userId: string, { rejectWithValue }) => {
|
|
63
|
+
try {
|
|
64
|
+
const response = await fetch(\`/api/users/\${userId}\`);
|
|
65
|
+
if (!response.ok) throw new Error('User not found');
|
|
66
|
+
return response.json();
|
|
67
|
+
} catch (error) {
|
|
68
|
+
return rejectWithValue((error as Error).message);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
export const updateUser = createAsyncThunk(
|
|
74
|
+
'user/updateUser',
|
|
75
|
+
async ({ id, data }: { id: string; data: Partial<User> }, { rejectWithValue }) => {
|
|
76
|
+
try {
|
|
77
|
+
const response = await fetch(\`/api/users/\${id}\`, {
|
|
78
|
+
method: 'PATCH',
|
|
79
|
+
headers: { 'Content-Type': 'application/json' },
|
|
80
|
+
body: JSON.stringify(data),
|
|
81
|
+
});
|
|
82
|
+
return response.json();
|
|
83
|
+
} catch (error) {
|
|
84
|
+
return rejectWithValue((error as Error).message);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
const userSlice = createSlice({
|
|
90
|
+
name: 'user',
|
|
91
|
+
initialState,
|
|
92
|
+
reducers: {
|
|
93
|
+
setUser: (state, action: PayloadAction<User>) => {
|
|
94
|
+
state.user = action.payload;
|
|
95
|
+
},
|
|
96
|
+
clearUser: (state) => {
|
|
97
|
+
state.user = null;
|
|
98
|
+
state.error = null;
|
|
99
|
+
},
|
|
100
|
+
updateUserField: (state, action: PayloadAction<{ field: keyof User; value: string }>) => {
|
|
101
|
+
if (state.user) {
|
|
102
|
+
state.user[action.payload.field] = action.payload.value;
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
extraReducers: (builder) => {
|
|
107
|
+
builder
|
|
108
|
+
// fetchUser
|
|
109
|
+
.addCase(fetchUser.pending, (state) => {
|
|
110
|
+
state.loading = true;
|
|
111
|
+
state.error = null;
|
|
112
|
+
})
|
|
113
|
+
.addCase(fetchUser.fulfilled, (state, action) => {
|
|
114
|
+
state.user = action.payload;
|
|
115
|
+
state.loading = false;
|
|
116
|
+
})
|
|
117
|
+
.addCase(fetchUser.rejected, (state, action) => {
|
|
118
|
+
state.loading = false;
|
|
119
|
+
state.error = action.payload as string;
|
|
120
|
+
})
|
|
121
|
+
// updateUser
|
|
122
|
+
.addCase(updateUser.fulfilled, (state, action) => {
|
|
123
|
+
state.user = action.payload;
|
|
124
|
+
});
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
export const { setUser, clearUser, updateUserField } = userSlice.actions;
|
|
129
|
+
export default userSlice.reducer;
|
|
130
|
+
|
|
131
|
+
// Selectors
|
|
132
|
+
export const selectUser = (state: RootState) => state.user.user;
|
|
133
|
+
export const selectUserLoading = (state: RootState) => state.user.loading;
|
|
134
|
+
export const selectUserError = (state: RootState) => state.user.error;
|
|
135
|
+
export const selectIsLoggedIn = (state: RootState) => !!state.user.user;
|
|
136
|
+
\`\`\`
|
|
137
|
+
|
|
138
|
+
## Using in Components
|
|
139
|
+
\`\`\`tsx
|
|
140
|
+
import { useAppDispatch, useAppSelector } from '../store';
|
|
141
|
+
import { fetchUser, selectUser, selectUserLoading, clearUser } from '../store/userSlice';
|
|
142
|
+
|
|
143
|
+
function UserProfile({ userId }: { userId: string }) {
|
|
144
|
+
const dispatch = useAppDispatch();
|
|
145
|
+
const user = useAppSelector(selectUser);
|
|
146
|
+
const loading = useAppSelector(selectUserLoading);
|
|
147
|
+
|
|
148
|
+
useEffect(() => {
|
|
149
|
+
dispatch(fetchUser(userId));
|
|
150
|
+
}, [dispatch, userId]);
|
|
151
|
+
|
|
152
|
+
const handleLogout = () => {
|
|
153
|
+
dispatch(clearUser());
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
if (loading) return <div>Loading...</div>;
|
|
157
|
+
if (!user) return <div>No user</div>;
|
|
158
|
+
|
|
159
|
+
return (
|
|
160
|
+
<div>
|
|
161
|
+
<h1>{user.name}</h1>
|
|
162
|
+
<button onClick={handleLogout}>Logout</button>
|
|
163
|
+
</div>
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
\`\`\`
|
|
167
|
+
|
|
168
|
+
## RTK Query (Data Fetching)
|
|
169
|
+
\`\`\`typescript
|
|
170
|
+
// store/api.ts
|
|
171
|
+
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
|
|
172
|
+
|
|
173
|
+
interface User {
|
|
174
|
+
id: string;
|
|
175
|
+
email: string;
|
|
176
|
+
name: string;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export const api = createApi({
|
|
180
|
+
reducerPath: 'api',
|
|
181
|
+
baseQuery: fetchBaseQuery({
|
|
182
|
+
baseUrl: '/api',
|
|
183
|
+
prepareHeaders: (headers, { getState }) => {
|
|
184
|
+
const token = (getState() as RootState).auth.token;
|
|
185
|
+
if (token) {
|
|
186
|
+
headers.set('Authorization', \`Bearer \${token}\`);
|
|
187
|
+
}
|
|
188
|
+
return headers;
|
|
189
|
+
},
|
|
190
|
+
}),
|
|
191
|
+
tagTypes: ['User', 'Post'],
|
|
192
|
+
endpoints: (builder) => ({
|
|
193
|
+
// Query endpoint
|
|
194
|
+
getUser: builder.query<User, string>({
|
|
195
|
+
query: (id) => \`/users/\${id}\`,
|
|
196
|
+
providesTags: (result, error, id) => [{ type: 'User', id }],
|
|
197
|
+
}),
|
|
198
|
+
|
|
199
|
+
getUsers: builder.query<User[], void>({
|
|
200
|
+
query: () => '/users',
|
|
201
|
+
providesTags: (result) =>
|
|
202
|
+
result
|
|
203
|
+
? [...result.map(({ id }) => ({ type: 'User' as const, id })), 'User']
|
|
204
|
+
: ['User'],
|
|
205
|
+
}),
|
|
206
|
+
|
|
207
|
+
// Mutation endpoint
|
|
208
|
+
createUser: builder.mutation<User, Partial<User>>({
|
|
209
|
+
query: (body) => ({
|
|
210
|
+
url: '/users',
|
|
211
|
+
method: 'POST',
|
|
212
|
+
body,
|
|
213
|
+
}),
|
|
214
|
+
invalidatesTags: ['User'],
|
|
215
|
+
}),
|
|
216
|
+
|
|
217
|
+
updateUser: builder.mutation<User, { id: string; data: Partial<User> }>({
|
|
218
|
+
query: ({ id, data }) => ({
|
|
219
|
+
url: \`/users/\${id}\`,
|
|
220
|
+
method: 'PATCH',
|
|
221
|
+
body: data,
|
|
222
|
+
}),
|
|
223
|
+
invalidatesTags: (result, error, { id }) => [{ type: 'User', id }],
|
|
224
|
+
}),
|
|
225
|
+
|
|
226
|
+
deleteUser: builder.mutation<void, string>({
|
|
227
|
+
query: (id) => ({
|
|
228
|
+
url: \`/users/\${id}\`,
|
|
229
|
+
method: 'DELETE',
|
|
230
|
+
}),
|
|
231
|
+
invalidatesTags: (result, error, id) => [{ type: 'User', id }],
|
|
232
|
+
}),
|
|
233
|
+
}),
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
export const {
|
|
237
|
+
useGetUserQuery,
|
|
238
|
+
useGetUsersQuery,
|
|
239
|
+
useCreateUserMutation,
|
|
240
|
+
useUpdateUserMutation,
|
|
241
|
+
useDeleteUserMutation,
|
|
242
|
+
} = api;
|
|
243
|
+
|
|
244
|
+
// Add to store
|
|
245
|
+
export const store = configureStore({
|
|
246
|
+
reducer: {
|
|
247
|
+
[api.reducerPath]: api.reducer,
|
|
248
|
+
// ... other reducers
|
|
249
|
+
},
|
|
250
|
+
middleware: (getDefaultMiddleware) =>
|
|
251
|
+
getDefaultMiddleware().concat(api.middleware),
|
|
252
|
+
});
|
|
253
|
+
\`\`\`
|
|
254
|
+
|
|
255
|
+
## RTK Query Usage
|
|
256
|
+
\`\`\`tsx
|
|
257
|
+
function UserList() {
|
|
258
|
+
const { data: users, isLoading, error, refetch } = useGetUsersQuery();
|
|
259
|
+
const [createUser, { isLoading: isCreating }] = useCreateUserMutation();
|
|
260
|
+
|
|
261
|
+
const handleCreate = async () => {
|
|
262
|
+
try {
|
|
263
|
+
await createUser({ email: 'new@example.com', name: 'New User' }).unwrap();
|
|
264
|
+
} catch (error) {
|
|
265
|
+
console.error('Failed to create user:', error);
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
if (isLoading) return <div>Loading...</div>;
|
|
270
|
+
if (error) return <div>Error loading users</div>;
|
|
271
|
+
|
|
272
|
+
return (
|
|
273
|
+
<div>
|
|
274
|
+
<button onClick={handleCreate} disabled={isCreating}>
|
|
275
|
+
Add User
|
|
276
|
+
</button>
|
|
277
|
+
<button onClick={refetch}>Refresh</button>
|
|
278
|
+
{users?.map((user) => (
|
|
279
|
+
<UserCard key={user.id} user={user} />
|
|
280
|
+
))}
|
|
281
|
+
</div>
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function UserCard({ user }: { user: User }) {
|
|
286
|
+
const [updateUser] = useUpdateUserMutation();
|
|
287
|
+
const [deleteUser] = useDeleteUserMutation();
|
|
288
|
+
|
|
289
|
+
return (
|
|
290
|
+
<div>
|
|
291
|
+
<span>{user.name}</span>
|
|
292
|
+
<button onClick={() => updateUser({ id: user.id, data: { name: 'Updated' } })}>
|
|
293
|
+
Edit
|
|
294
|
+
</button>
|
|
295
|
+
<button onClick={() => deleteUser(user.id)}>Delete</button>
|
|
296
|
+
</div>
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
\`\`\`
|
|
300
|
+
|
|
301
|
+
## Selectors with Reselect
|
|
302
|
+
\`\`\`typescript
|
|
303
|
+
import { createSelector } from '@reduxjs/toolkit';
|
|
304
|
+
|
|
305
|
+
// Memoized selector
|
|
306
|
+
export const selectUserPosts = createSelector(
|
|
307
|
+
[selectUser, (state: RootState) => state.posts.items],
|
|
308
|
+
(user, posts) => posts.filter((post) => post.authorId === user?.id)
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
// Parameterized selector
|
|
312
|
+
export const makeSelectPostsByCategory = () =>
|
|
313
|
+
createSelector(
|
|
314
|
+
[(state: RootState) => state.posts.items, (_, category: string) => category],
|
|
315
|
+
(posts, category) => posts.filter((post) => post.category === category)
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
// Usage
|
|
319
|
+
const userPosts = useAppSelector(selectUserPosts);
|
|
320
|
+
const selectPostsByCategory = useMemo(makeSelectPostsByCategory, []);
|
|
321
|
+
const techPosts = useAppSelector((state) => selectPostsByCategory(state, 'tech'));
|
|
322
|
+
\`\`\`
|
|
323
|
+
|
|
324
|
+
## ❌ DON'T
|
|
325
|
+
- Mutate state outside of reducers
|
|
326
|
+
- Put non-serializable values in state (functions, class instances)
|
|
327
|
+
- Use createAsyncThunk for simple fetches (use RTK Query)
|
|
328
|
+
- Create selectors inside components (causes re-renders)
|
|
329
|
+
- Dispatch in reducers
|
|
330
|
+
|
|
331
|
+
## ✅ DO
|
|
332
|
+
- Use RTK Query for data fetching
|
|
333
|
+
- Create typed hooks (useAppDispatch, useAppSelector)
|
|
334
|
+
- Use createSelector for derived data
|
|
335
|
+
- Keep state normalized (by ID)
|
|
336
|
+
- Use PayloadAction for typed action payloads
|
|
337
|
+
- Handle all async states (pending, fulfilled, rejected)
|