@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 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 `setData` to manually update the data ref. Supports direct values or updater functions (like React's `setState`).
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 `setData`:**
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, setData } = useApi<Todo[]>('/todos', { immediate: true })
395
+ const { data, mutate } = useApi<Todo[]>('/todos', { immediate: true })
394
396
 
395
397
  // Add item
396
398
  const addTodo = (newTodo: Todo) => {
397
- setData(prev => prev ? [...prev, newTodo] : [newTodo])
399
+ mutate(prev => prev ? [...prev, newTodo] : [newTodo])
398
400
  }
399
401
 
400
402
  // Remove item
401
403
  const removeTodo = (id: number) => {
402
- setData(prev => prev?.filter(t => t.id !== id) ?? null)
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
- setData(prev =>
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, setData } = useApi<Product[]>('/products', { immediate: true })
417
+ const { data, mutate } = useApi<Product[]>('/products', { immediate: true })
416
418
 
417
419
  const sortByPrice = () => {
418
- setData(prev => prev ? [...prev].sort((a, b) => a.price - b.price) : null)
420
+ mutate(prev => prev ? [...prev].sort((a, b) => a.price - b.price) : null)
419
421
  }
420
422
 
421
423
  const filterActive = () => {
422
- setData(prev => prev?.filter(p => p.active) ?? null)
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 `setData` in `onSuccess` to transform data right after fetching. Two approaches:
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, setData } = useApi<User[]>('/users', {
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
- setData(users.map(u => ({
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 `setData` in `onSuccess`** if you're adding/modifying fields but keeping the same base type
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
- setData: (data: T | null | ((prev: T | null) => T | null)) => void
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
- #### `setData(newData)`
1111
+ #### `mutate(newData)`
897
1112
  Manually update the `data` ref. Supports direct values or updater functions:
898
1113
 
899
1114
  ```typescript
900
- const { data, setData } = useApi<User[]>('/users')
1115
+ const { data, mutate } = useApi<User[]>('/users')
901
1116
 
902
1117
  // Direct value
903
- setData([{ id: 1, name: 'John' }])
1118
+ mutate([{ id: 1, name: 'John' }])
904
1119
 
905
1120
  // Updater function (like React's setState)
906
- setData(prev => prev ? [...prev, newUser] : [newUser])
1121
+ mutate(prev => prev ? [...prev, newUser] : [newUser])
907
1122
 
908
1123
  // Remove item
909
- setData(prev => prev?.filter(u => u.id !== userId) ?? null)
1124
+ mutate(prev => prev?.filter(u => u.id !== userId) ?? null)
910
1125
  ```
911
1126
 
912
- > **Note:** `setData` automatically clears any existing error.
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.