@emmvish/stable-request 1.0.8 โ†’ 1.1.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.
Files changed (44) hide show
  1. package/README.md +521 -180
  2. package/dist/constants/index.d.ts +3 -0
  3. package/dist/constants/index.d.ts.map +1 -0
  4. package/dist/constants/index.js +16 -0
  5. package/dist/constants/index.js.map +1 -0
  6. package/dist/core/stable-api-gateway.d.ts.map +1 -1
  7. package/dist/core/stable-api-gateway.js +2 -1
  8. package/dist/core/stable-api-gateway.js.map +1 -1
  9. package/dist/index.d.ts +1 -1
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +1 -1
  12. package/dist/index.js.map +1 -1
  13. package/dist/types/index.d.ts +14 -1
  14. package/dist/types/index.d.ts.map +1 -1
  15. package/dist/utilities/execute-concurrently.d.ts.map +1 -1
  16. package/dist/utilities/execute-concurrently.js +4 -2
  17. package/dist/utilities/execute-concurrently.js.map +1 -1
  18. package/dist/utilities/execute-sequentially.d.ts.map +1 -1
  19. package/dist/utilities/execute-sequentially.js +4 -2
  20. package/dist/utilities/execute-sequentially.js.map +1 -1
  21. package/dist/utilities/prepare-api-request-data.d.ts.map +1 -1
  22. package/dist/utilities/prepare-api-request-data.js +3 -1
  23. package/dist/utilities/prepare-api-request-data.js.map +1 -1
  24. package/dist/utilities/prepare-api-request-options.d.ts.map +1 -1
  25. package/dist/utilities/prepare-api-request-options.js +6 -16
  26. package/dist/utilities/prepare-api-request-options.js.map +1 -1
  27. package/package.json +3 -2
  28. package/dist/core.d.ts +0 -3
  29. package/dist/core.d.ts.map +0 -1
  30. package/dist/core.js +0 -101
  31. package/dist/core.js.map +0 -1
  32. package/dist/test.d.ts +0 -2
  33. package/dist/test.d.ts.map +0 -1
  34. package/dist/test.js +0 -5
  35. package/dist/test.js.map +0 -1
  36. package/dist/tsconfig.tsbuildinfo +0 -1
  37. package/dist/utilities/extract-common-options.d.ts +0 -3
  38. package/dist/utilities/extract-common-options.d.ts.map +0 -1
  39. package/dist/utilities/extract-common-options.js +0 -26
  40. package/dist/utilities/extract-common-options.js.map +0 -1
  41. package/dist/utilities/safely-execute-unknown-functions.d.ts +0 -2
  42. package/dist/utilities/safely-execute-unknown-functions.d.ts.map +0 -1
  43. package/dist/utilities/safely-execute-unknown-functions.js +0 -8
  44. package/dist/utilities/safely-execute-unknown-functions.js.map +0 -1
package/README.md CHANGED
@@ -1,17 +1,18 @@
1
1
  ## stable-request
2
2
 
3
- `stable-request` is a TypeScript-first HTTP reliability framework that goes beyond status-code retries by validating response content, handling eventual consistency, coordinating batch workflows, and providing deep observability into every request attempt. It is designed for real-world distributed systems where HTTP success does not guarantee business success.
3
+ `stable-request` is a TypeScript-first HTTP reliability framework that goes beyond status-code retries by validating response content, handling eventual consistency, coordinating batch workflows with intelligent grouping, and providing deep observability into every request attempt. It is designed for real-world distributed systems where HTTP success does not guarantee business success.
4
4
 
5
5
  ## Why stable-request?
6
6
 
7
7
  Most HTTP client libraries only retry on network failures or specific HTTP status codes. **stable-request** goes further by providing:
8
8
 
9
9
  - โœ… **Content-aware retries** - Validate response content and retry even on successful HTTP responses
10
- - ๐Ÿš€ **Batch processing** - Execute multiple requests concurrently or sequentially with shared configuration
10
+ - ๐Ÿš€ **Batch processing with groups** - Execute multiple requests with hierarchical configuration (global โ†’ group โ†’ request)
11
+ - ๐ŸŽฏ **Request grouping** - Organize related requests with shared settings and logical boundaries
11
12
  - ๐Ÿงช **Trial mode** - Simulate failures to test your retry logic without depending on real network instability
12
13
  - ๐Ÿ“Š **Granular observability** - Monitor every attempt with detailed hooks
13
14
  - โšก **Multiple retry strategies** - Fixed, linear, or exponential backoff
14
- - ๐ŸŽฏ **Flexible error handling** - Custom error analysis and graceful degradation
15
+ - ๐Ÿ”ง **Flexible error handling** - Custom error analysis and graceful degradation
15
16
 
16
17
  ## Installation
17
18
 
@@ -70,6 +71,56 @@ const results = await stableApiGateway(requests, {
70
71
  });
