@ametie/vue-muza-use 0.0.1
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 +267 -0
- package/dist/index.cjs +573 -0
- package/dist/index.d.cts +350 -0
- package/dist/index.d.ts +350 -0
- package/dist/index.mjs +521 -0
- package/package.json +48 -0
package/README.md
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# Vue Muza Use 🎹
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@mortyq/vue-muza-use)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://vuejs.org/)
|
|
6
|
+
[](https://www.typescriptlang.org/)
|
|
7
|
+
|
|
8
|
+
**The Enterprise-Ready API Composable for Vue 3.**
|
|
9
|
+
|
|
10
|
+
`vue-muza-use` is a powerful, framework-agnostic wrapper around **Axios** designed to solve the most common headaches in modern frontend development: race conditions, token refreshing, request cancellation, and strict typing.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 🚀 Why is this cool?
|
|
15
|
+
|
|
16
|
+
* 🛡 **Zombie Retry Protection**
|
|
17
|
+
Smart retries that respect component lifecycle. If your component is unmounted, the retries stop immediately. No more memory leaks or errors trying to update dead components.
|
|
18
|
+
|
|
19
|
+
* 💉 **Dependency Injection**
|
|
20
|
+
Fully decoupled architecture. You inject your own Axios instance via the Vue plugin system (`app.use`). This means you keep full control over your HTTP client configuration.
|
|
21
|
+
|
|
22
|
+
* 🔄 **Smart Auth Refresh**
|
|
23
|
+
Built-in (but optional) **Interceptor Queue**. If your Access Token expires (`401`), the library pauses all outgoing requests, refreshes the token once, replays the queue, and then resumes normal operation.
|
|
24
|
+
|
|
25
|
+
* ⚡ **Developer Experience**
|
|
26
|
+
* **Auto-Cleanup**: Aborts pending requests automatically when the component unmounts.
|
|
27
|
+
* **Global Abort**: Cancel all previous pending requests when filters change (race condition killer).
|
|
28
|
+
* **Debounce**: Built-in debouncing for search inputs.
|
|
29
|
+
* **TypeScript First**: strict typing for Request/Response definitions.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## 📦 Installation
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# npm
|
|
37
|
+
npm install @mortyq/vue-muza-use axios
|
|
38
|
+
|
|
39
|
+
# pnpm
|
|
40
|
+
pnpm add @mortyq/vue-muza-use axios
|
|
41
|
+
|
|
42
|
+
# yarn
|
|
43
|
+
yarn add @mortyq/vue-muza-use axios
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## ⚡ Quick Start
|
|
49
|
+
|
|
50
|
+
### 1. Setup in `main.ts`
|
|
51
|
+
|
|
52
|
+
Use `createApiClient` for a "batteries-included" setup, or bring your own Axios instance.
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
import { createApp } from 'vue'
|
|
56
|
+
import { createApi, createApiClient, setAuthMonitor } from '@mortyq/vue-muza-use'
|
|
57
|
+
import App from './App.vue'
|
|
58
|
+
|
|
59
|
+
const app = createApp(App)
|
|
60
|
+
|
|
61
|
+
// 1. Create Axios instance with Auth features
|
|
62
|
+
const api = createApiClient({
|
|
63
|
+
baseURL: 'https://api.example.com',
|
|
64
|
+
withAuth: true, // Enable automatic token injection
|
|
65
|
+
authOptions: {
|
|
66
|
+
refreshUrl: '/auth/refresh', // Endpoint for refreshing tokens
|
|
67
|
+
onTokenRefreshFailed: () => {
|
|
68
|
+
window.location.href = '/login' // Redirect on session expiry
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
// 2. Install Plugin
|
|
74
|
+
app.use(createApi({
|
|
75
|
+
axios: api,
|
|
76
|
+
// Global Error Handler (e.g., Toast notifications)
|
|
77
|
+
onError: (error) => {
|
|
78
|
+
console.error('Global API Error:', error.message)
|
|
79
|
+
// toast.error(error.message)
|
|
80
|
+
}
|
|
81
|
+
}))
|
|
82
|
+
|
|
83
|
+
// Optional: Monitor Auth Events (Debug or Analytics)
|
|
84
|
+
setAuthMonitor((event, payload) => {
|
|
85
|
+
console.log(`[Auth] ${event}`, payload)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
app.mount('#app')
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### 2. Basic Usage (Component)
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
<script setup lang="ts">
|
|
95
|
+
import { useApi } from '@mortyq/vue-muza-use'
|
|
96
|
+
|
|
97
|
+
interface User {
|
|
98
|
+
id: number
|
|
99
|
+
name: string
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// GET request
|
|
103
|
+
const { data, loading, error, execute } = useApi<User>('/users/1', {
|
|
104
|
+
immediate: true, // Fetch on mount
|
|
105
|
+
retry: 3 // Retry 3 times on failure
|
|
106
|
+
})
|
|
107
|
+
</script>
|
|
108
|
+
|
|
109
|
+
<template>
|
|
110
|
+
<div v-if="loading">Loading...</div>
|
|
111
|
+
<div v-else-if="error">Error: {{ error.message }}</div>
|
|
112
|
+
<div v-else-if="data">Hello, {{ data.name }}</div>
|
|
113
|
+
</template>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## 🛠 Usage Examples
|
|
119
|
+
|
|
120
|
+
### Reactive POST Request
|
|
121
|
+
Typically used for forms. Pass a `ref` as `data`, and it automatically unwraps current values.
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
<script setup lang="ts">
|
|
125
|
+
import { ref } from 'vue'
|
|
126
|
+
import { useApiPost } from '@mortyq/vue-muza-use'
|
|
127
|
+
|
|
128
|
+
const formData = ref({ email: '', password: '' })
|
|
129
|
+
|
|
130
|
+
const { execute, loading } = useApiPost('/auth/login', {
|
|
131
|
+
authMode: 'public', // 👈 Important for Login/Register to skip token injection
|
|
132
|
+
// Data can be a static object or a Ref
|
|
133
|
+
data: formData,
|
|
134
|
+
onSuccess: (res) => console.log('Logged in!', res.data)
|
|
135
|
+
})
|
|
136
|
+
</script>
|
|
137
|
+
|
|
138
|
+
<button @click="execute()" :disabled="loading">Login</button>
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Public Endpoints (No Auth)
|
|
142
|
+
For endpoints that don't require authentication (like Registration, Forgot Password, or Public Data), explicitly set `authMode: 'public'`.
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
const { execute } = useApiPost('/auth/register', {
|
|
146
|
+
authMode: 'public', // 👈 Prevents Authorization header injection
|
|
147
|
+
data: registrationForm
|
|
148
|
+
})
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Debounced Search
|
|
152
|
+
Perfect for search inputs to avoid spamming the API.
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
const searchQuery = ref('')
|
|
156
|
+
|
|
157
|
+
const { data, execute } = useApi<SearchResult[]>('/search', {
|
|
158
|
+
method: 'GET',
|
|
159
|
+
debounce: 500, // Wait 500ms after last call
|
|
160
|
+
abortPrevious: true // Kill previous request if new one starts
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
// Watcher triggers execute wrapped in debounce
|
|
164
|
+
watch(searchQuery, (newVal) => {
|
|
165
|
+
execute({ params: { q: newVal } })
|
|
166
|
+
})
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Global Abort (Race Condition Killer)
|
|
170
|
+
Useful for complex filters where changing one filter should invalidate all pending requests on the page (or scope).
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
import { useAbortController } from '@mortyq/vue-muza-use'
|
|
174
|
+
|
|
175
|
+
// In your specific composable or component
|
|
176
|
+
const { execute } = useApi('/heavy-report', {
|
|
177
|
+
useGlobalAbort: true // Subscribe to global abort signal
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
// Somewhere else (e.g. "Clear Filters" button)
|
|
181
|
+
const { abortAll } = useAbortController()
|
|
182
|
+
// abortAll() will cancel the '/heavy-report' request if it's pending
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## 📚 API Reference
|
|
188
|
+
|
|
189
|
+
### `useApi<T, D>(url, options)`
|
|
190
|
+
|
|
191
|
+
The main composable.
|
|
192
|
+
|
|
193
|
+
**Arguments:**
|
|
194
|
+
|
|
195
|
+
| Argument | Type | Description |
|
|
196
|
+
|---|---|---|
|
|
197
|
+
| `url` | `string | Ref<string>` | The API endpoint URL. |
|
|
198
|
+
| `options` | `UseApiOptions` | Configuration object (see below). |
|
|
199
|
+
|
|
200
|
+
**Options (`options`):**
|
|
201
|
+
|
|
202
|
+
| Option | Type | Default | Description |
|
|
203
|
+
|---|---|---|---|
|
|
204
|
+
| `method` | `'GET' \| 'POST' ...` | `'GET'` | HTTP method. |
|
|
205
|
+
| `data` | `Ref<D> \| D` | `undefined` | Request body. |
|
|
206
|
+
| `immediate` | `boolean` | `false` | Trigger request automatically on creation. |
|
|
207
|
+
| `retry` | `boolean \| number` | `false` | Number of retries on failure. |
|
|
208
|
+
| `debounce` | `number` | `0` | Debounce time in ms. |
|
|
209
|
+
| `authMode` | `'default' \| 'public'` | `'default'` | `'public'` skips token injection. |
|
|
210
|
+
| `initialData` | `T` | `null` | Initial value for `data` ref. |
|
|
211
|
+
| `onSuccess` | `(res) => void` | - | Callback on 2xx response. |
|
|
212
|
+
| `onError` | `(err) => void` | - | Callback on error. |
|
|
213
|
+
| `skipErrorNotification`| `boolean` | `false` | Prevents triggering the global `onError`. |
|
|
214
|
+
|
|
215
|
+
**Return Values:**
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
{
|
|
219
|
+
data: Ref<T | null> // The response data
|
|
220
|
+
loading: Ref<boolean> // Loading state
|
|
221
|
+
error: Ref<ApiError | null> // Typed error object
|
|
222
|
+
execute: (config?) => Promise<T | null> // Manual trigger
|
|
223
|
+
abort: (msg?) => void // Cancel current request
|
|
224
|
+
response: Ref<AxiosResponse> // Full axios response object
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### `createApiClient(options)`
|
|
229
|
+
|
|
230
|
+
Factory function to create a configured Axios instance.
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
interface CreateApiClientOptions {
|
|
234
|
+
// Standard Axios config (baseURL, timeout, etc.)
|
|
235
|
+
baseURL?: string;
|
|
236
|
+
timeout?: number;
|
|
237
|
+
|
|
238
|
+
// Custom auth features
|
|
239
|
+
withAuth?: boolean; // Default: true. Inject Authorization header?
|
|
240
|
+
authOptions?: {
|
|
241
|
+
refreshUrl?: string; // Default: '/auth/refresh'
|
|
242
|
+
onTokenRefreshFailed?: () => void;
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## 🔐 Auth & Refresh Logic
|
|
250
|
+
|
|
251
|
+
`vue-muza-use` implements a robust **Interceptor Queue** pattern for handling JWTs.
|
|
252
|
+
|
|
253
|
+
1. **Request Flow**: Every request automatically gets the `Authorization: Bearer ...` header if `withAuth` is enabled.
|
|
254
|
+
2. **401 Detection**: If a request fails with `401 Unauthorized`, it is **caught** by the interceptor.
|
|
255
|
+
3. **Queueing**: The failed request is added to a `failedQueue`. Any subsequent requests that happen while refreshing are also paused and added to this queue.
|
|
256
|
+
4. **Reference Refresh**: The system triggers a call to `refreshUrl` (e.g., sends a `refresh_token` HTTP-only cookie).
|
|
257
|
+
5. **Retry**:
|
|
258
|
+
* **Success**: The system gets a new Access Token, updates the store, and **replays** all queued requests with the new token.
|
|
259
|
+
* **Failure**: If the refresh fails, `onTokenRefreshFailed` is called (use this to logout user) and all queued requests are rejected.
|
|
260
|
+
|
|
261
|
+
This happens transparently to your components. They just "wait" a bit longer for the response.
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## License
|
|
266
|
+
|
|
267
|
+
MIT © [MortyQ](https://github.com/MortyQ)
|