@ametie/vue-muza-use 0.4.10 โ†’ 0.6.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 CHANGED
@@ -13,18 +13,50 @@ A production-ready composable that eliminates boilerplate and solves the hard pr
13
13
 
14
14
  ## โœจ Features
15
15
 
16
+ **Core Features** (Get started in minutes):
16
17
  - ๐ŸŽฏ **Fully Type-Safe** โ€” End-to-end TypeScript support with strict typing for requests and responses
17
18
  - ๐Ÿ”„ **Smart Reactivity** โ€” Watch refs and automatically refetch when dependencies change
18
19
  - โฑ๏ธ **Built-in Debouncing** โ€” Perfect for search inputs and auto-save forms
19
20
  - ๐Ÿ›ก๏ธ **Race Condition Protection** โ€” Global abort controller cancels stale requests automatically
20
- - ๐Ÿ” **JWT Token Management** โ€” Automatic token refresh with request queueing on 401 responses
21
- - โ™ป๏ธ **Intelligent Retries** โ€” Lifecycle-aware retry logic that respects component unmounting
22
21
  - ๐Ÿ“Š **Auto-Polling** โ€” Built-in interval fetching with smart tab visibility detection
22
+ - ๐Ÿš€ **Batch Requests** โ€” Execute multiple requests in parallel with progress tracking
23
23
  - ๐Ÿงน **Zero Memory Leaks** โ€” Automatic cleanup of pending requests on component unmount
24
+
25
+ **Advanced Features** (When you need them):
26
+ - โ™ป๏ธ **Intelligent Retries** โ€” Lifecycle-aware retry logic that respects component unmounting
27
+ - ๐Ÿ” **JWT Token Management** โ€” Automatic token refresh with request queueing on 401 responses
24
28
  - ๐ŸŽ›๏ธ **Flexible Architecture** โ€” Bring your own Axios instance with full configuration control
25
29
 
26
30
  ---
27
31
 