71
72
  ```
72
73
 
74
+ ### Grouped Requests
75
+
76
+ ```typescript
77
+ import { stableApiGateway, RETRY_STRATEGIES } from '@emmvish/stable-request';
78
+
79
+ const results = await stableApiGateway(
80
+ [
81
+ {
82
+ id: 'auth-check',
83
+ groupId: 'critical-services',
84
+ requestOptions: {
85
+ reqData: { path: '/auth/verify' },
86
+ resReq: true
87
+ }
88
+ },
89
+ {
90
+ id: 'analytics-track',
91
+ groupId: 'optional-services',
92
+ requestOptions: {
93
+ reqData: { path: '/analytics/event' },
94
+ resReq: true
95
+ }
96
+ }
97
+ ],
98
+ {
99
+ // Global defaults
100
+ commonAttempts: 2,
101
+ commonRequestData: { hostname: 'api.example.com' },
102
+
103
+ // Define groups with their own configurations
104
+ requestGroups: [
105
+ {
106
+ id: 'critical-services',
107
+ commonConfig: {
108
+ commonAttempts: 10,
109
+ commonRetryStrategy: RETRY_STRATEGIES.EXPONENTIAL
110
+ }
111
+ },
112
+ {
113
+ id: 'optional-services',
114
+ commonConfig: {
115
+ commonAttempts: 1,
116
+ commonFinalErrorAnalyzer: async () => true // Don't throw on failure
117
+ }
118
+ }
119
+ ]
120
+ }
121
+ );
122
+ ```
123
+
73
124
  ## Core Features
74
125
 
75
126
  ### 1. Content-Aware Retries with `stableRequest`
@@ -142,9 +193,9 @@ const results = await stableApiGateway(requests, {
142
193
  // Process results
143
194
  results.forEach(result => {
144
195
  if (result.success) {
145
- console.log(`${result.id} succeeded:`, result.data);
196
+ console.log(`${result.requestId} succeeded:`, result.data);
146
197
  } else {
147
- console.error(`${result.id} failed:`, result.error);
198
+ console.error(`${result.requestId} failed:`, result.error);
148
199
  }
149
200
  });
150
201
  ```
@@ -159,7 +210,98 @@ const results = await stableApiGateway(requests, {
159
210
  });
160
211
  ```
161
212
 
162
- ### 3. Trial Mode - Test Your Retry Logic
213
+ ### 3. Request Grouping - Hierarchical Configuration
214
+
215
+ Organize requests into logical groups with their own configuration. The configuration priority is:
216
+
217
+ **Individual Request Options** (highest) โ†’ **Group Common Options** (middle) โ†’ **Global Common Options** (lowest)
218
+
219
+ ```typescript
220
+ const results = await stableApiGateway(
221
+ [
222
+ {
223
+ id: 'payment-stripe',
224
+ groupId: 'payment-providers',
225
+ requestOptions: {
226
+ reqData: { path: '/stripe/charge' },
227
+ resReq: true,
228
+ // Individual override: even more attempts for Stripe
229
+ attempts: 15
230
+ }
231
+ },
232
+ {
233
+ id: 'payment-paypal',
234
+ groupId: 'payment-providers',
235
+ requestOptions: {
236
+ reqData: { path: '/paypal/charge' },
237
+ resReq: true
238
+ }
239
+ },
240
+ {
241
+ id: 'analytics-event',
242
+ groupId: 'analytics',
243
+ requestOptions: {
244
+ reqData: { path: '/track' },
245
+ resReq: true
246
+ }
247
+ }
248
+ ],
249
+ {
250
+ // Global configuration - applies to all ungrouped requests
251
+ commonAttempts: 2,
252
+ commonWait: 500,
253
+ commonRequestData: {
254
+ hostname: 'api.example.com',
255
+ method: REQUEST_METHODS.POST
256
+ },
257
+
258
+ // Group-specific configurations
259
+ requestGroups: [
260
+ {
261
+ id: 'payment-providers',
262
+ commonConfig: {
263
+ // Payment group: aggressive retries
264
+ commonAttempts: 10,
265
+ commonWait: 2000,
266
+ commonRetryStrategy: RETRY_STRATEGIES.EXPONENTIAL,
267
+ commonRequestData: {
268
+ headers: { 'X-Idempotency-Key': crypto.randomUUID() }
269
+ },
270
+ commonHandleErrors: async (reqData, error) => {
271
+ await alertPagerDuty('Payment failure', error);
272
+ }
273
+ }
274
+ },
275
+ {
276
+ id: 'analytics',
277
+ commonConfig: {
278
+ // Analytics group: minimal retries, failures acceptable
279
+ commonAttempts: 1,
280
+ commonFinalErrorAnalyzer: async () => true // Don't throw
281
+ }
282
+ }
283
+ ]
284
+ }
285
+ );
286
+
287
+ // Filter results by group
288
+ const paymentResults = results.filter(r => r.groupId === 'payment-providers');
289
+ const analyticsResults = results.filter(r => r.groupId === 'analytics');
290
+
291
+ console.log('Payment success rate:',
292
+ paymentResults.filter(r => r.success).length / paymentResults.length);
293
+ ```
294
+
295
+ **Key Benefits of Request Grouping:**
296
+
297
+ 1. **Service Tiering** - Different retry strategies for critical vs. optional services
298
+ 2. **Regional Configuration** - Customize timeouts and retries per geographic region
299
+ 3. **Priority Management** - Handle high-priority requests more aggressively
300
+ 4. **Organized Configuration** - Group related requests with shared settings
301
+ 5. **Simplified Maintenance** - Update group config instead of individual requests
302
+ 6. **Better Monitoring** - Track metrics and failures by logical groups
303
+
304
+ ### 4. Trial Mode - Test Your Retry Logic
163
305
 
164
306
  Simulate request and retry failures with configurable probabilities.
165
307
 
@@ -186,7 +328,7 @@ await stableRequest({
186
328
  - Validating monitoring and alerting
187
329
  - Testing circuit breaker patterns
188
330
 
189
- ### 4. Multiple Retry Strategies
331
+ ### 5. Multiple Retry Strategies
190
332
 
191
333
  Choose the backoff strategy that fits your use case.
192
334
 
@@ -221,7 +363,7 @@ await stableRequest({
221
363
  });
222
364
  ```
223
365
 
224
- ### 5. Comprehensive Observability
366
+ ### 6. Comprehensive Observability
225
367
 
226
368
  Monitor every request attempt with detailed logging hooks.
227
369
 
