symfonia-ai-tools 1.0.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/README.md +489 -0
- package/bin/cli.mjs +35 -0
- package/lib/installer.mjs +495 -0
- package/lib/questions.mjs +332 -0
- package/lib/ui.mjs +76 -0
- package/lib/utils.mjs +231 -0
- package/package.json +26 -0
- package/templates/base/CLAUDE.md +34 -0
- package/templates/base/_ai/_guidelines_header.md +70 -0
- package/templates/base/_ai/context/README.md +20 -0
- package/templates/base/_ai/prompts/codereview.prompt.md +324 -0
- package/templates/base/_ai/prompts/duplicate-code-analysis.prompt.md +128 -0
- package/templates/base/_ai/prompts/figma-analysis.prompt.md +155 -0
- package/templates/base/_ai/prompts/security-review.prompt.md +46 -0
- package/templates/base/_ai/skills/README.md +80 -0
- package/templates/base/_ai/skills/TEMPLATE.md +106 -0
- package/templates/base/_ai/skills/babysit-prs/SKILL.md +105 -0
- package/templates/base/_ai/skills/debug/SKILL.md +93 -0
- package/templates/base/_ai/skills/fill-worklogs/SKILL.md +158 -0
- package/templates/base/_ai/skills/hotfix/SKILL.md +52 -0
- package/templates/base/_ai/skills/jira-task/SKILL.md +170 -0
- package/templates/base/_ai/skills/my-prs/SKILL.md +78 -0
- package/templates/base/_ai/skills/pr-dashboard/SKILL.md +43 -0
- package/templates/base/_ai/skills/pr-prepare/SKILL.md +106 -0
- package/templates/base/_ai/skills/refactor/SKILL.md +87 -0
- package/templates/base/_ai/skills/write-tests/SKILL.md +109 -0
- package/templates/base/_claude/settings.local.json +37 -0
- package/templates/base/_cursor/rules/global.mdc +7 -0
- package/templates/base/_editorconfig +18 -0
- package/templates/base/_gemini/settings.json +3 -0
- package/templates/base/_github/copilot-instructions.md +1 -0
- package/templates/base/_github/pull_request_template.md +23 -0
- package/templates/base/_gitignore +22 -0
- package/templates/base/_junie/guidelines.md +1 -0
- package/templates/base/commit-instructions.md +92 -0
- package/templates/packs/docker/_ai/instructions/docker.instructions.md +193 -0
- package/templates/packs/docker/_guidelines.md +10 -0
- package/templates/packs/docker/pack.json +8 -0
- package/templates/packs/laravel/_ai/instructions/api-resource.instructions.md +251 -0
- package/templates/packs/laravel/_ai/instructions/module.instructions.md +133 -0
- package/templates/packs/laravel/_ai/instructions/service-repository.instructions.md +215 -0
- package/templates/packs/laravel/_ai/instructions/testing.instructions.md +278 -0
- package/templates/packs/laravel/_ai/skills/migration/SKILL.md +172 -0
- package/templates/packs/laravel/_ai/skills/new-endpoint/SKILL.md +165 -0
- package/templates/packs/laravel/_ai/skills/new-module/SKILL.md +208 -0
- package/templates/packs/laravel/_ai/skills/queued-job/SKILL.md +248 -0
- package/templates/packs/laravel/_ai/skills/testing-feature/SKILL.md +196 -0
- package/templates/packs/laravel/_ai/skills/testing-manual/SKILL.md +186 -0
- package/templates/packs/laravel/_ai/skills/testing-unit/SKILL.md +200 -0
- package/templates/packs/laravel/_guidelines.md +25 -0
- package/templates/packs/laravel/pack.json +6 -0
- package/templates/packs/playwright/_ai/instructions/playwright.instructions.md +219 -0
- package/templates/packs/playwright/_ai/skills/playwright/README.md +194 -0
- package/templates/packs/playwright/_ai/skills/playwright/SKILL.md +1245 -0
- package/templates/packs/playwright/_ai/skills/playwright-codereview/SKILL.md +642 -0
- package/templates/packs/playwright/_ai/skills/playwright-record/README.md +87 -0
- package/templates/packs/playwright/_ai/skills/playwright-record/SKILL.md +564 -0
- package/templates/packs/playwright/_guidelines.md +12 -0
- package/templates/packs/playwright/pack.json +9 -0
- package/templates/packs/storybook/_ai/instructions/storybook.instructions.md +181 -0
- package/templates/packs/storybook/pack.json +6 -0
- package/templates/packs/vitest/_ai/instructions/vitest.instructions.md +688 -0
- package/templates/packs/vitest/pack.json +6 -0
- package/templates/packs/vue3/_ai/instructions/api.instructions.md +163 -0
- package/templates/packs/vue3/_ai/instructions/coding-conventions.instructions.md +160 -0
- package/templates/packs/vue3/_ai/instructions/composables.instructions.md +218 -0
- package/templates/packs/vue3/_ai/instructions/forms.instructions.md +227 -0
- package/templates/packs/vue3/_ai/instructions/store.instructions.md +504 -0
- package/templates/packs/vue3/_ai/instructions/vue.instructions.md +339 -0
- package/templates/packs/vue3/_ai/skills/api-integration/SKILL.md +195 -0
- package/templates/packs/vue3/_ai/skills/new-component/SKILL.md +133 -0
- package/templates/packs/vue3/_ai/skills/new-module/SKILL.md +177 -0
- package/templates/packs/vue3/_guidelines.md +45 -0
- package/templates/packs/vue3/pack.json +11 -0
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
---
|
|
2
|
+
applyTo: "**/*.store.ts"
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Pinia Store Instructions
|
|
6
|
+
|
|
7
|
+
## Core Pinia Store Principles
|
|
8
|
+
|
|
9
|
+
### Architecture Rules
|
|
10
|
+
- **Stores handle state management ONLY** - no business logic
|
|
11
|
+
- **Use Composition API syntax** with `defineStore`
|
|
12
|
+
- **Business logic goes in composables**, not stores
|
|
13
|
+
- **Stores are reactive data containers** with actions for mutations
|
|
14
|
+
- **Follow strict TypeScript typing** - never use `any`
|
|
15
|
+
|
|
16
|
+
### Store Structure Template
|
|
17
|
+
```typescript
|
|
18
|
+
import { defineStore } from 'pinia'
|
|
19
|
+
import { ref, computed } from 'vue'
|
|
20
|
+
import type { IUser, IUserFilters, IUserState } from '@/types/user'
|
|
21
|
+
|
|
22
|
+
export const useUserStore = defineStore('user', () => {
|
|
23
|
+
// 1. State (reactive refs)
|
|
24
|
+
const users = ref<IUser[]>([])
|
|
25
|
+
const loading = ref(false)
|
|
26
|
+
const error = ref<string | null>(null)
|
|
27
|
+
const filters = ref<IUserFilters>({
|
|
28
|
+
search: '',
|
|
29
|
+
category: null,
|
|
30
|
+
isActive: true
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
// 2. Getters (computed properties)
|
|
34
|
+
const filteredUsers = computed((): IUser[] => {
|
|
35
|
+
return users.value.filter(user => {
|
|
36
|
+
const matchesSearch = !filters.value.search ||
|
|
37
|
+
user.name.toLowerCase().includes(filters.value.search.toLowerCase())
|
|
38
|
+
const matchesCategory = !filters.value.category ||
|
|
39
|
+
user.categoryId === filters.value.category
|
|
40
|
+
const matchesActive = user.isActive === filters.value.isActive
|
|
41
|
+
|
|
42
|
+
return matchesSearch && matchesCategory && matchesActive
|
|
43
|
+
})
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
const totalUsers = computed((): number => users.value.length)
|
|
47
|
+
|
|
48
|
+
const activeUsersCount = computed((): number =>
|
|
49
|
+
users.value.filter(user => user.isActive).length
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
// 3. Actions (state mutations only)
|
|
53
|
+
const setUsers = (newUsers: IUser[]): void => {
|
|
54
|
+
users.value = newUsers
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const addUser = (user: IUser): void => {
|
|
58
|
+
users.value.push(user)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const updateUser = (userId: string, updatedUser: Partial<IUser>): void => {
|
|
62
|
+
const index = users.value.findIndex(user => user.id === userId)
|
|
63
|
+
if (index !== -1) {
|
|
64
|
+
users.value[index] = { ...users.value[index], ...updatedUser }
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const removeUser = (userId: string): void => {
|
|
69
|
+
const index = users.value.findIndex(user => user.id === userId)
|
|
70
|
+
if (index !== -1) {
|
|
71
|
+
users.value.splice(index, 1)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const setLoading = (isLoading: boolean): void => {
|
|
76
|
+
loading.value = isLoading
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const setError = (errorMessage: string | null): void => {
|
|
80
|
+
error.value = errorMessage
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const updateFilters = (newFilters: Partial<IUserFilters>): void => {
|
|
84
|
+
filters.value = { ...filters.value, ...newFilters }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const resetFilters = (): void => {
|
|
88
|
+
filters.value = {
|
|
89
|
+
search: '',
|
|
90
|
+
category: null,
|
|
91
|
+
isActive: true
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const clearError = (): void => {
|
|
96
|
+
error.value = null
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const resetStore = (): void => {
|
|
100
|
+
users.value = []
|
|
101
|
+
loading.value = false
|
|
102
|
+
error.value = null
|
|
103
|
+
resetFilters()
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// 4. Return everything that should be exposed
|
|
107
|
+
return {
|
|
108
|
+
// State
|
|
109
|
+
users,
|
|
110
|
+
loading,
|
|
111
|
+
error,
|
|
112
|
+
filters,
|
|
113
|
+
|
|
114
|
+
// Getters
|
|
115
|
+
filteredUsers,
|
|
116
|
+
totalUsers,
|
|
117
|
+
activeUsersCount,
|
|
118
|
+
|
|
119
|
+
// Actions
|
|
120
|
+
setUsers,
|
|
121
|
+
addUser,
|
|
122
|
+
updateUser,
|
|
123
|
+
removeUser,
|
|
124
|
+
setLoading,
|
|
125
|
+
setError,
|
|
126
|
+
updateFilters,
|
|
127
|
+
resetFilters,
|
|
128
|
+
clearError,
|
|
129
|
+
resetStore
|
|
130
|
+
}
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
// 5. Export type for store usage in composables
|
|
134
|
+
export type UserStore = ReturnType<typeof useUserStore>
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Mandatory Requirements
|
|
138
|
+
|
|
139
|
+
### 1. TypeScript Interfaces
|
|
140
|
+
- **ALL state properties must be typed** with proper interfaces
|
|
141
|
+
- **Prefix interfaces with `I`** (e.g., `IUserState`, `IUserFilters`)
|
|
142
|
+
- **Export store type** for use in composables: `export type UserStore = ReturnType<typeof useUserStore>`
|
|
143
|
+
- **NEVER use `any` type** - strict typing required
|
|
144
|
+
|
|
145
|
+
### 2. State Management Rules
|
|
146
|
+
- **Only reactive data storage** - no business logic in stores
|
|
147
|
+
- **Use `ref()` for state** and `computed()` for derived state
|
|
148
|
+
- **Actions only mutate state** - no API calls or complex logic
|
|
149
|
+
- **Clear separation**: state -> getters -> actions
|
|
150
|
+
|
|
151
|
+
### 3. Naming Conventions
|
|
152
|
+
- **Store names**: `use[Entity]Store` (e.g., `useUserStore`, `useProductStore`)
|
|
153
|
+
- **File names**: `[entity].store.ts` (e.g., `user.store.ts`, `product.store.ts`)
|
|
154
|
+
- **State properties**: descriptive, camelCase
|
|
155
|
+
- **Actions**: verb-based (e.g., `setUsers`, `addUser`, `updateUser`)
|
|
156
|
+
|
|
157
|
+
### 4. Store Organization
|
|
158
|
+
- **Module-specific stores**: `{{MODULE_PATH}}/stores/`
|
|
159
|
+
- **Global stores**: `/src/stores/`
|
|
160
|
+
- **One entity per store** - avoid mega-stores
|
|
161
|
+
|
|
162
|
+
## Store Development Guidelines
|
|
163
|
+
|
|
164
|
+
### State Definition Best Practices
|
|
165
|
+
```typescript
|
|
166
|
+
// Good: Proper state typing and initialization
|
|
167
|
+
const users = ref<IUser[]>([])
|
|
168
|
+
const currentUser = ref<IUser | null>(null)
|
|
169
|
+
const loading = ref<boolean>(false)
|
|
170
|
+
const error = ref<string | null>(null)
|
|
171
|
+
const pagination = ref<IPagination>({
|
|
172
|
+
page: 1,
|
|
173
|
+
limit: 20,
|
|
174
|
+
total: 0
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
// Bad: No typing, unclear initialization
|
|
178
|
+
const data = ref()
|
|
179
|
+
const isLoading = ref()
|
|
180
|
+
const err = ref()
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Getters (Computed Properties)
|
|
184
|
+
```typescript
|
|
185
|
+
// Good: Clear, typed computed properties
|
|
186
|
+
const filteredUsers = computed((): IUser[] => {
|
|
187
|
+
return users.value.filter(user =>
|
|
188
|
+
user.name.toLowerCase().includes(searchTerm.value.toLowerCase())
|
|
189
|
+
)
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
const hasUsers = computed((): boolean => users.value.length > 0)
|
|
193
|
+
|
|
194
|
+
const usersByCategory = computed((): Record<string, IUser[]> => {
|
|
195
|
+
return users.value.reduce((acc, user) => {
|
|
196
|
+
const category = user.category || 'unassigned'
|
|
197
|
+
if (!acc[category]) acc[category] = []
|
|
198
|
+
acc[category].push(user)
|
|
199
|
+
return acc
|
|
200
|
+
}, {} as Record<string, IUser[]>)
|
|
201
|
+
})
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Actions Best Practices
|
|
205
|
+
```typescript
|
|
206
|
+
// Good: Simple state mutations
|
|
207
|
+
const setUsers = (newUsers: IUser[]): void => {
|
|
208
|
+
users.value = newUsers
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const updateUserStatus = (userId: string, isActive: boolean): void => {
|
|
212
|
+
const user = users.value.find(u => u.id === userId)
|
|
213
|
+
if (user) {
|
|
214
|
+
user.isActive = isActive
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Good: Batch operations
|
|
219
|
+
const updateMultipleUsers = (updates: Array<{ id: string; data: Partial<IUser> }>): void => {
|
|
220
|
+
updates.forEach(({ id, data }) => {
|
|
221
|
+
const index = users.value.findIndex(user => user.id === id)
|
|
222
|
+
if (index !== -1) {
|
|
223
|
+
users.value[index] = { ...users.value[index], ...data }
|
|
224
|
+
}
|
|
225
|
+
})
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Error and Loading State Management
|
|
230
|
+
```typescript
|
|
231
|
+
// Good: Consistent error/loading patterns
|
|
232
|
+
const setLoading = (isLoading: boolean): void => {
|
|
233
|
+
loading.value = isLoading
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const setError = (errorMessage: string | null): void => {
|
|
237
|
+
error.value = errorMessage
|
|
238
|
+
if (errorMessage) {
|
|
239
|
+
loading.value = false
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const clearError = (): void => {
|
|
244
|
+
error.value = null
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Good: Reset functionality
|
|
248
|
+
const resetStore = (): void => {
|
|
249
|
+
users.value = []
|
|
250
|
+
loading.value = false
|
|
251
|
+
error.value = null
|
|
252
|
+
currentUser.value = null
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## What NOT to Do in Stores
|
|
257
|
+
|
|
258
|
+
### Never Do This
|
|
259
|
+
```typescript
|
|
260
|
+
// BAD: Business logic in stores
|
|
261
|
+
const fetchUsers = async (): Promise<void> => {
|
|
262
|
+
try {
|
|
263
|
+
loading.value = true
|
|
264
|
+
const response = await api.get('/users')
|
|
265
|
+
users.value = response.data
|
|
266
|
+
} catch (error) {
|
|
267
|
+
handleError(error)
|
|
268
|
+
} finally {
|
|
269
|
+
loading.value = false
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// BAD: Complex business calculations
|
|
274
|
+
const calculateUserMetrics = (): void => {
|
|
275
|
+
// Complex business logic here
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// BAD: Direct API calls
|
|
279
|
+
const saveUser = async (user: IUser): Promise<void> => {
|
|
280
|
+
const response = await userService.save(user)
|
|
281
|
+
// ...
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// BAD: Using any type
|
|
285
|
+
const data = ref<any>([])
|
|
286
|
+
|
|
287
|
+
// BAD: Mega-store with multiple entities
|
|
288
|
+
const useAppStore = defineStore('app', () => {
|
|
289
|
+
const users = ref([])
|
|
290
|
+
const products = ref([])
|
|
291
|
+
const orders = ref([])
|
|
292
|
+
// Too many responsibilities
|
|
293
|
+
})
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Always Do This
|
|
297
|
+
```typescript
|
|
298
|
+
// GOOD: Simple state mutations only
|
|
299
|
+
const setUsers = (newUsers: IUser[]): void => {
|
|
300
|
+
users.value = newUsers
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const updateUser = (userId: string, userData: Partial<IUser>): void => {
|
|
304
|
+
const index = users.value.findIndex(user => user.id === userId)
|
|
305
|
+
if (index !== -1) {
|
|
306
|
+
users.value[index] = { ...users.value[index], ...userData }
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// GOOD: Proper typing
|
|
311
|
+
const users = ref<IUser[]>([])
|
|
312
|
+
const loading = ref<boolean>(false)
|
|
313
|
+
|
|
314
|
+
// GOOD: Single responsibility
|
|
315
|
+
export const useUserStore = defineStore('user', () => {
|
|
316
|
+
// Only user-related state and mutations
|
|
317
|
+
})
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
## Store Integration Patterns
|
|
321
|
+
|
|
322
|
+
### Using Stores in Composables
|
|
323
|
+
```typescript
|
|
324
|
+
// {{MODULE_PATH}}/composables/useUserManagement.ts
|
|
325
|
+
import { useUserStore } from '{{MODULE_PATH}}/stores/user.store'
|
|
326
|
+
import { userService } from '{{MODULE_PATH}}/services/user.service'
|
|
327
|
+
|
|
328
|
+
export function useUserManagement() {
|
|
329
|
+
const userStore = useUserStore()
|
|
330
|
+
|
|
331
|
+
const fetchUsers = async (): Promise<void> => {
|
|
332
|
+
try {
|
|
333
|
+
userStore.setLoading(true)
|
|
334
|
+
userStore.clearError()
|
|
335
|
+
|
|
336
|
+
const users = await userService.getUsers()
|
|
337
|
+
userStore.setUsers(users)
|
|
338
|
+
} catch (error) {
|
|
339
|
+
userStore.setError('Failed to fetch users')
|
|
340
|
+
printError(error)
|
|
341
|
+
} finally {
|
|
342
|
+
userStore.setLoading(false)
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return {
|
|
347
|
+
// Expose store state
|
|
348
|
+
users: userStore.users,
|
|
349
|
+
loading: userStore.loading,
|
|
350
|
+
error: userStore.error,
|
|
351
|
+
|
|
352
|
+
// Expose business logic
|
|
353
|
+
fetchUsers
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### Store Communication Patterns
|
|
359
|
+
```typescript
|
|
360
|
+
// Good: Store-to-store communication through composables
|
|
361
|
+
export function useUserCategorySync() {
|
|
362
|
+
const userStore = useUserStore()
|
|
363
|
+
const categoryStore = useCategoryStore()
|
|
364
|
+
|
|
365
|
+
const syncUserCategories = (): void => {
|
|
366
|
+
const categoryIds = userStore.users.map(user => user.categoryId)
|
|
367
|
+
const uniqueCategoryIds = [...new Set(categoryIds)]
|
|
368
|
+
categoryStore.setActiveCategories(uniqueCategoryIds)
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return { syncUserCategories }
|
|
372
|
+
}
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
## Testing Store Requirements
|
|
376
|
+
|
|
377
|
+
### Store Unit Testing with VITEST
|
|
378
|
+
```typescript
|
|
379
|
+
// user.store.test.ts
|
|
380
|
+
import { describe, it, expect, beforeEach } from 'vitest'
|
|
381
|
+
import { setActivePinia, createPinia } from 'pinia'
|
|
382
|
+
import { useUserStore } from './user.store'
|
|
383
|
+
|
|
384
|
+
describe('User Store', () => {
|
|
385
|
+
beforeEach(() => {
|
|
386
|
+
setActivePinia(createPinia())
|
|
387
|
+
})
|
|
388
|
+
|
|
389
|
+
it('should initialize with empty state', () => {
|
|
390
|
+
const store = useUserStore()
|
|
391
|
+
|
|
392
|
+
expect(store.users).toEqual([])
|
|
393
|
+
expect(store.loading).toBe(false)
|
|
394
|
+
expect(store.error).toBe(null)
|
|
395
|
+
})
|
|
396
|
+
|
|
397
|
+
it('should set users correctly', () => {
|
|
398
|
+
const store = useUserStore()
|
|
399
|
+
const mockUsers = [
|
|
400
|
+
{ id: '1', name: 'John Doe', isActive: true }
|
|
401
|
+
]
|
|
402
|
+
|
|
403
|
+
store.setUsers(mockUsers)
|
|
404
|
+
|
|
405
|
+
expect(store.users).toEqual(mockUsers)
|
|
406
|
+
expect(store.totalUsers).toBe(1)
|
|
407
|
+
})
|
|
408
|
+
|
|
409
|
+
it('should filter users correctly', () => {
|
|
410
|
+
const store = useUserStore()
|
|
411
|
+
const mockUsers = [
|
|
412
|
+
{ id: '1', name: 'John Doe', isActive: true },
|
|
413
|
+
{ id: '2', name: 'Jane Smith', isActive: false }
|
|
414
|
+
]
|
|
415
|
+
|
|
416
|
+
store.setUsers(mockUsers)
|
|
417
|
+
store.updateFilters({ isActive: true })
|
|
418
|
+
|
|
419
|
+
expect(store.filteredUsers).toHaveLength(1)
|
|
420
|
+
expect(store.filteredUsers[0].name).toBe('John Doe')
|
|
421
|
+
})
|
|
422
|
+
})
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
## Performance Considerations
|
|
426
|
+
|
|
427
|
+
### Store Optimization
|
|
428
|
+
```typescript
|
|
429
|
+
// Good: Efficient state updates
|
|
430
|
+
const updateUsersBatch = (updates: IUserUpdate[]): void => {
|
|
431
|
+
// Batch updates to minimize reactivity triggers
|
|
432
|
+
const updatedUsers = [...users.value]
|
|
433
|
+
|
|
434
|
+
updates.forEach(({ id, data }) => {
|
|
435
|
+
const index = updatedUsers.findIndex(user => user.id === id)
|
|
436
|
+
if (index !== -1) {
|
|
437
|
+
updatedUsers[index] = { ...updatedUsers[index], ...data }
|
|
438
|
+
}
|
|
439
|
+
})
|
|
440
|
+
|
|
441
|
+
users.value = updatedUsers
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Good: Memoized complex getters
|
|
445
|
+
const expensiveComputation = computed(() => {
|
|
446
|
+
// Use shallowRef for large datasets if needed
|
|
447
|
+
return users.value.reduce((acc, user) => {
|
|
448
|
+
// Complex calculation
|
|
449
|
+
return acc
|
|
450
|
+
}, {})
|
|
451
|
+
})
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
## File Organization Standards
|
|
455
|
+
|
|
456
|
+
### Store File Structure
|
|
457
|
+
```
|
|
458
|
+
src/
|
|
459
|
+
├── stores/ # Global stores
|
|
460
|
+
│ ├── index.ts # Store exports
|
|
461
|
+
│ ├── auth.store.ts # Authentication store
|
|
462
|
+
│ └── app.store.ts # Application-wide state
|
|
463
|
+
└── modules/
|
|
464
|
+
└── user/
|
|
465
|
+
└── stores/ # Module-specific stores
|
|
466
|
+
└── user.store.ts
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
### Store Index File
|
|
470
|
+
```typescript
|
|
471
|
+
// /src/stores/index.ts
|
|
472
|
+
export { useAuthStore } from './auth.store'
|
|
473
|
+
export { useAppStore } from './app.store'
|
|
474
|
+
|
|
475
|
+
// Module stores
|
|
476
|
+
export { useUserStore } from '{{MODULE_PATH}}/stores/user.store'
|
|
477
|
+
export { useProductStore } from '{{MODULE_PATH}}/stores/product.store'
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
## Store Persistence
|
|
481
|
+
|
|
482
|
+
### Local Storage Integration
|
|
483
|
+
```typescript
|
|
484
|
+
// Good: Persistence through composables, not stores
|
|
485
|
+
export function useUserStorePersistence() {
|
|
486
|
+
const userStore = useUserStore()
|
|
487
|
+
|
|
488
|
+
const saveToLocalStorage = (): void => {
|
|
489
|
+
localStorage.setItem('userFilters', JSON.stringify(userStore.filters))
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
const loadFromLocalStorage = (): void => {
|
|
493
|
+
const saved = localStorage.getItem('userFilters')
|
|
494
|
+
if (saved) {
|
|
495
|
+
const filters = JSON.parse(saved)
|
|
496
|
+
userStore.updateFilters(filters)
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
return { saveToLocalStorage, loadFromLocalStorage }
|
|
501
|
+
}
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
Remember: Pinia stores are reactive data containers. Keep them simple, focused, and free of business logic. Use composables to orchestrate store operations and handle complex business requirements.
|