32
+ ## ๐Ÿ“– Table of Contents
33
+
34
+ **Getting Started:**
35
+ - [Installation](#-installation)
36
+ - [Quick Start](#-quick-start)
37
+ - [Basic Usage](#-basic-usage)
38
+
39
+ **Core Features:**
40
+ - [Watch & Auto-Refetch](#watch--auto-refetch)
41
+ - [Polling](#polling-background-updates)
42
+ - [Error Handling](#error-handling)
43
+ - [Loading States](#loading-states)
44
+ - [Manual Data Updates](#manual-data-updates)
45
+
46
+ **Real-World Examples:**
47
+ - [Data Table with Pagination](#data-table-with-pagination--sorting)
48
+ - [Request Cancellation](#request-cancellation)
49
+ - [Batch Requests](#batch-requests)
50
+
51
+ **Advanced:**
52
+ - [Custom Axios Instance](#-advanced-configuration)
53
+ - [Authentication & Tokens](#-authentication--token-management) *(Optional)*
54
+ - [API Reference](#-api-reference)
55
+
56
+ > ๐Ÿ’ก **New to the library?** Start with [Quick Start](#-quick-start), then explore [Basic Usage](#-basic-usage). Skip authentication until you need it!
57
+
58
+ ---
59
+
28
60
  ## ๐Ÿ“ฆ Installation
29
61
 
30
62
  ```bash
@@ -344,6 +376,108 @@ const { execute } = useApi('/analytics', {
344
376
 
345
377
  ---
346
378
 
379
+ ### Manual Data Updates
380
+
381
+ Use `setData` to manually update the data ref. Supports direct values or updater functions (like React's `setState`).
382
+
383
+ > ๐ŸŽ“ **When to use `setData`:**
384
+ > โœ… Adding/removing/updating items in arrays
385
+ > โœ… Local sorting/filtering (without refetching)
386
+ > โœ… Transform data in `onSuccess` (adding computed fields)
387
+ >
388
+ > **When to use `computed` instead:**
389
+ > โœ… Completely changing data structure (e.g., API format โ†’ App format)
390
+ > โœ… Extracting nested data that changes the return type
391
+ > โœ… Complex transformations that depend on other refs
392
+
393
+ #### Add/Remove/Update Items
394
+ ```typescript
395
+ const { data, setData } = useApi<Todo[]>('/todos', { immediate: true })
396
+
397
+ // Add item
398
+ const addTodo = (newTodo: Todo) => {
399
+ setData(prev => prev ? [...prev, newTodo] : [newTodo])
400
+ }
401
+
402
+ // Remove item
403
+ const removeTodo = (id: number) => {
404
+ setData(prev => prev?.filter(t => t.id !== id) ?? null)
405
+ }
406
+
407
+ // Update item
408
+ const updateTodo = (id: number, updates: Partial<Todo>) => {
409
+ setData(prev =>
410
+ prev?.map(t => t.id === id ? { ...t, ...updates } : t) ?? null
411
+ )
412
+ }
413
+ ```
414
+
415
+ #### Sort/Filter Locally
416
+ ```typescript
417
+ const { data, setData } = useApi<Product[]>('/products', { immediate: true })
418
+
419
+ const sortByPrice = () => {
420
+ setData(prev => prev ? [...prev].sort((a, b) => a.price - b.price) : null)
421
+ }
422
+
423
+ const filterActive = () => {
424
+ setData(prev => prev?.filter(p => p.active) ?? null)
425
+ }
426
+
427
+ // Reset to original
428
+ const resetFilters = () => execute()
429
+ ```
430
+
431
+ #### Transform in `onSuccess`
432
+
433
+ Use `setData` in `onSuccess` to transform data right after fetching. Two approaches:
434
+
435
+ **Approach 1: Same type (recommended)**
436
+ ```typescript
437
+ interface User {
438
+ id: number
439
+ firstName: string
440
+ lastName: string
441
+ fullName?: string // Optional field
442
+ }
443
+
444
+ const { data, setData } = useApi<User[]>('/users', {
445
+ immediate: true,
446
+ onSuccess: ({ data: users }) => {
447
+ // Add computed field - still User[] type
448
+ setData(users.map(u => ({
449
+ ...u,
450
+ fullName: `${u.firstName} ${u.lastName}`
451
+ })))
452
+ }
453
+ })
454
+ ```
455
+
456
+ **Approach 2: Different structure (use separate computed)**
457
+ ```typescript
458
+ interface ApiUser {
459
+ first_name: string
460
+ last_name: string
461
+ }
462
+
463
+ // If API returns different structure, use computed for transformation
464
+ const { data: rawData } = useApi<ApiUser[]>('/users', { immediate: true })
465
+
466
+ const users = computed(() =>
467
+ rawData.value?.map(u => ({
468
+ firstName: u.first_name,
469
+ lastName: u.last_name,
470
+ fullName: `${u.first_name} ${u.last_name}`
471
+ })) ?? []
472
+ )
473
+ ```
474
+
475
+ > ๐Ÿ’ก **Rule of thumb:**
476
+ > - โœ… **Use `setData` in `onSuccess`** if you're adding/modifying fields but keeping the same base type
477
+ > - โœ… **Use `computed`** if you're completely changing the data structure (e.g., snake_case โ†’ camelCase)
478
+
479
+ ---
480
+
347
481
  ## ๐Ÿ“Š Real-World Examples
348
482
 
349
483
  ### Data Table with Pagination & Sorting
@@ -389,21 +523,6 @@ const { data, loading } = useApi('/orders', {
389
523
  </template>
390
524
  ```
391
525
 
392
- ### Optimistic UI Updates
393
- ```typescript
394
- const { execute: updateProfile } = useApi('/user/profile', {
395
- method: 'PUT',
396
- onBefore: () => {
397
- // Show update immediately
398
- localProfile.value = { ...localProfile.value, ...changes }
399
- },
400
- onError: () => {
401
- // Rollback on error
402
- localProfile.value = originalProfile
403
- toast.error('Update failed')
404
- }
405
- })
406
- ```
407
526
 
408
527
  ### Request Cancellation
409
528
  ```typescript
@@ -424,6 +543,219 @@ const resetFilters = () => {
424
543
 
425
544
  ---
426
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
+
427
759
  ## โš™๏ธ Advanced Configuration
428
760
 
429
761
  ### Custom Axios Instance
@@ -752,11 +1084,14 @@ The main composable for making HTTP requests.
752
1084
  data: Ref<T | null> // Response data
753
1085
  loading: Ref<boolean> // Loading state
754
1086
  error: Ref<ApiError | null> // Error object
1087
+ statusCode: Ref<number | null> // HTTP status code
755
1088
  response: Ref<AxiosResponse<T>> // Full Axios response
756
1089
 
757
1090
  // Methods
758
1091
  execute: (config?: AxiosRequestConfig) => Promise<T | null>
1092
+ setData: (data: T | null | ((prev: T | null) => T | null)) => void
759
1093
  abort: (reason?: string) => void
1094
+ reset: () => void
760
1095
  }
761
1096
  ```
762
1097
 
@@ -773,6 +1108,24 @@ await execute()
773
1108
  await execute({ params: { page: 2 } })
774
1109
  ```
775
1110
 
1111
+ #### `setData(newData)`
1112
+ Manually update the `data` ref. Supports direct values or updater functions:
1113
+
1114
+ ```typescript
1115
+ const { data, setData } = useApi<User[]>('/users')
1116
+
1117
+ // Direct value
1118
+ setData([{ id: 1, name: 'John' }])
1119
+
1120
+ // Updater function (like React's setState)
1121
+ setData(prev => prev ? [...prev, newUser] : [newUser])
1122
+
1123
+ // Remove item
1124
+ setData(prev => prev?.filter(u => u.id !== userId) ?? null)
1125
+ ```
1126
+
1127
+ > **Note:** `setData` automatically clears any existing error.
1128
+
776
1129
  #### `abort(reason?)`
777
1130
  Cancel the current request:
778
1131
 
@@ -785,6 +1138,20 @@ execute()
785
1138
  setTimeout(() => abort('Timeout'), 5000)
786
1139
  ```
787
1140
 
1141
+ #### `reset()`
1142
+ Reset all state to initial values:
1143
+
1144
+ ```typescript
1145
+ const { data, error, loading, reset } = useApi('/users')
1146
+
1147
+ // Clear everything
1148
+ reset()
1149
+ // data.value = null, error.value = null, loading.value = false
1150
+
1151
+ // Cancel after 5 seconds
1152
+ setTimeout(() => abort('Timeout'), 5000)
1153
+ ```
1154
+
788
1155
  ---
789
1156
 
790
1157
  ### `createApiClient(options)`
@@ -899,6 +1266,89 @@ app.use(createApi({
899
1266
 
900
1267
  ---
901
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
+
902
1352
  ### `useAbortController()`
903
1353
 
904
1354
  Access the global abort controller for cancelling multiple requests.