@@ -260,7 +402,7 @@ await stableRequest({
260
402
  });
261
403
  ```
262
404
 
263
- ### 6. Smart Retry Logic
405
+ ### 7. Smart Retry Logic
264
406
 
265
407
  Automatically retries on common transient errors:
266
408
 
@@ -284,7 +426,7 @@ await stableRequest({
284
426
  });
285
427
  ```
286
428
 
287
- ### 7. Final Error Analysis
429
+ ### 8. Final Error Analysis
288
430
 
289
431
  Decide whether to throw or return false based on error analysis.
290
432
 
@@ -312,7 +454,7 @@ if (result === false) {
312
454
  }
313
455
  ```
314
456
 
315
- ### 8. Request Cancellation Support
457
+ ### 9. Request Cancellation Support
316
458
 
317
459
  Support for AbortController to cancel requests.
318
460
 
@@ -336,7 +478,7 @@ try {
336
478
  }
337
479
  ```
338
480
 
339
- ### 9. Perform All Attempts Mode
481
+ ### 10. Perform All Attempts Mode
340
482
 
341
483
  Execute all retry attempts regardless of success, useful for warm-up scenarios.
342
484
 
@@ -396,7 +538,7 @@ interface REQUEST_DATA<RequestDataType = any> {
396
538
 
397
539
  ### `stableApiGateway<RequestDataType, ResponseDataType>(requests, options)`
398
540
 
399
- Execute multiple HTTP requests with shared configuration.
541
+ Execute multiple HTTP requests with shared configuration and optional grouping.
400
542
 
401
543
  #### Gateway Configuration Options
402
544
 
@@ -404,6 +546,7 @@ Execute multiple HTTP requests with shared configuration.
404
546
  |--------|------|---------|-------------|
405
547
  | `concurrentExecution` | `boolean` | `true` | Execute requests concurrently or sequentially |
406
548
  | `stopOnFirstError` | `boolean` | `false` | Stop execution on first error (sequential only) |
549
+ | `requestGroups` | `RequestGroup[]` | `[]` | Define groups with their own common configurations |
407
550
  | `commonAttempts` | `number` | `1` | Default attempts for all requests |
408
551
  | `commonPerformAllAttempts` | `boolean` | `false` | Default performAllAttempts for all requests |
409
552
  | `commonWait` | `number` | `1000` | Default wait time for all requests |
@@ -417,25 +560,44 @@ Execute multiple HTTP requests with shared configuration.
417
560
  | `commonFinalErrorAnalyzer` | `function` | `() => false` | Default final error analyzer for all requests |
418
561
  | `commonHandleErrors` | `function` | console.log | Default error handler for all requests |
419
562
  | `commonHandleSuccessfulAttemptData` | `function` | console.log | Default success handler for all requests |
420
- | `commonRequestData` | `Partial REQUEST_DATA` | { hostname: '' } | Common set of axios options for each request |
563
+ | `commonRequestData` | `Partial<REQUEST_DATA>` | `{ hostname: '' }` | Common set of request options for each request |
421
564
 
565
+ #### Request Group Configuration
566
+
567
+ ```typescript
568
+ interface RequestGroup {
569
+ id: string; // Unique group identifier
570
+ commonConfig?: {
571
+ // Any common* option can be specified here
572
+ commonAttempts?: number;
573
+ commonWait?: number;
574
+ commonRetryStrategy?: RETRY_STRATEGY_TYPES;
575
+ commonRequestData?: Partial<REQUEST_DATA>;
576
+ commonResponseAnalyzer?: function;
577
+ commonHandleErrors?: function;
578
+ // ... all other common* options
579
+ };
580
+ }
581
+ ```
422
582
 
423
583
  #### Request Format
424
584
 
425
585
  ```typescript
426
586
  interface API_GATEWAY_REQUEST<RequestDataType, ResponseDataType> {
427
587
  id: string; // Unique identifier for the request
588
+ groupId?: string; // Optional group identifier
428
589
  requestOptions: API_GATEWAY_REQUEST_OPTIONS_TYPE<RequestDataType, ResponseDataType>;
429
590
  }
430
591
  ```
431
592
 
432
- **Note:** Individual request options override common options. If a specific option is not provided in `requestOptions`, the corresponding `common*` option is used.
593
+ **Configuration Priority:** Individual request options override group options, which override global common options.
433
594
 
434
595
  #### Response Format
435
596
 
436
597
  ```typescript
437
598
  interface API_GATEWAY_RESPONSE<ResponseDataType> {
438
- id: string; // Request identifier
599
+ requestId: string; // Request identifier
600
+ groupId?: string; // Group identifier (if request was grouped)
439
601
  success: boolean; // Whether the request succeeded
440
602
  data?: ResponseDataType; // Response data (if success is true)
441
603
  error?: string; // Error message (if success is false)
@@ -444,7 +606,224 @@ interface API_GATEWAY_RESPONSE<ResponseDataType> {
444
606
 
445
607
  ## Real-World Use Cases
446
608
 
447
- ### 1. Polling for Async Job Completion
609
+ ### 1. Environment-Based Service Tiers with Groups
610
+
611
+ Organize API calls by criticality with different retry strategies per tier.
612
+
613
+ ```typescript
614
+ const results = await stableApiGateway(
615
+ [
616
+ // Critical services
617
+ { id: 'auth-validate', groupId: 'critical', requestOptions: { reqData: { path: '/auth/validate' }, resReq: true } },
618
+ { id: 'db-primary', groupId: 'critical', requestOptions: { reqData: { path: '/db/health' }, resReq: true } },
619
+
620
+ // Payment services
621
+ { id: 'stripe-charge', groupId: 'payments', requestOptions: { reqData: { path: '/stripe/charge', body: { amount: 1000 } }, resReq: true } },
622
+ { id: 'paypal-charge', groupId: 'payments', requestOptions: { reqData: { path: '/paypal/charge', body: { amount: 1000 } }, resReq: true } },
623
+
624
+ // Analytics services
625
+ { id: 'track-event', groupId: 'analytics', requestOptions: { reqData: { path: '/track' }, resReq: true } }
626
+ ],
627
+ {
628
+ // Global defaults
629
+ commonAttempts: 2,
630
+ commonWait: 500,
631
+ commonRequestData: { hostname: 'api.example.com', method: REQUEST_METHODS.POST },
632
+
633
+ requestGroups: [
634
+ {
635
+ id: 'critical',
636
+ commonConfig: {
637
+ commonAttempts: 10,
638
+ commonWait: 2000,
639
+ commonRetryStrategy: RETRY_STRATEGIES.EXPONENTIAL,
640
+ commonHandleErrors: async (reqData, error) => {
641
+ await pagerDuty.alert('CRITICAL', error);
642
+ }
643
+ }
644
+ },
645
+ {
646
+ id: 'payments',
647
+ commonConfig: {
648
+ commonAttempts: 5,
649
+ commonWait: 1500,
650
+ commonRetryStrategy: RETRY_STRATEGIES.LINEAR,
651
+ commonRequestData: {
652
+ headers: { 'X-Idempotency-Key': crypto.randomUUID() }
653
+ },
654
+ commonResponseAnalyzer: async (reqData, data) => {
655
+ return data?.status === 'succeeded' && data?.transactionId;
656
+ }
657
+ }
658
+ },
659
+ {
660
+ id: 'analytics',
661
+ commonConfig: {
662
+ commonAttempts: 1,
663
+ commonFinalErrorAnalyzer: async () => true // Don't throw
664
+ }
665
+ }
666
+ ]
667
+ }
668
+ );
669
+
670
+ // Analyze results by group
671
+ const criticalHealth = results.filter(r => r.groupId === 'critical').every(r => r.success);
672
+ const paymentSuccess = results.filter(r => r.groupId === 'payments').filter(r => r.success).length;
673
+
674
+ if (!criticalHealth) {
675
+ console.error('CRITICAL SERVICES DEGRADED');
676
+ }
677
+ console.log(`Payments: ${paymentSuccess} successful`);
678
+ ```
679
+
680
+ ### 2. Multi-Region API Deployment with Groups
681
+
682
+ Handle requests to different geographic regions with region-specific configurations.
683
+
684
+ ```typescript
685
+ const results = await stableApiGateway(
686
+ [
687
+ { id: 'us-user-profile', groupId: 'us-east', requestOptions: { reqData: { path: '/users/profile' }, resReq: true } },
688
+ { id: 'us-orders', groupId: 'us-east', requestOptions: { reqData: { path: '/orders' }, resReq: true } },
689
+
690
+ { id: 'eu-user-profile', groupId: 'eu-west', requestOptions: { reqData: { path: '/users/profile' }, resReq: true } },
691
+ { id: 'eu-orders', groupId: 'eu-west', requestOptions: { reqData: { path: '/orders' }, resReq: true } },
692
+
693
+ { id: 'ap-user-profile', groupId: 'ap-southeast', requestOptions: { reqData: { path: '/users/profile' }, resReq: true } },
694
+ { id: 'ap-orders', groupId: 'ap-southeast', requestOptions: { reqData: { path: '/orders' }, resReq: true } }
695
+ ],
696
+ {
697
+ commonAttempts: 3,
698
+ commonWait: 1000,
699
+
700
+ requestGroups: [
701
+ {
702
+ id: 'us-east',
703
+ commonConfig: {
704
+ commonRequestData: {
705
+ hostname: 'api-us-east.example.com',
706
+ headers: { 'X-Region': 'us-east-1' },
707
+ timeout: 5000 // Lower latency expected
708
+ },
709
+ commonAttempts: 3,
710
+ commonRetryStrategy: RETRY_STRATEGIES.LINEAR
711
+ }
712
+ },
713
+ {
714
+ id: 'eu-west',
715
+ commonConfig: {
716
+ commonRequestData: {
717
+ hostname: 'api-eu-west.example.com',
718
+ headers: { 'X-Region': 'eu-west-1' },
719
+ timeout: 8000
720
+ },
721
+ commonAttempts: 5,
722
+ commonRetryStrategy: RETRY_STRATEGIES.EXPONENTIAL
723
+ }
724
+ },
725
+ {
726
+ id: 'ap-southeast',
727
+ commonConfig: {
728
+ commonRequestData: {
729
+ hostname: 'api-ap-southeast.example.com',
730
+ headers: { 'X-Region': 'ap-southeast-1' },
731
+ timeout: 10000 // Higher latency expected
732
+ },
733
+ commonAttempts: 7,
734
+ commonWait: 1500,
735
+ commonRetryStrategy: RETRY_STRATEGIES.EXPONENTIAL
736
+ }
737
+ }
738
+ ]
739
+ }
740
+ );
741
+
742
+ // Regional performance analysis
743
+ const regionPerformance = results.reduce((acc, result) => {
744
+ if (!acc[result.groupId!]) acc[result.groupId!] = { success: 0, failed: 0 };
745
+ result.success ? acc[result.groupId!].success++ : acc[result.groupId!].failed++;
746
+ return acc;
747
+ }, {} as Record<string, { success: number; failed: number }>);
748
+
749
+ console.log('Regional performance:', regionPerformance);
750
+ ```
751
+
752
+ ### 3. Microservices Health Monitoring with Groups
753
+
754
+ Monitor different microservices with service-specific health check configurations.
755
+
756
+ ```typescript
757
+ const healthChecks = await stableApiGateway(
758
+ [
759
+ // Core services
760
+ { id: 'auth-health', groupId: 'core', requestOptions: { reqData: { hostname: 'auth.internal.example.com', path: '/health' } } },
761
+ { id: 'user-health', groupId: 'core', requestOptions: { reqData: { hostname: 'users.internal.example.com', path: '/health' } } },
762
+ { id: 'order-health', groupId: 'core', requestOptions: { reqData: { hostname: 'orders.internal.example.com', path: '/health' } } },
763
+
764
+ // Auxiliary services
765
+ { id: 'cache-health', groupId: 'auxiliary', requestOptions: { reqData: { hostname: 'cache.internal.example.com', path: '/health' } } },
766
+ { id: 'search-health', groupId: 'auxiliary', requestOptions: { reqData: { hostname: 'search.internal.example.com', path: '/health' } } },
767
+
768
+ // Third-party
769
+ { id: 'stripe-health', groupId: 'third-party', requestOptions: { reqData: { hostname: 'api.stripe.com', path: '/v1/health' } } }
770
+ ],
771
+ {
772
+ commonResReq: true,
773
+ concurrentExecution: true,
774
+
775
+ requestGroups: [
776
+ {
777
+ id: 'core',
778
+ commonConfig: {
779
+ commonAttempts: 5,
780
+ commonWait: 2000,
781
+ commonRetryStrategy: RETRY_STRATEGIES.EXPONENTIAL,
782
+ commonResponseAnalyzer: async (reqData, data) => {
783
+ return data?.status === 'healthy' &&
784
+ data?.dependencies?.every(d => d.status === 'healthy');
785
+ },
786
+ commonHandleErrors: async (reqData, error) => {
787
+ await pagerDuty.trigger({ severity: 'critical', message: `Core service down: ${error.error}` });
788
+ }
789
+ }
790
+ },
791
+ {
792
+ id: 'auxiliary',
793
+ commonConfig: {
794
+ commonAttempts: 2,
795
+ commonResponseAnalyzer: async (reqData, data) => data?.status === 'ok',
796
+ commonFinalErrorAnalyzer: async () => true // Don't fail on auxiliary issues
797
+ }
798
+ },
799
+ {
800
+ id: 'third-party',
801
+ commonConfig: {
802
+ commonAttempts: 3,
803
+ commonWait: 3000,
804
+ commonRequestData: { timeout: 15000 },
805
+ commonHandleErrors: async (reqData, error) => {
806
+ logger.warn('Third-party health check failed', { error });
807
+ },
808
+ commonFinalErrorAnalyzer: async () => true
809
+ }
810
+ }
811
+ ]
812
+ }
813
+ );
814
+
815
+ const healthReport = {
816
+ timestamp: new Date().toISOString(),
817
+ core: healthChecks.filter(r => r.groupId === 'core').every(r => r.success),
818
+ auxiliary: healthChecks.filter(r => r.groupId === 'auxiliary').every(r => r.success),
819
+ thirdParty: healthChecks.filter(r => r.groupId === 'third-party').every(r => r.success),
820
+ overall: healthChecks.every(r => r.success) ? 'HEALTHY' : 'DEGRADED'
821
+ };
822
+
823
+ console.log('Health Report:', healthReport);
824
+ ```
825
+
826
+ ### 4. Polling for Async Job Completion
448
827
 
449
828
  ```typescript
450
829
  const jobResult = await stableRequest({
@@ -465,7 +844,7 @@ const jobResult = await stableRequest({
465
844
  });
466
845
  ```
467
846
 
468
- ### 2. Resilient External API Integration
847
+ ### 5. Resilient External API Integration
469
848
 
470
849
  ```typescript
471
850
  const weatherData = await stableRequest({
@@ -489,7 +868,7 @@ const weatherData = await stableRequest({
489
868
  });
490
869
  ```
491
870
 
492
- ### 3. Database Replication Consistency Check
871
+ ### 6. Database Replication Consistency Check
493
872
 
494
873
  ```typescript
495
874
  const consistentData = await stableRequest({
@@ -508,7 +887,7 @@ const consistentData = await stableRequest({
508
887
  });
509
888
  ```
510
889
 
511
- ### 4. Batch User Creation
890
+ ### 7. Batch User Creation
512
891
 
513
892
  ```typescript
514
893
  const users = [
@@ -537,9 +916,9 @@ const results = await stableApiGateway(requests, {
537
916
  console.log(`Failed to create user: ${error.error}`);
538
917
  },
539
918
  commonRequestData: {
540
- hostname: 'api.example.com',
541
- path: '/users',
542
- method: REQUEST_METHODS.POST
919
+ hostname: 'api.example.com',
920
+ path: '/users',
921
+ method: REQUEST_METHODS.POST
543
922
  }
544
923
  });
545
924
 
@@ -550,156 +929,97 @@ console.log(`Created ${successful.length} users`);
550
929
  console.log(`Failed to create ${failed.length} users`);
551
930
  ```
552
931
 
553
- ### 5. Rate-Limited API with Backoff
554
-
555
- ```typescript
556
- const searchResults = await stableRequest({
557
- reqData: {
558
- hostname: 'api.ratelimited-service.com',
559
- path: '/search',
560
- query: { q: 'nodejs' }
561
- },
562
- resReq: true,
563
- attempts: 10,
564
- wait: 1000,
565
- retryStrategy: RETRY_STRATEGIES.EXPONENTIAL, // Exponential backoff for rate limits
566
- handleErrors: async (reqConfig, error) => {
567
- if (error.type === 'HTTP_ERROR' && error.error.includes('429')) {
568
- console.log('Rate limited, backing off...');
569
- }
570
- }
571
- });
572
- ```
573
-
574
- ### 6. Microservices Health Check
932
+ ### 8. Batch Data Processing with Tiered Priority Groups
575
933
 
576
934
  ```typescript
577
- const services = ['auth', 'users', 'orders', 'payments'];
935
+ const dataItems = [
936
+ { id: 1, data: 'critical-transaction', priority: 'high' },
937
+ { id: 2, data: 'user-action', priority: 'medium' },
938
+ { id: 3, data: 'analytics-event', priority: 'low' }
939
+ ];
578
940
 
579
- const healthChecks = services.map(service => ({
580
- id: `health-${service}`,
941
+ const requests = dataItems.map(item => ({
942
+ id: `item-${item.id}`,
943
+ groupId: item.priority === 'high' ? 'high-priority' :
944
+ item.priority === 'medium' ? 'medium-priority' : 'low-priority',
581
945
  requestOptions: {
582
- reqData: {
583
- hostname: `${service}.internal.example.com`,
584
- },
585
- resReq: true,
586
- attempts: 3,
587
- wait: 500
946
+ reqData: { body: item },
947
+ resReq: true
588
948
  }
589
949
  }));
590
950
 
591
- const results = await stableApiGateway(healthChecks, {
951
+ const results = await stableApiGateway(requests, {
592
952
  concurrentExecution: true,
593
953
  commonRequestData: {
594
- path: '/health'
954
+ hostname: 'processing.example.com',
955
+ path: '/process',
956
+ method: REQUEST_METHODS.POST
595
957
  },
596
- commonRetryStrategy: RETRY_STRATEGIES.LINEAR
958
+
959
+ requestGroups: [
960
+ {
961
+ id: 'high-priority',
962
+ commonConfig: {
963
+ commonAttempts: 10,
964
+ commonWait: 2000,
965
+ commonRetryStrategy: RETRY_STRATEGIES.EXPONENTIAL,
966
+ commonRequestData: { headers: { 'X-Priority': 'high' } },
967
+ commonResponseAnalyzer: async (reqData, data) => {
968
+ return data?.processed && !data?.errors?.length && data?.validationStatus === 'passed';
969
+ }
970
+ }
971
+ },
972
+ {
973
+ id: 'medium-priority',
974
+ commonConfig: {
975
+ commonAttempts: 5,
976
+ commonWait: 1000,
977
+ commonRetryStrategy: RETRY_STRATEGIES.LINEAR,
978
+ commonRequestData: { headers: { 'X-Priority': 'medium' } }
979
+ }
980
+ },
981
+ {
982
+ id: 'low-priority',
983
+ commonConfig: {
984
+ commonAttempts: 2,
985
+ commonRequestData: { headers: { 'X-Priority': 'low' } },
986
+ commonFinalErrorAnalyzer: async () => true // Accept failures
987
+ }
988
+ }
989
+ ]
597
990
  });
598
991
 
599
- const healthStatus = results.reduce((acc, result) => {
600
- const serviceName = result.id.replace('health-', '');
601
- acc[serviceName] = result.success ? 'healthy' : 'unhealthy';
602
- return acc;
603
- }, {});
992
+ // Report by priority
993
+ const report = {
994
+ high: results.filter(r => r.groupId === 'high-priority'),
995
+ medium: results.filter(r => r.groupId === 'medium-priority'),
996
+ low: results.filter(r => r.groupId === 'low-priority')
997
+ };
604
998
 
605
- console.log('Service health status:', healthStatus);
999
+ console.log(`High: ${report.high.filter(r => r.success).length}/${report.high.length}`);
1000
+ console.log(`Medium: ${report.medium.filter(r => r.success).length}/${report.medium.length}`);
1001
+ console.log(`Low: ${report.low.filter(r => r.success).length}/${report.low.length}`);
606
1002
  ```
607
1003
 
608
- ### 7. Payment Processing with Idempotency
1004
+ ### 9. Rate-Limited API with Backoff
609
1005
 
610
1006
  ```typescript
611
- const payment = await stableRequest({
1007
+ const searchResults = await stableRequest({
612
1008
  reqData: {
613
- hostname: 'payment-gateway.com',
614
- path: '/charge',
615
- method: REQUEST_METHODS.POST,
616
- headers: { 'Idempotency-Key': uniqueId },
617
- body: { amount: 1000, currency: 'USD' }
1009
+ hostname: 'api.ratelimited-service.com',
1010
+ path: '/search',
1011
+ query: { q: 'nodejs' }
618
1012
  },
619
1013
  resReq: true,
620
- attempts: 3,
1014
+ attempts: 10,
621
1015
  wait: 1000,
622
- responseAnalyzer: async (reqConfig, data) => {
623
- return data.status === 'succeeded';
624
- },
625
- finalErrorAnalyzer: async (reqConfig, error) => {
626
- // Check if payment actually went through despite error
627
- const status = await checkPaymentStatus(uniqueId);
628
- return status === 'succeeded';
629
- }
630
- });
631
- ```
632
-
633
- ### 8. Bulk Data Migration
634
-
635
- ```typescript
636
- const records = await fetchLegacyRecords();
637
-
638
- const migrationRequests = records.map((record, index) => ({
639
- id: `migrate-${record.id}`,
640
- requestOptions: {
641
- reqData: {
642
- body: record
643
- },
644
- resReq: true,
645
- // Individual retry config for critical records
646
- ...(record.critical && {
647
- attempts: 5,
648
- wait: 2000,
649
- retryStrategy: RETRY_STRATEGIES.EXPONENTIAL
650
- })
651
- }
652
- }));
653
-
654
- const results = await stableApiGateway(migrationRequests, {
655
- concurrentExecution: true,
656
- commonRequestData: {
657
- hostname: 'new-system.example.com',
658
- path: '/import',
659
- method: REQUEST_METHODS.POST,
660
- }
661
- commonAttempts: 3,
662
- commonWait: 1000,
663
- commonRetryStrategy: RETRY_STRATEGIES.LINEAR,
664
- commonHandleErrors: async (reqConfig, error) => {
665
- await logMigrationError(reqConfig.data.id, error);
666
- }
667
- });
668
- ```
669
-
670
- ### 9. Multi-Source Data Aggregation
671
-
672
- ```typescript
673
- const sources = [
674
- { id: 'source-1', hostname: 'api1.example.com', path: '/data' },
675
- { id: 'source-2', hostname: 'api2.example.com', path: '/info' },
676
- { id: 'source-3', hostname: 'api3.example.com', path: '/stats' }
677
- ];
678
-
679
- const requests = sources.map(source => ({
680
- id: source.id,
681
- requestOptions: {
682
- reqData: {
683
- hostname: source.hostname,
684
- path: source.path
685
- },
686
- resReq: true,
687
- attempts: 3,
688
- finalErrorAnalyzer: async () => true // Don't fail if one source is down
1016
+ retryStrategy: RETRY_STRATEGIES.EXPONENTIAL,
1017
+ handleErrors: async (reqConfig, error) => {
1018
+ if (error.type === 'HTTP_ERROR' && error.error.includes('429')) {
1019
+ console.log('Rate limited, backing off...');
1020
+ }
689
1021
  }
690
- }));
691
-
692
- const results = await stableApiGateway(requests, {
693
- concurrentExecution: true,
694
- commonWait: 1000,
695
- commonRetryStrategy: RETRY_STRATEGIES.EXPONENTIAL
696
1022
  });
697
-
698
- const aggregatedData = results
699
- .filter(r => r.success)
700
- .map(r => r.data);
701
-
702
- console.log(`Collected data from ${aggregatedData.length}/${sources.length} sources`);
703
1023
  ```
704
1024
 
705
1025
  ### 10. Sequential Workflow with Dependencies
@@ -709,18 +1029,14 @@ const workflowSteps = [
709
1029
  {
710
1030
  id: 'step-1-init',
711
1031
  requestOptions: {
712
- reqData: {
713
- path: '/init',
714
- },
1032
+ reqData: { path: '/init' },
715
1033
  resReq: true
716
1034
  }
717
1035
  },
718
1036
  {
719
1037
  id: 'step-2-process',
720
1038
  requestOptions: {
721
- reqData: {
722
- path: '/process',
723
- },
1039
+ reqData: { path: '/process' },
724
1040
  resReq: true,
725
1041
  responseAnalyzer: async (reqConfig, data) => {
726
1042
  return data.status === 'completed';
@@ -730,22 +1046,20 @@ const workflowSteps = [
730
1046
  {
731
1047
  id: 'step-3-finalize',
732
1048
  requestOptions: {
733
- reqData: {
734
- path: '/finalize',
735
- },
1049
+ reqData: { path: '/finalize' },
736
1050
  resReq: true
737
1051
  }
738
1052
  }
739
1053
  ];
740
1054
 
741
1055
  const results = await stableApiGateway(workflowSteps, {
742
- concurrentExecution: false, // Execute sequentially
743
- stopOnFirstError: true, // Stop if any step fails
1056
+ concurrentExecution: false,
1057
+ stopOnFirstError: true,
744
1058
  commonRequestData: {
745
1059
  hostname: 'workflow.example.com',
746
1060
  method: REQUEST_METHODS.POST,
747
1061
  body: { workflowId: 'wf-123' }
748
- }
1062
+ },
749
1063
  commonAttempts: 5,
750
1064
  commonWait: 2000,
751
1065
  commonRetryStrategy: RETRY_STRATEGIES.EXPONENTIAL
@@ -780,25 +1094,26 @@ async function resilientRequest(endpoint: string) {
780
1094
  failureCount++;
781
1095
  }
782
1096
  });
783
- failureCount = 0; // Reset on success
1097
+ failureCount = 0;
784
1098
  return result;
785
1099
  } catch (error) {
786
1100
  if (failureCount >= CIRCUIT_THRESHOLD) {
787
1101
  console.log('Circuit breaker activated');
788
- setTimeout(() => { failureCount = 0; }, 60000); // Reset after 1 minute
1102
+ setTimeout(() => { failureCount = 0; }, 60000);
789
1103
  }
790
1104
  throw error;
791
1105
  }
792
1106
  }
793
1107
  ```
794
1108
 
795
- ### Dynamic Request Configuration
1109
+ ### Dynamic Request Configuration with Groups
796
1110
 
797
1111
  ```typescript
798
1112
  const endpoints = await getEndpointsFromConfig();
799
1113
 
800
1114
  const requests = endpoints.map(endpoint => ({
801
1115
  id: endpoint.id,
1116
+ groupId: endpoint.tier, // 'critical', 'standard', or 'optional'
802
1117
  requestOptions: {
803
1118
  reqData: {
804
1119
  hostname: endpoint.hostname,
@@ -808,15 +1123,37 @@ const requests = endpoints.map(endpoint => ({
808
1123
  headers: { Authorization: `Bearer ${endpoint.auth}` }
809
1124
  })
810
1125
  },
811
- resReq: true,
812
- attempts: endpoint.critical ? 5 : 3,
813
- retryStrategy: endpoint.critical ? RETRY_STRATEGIES.EXPONENTIAL : RETRY_STRATEGIES.FIXED
1126
+ resReq: true
814
1127
  }
815
1128
  }));
816
1129
 
817
1130
  const results = await stableApiGateway(requests, {
818
1131
  concurrentExecution: true,
819
- commonWait: 1000
1132
+ commonWait: 1000,
1133
+
1134
+ requestGroups: [
1135
+ {
1136
+ id: 'critical',
1137
+ commonConfig: {
1138
+ commonAttempts: 10,
1139
+ commonRetryStrategy: RETRY_STRATEGIES.EXPONENTIAL
1140
+ }
1141
+ },
1142
+ {
1143
+ id: 'standard',
1144
+ commonConfig: {
1145
+ commonAttempts: 5,
1146
+ commonRetryStrategy: RETRY_STRATEGIES.LINEAR
1147
+ }
1148
+ },
1149
+ {
1150
+ id: 'optional',
1151
+ commonConfig: {
1152
+ commonAttempts: 2,
1153
+ commonFinalErrorAnalyzer: async () => true
1154
+ }
1155
+ }
1156
+ ]
820
1157
  });
821
1158
  ```
822
1159
 
@@ -831,13 +1168,11 @@ await stableRequest({
831
1168
  resReq: true,
832
1169
  attempts: 5,
833
1170
  responseAnalyzer: async (reqConfig, data) => {
834
- // Only retry if data is incomplete
835
1171
  if (!data.complete) {
836
1172
  console.log('Data incomplete, retrying...');
837
1173
  return false;
838
1174
  }
839
1175
 
840
- // Don't retry if data is invalid (different from incomplete)
841
1176
  if (data.error) {
842
1177
  throw new Error('Invalid data, cannot retry');
843
1178
  }
@@ -890,6 +1225,7 @@ console.log(user.id);
890
1225
  |---------|----------------|-------------|
891
1226
  | **Content validation** | โœ… Full support with `responseAnalyzer` | โŒ Only HTTP status codes |
892
1227
  | **Batch processing** | โœ… Built-in `stableApiGateway` | โŒ Manual implementation needed |
1228
+ | **Request grouping** | โœ… Hierarchical configuration | โŒ No grouping support |
893
1229
  | **Trial mode** | โœ… Built-in failure simulation | โŒ No testing utilities |
894
1230
  | **Retry strategies** | โœ… Fixed, Linear, Exponential | โœ… Exponential only |
895
1231
  | **Observability** | โœ… Granular hooks for every attempt | โš ๏ธ Limited |
@@ -901,7 +1237,8 @@ console.log(user.id);
901
1237
  |---------|----------------|-----|
902
1238
  | **Built on Axios** | โœ… Leverages Axios ecosystem | โŒ Standalone client |
903
1239
  | **Content validation** | โœ… Response analyzer | โŒ Only HTTP errors |
904
- | **Batch processing** | โœ… Built-in gateway | โŒ Manual implementation |
1240
+ | **Batch processing** | โœ… Built-in gateway with grouping | โŒ Manual implementation |
1241
+ | **Request grouping** | โœ… Multi-tier configuration | โŒ No grouping |
905
1242
  | **Trial mode** | โœ… Simulation for testing | โŒ No |
906
1243
  | **Retry strategies** | โœ… 3 configurable strategies | โœ… Exponential with jitter |
907
1244
 
@@ -912,19 +1249,23 @@ console.log(user.id);
912
1249
  | **All-in-one** | โœ… Single package | โŒ Requires multiple packages |
913
1250
  | **HTTP-aware** | โœ… Built for HTTP | โŒ Generic retry wrapper |
914
1251
  | **Content validation** | โœ… Built-in | โŒ Manual implementation |
915
- | **Batch processing** | โœ… Built-in | โŒ Manual implementation |
1252
+ | **Batch processing** | โœ… Built-in with groups | โŒ Manual implementation |
1253
+ | **Request grouping** | โœ… Native support | โŒ No grouping |
916
1254
  | **Observability** | โœ… Request-specific hooks | โš ๏ธ Generic callbacks |
917
1255
 
918
1256
  ## Best Practices
919
1257
 
920
1258
  1. **Use exponential backoff for rate-limited APIs** to avoid overwhelming the server
921
- 2. **Set reasonable timeout values** in `reqData.timeout` to prevent hanging requests
922
- 3. **Implement responseAnalyzer** for APIs that return 200 OK with error details in the body
923
- 4. **Use concurrent execution** in `stableApiGateway` for independent requests
924
- 5. **Use sequential execution** when requests have dependencies or need to maintain order
925
- 6. **Leverage finalErrorAnalyzer** for graceful degradation in non-critical paths
926
- 7. **Enable logging in development** with `logAllErrors` and `logAllSuccessfulAttempts`
927
- 8. **Use Trial Mode** to test your error handling without relying on actual failures
1259
+ 2. **Organize related requests into groups** for easier configuration management
1260
+ 3. **Set reasonable timeout values** in `reqData.timeout` to prevent hanging requests
1261
+ 4. **Implement responseAnalyzer** for APIs that return 200 OK with error details in the body
1262
+ 5. **Use concurrent execution** in `stableApiGateway` for independent requests
1263
+ 6. **Use sequential execution** when requests have dependencies or need to maintain order
1264
+ 7. **Leverage request groups** to differentiate between critical and optional services
1265
+ 8. **Use finalErrorAnalyzer** for graceful degradation in non-critical paths
1266
+ 9. **Enable logging in development** with `logAllErrors` and `logAllSuccessfulAttempts`
1267
+ 10. **Use Trial Mode** to test your error handling without relying on actual failures
1268
+ 11. **Group requests by region or service tier** for better monitoring and configuration
928
1269
 
929
1270
  ## License
930
1271