autoworkflow 3.1.5 → 3.5.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/pre-edit.sh +129 -0
- package/.claude/hooks/session-check.sh +79 -0
- package/.claude/settings.json +40 -6
- package/.claude/settings.local.json +3 -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 +135 -52
- package/package.json +1 -1
- package/system/triggers.md +152 -11
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
# Pinia Skill
|
|
2
|
+
|
|
3
|
+
## Setup
|
|
4
|
+
\`\`\`typescript
|
|
5
|
+
// main.ts
|
|
6
|
+
import { createApp } from 'vue';
|
|
7
|
+
import { createPinia } from 'pinia';
|
|
8
|
+
import App from './App.vue';
|
|
9
|
+
|
|
10
|
+
const app = createApp(App);
|
|
11
|
+
const pinia = createPinia();
|
|
12
|
+
|
|
13
|
+
app.use(pinia);
|
|
14
|
+
app.mount('#app');
|
|
15
|
+
\`\`\`
|
|
16
|
+
|
|
17
|
+
## Setup Stores (Composition API - Recommended)
|
|
18
|
+
\`\`\`typescript
|
|
19
|
+
// stores/user.ts
|
|
20
|
+
import { defineStore } from 'pinia';
|
|
21
|
+
import { ref, computed } from 'vue';
|
|
22
|
+
|
|
23
|
+
interface User {
|
|
24
|
+
id: string;
|
|
25
|
+
email: string;
|
|
26
|
+
name: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const useUserStore = defineStore('user', () => {
|
|
30
|
+
// State (refs)
|
|
31
|
+
const user = ref<User | null>(null);
|
|
32
|
+
const loading = ref(false);
|
|
33
|
+
const error = ref<string | null>(null);
|
|
34
|
+
|
|
35
|
+
// Getters (computed)
|
|
36
|
+
const isLoggedIn = computed(() => !!user.value);
|
|
37
|
+
const userName = computed(() => user.value?.name ?? 'Guest');
|
|
38
|
+
|
|
39
|
+
// Actions (functions)
|
|
40
|
+
async function login(email: string, password: string) {
|
|
41
|
+
loading.value = true;
|
|
42
|
+
error.value = null;
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
const response = await fetch('/api/auth/login', {
|
|
46
|
+
method: 'POST',
|
|
47
|
+
headers: { 'Content-Type': 'application/json' },
|
|
48
|
+
body: JSON.stringify({ email, password }),
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
if (!response.ok) throw new Error('Login failed');
|
|
52
|
+
|
|
53
|
+
user.value = await response.json();
|
|
54
|
+
} catch (e) {
|
|
55
|
+
error.value = (e as Error).message;
|
|
56
|
+
throw e;
|
|
57
|
+
} finally {
|
|
58
|
+
loading.value = false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function logout() {
|
|
63
|
+
await fetch('/api/auth/logout', { method: 'POST' });
|
|
64
|
+
user.value = null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function updateProfile(data: Partial<User>) {
|
|
68
|
+
if (user.value) {
|
|
69
|
+
user.value = { ...user.value, ...data };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Return everything that should be exposed
|
|
74
|
+
return {
|
|
75
|
+
// State
|
|
76
|
+
user,
|
|
77
|
+
loading,
|
|
78
|
+
error,
|
|
79
|
+
// Getters
|
|
80
|
+
isLoggedIn,
|
|
81
|
+
userName,
|
|
82
|
+
// Actions
|
|
83
|
+
login,
|
|
84
|
+
logout,
|
|
85
|
+
updateProfile,
|
|
86
|
+
};
|
|
87
|
+
});
|
|
88
|
+
\`\`\`
|
|
89
|
+
|
|
90
|
+
## Options Stores (Options API)
|
|
91
|
+
\`\`\`typescript
|
|
92
|
+
// stores/cart.ts
|
|
93
|
+
import { defineStore } from 'pinia';
|
|
94
|
+
|
|
95
|
+
interface CartItem {
|
|
96
|
+
id: string;
|
|
97
|
+
name: string;
|
|
98
|
+
price: number;
|
|
99
|
+
quantity: number;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export const useCartStore = defineStore('cart', {
|
|
103
|
+
// State
|
|
104
|
+
state: () => ({
|
|
105
|
+
items: [] as CartItem[],
|
|
106
|
+
coupon: null as string | null,
|
|
107
|
+
}),
|
|
108
|
+
|
|
109
|
+
// Getters
|
|
110
|
+
getters: {
|
|
111
|
+
totalItems: (state) => state.items.reduce((sum, item) => sum + item.quantity, 0),
|
|
112
|
+
|
|
113
|
+
totalPrice: (state) =>
|
|
114
|
+
state.items.reduce((sum, item) => sum + item.price * item.quantity, 0),
|
|
115
|
+
|
|
116
|
+
// Getter using other getters
|
|
117
|
+
formattedTotal(): string {
|
|
118
|
+
return \`$\${this.totalPrice.toFixed(2)}\`;
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
// Getter with parameters
|
|
122
|
+
getItemById: (state) => (id: string) =>
|
|
123
|
+
state.items.find((item) => item.id === id),
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
// Actions
|
|
127
|
+
actions: {
|
|
128
|
+
addItem(item: Omit<CartItem, 'quantity'>) {
|
|
129
|
+
const existing = this.items.find((i) => i.id === item.id);
|
|
130
|
+
if (existing) {
|
|
131
|
+
existing.quantity++;
|
|
132
|
+
} else {
|
|
133
|
+
this.items.push({ ...item, quantity: 1 });
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
removeItem(id: string) {
|
|
138
|
+
const index = this.items.findIndex((item) => item.id === id);
|
|
139
|
+
if (index !== -1) {
|
|
140
|
+
this.items.splice(index, 1);
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
updateQuantity(id: string, quantity: number) {
|
|
145
|
+
const item = this.items.find((i) => i.id === id);
|
|
146
|
+
if (item) {
|
|
147
|
+
item.quantity = Math.max(0, quantity);
|
|
148
|
+
if (item.quantity === 0) {
|
|
149
|
+
this.removeItem(id);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
clearCart() {
|
|
155
|
+
this.items = [];
|
|
156
|
+
this.coupon = null;
|
|
157
|
+
},
|
|
158
|
+
|
|
159
|
+
// Async action
|
|
160
|
+
async applyCoupon(code: string) {
|
|
161
|
+
const response = await fetch(\`/api/coupons/\${code}\`);
|
|
162
|
+
if (response.ok) {
|
|
163
|
+
this.coupon = code;
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
\`\`\`
|
|
169
|
+
|
|
170
|
+
## Usage in Components
|
|
171
|
+
\`\`\`vue
|
|
172
|
+
<script setup lang="ts">
|
|
173
|
+
import { useUserStore } from '@/stores/user';
|
|
174
|
+
import { useCartStore } from '@/stores/cart';
|
|
175
|
+
import { storeToRefs } from 'pinia';
|
|
176
|
+
|
|
177
|
+
// Setup store usage
|
|
178
|
+
const userStore = useUserStore();
|
|
179
|
+
const cartStore = useCartStore();
|
|
180
|
+
|
|
181
|
+
// Destructure reactive state (use storeToRefs to keep reactivity)
|
|
182
|
+
const { user, loading, isLoggedIn } = storeToRefs(userStore);
|
|
183
|
+
|
|
184
|
+
// Actions can be destructured directly
|
|
185
|
+
const { login, logout } = userStore;
|
|
186
|
+
|
|
187
|
+
// Getters
|
|
188
|
+
const { totalItems, formattedTotal } = storeToRefs(cartStore);
|
|
189
|
+
|
|
190
|
+
// Direct access
|
|
191
|
+
const handleLogin = async () => {
|
|
192
|
+
await userStore.login(email.value, password.value);
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
// Watch store changes
|
|
196
|
+
watch(
|
|
197
|
+
() => userStore.user,
|
|
198
|
+
(newUser) => {
|
|
199
|
+
console.log('User changed:', newUser);
|
|
200
|
+
}
|
|
201
|
+
);
|
|
202
|
+
</script>
|
|
203
|
+
|
|
204
|
+
<template>
|
|
205
|
+
<div>
|
|
206
|
+
<div v-if="loading">Loading...</div>
|
|
207
|
+
<div v-else-if="isLoggedIn">
|
|
208
|
+
Welcome, {{ user?.name }}
|
|
209
|
+
<button @click="logout">Logout</button>
|
|
210
|
+
</div>
|
|
211
|
+
<div v-else>
|
|
212
|
+
<button @click="handleLogin">Login</button>
|
|
213
|
+
</div>
|
|
214
|
+
|
|
215
|
+
<div>Cart: {{ totalItems }} items ({{ formattedTotal }})</div>
|
|
216
|
+
</div>
|
|
217
|
+
</template>
|
|
218
|
+
\`\`\`
|
|
219
|
+
|
|
220
|
+
## Store Composition (Using Other Stores)
|
|
221
|
+
\`\`\`typescript
|
|
222
|
+
// stores/checkout.ts
|
|
223
|
+
import { defineStore } from 'pinia';
|
|
224
|
+
import { useUserStore } from './user';
|
|
225
|
+
import { useCartStore } from './cart';
|
|
226
|
+
|
|
227
|
+
export const useCheckoutStore = defineStore('checkout', () => {
|
|
228
|
+
const userStore = useUserStore();
|
|
229
|
+
const cartStore = useCartStore();
|
|
230
|
+
|
|
231
|
+
const canCheckout = computed(() =>
|
|
232
|
+
userStore.isLoggedIn && cartStore.totalItems > 0
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
async function processCheckout() {
|
|
236
|
+
if (!canCheckout.value) {
|
|
237
|
+
throw new Error('Cannot checkout');
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const order = {
|
|
241
|
+
userId: userStore.user!.id,
|
|
242
|
+
items: cartStore.items,
|
|
243
|
+
total: cartStore.totalPrice,
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
const response = await fetch('/api/orders', {
|
|
247
|
+
method: 'POST',
|
|
248
|
+
body: JSON.stringify(order),
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
if (response.ok) {
|
|
252
|
+
cartStore.clearCart();
|
|
253
|
+
return response.json();
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
throw new Error('Checkout failed');
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return { canCheckout, processCheckout };
|
|
260
|
+
});
|
|
261
|
+
\`\`\`
|
|
262
|
+
|
|
263
|
+
## Plugins
|
|
264
|
+
|
|
265
|
+
### Persistence Plugin
|
|
266
|
+
\`\`\`typescript
|
|
267
|
+
// plugins/piniaPersistedState.ts
|
|
268
|
+
import { createPinia } from 'pinia';
|
|
269
|
+
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';
|
|
270
|
+
|
|
271
|
+
const pinia = createPinia();
|
|
272
|
+
pinia.use(piniaPluginPersistedstate);
|
|
273
|
+
|
|
274
|
+
// In store
|
|
275
|
+
export const useUserStore = defineStore('user', () => {
|
|
276
|
+
const user = ref<User | null>(null);
|
|
277
|
+
// ...
|
|
278
|
+
return { user };
|
|
279
|
+
}, {
|
|
280
|
+
persist: true, // Persist entire store
|
|
281
|
+
|
|
282
|
+
// Or with options
|
|
283
|
+
persist: {
|
|
284
|
+
key: 'user-store',
|
|
285
|
+
storage: sessionStorage, // Default: localStorage
|
|
286
|
+
paths: ['user'], // Only persist specific paths
|
|
287
|
+
},
|
|
288
|
+
});
|
|
289
|
+
\`\`\`
|
|
290
|
+
|
|
291
|
+
### Custom Plugin
|
|
292
|
+
\`\`\`typescript
|
|
293
|
+
// plugins/logger.ts
|
|
294
|
+
import { PiniaPluginContext } from 'pinia';
|
|
295
|
+
|
|
296
|
+
export function loggerPlugin({ store }: PiniaPluginContext) {
|
|
297
|
+
store.$subscribe((mutation, state) => {
|
|
298
|
+
console.log(\`[\${mutation.storeId}] \${mutation.type}\`, state);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
store.$onAction(({ name, store, args, after, onError }) => {
|
|
302
|
+
console.log(\`Action "\${name}" started on \${store.$id}\`);
|
|
303
|
+
|
|
304
|
+
after((result) => {
|
|
305
|
+
console.log(\`Action "\${name}" finished. Result:\`, result);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
onError((error) => {
|
|
309
|
+
console.error(\`Action "\${name}" failed:\`, error);
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Register plugin
|
|
315
|
+
pinia.use(loggerPlugin);
|
|
316
|
+
\`\`\`
|
|
317
|
+
|
|
318
|
+
## Reset Store
|
|
319
|
+
\`\`\`typescript
|
|
320
|
+
// Setup store - manual reset
|
|
321
|
+
export const useUserStore = defineStore('user', () => {
|
|
322
|
+
const initialState = {
|
|
323
|
+
user: null as User | null,
|
|
324
|
+
loading: false,
|
|
325
|
+
error: null as string | null,
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
const user = ref(initialState.user);
|
|
329
|
+
const loading = ref(initialState.loading);
|
|
330
|
+
const error = ref(initialState.error);
|
|
331
|
+
|
|
332
|
+
function $reset() {
|
|
333
|
+
user.value = initialState.user;
|
|
334
|
+
loading.value = initialState.loading;
|
|
335
|
+
error.value = initialState.error;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return { user, loading, error, $reset };
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
// Options store - $reset is automatic
|
|
342
|
+
const cartStore = useCartStore();
|
|
343
|
+
cartStore.$reset(); // Resets to initial state
|
|
344
|
+
\`\`\`
|
|
345
|
+
|
|
346
|
+
## Testing
|
|
347
|
+
\`\`\`typescript
|
|
348
|
+
import { setActivePinia, createPinia } from 'pinia';
|
|
349
|
+
import { useUserStore } from '@/stores/user';
|
|
350
|
+
|
|
351
|
+
describe('User Store', () => {
|
|
352
|
+
beforeEach(() => {
|
|
353
|
+
setActivePinia(createPinia());
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
it('should login user', async () => {
|
|
357
|
+
const store = useUserStore();
|
|
358
|
+
|
|
359
|
+
expect(store.isLoggedIn).toBe(false);
|
|
360
|
+
|
|
361
|
+
// Mock fetch
|
|
362
|
+
global.fetch = vi.fn().mockResolvedValue({
|
|
363
|
+
ok: true,
|
|
364
|
+
json: () => Promise.resolve({ id: '1', name: 'John', email: 'john@example.com' }),
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
await store.login('john@example.com', 'password');
|
|
368
|
+
|
|
369
|
+
expect(store.isLoggedIn).toBe(true);
|
|
370
|
+
expect(store.user?.name).toBe('John');
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
it('should handle login error', async () => {
|
|
374
|
+
const store = useUserStore();
|
|
375
|
+
|
|
376
|
+
global.fetch = vi.fn().mockResolvedValue({
|
|
377
|
+
ok: false,
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
await expect(store.login('test@example.com', 'wrong')).rejects.toThrow();
|
|
381
|
+
expect(store.error).toBe('Login failed');
|
|
382
|
+
});
|
|
383
|
+
});
|
|
384
|
+
\`\`\`
|
|
385
|
+
|
|
386
|
+
## Outside Components
|
|
387
|
+
\`\`\`typescript
|
|
388
|
+
// router/guards.ts
|
|
389
|
+
import { useUserStore } from '@/stores/user';
|
|
390
|
+
import type { Router } from 'vue-router';
|
|
391
|
+
|
|
392
|
+
export function setupGuards(router: Router) {
|
|
393
|
+
router.beforeEach((to) => {
|
|
394
|
+
// Must be called after pinia is installed
|
|
395
|
+
const userStore = useUserStore();
|
|
396
|
+
|
|
397
|
+
if (to.meta.requiresAuth && !userStore.isLoggedIn) {
|
|
398
|
+
return { name: 'login', query: { redirect: to.fullPath } };
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// api/client.ts
|
|
404
|
+
import { useUserStore } from '@/stores/user';
|
|
405
|
+
|
|
406
|
+
export async function apiClient(url: string, options?: RequestInit) {
|
|
407
|
+
const userStore = useUserStore();
|
|
408
|
+
|
|
409
|
+
return fetch(url, {
|
|
410
|
+
...options,
|
|
411
|
+
headers: {
|
|
412
|
+
...options?.headers,
|
|
413
|
+
Authorization: userStore.user ? \`Bearer \${userStore.token}\` : '',
|
|
414
|
+
},
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
\`\`\`
|
|
418
|
+
|
|
419
|
+
## ❌ DON'T
|
|
420
|
+
- Destructure state directly (loses reactivity)
|
|
421
|
+
- Mutate state outside actions
|
|
422
|
+
- Create stores inside components
|
|
423
|
+
- Forget to use storeToRefs for reactive destructuring
|
|
424
|
+
|
|
425
|
+
## ✅ DO
|
|
426
|
+
- Use Setup Stores for better TypeScript support
|
|
427
|
+
- Use storeToRefs when destructuring state/getters
|
|
428
|
+
- Use store composition for related stores
|
|
429
|
+
- Use persistence plugin for localStorage sync
|
|
430
|
+
- Reset stores in tests with setActivePinia
|
|
431
|
+
- Define $reset manually for Setup Stores
|