@ametie/vue-muza-use 0.5.0 → 0.6.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 +318 -20
- package/dist/index.cjs +241 -3
- package/dist/index.d.cts +146 -12
- package/dist/index.d.ts +146 -12
- package/dist/index.mjs +240 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,6 +19,7 @@ A production-ready composable that eliminates boilerplate and solves the hard pr
|
|
|
19
19
|
- ⏱️ **Built-in Debouncing** — Perfect for search inputs and auto-save forms
|
|
20
20
|
- 🛡️ **Race Condition Protection** — Global abort controller cancels stale requests automatically
|
|
21
21
|
- 📊 **Auto-Polling** — Built-in interval fetching with smart tab visibility detection
|
|
22
|
+
- 🚀 **Batch Requests** — Execute multiple requests in parallel with progress tracking
|
|
22
23
|
- 🧹 **Zero Memory Leaks** — Automatic cleanup of pending requests on component unmount
|
|
23
24
|
|
|
24
25
|
**Advanced Features** (When you need them):
|
|
@@ -45,6 +46,7 @@ A production-ready composable that eliminates boilerplate and solves the hard pr
|
|
|
45
46
|
**Real-World Examples:**
|
|
46
47
|
- [Data Table with Pagination](#data-table-with-pagination--sorting)
|
|
47
48
|
- [Request Cancellation](#request-cancellation)
|
|
49
|
+
- [Batch Requests](#batch-requests)
|
|
48
50
|
|
|
49
51
|
**Advanced:**
|
|
50
52
|
- [Custom Axios Instance](#-advanced-configuration)
|
|
@@ -376,9 +378,9 @@ const { execute } = useApi('/analytics', {
|
|
|
376
378
|
|
|
377
379
|
### Manual Data Updates
|
|
378
380
|
|
|
379
|
-
Use `
|
|
381
|
+
Use `mutate` to manually update the data ref. Supports direct values or updater functions (like React's `setState`).
|
|
380
382
|
|
|
381
|
-
> 🎓 **When to use `
|
|
383
|
+
> 🎓 **When to use `mutate`:**
|
|
382
384
|
> ✅ Adding/removing/updating items in arrays
|
|
383
385
|
> ✅ Local sorting/filtering (without refetching)
|
|
384
386
|
> ✅ Transform data in `onSuccess` (adding computed fields)
|
|
@@ -390,21 +392,21 @@ Use `setData` to manually update the data ref. Supports direct values or updater
|
|
|
390
392
|
|
|
391
393
|
#### Add/Remove/Update Items
|
|
392
394
|
```typescript
|
|
393
|
-
const { data,
|
|
395
|
+
const { data, mutate } = useApi<Todo[]>('/todos', { immediate: true })
|
|
394
396
|
|
|
395
397
|
// Add item
|
|
396
398
|
const addTodo = (newTodo: Todo) => {
|
|
397
|
-
|
|
399
|
+
mutate(prev => prev ? [...prev, newTodo] : [newTodo])
|
|
398
400
|
}
|
|
399
401
|
|
|
400
402
|
// Remove item
|
|
401
403
|
const removeTodo = (id: number) => {
|
|
402
|
-
|
|
404
|
+
mutate(prev => prev?.filter(t => t.id !== id) ?? null)
|
|
403
405
|
}
|
|
404
406
|
|
|
405
407
|
// Update item
|
|
406
408
|
const updateTodo = (id: number, updates: Partial<Todo>) => {
|
|
407
|
-
|
|
409
|
+
mutate(prev =>
|
|
408
410
|
prev?.map(t => t.id === id ? { ...t, ...updates } : t) ?? null
|
|
409
411
|
)
|
|
410
412
|
}
|
|
@@ -412,14 +414,14 @@ const updateTodo = (id: number, updates: Partial<Todo>) => {
|
|
|
412
414
|
|
|
413
415
|
#### Sort/Filter Locally
|
|
414
416
|
```typescript
|
|
415
|
-
const { data,
|
|
417
|
+
const { data, mutate } = useApi<Product[]>('/products', { immediate: true })
|
|
416
418
|
|
|
417
419
|
const sortByPrice = () => {
|
|
418
|
-
|
|
420
|
+
mutate(prev => prev ? [...prev].sort((a, b) => a.price - b.price) : null)
|
|
419
421
|
}
|
|
420
422
|
|
|
421
423
|
const filterActive = () => {
|
|
422
|
-
|
|
424
|
+
mutate(prev => prev?.filter(p => p.active) ?? null)
|
|
423
425
|
}
|
|
424
426
|
|
|
425
427
|
// Reset to original
|
|
@@ -428,7 +430,7 @@ const resetFilters = () => execute()
|
|
|
428
430
|
|
|
429
431
|
#### Transform in `onSuccess`
|
|
430
432
|
|
|
431
|
-
Use `
|
|
433
|
+
Use `mutate` in `onSuccess` to transform data right after fetching. Two approaches:
|
|
432
434
|
|
|
433
435
|
**Approach 1: Same type (recommended)**
|
|
434
436
|
```typescript
|
|
@@ -439,11 +441,11 @@ interface User {
|
|
|
439
441
|
fullName?: string // Optional field
|
|
440
442
|
}
|
|
441
443
|
|
|
442
|
-
const { data,
|
|
444
|
+
const { data, mutate } = useApi<User[]>('/users', {
|
|
443
445
|
immediate: true,
|
|
444
446
|
onSuccess: ({ data: users }) => {
|
|
445
447
|
// Add computed field - still User[] type
|
|
446
|
-
|
|
448
|
+
mutate(users.map(u => ({
|
|
447
449
|
...u,
|
|
448
450
|
fullName: `${u.firstName} ${u.lastName}`
|
|
449
451
|
})))
|
|
@@ -471,7 +473,7 @@ const users = computed(() =>
|
|
|
471
473
|
```
|
|
472
474
|
|
|
473
475
|
> 💡 **Rule of thumb:**
|
|
474
|
-
> - ✅ **Use `
|
|
476
|
+
> - ✅ **Use `mutate` in `onSuccess`** if you're adding/modifying fields but keeping the same base type
|
|
475
477
|
> - ✅ **Use `computed`** if you're completely changing the data structure (e.g., snake_case → camelCase)
|
|
476
478
|
|
|
477
479
|
---
|
|
@@ -541,6 +543,219 @@ const resetFilters = () => {
|
|
|
541
543
|
|
|
542
544
|
---
|
|
543
545
|
|
|
546
|
+
### Batch Requests
|
|
547
|
+
|
|
548
|
+
Execute multiple API requests in parallel with full reactive state, progress tracking, and error tolerance.
|
|
549
|
+
|
|
550
|
+
#### Basic Usage
|
|
551
|
+
|
|
552
|
+
```typescript
|
|
553
|
+
import { useApiBatch } from '@ametie/vue-muza-use'
|
|
554
|
+
|
|
555
|
+
interface User {
|
|
556
|
+
id: number
|
|
557
|
+
name: string
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
const {
|
|
561
|
+
successfulData, // Ref<User[]> - only successful results
|
|
562
|
+
loading, // Ref<boolean>
|
|
563
|
+
progress, // Ref<{ completed, total, percentage, succeeded, failed }>
|
|
564
|
+
execute
|
|
565
|
+
} = useApiBatch<User>([
|
|
566
|
+
'/users/1',
|
|
567
|
+
'/users/2',
|
|
568
|
+
'/users/3'
|
|
569
|
+
])
|
|
570
|
+
|
|
571
|
+
await execute()
|
|
572
|
+
console.log(successfulData.value) // [User, User, User]
|
|
573
|
+
console.log(progress.value) // { completed: 3, total: 3, percentage: 100, succeeded: 3, failed: 0 }
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
#### Error Tolerance (Default)
|
|
577
|
+
|
|
578
|
+
By default, `useApiBatch` uses `settled: true` — failed requests don't stop the batch:
|
|
579
|
+
|
|
580
|
+
```typescript
|
|
581
|
+
const {
|
|
582
|
+
successfulData,
|
|
583
|
+
errors, // Ref<ApiError[]> - all errors
|
|
584
|
+
progress,
|
|
585
|
+
execute
|
|
586
|
+
} = useApiBatch<User>([
|
|
587
|
+
'/users/1',
|
|
588
|
+
'/users/999', // Will fail (404)
|
|
589
|
+
'/users/3'
|
|
590
|
+
])
|
|
591
|
+
|
|
592
|
+
await execute()
|
|
593
|
+
|
|
594
|
+
console.log(successfulData.value.length) // 2 (successful)
|
|
595
|
+
console.log(errors.value.length) // 1 (failed)
|
|
596
|
+
console.log(progress.value) // { succeeded: 2, failed: 1, ... }
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
#### Strict Mode
|
|
600
|
+
|
|
601
|
+
Fail immediately on first error:
|
|
602
|
+
|
|
603
|
+
```typescript
|
|
604
|
+
const { execute } = useApiBatch<User>(urls, {
|
|
605
|
+
settled: false // First error will reject the entire batch
|
|
606
|
+
})
|
|
607
|
+
|
|
608
|
+
try {
|
|
609
|
+
await execute()
|
|
610
|
+
} catch (error) {
|
|
611
|
+
console.log('Batch failed:', error.message)
|
|
612
|
+
}
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
#### With Progress Tracking
|
|
616
|
+
|
|
617
|
+
Perfect for loading indicators and progress bars:
|
|
618
|
+
|
|
619
|
+
```vue
|
|
620
|
+
<script setup lang="ts">
|
|
621
|
+
const { loading, progress, execute } = useApiBatch<User>(urls, {
|
|
622
|
+
onProgress: (p) => {
|
|
623
|
+
console.log(`${p.percentage}% complete (${p.succeeded} ok, ${p.failed} failed)`)
|
|
624
|
+
}
|
|
625
|
+
})
|
|
626
|
+
</script>
|
|
627
|
+
|
|
628
|
+
<template>
|
|
629
|
+
<div v-if="loading">
|
|
630
|
+
<div class="progress-bar">
|
|
631
|
+
<div :style="{ width: progress.percentage + '%' }"></div>
|
|
632
|
+
</div>
|
|
633
|
+
<span>{{ progress.completed }} / {{ progress.total }}</span>
|
|
634
|
+
</div>
|
|
635
|
+
</template>
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
#### Concurrency Limit
|
|
639
|
+
|
|
640
|
+
Control how many requests run in parallel (useful for rate limiting):
|
|
641
|
+
|
|
642
|
+
```typescript
|
|
643
|
+
// Only 3 requests at a time
|
|
644
|
+
const { execute } = useApiBatch<User>(hundredUrls, {
|
|
645
|
+
concurrency: 3
|
|
646
|
+
})
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
#### Reactive URLs
|
|
650
|
+
|
|
651
|
+
URLs can be reactive — use refs or computed:
|
|
652
|
+
|
|
653
|
+
```typescript
|
|
654
|
+
const userIds = ref([1, 2, 3])
|
|
655
|
+
const urls = computed(() => userIds.value.map(id => `/users/${id}`))
|
|
656
|
+
|
|
657
|
+
const { successfulData, execute } = useApiBatch<User>(urls, {
|
|
658
|
+
immediate: true // Execute on mount
|
|
659
|
+
})
|
|
660
|
+
|
|
661
|
+
// When userIds changes, call execute() to refetch
|
|
662
|
+
watch(userIds, () => execute())
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
#### Auto Re-Execute with Watch
|
|
666
|
+
|
|
667
|
+
```typescript
|
|
668
|
+
const filters = ref({ status: 'active' })
|
|
669
|
+
|
|
670
|
+
const { data } = useApiBatch<User>(urls, {
|
|
671
|
+
watch: filters, // Re-execute when filters change
|
|
672
|
+
immediate: true
|
|
673
|
+
})
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
#### Item-Level Callbacks
|
|
677
|
+
|
|
678
|
+
```typescript
|
|
679
|
+
const { execute } = useApiBatch<User>(urls, {
|
|
680
|
+
onItemSuccess: (item, index) => {
|
|
681
|
+
console.log(`✅ [${index}] Loaded: ${item.url}`)
|
|
682
|
+
},
|
|
683
|
+
onItemError: (item, index) => {
|
|
684
|
+
console.log(`❌ [${index}] Failed: ${item.url}`, item.error?.message)
|
|
685
|
+
},
|
|
686
|
+
onFinish: (results) => {
|
|
687
|
+
console.log(`Batch complete: ${results.length} items processed`)
|
|
688
|
+
}
|
|
689
|
+
})
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
#### Full Return Type
|
|
693
|
+
|
|
694
|
+
```typescript
|
|
695
|
+
const {
|
|
696
|
+
data, // Ref<BatchResultItem<T>[]> - all results with metadata
|
|
697
|
+
successfulData, // Ref<T[]> - only successful data (computed)
|
|
698
|
+
loading, // Ref<boolean>
|
|
699
|
+
error, // Ref<ApiError | null> - set if ALL requests failed
|
|
700
|
+
errors, // Ref<ApiError[]> - all individual errors
|
|
701
|
+
progress, // Ref<BatchProgress>
|
|
702
|
+
execute, // () => Promise<BatchResultItem<T>[]>
|
|
703
|
+
abort, // (message?: string) => void
|
|
704
|
+
reset // () => void
|
|
705
|
+
} = useApiBatch<User>(urls)
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
#### BatchResultItem Structure
|
|
709
|
+
|
|
710
|
+
Each item in `data` contains:
|
|
711
|
+
|
|
712
|
+
```typescript
|
|
713
|
+
interface BatchResultItem<T> {
|
|
714
|
+
url: string // The requested URL
|
|
715
|
+
index: number // Position in original array
|
|
716
|
+
success: boolean // Whether request succeeded
|
|
717
|
+
data: T | null // Response data (null if failed)
|
|
718
|
+
error: ApiError | null // Error details (null if succeeded)
|
|
719
|
+
statusCode: number | null
|
|
720
|
+
}
|
|
721
|
+
```
|
|
722
|
+
|
|
723
|
+
#### Real-World Example: Dashboard Loader
|
|
724
|
+
|
|
725
|
+
```vue
|
|
726
|
+
<script setup lang="ts">
|
|
727
|
+
const dashboardUrls = [
|
|
728
|
+
'/api/stats',
|
|
729
|
+
'/api/recent-orders',
|
|
730
|
+
'/api/notifications',
|
|
731
|
+
'/api/user-activity'
|
|
732
|
+
]
|
|
733
|
+
|
|
734
|
+
const {
|
|
735
|
+
data: results,
|
|
736
|
+
loading,
|
|
737
|
+
progress,
|
|
738
|
+
execute
|
|
739
|
+
} = useApiBatch(dashboardUrls, {
|
|
740
|
+
immediate: true,
|
|
741
|
+
onProgress: (p) => console.log(`Dashboard loading: ${p.percentage}%`)
|
|
742
|
+
})
|
|
743
|
+
|
|
744
|
+
// Extract individual data
|
|
745
|
+
const stats = computed(() => results.value.find(r => r.url.includes('stats'))?.data)
|
|
746
|
+
const orders = computed(() => results.value.find(r => r.url.includes('orders'))?.data)
|
|
747
|
+
</script>
|
|
748
|
+
|
|
749
|
+
<template>
|
|
750
|
+
<div v-if="loading" class="loading">
|
|
751
|
+
Loading dashboard... {{ progress.percentage }}%
|
|
752
|
+
</div>
|
|
753
|
+
<Dashboard v-else :stats="stats" :orders="orders" />
|
|
754
|
+
</template>
|
|
755
|
+
```
|
|
756
|
+
|
|
757
|
+
---
|
|
758
|
+
|
|
544
759
|
## ⚙️ Advanced Configuration
|
|
545
760
|
|
|
546
761
|
### Custom Axios Instance
|
|
@@ -874,7 +1089,7 @@ The main composable for making HTTP requests.
|
|
|
874
1089
|
|
|
875
1090
|
// Methods
|
|
876
1091
|
execute: (config?: AxiosRequestConfig) => Promise<T | null>
|
|
877
|
-
|
|
1092
|
+
mutate: (data: T | null | ((prev: T | null) => T | null)) => void
|
|
878
1093
|
abort: (reason?: string) => void
|
|
879
1094
|
reset: () => void
|
|
880
1095
|
}
|
|
@@ -893,23 +1108,23 @@ await execute()
|
|
|
893
1108
|
await execute({ params: { page: 2 } })
|
|
894
1109
|
```
|
|
895
1110
|
|
|
896
|
-
#### `
|
|
1111
|
+
#### `mutate(newData)`
|
|
897
1112
|
Manually update the `data` ref. Supports direct values or updater functions:
|
|
898
1113
|
|
|
899
1114
|
```typescript
|
|
900
|
-
const { data,
|
|
1115
|
+
const { data, mutate } = useApi<User[]>('/users')
|
|
901
1116
|
|
|
902
1117
|
// Direct value
|
|
903
|
-
|
|
1118
|
+
mutate([{ id: 1, name: 'John' }])
|
|
904
1119
|
|
|
905
1120
|
// Updater function (like React's setState)
|
|
906
|
-
|
|
1121
|
+
mutate(prev => prev ? [...prev, newUser] : [newUser])
|
|
907
1122
|
|
|
908
1123
|
// Remove item
|
|
909
|
-
|
|
1124
|
+
mutate(prev => prev?.filter(u => u.id !== userId) ?? null)
|
|
910
1125
|
```
|
|
911
1126
|
|
|
912
|
-
> **Note:** `
|
|
1127
|
+
> **Note:** `mutate` automatically clears any existing error.
|
|
913
1128
|
|
|
914
1129
|
#### `abort(reason?)`
|
|
915
1130
|
Cancel the current request:
|
|
@@ -1051,6 +1266,89 @@ app.use(createApi({
|
|
|
1051
1266
|
|
|
1052
1267
|
---
|
|
1053
1268
|
|
|
1269
|
+
### `useApiBatch<T>(urls, options)`
|
|
1270
|
+
|
|
1271
|
+
Execute multiple API requests in parallel with full reactive state.
|
|
1272
|
+
|
|
1273
|
+
**Type Parameters:**
|
|
1274
|
+
- `T` — Response data type for each request
|
|
1275
|
+
|
|
1276
|
+
**Arguments:**
|
|
1277
|
+
|
|
1278
|
+
| Argument | Type | Description |
|
|
1279
|
+
|----------|------|-------------|
|
|
1280
|
+
| `urls` | `MaybeRefOrGetter<string[]>` | Array of API endpoints. Can be static array, ref, or getter. |
|
|
1281
|
+
| `options` | `UseApiBatchOptions<T>` | Configuration object (see below). |
|
|
1282
|
+
|
|
1283
|
+
---
|
|
1284
|
+
|
|
1285
|
+
#### Batch Options
|
|
1286
|
+
|
|
1287
|
+
| Option | Type | Default | Description |
|
|
1288
|
+
|--------|------|---------|-------------|
|
|
1289
|
+
| `settled` | `boolean` | `true` | If `true`, failed requests don't stop the batch. If `false`, first error rejects entire batch. |
|
|
1290
|
+
| `concurrency` | `number` | `undefined` | Max parallel requests. Default: unlimited. |
|
|
1291
|
+
| `immediate` | `boolean` | `false` | Auto-execute on mount. |
|
|
1292
|
+
| `skipErrorNotification` | `boolean` | `true` | Skip global error handler for individual failures. |
|
|
1293
|
+
| `watch` | `WatchSource \| WatchSource[]` | `undefined` | Re-execute when sources change. |
|
|
1294
|
+
|
|
1295
|
+
**Callbacks:**
|
|
1296
|
+
|
|
1297
|
+
| Option | Type | Description |
|
|
1298
|
+
|--------|------|-------------|
|
|
1299
|
+
| `onItemSuccess` | `(item: BatchResultItem<T>, index: number) => void` | Called when individual request succeeds. |
|
|
1300
|
+
| `onItemError` | `(item: BatchResultItem<T>, index: number) => void` | Called when individual request fails. |
|
|
1301
|
+
| `onProgress` | `(progress: BatchProgress) => void` | Called when progress updates. |
|
|
1302
|
+
| `onFinish` | `(results: BatchResultItem<T>[]) => void` | Called when all requests complete. |
|
|
1303
|
+
|
|
1304
|
+
---
|
|
1305
|
+
|
|
1306
|
+
#### Batch Return Values
|
|
1307
|
+
|
|
1308
|
+
```typescript
|
|
1309
|
+
{
|
|
1310
|
+
// State
|
|
1311
|
+
data: Ref<BatchResultItem<T>[]> // All results with metadata
|
|
1312
|
+
successfulData: Ref<T[]> // Only successful data (computed)
|
|
1313
|
+
loading: Ref<boolean> // True while any request pending
|
|
1314
|
+
error: Ref<ApiError | null> // Set if ALL requests failed
|
|
1315
|
+
errors: Ref<ApiError[]> // All individual errors
|
|
1316
|
+
progress: Ref<BatchProgress> // Progress tracking
|
|
1317
|
+
|
|
1318
|
+
// Methods
|
|
1319
|
+
execute: () => Promise<BatchResultItem<T>[]>
|
|
1320
|
+
abort: (message?: string) => void
|
|
1321
|
+
reset: () => void
|
|
1322
|
+
}
|
|
1323
|
+
```
|
|
1324
|
+
|
|
1325
|
+
#### `BatchProgress`
|
|
1326
|
+
|
|
1327
|
+
```typescript
|
|
1328
|
+
interface BatchProgress {
|
|
1329
|
+
completed: number // Requests finished (success + failed)
|
|
1330
|
+
total: number // Total requests
|
|
1331
|
+
percentage: number // 0-100
|
|
1332
|
+
succeeded: number // Successful requests
|
|
1333
|
+
failed: number // Failed requests
|
|
1334
|
+
}
|
|
1335
|
+
```
|
|
1336
|
+
|
|
1337
|
+
#### `BatchResultItem<T>`
|
|
1338
|
+
|
|
1339
|
+
```typescript
|
|
1340
|
+
interface BatchResultItem<T> {
|
|
1341
|
+
url: string // Requested URL
|
|
1342
|
+
index: number // Position in original array
|
|
1343
|
+
success: boolean // Whether succeeded
|
|
1344
|
+
data: T | null // Response data
|
|
1345
|
+
error: ApiError | null // Error if failed
|
|
1346
|
+
statusCode: number | null
|
|
1347
|
+
}
|
|
1348
|
+
```
|
|
1349
|
+
|
|
1350
|
+
---
|
|
1351
|
+
|
|
1054
1352
|
### `useAbortController()`
|
|
1055
1353
|
|
|
1056
1354
|
Access the global abort controller for cancelling multiple requests.
|