@emmvish/stable-request 1.7.2 → 1.8.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 +354 -19
- package/dist/core/stable-api-gateway.d.ts +2 -2
- package/dist/core/stable-api-gateway.d.ts.map +1 -1
- package/dist/core/stable-api-gateway.js +58 -4
- package/dist/core/stable-api-gateway.js.map +1 -1
- package/dist/core/stable-request.d.ts +2 -2
- package/dist/core/stable-request.d.ts.map +1 -1
- package/dist/core/stable-request.js +35 -5
- package/dist/core/stable-request.js.map +1 -1
- package/dist/core/stable-workflow.d.ts.map +1 -1
- package/dist/core/stable-workflow.js +16 -4
- package/dist/core/stable-workflow.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/types/index.d.ts +248 -16
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utilities/cache-manager.d.ts +19 -0
- package/dist/utilities/cache-manager.d.ts.map +1 -1
- package/dist/utilities/cache-manager.js +40 -4
- package/dist/utilities/cache-manager.js.map +1 -1
- package/dist/utilities/circuit-breaker.d.ts +22 -0
- package/dist/utilities/circuit-breaker.d.ts.map +1 -1
- package/dist/utilities/circuit-breaker.js +65 -1
- package/dist/utilities/circuit-breaker.js.map +1 -1
- package/dist/utilities/concurrency-limiter.d.ts +26 -0
- package/dist/utilities/concurrency-limiter.d.ts.map +1 -1
- package/dist/utilities/concurrency-limiter.js +74 -4
- package/dist/utilities/concurrency-limiter.js.map +1 -1
- package/dist/utilities/execute-branch-workflow.d.ts.map +1 -1
- package/dist/utilities/execute-branch-workflow.js +18 -12
- package/dist/utilities/execute-branch-workflow.js.map +1 -1
- package/dist/utilities/execute-concurrently.d.ts.map +1 -1
- package/dist/utilities/execute-concurrently.js +5 -6
- package/dist/utilities/execute-concurrently.js.map +1 -1
- package/dist/utilities/execute-non-linear-workflow.d.ts.map +1 -1
- package/dist/utilities/execute-non-linear-workflow.js +2 -4
- package/dist/utilities/execute-non-linear-workflow.js.map +1 -1
- package/dist/utilities/execute-phase.d.ts.map +1 -1
- package/dist/utilities/execute-phase.js +11 -5
- package/dist/utilities/execute-phase.js.map +1 -1
- package/dist/utilities/execute-sequentially.d.ts.map +1 -1
- package/dist/utilities/execute-sequentially.js +5 -6
- package/dist/utilities/execute-sequentially.js.map +1 -1
- package/dist/utilities/index.d.ts +4 -3
- package/dist/utilities/index.d.ts.map +1 -1
- package/dist/utilities/index.js +4 -3
- package/dist/utilities/index.js.map +1 -1
- package/dist/utilities/metrics-aggregator.d.ts +51 -0
- package/dist/utilities/metrics-aggregator.d.ts.map +1 -0
- package/dist/utilities/metrics-aggregator.js +311 -0
- package/dist/utilities/metrics-aggregator.js.map +1 -0
- package/dist/utilities/rate-limiter.d.ts +19 -0
- package/dist/utilities/rate-limiter.d.ts.map +1 -1
- package/dist/utilities/rate-limiter.js +56 -3
- package/dist/utilities/rate-limiter.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,6 +16,11 @@ A production-grade HTTP Workflow Execution Engine for Node.js that transforms un
|
|
|
16
16
|
- [Circuit Breaker Pattern](#circuit-breaker-pattern)
|
|
17
17
|
- [Response Caching](#response-caching)
|
|
18
18
|
- [Rate Limiting and Concurrency Control](#rate-limiting-and-concurrency-control)
|
|
19
|
+
- [Metrics and Observability](#metrics-and-observability)
|
|
20
|
+
- [Request-Level Metrics](#request-level-metrics)
|
|
21
|
+
- [API Gateway Metrics](#api-gateway-metrics)
|
|
22
|
+
- [Workflow Metrics](#workflow-metrics)
|
|
23
|
+
- [MetricsAggregator Utility](#metricsaggregator-utility)
|
|
19
24
|
- [Workflow Execution Patterns](#workflow-execution-patterns)
|
|
20
25
|
- [Sequential and Concurrent Phases](#sequential-and-concurrent-phases)
|
|
21
26
|
- [Mixed Execution Mode](#mixed-execution-mode)
|
|
@@ -260,9 +265,10 @@ await stableRequest({
|
|
|
260
265
|
reqData: { hostname: 'unreliable-api.example.com', path: '/data' },
|
|
261
266
|
attempts: 3,
|
|
262
267
|
circuitBreaker: {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
268
|
+
failureThresholdPercentage: 50, // Open after 50% failures
|
|
269
|
+
minimumRequests: 10, // Minimum requests before evaluation
|
|
270
|
+
recoveryTimeoutMs: 60000, // Wait 60s before trying again (half-open)
|
|
271
|
+
successThresholdPercentage: 20, // Close after 20% successes in half-open
|
|
266
272
|
trackIndividualAttempts: false // Track at request level (not attempt level)
|
|
267
273
|
}
|
|
268
274
|
});
|
|
@@ -278,9 +284,10 @@ await stableRequest({
|
|
|
278
284
|
import { CircuitBreaker } from '@emmvish/stable-request';
|
|
279
285
|
|
|
280
286
|
const sharedBreaker = new CircuitBreaker({
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
287
|
+
failureThresholdPercentage: 50, // 50% failure rate triggers open
|
|
288
|
+
minimumRequests: 10, // Minimum 10 requests before evaluation
|
|
289
|
+
recoveryTimeoutMs: 120000, // 120s timeout in open state
|
|
290
|
+
successThresholdPercentage: 50 // 50% success rate closes circuit
|
|
284
291
|
});
|
|
285
292
|
|
|
286
293
|
await stableWorkflow(phases, {
|
|
@@ -347,7 +354,7 @@ await stableWorkflow(phases, {
|
|
|
347
354
|
// Rate limiting (token bucket algorithm)
|
|
348
355
|
rateLimit: {
|
|
349
356
|
maxRequests: 100, // 100 requests
|
|
350
|
-
|
|
357
|
+
windowMs: 60000 // per 60 seconds
|
|
351
358
|
},
|
|
352
359
|
|
|
353
360
|
// Concurrency limiting
|
|
@@ -363,7 +370,7 @@ const phases = [
|
|
|
363
370
|
maxConcurrentRequests: 10, // Override workflow limit
|
|
364
371
|
rateLimit: {
|
|
365
372
|
maxRequests: 50,
|
|
366
|
-
|
|
373
|
+
windowMs: 10000
|
|
367
374
|
},
|
|
368
375
|
requests: [...]
|
|
369
376
|
}
|
|
@@ -374,15 +381,343 @@ const phases = [
|
|
|
374
381
|
```typescript
|
|
375
382
|
import { RateLimiter } from '@emmvish/stable-request';
|
|
376
383
|
|
|
377
|
-
const limiter = new RateLimiter(
|
|
378
|
-
|
|
379
|
-
|
|
384
|
+
const limiter = new RateLimiter(1000, 3600000); // 1000 requests per hour
|
|
385
|
+
|
|
386
|
+
const state = await limiter.getState(); // Get current state
|
|
387
|
+
console.log(state);
|
|
388
|
+
// { availableTokens: 1000, queueLength: 0, maxRequests: 1000, windowMs: 3600000 }
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
## Metrics and Observability
|
|
392
|
+
|
|
393
|
+
`@emmvish/stable-request` provides comprehensive metrics at every level of execution, from individual requests to complete workflows. All metrics are automatically computed and included in results.
|
|
394
|
+
|
|
395
|
+
### Request-Level Metrics
|
|
396
|
+
|
|
397
|
+
Every `stableRequest` call returns detailed metrics about the request execution:
|
|
398
|
+
|
|
399
|
+
```typescript
|
|
400
|
+
import { stableRequest } from '@emmvish/stable-request';
|
|
401
|
+
|
|
402
|
+
const result = await stableRequest({
|
|
403
|
+
reqData: {
|
|
404
|
+
hostname: 'api.example.com',
|
|
405
|
+
path: '/users/123'
|
|
406
|
+
},
|
|
407
|
+
resReq: true,
|
|
408
|
+
attempts: 3,
|
|
409
|
+
wait: 1000,
|
|
410
|
+
logAllErrors: true
|
|
380
411
|
});
|
|
381
412
|
|
|
382
|
-
|
|
383
|
-
|
|
413
|
+
// Access request metrics
|
|
414
|
+
console.log('Request Result:', {
|
|
415
|
+
success: result.success, // true/false
|
|
416
|
+
data: result.data, // Response data
|
|
417
|
+
error: result.error, // Error message (if failed)
|
|
418
|
+
errorLogs: result.errorLogs, // All failed attempts
|
|
419
|
+
successfulAttempts: result.successfulAttempts, // All successful attempts
|
|
420
|
+
metrics: {
|
|
421
|
+
totalAttempts: result.metrics.totalAttempts, // 3
|
|
422
|
+
successfulAttempts: result.metrics.successfulAttempts, // 1
|
|
423
|
+
failedAttempts: result.metrics.failedAttempts, // 2
|
|
424
|
+
totalExecutionTime: result.metrics.totalExecutionTime, // ms
|
|
425
|
+
averageAttemptTime: result.metrics.averageAttemptTime, // ms
|
|
426
|
+
infrastructureMetrics: {
|
|
427
|
+
circuitBreaker: result.metrics.infrastructureMetrics?.circuitBreaker,
|
|
428
|
+
cache: result.metrics.infrastructureMetrics?.cache
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
// Error logs provide detailed attempt information
|
|
434
|
+
result.errorLogs?.forEach(log => {
|
|
435
|
+
console.log({
|
|
436
|
+
attempt: log.attempt, // "1/3"
|
|
437
|
+
timestamp: log.timestamp,
|
|
438
|
+
error: log.error,
|
|
439
|
+
statusCode: log.statusCode,
|
|
440
|
+
type: log.type, // "HTTP_ERROR" | "INVALID_CONTENT"
|
|
441
|
+
isRetryable: log.isRetryable,
|
|
442
|
+
executionTime: log.executionTime
|
|
443
|
+
});
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
// Successful attempts show what worked
|
|
447
|
+
result.successfulAttempts?.forEach(attempt => {
|
|
448
|
+
console.log({
|
|
449
|
+
attempt: attempt.attempt, // "3/3"
|
|
450
|
+
timestamp: attempt.timestamp,
|
|
451
|
+
executionTime: attempt.executionTime,
|
|
452
|
+
data: attempt.data,
|
|
453
|
+
statusCode: attempt.statusCode
|
|
454
|
+
});
|
|
455
|
+
});
|
|
384
456
|
```
|
|
385
457
|
|
|
458
|
+
**STABLE_REQUEST_RESULT Structure:**
|
|
459
|
+
- `success`: Boolean indicating if request succeeded
|
|
460
|
+
- `data`: Response data (if `resReq: true`)
|
|
461
|
+
- `error`: Error message (if request failed)
|
|
462
|
+
- `errorLogs`: Array of all failed attempt details
|
|
463
|
+
- `successfulAttempts`: Array of all successful attempt details
|
|
464
|
+
- `metrics`: Computed execution metrics and infrastructure statistics
|
|
465
|
+
|
|
466
|
+
### API Gateway Metrics
|
|
467
|
+
|
|
468
|
+
`stableApiGateway` provides aggregated metrics for batch requests:
|
|
469
|
+
|
|
470
|
+
```typescript
|
|
471
|
+
import { stableApiGateway } from '@emmvish/stable-request';
|
|
472
|
+
|
|
473
|
+
const requests = [
|
|
474
|
+
{ id: 'user-1', groupId: 'users', requestOptions: { reqData: { path: '/users/1' }, resReq: true } },
|
|
475
|
+
{ id: 'user-2', groupId: 'users', requestOptions: { reqData: { path: '/users/2' }, resReq: true } },
|
|
476
|
+
{ id: 'order-1', groupId: 'orders', requestOptions: { reqData: { path: '/orders/1' }, resReq: true } },
|
|
477
|
+
{ id: 'product-1', requestOptions: { reqData: { path: '/products/1' }, resReq: true } }
|
|
478
|
+
];
|
|
479
|
+
|
|
480
|
+
const results = await stableApiGateway(requests, {
|
|
481
|
+
concurrentExecution: true,
|
|
482
|
+
commonRequestData: { hostname: 'api.example.com' },
|
|
483
|
+
commonAttempts: 3,
|
|
484
|
+
circuitBreaker: { failureThresholdPercentage: 50, minimumRequests: 5 },
|
|
485
|
+
rateLimit: { maxRequests: 100, windowMs: 60000 },
|
|
486
|
+
maxConcurrentRequests: 5
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
// Gateway-level metrics
|
|
490
|
+
console.log('Gateway Metrics:', {
|
|
491
|
+
totalRequests: results.metrics.totalRequests, // 4
|
|
492
|
+
successfulRequests: results.metrics.successfulRequests, // 3
|
|
493
|
+
failedRequests: results.metrics.failedRequests, // 1
|
|
494
|
+
successRate: results.metrics.successRate, // 75%
|
|
495
|
+
failureRate: results.metrics.failureRate // 25%
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
// Request group metrics
|
|
499
|
+
results.metrics.requestGroups?.forEach(group => {
|
|
500
|
+
console.log(`Group ${group.groupId}:`, {
|
|
501
|
+
totalRequests: group.totalRequests,
|
|
502
|
+
successfulRequests: group.successfulRequests,
|
|
503
|
+
failedRequests: group.failedRequests,
|
|
504
|
+
successRate: group.successRate, // %
|
|
505
|
+
failureRate: group.failureRate, // %
|
|
506
|
+
requestIds: group.requestIds // Array of request IDs
|
|
507
|
+
});
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
// Infrastructure metrics (when utilities are used)
|
|
511
|
+
if (results.metrics.infrastructureMetrics) {
|
|
512
|
+
const infra = results.metrics.infrastructureMetrics;
|
|
513
|
+
|
|
514
|
+
// Circuit Breaker metrics
|
|
515
|
+
if (infra.circuitBreaker) {
|
|
516
|
+
console.log('Circuit Breaker:', {
|
|
517
|
+
state: infra.circuitBreaker.state, // CLOSED | OPEN | HALF_OPEN
|
|
518
|
+
isHealthy: infra.circuitBreaker.isHealthy,
|
|
519
|
+
totalRequests: infra.circuitBreaker.totalRequests,
|
|
520
|
+
failurePercentage: infra.circuitBreaker.failurePercentage,
|
|
521
|
+
openCount: infra.circuitBreaker.openCount,
|
|
522
|
+
recoveryAttempts: infra.circuitBreaker.recoveryAttempts
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// Cache metrics
|
|
527
|
+
if (infra.cache) {
|
|
528
|
+
console.log('Cache:', {
|
|
529
|
+
hitRate: infra.cache.hitRate, // %
|
|
530
|
+
currentSize: infra.cache.currentSize,
|
|
531
|
+
networkRequestsSaved: infra.cache.networkRequestsSaved,
|
|
532
|
+
cacheEfficiency: infra.cache.cacheEfficiency // %
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// Rate Limiter metrics
|
|
537
|
+
if (infra.rateLimiter) {
|
|
538
|
+
console.log('Rate Limiter:', {
|
|
539
|
+
throttledRequests: infra.rateLimiter.throttledRequests,
|
|
540
|
+
throttleRate: infra.rateLimiter.throttleRate, // %
|
|
541
|
+
peakRequestRate: infra.rateLimiter.peakRequestRate
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// Concurrency Limiter metrics
|
|
546
|
+
if (infra.concurrencyLimiter) {
|
|
547
|
+
console.log('Concurrency:', {
|
|
548
|
+
peakConcurrency: infra.concurrencyLimiter.peakConcurrency,
|
|
549
|
+
utilizationPercentage: infra.concurrencyLimiter.utilizationPercentage,
|
|
550
|
+
averageQueueWaitTime: infra.concurrencyLimiter.averageQueueWaitTime
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
### Workflow Metrics
|
|
557
|
+
|
|
558
|
+
`stableWorkflow` provides end-to-end metrics for complex orchestrations:
|
|
559
|
+
|
|
560
|
+
```typescript
|
|
561
|
+
import { stableWorkflow, PHASE_DECISION_ACTIONS } from '@emmvish/stable-request';
|
|
562
|
+
|
|
563
|
+
const phases = [
|
|
564
|
+
{
|
|
565
|
+
id: 'fetch-users',
|
|
566
|
+
requests: [/* ... */]
|
|
567
|
+
},
|
|
568
|
+
{
|
|
569
|
+
id: 'process-data',
|
|
570
|
+
concurrent: true,
|
|
571
|
+
requests: [/* ... */]
|
|
572
|
+
},
|
|
573
|
+
{
|
|
574
|
+
id: 'store-results',
|
|
575
|
+
requests: [/* ... */]
|
|
576
|
+
}
|
|
577
|
+
];
|
|
578
|
+
|
|
579
|
+
const result = await stableWorkflow(phases, {
|
|
580
|
+
workflowId: 'data-processing-pipeline',
|
|
581
|
+
enableMixedExecution: true,
|
|
582
|
+
commonRequestData: { hostname: 'api.example.com' },
|
|
583
|
+
logPhaseResults: true
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
// Workflow-level metrics
|
|
587
|
+
console.log('Workflow Metrics:', {
|
|
588
|
+
workflowId: result.metrics.workflowId,
|
|
589
|
+
success: result.metrics.success,
|
|
590
|
+
executionTime: result.metrics.executionTime, // Total time in ms
|
|
591
|
+
|
|
592
|
+
// Phase statistics
|
|
593
|
+
totalPhases: result.metrics.totalPhases,
|
|
594
|
+
completedPhases: result.metrics.completedPhases,
|
|
595
|
+
skippedPhases: result.metrics.skippedPhases,
|
|
596
|
+
failedPhases: result.metrics.failedPhases,
|
|
597
|
+
phaseCompletionRate: result.metrics.phaseCompletionRate, // %
|
|
598
|
+
averagePhaseExecutionTime: result.metrics.averagePhaseExecutionTime, // ms
|
|
599
|
+
|
|
600
|
+
// Request statistics
|
|
601
|
+
totalRequests: result.metrics.totalRequests,
|
|
602
|
+
successfulRequests: result.metrics.successfulRequests,
|
|
603
|
+
failedRequests: result.metrics.failedRequests,
|
|
604
|
+
requestSuccessRate: result.metrics.requestSuccessRate, // %
|
|
605
|
+
requestFailureRate: result.metrics.requestFailureRate, // %
|
|
606
|
+
|
|
607
|
+
// Performance
|
|
608
|
+
throughput: result.metrics.throughput, // requests/second
|
|
609
|
+
totalPhaseReplays: result.metrics.totalPhaseReplays,
|
|
610
|
+
totalPhaseSkips: result.metrics.totalPhaseSkips,
|
|
611
|
+
|
|
612
|
+
// Branch metrics (if using branch execution)
|
|
613
|
+
totalBranches: result.metrics.totalBranches,
|
|
614
|
+
completedBranches: result.metrics.completedBranches,
|
|
615
|
+
failedBranches: result.metrics.failedBranches,
|
|
616
|
+
branchSuccessRate: result.metrics.branchSuccessRate // %
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
// Request group metrics aggregated across entire workflow
|
|
620
|
+
result.requestGroupMetrics?.forEach(group => {
|
|
621
|
+
console.log(`Request Group ${group.groupId}:`, {
|
|
622
|
+
totalRequests: group.totalRequests,
|
|
623
|
+
successRate: group.successRate, // %
|
|
624
|
+
requestIds: group.requestIds
|
|
625
|
+
});
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
// Per-phase metrics
|
|
629
|
+
result.phases.forEach(phase => {
|
|
630
|
+
console.log(`Phase ${phase.phaseId}:`, {
|
|
631
|
+
executionTime: phase.metrics?.executionTime,
|
|
632
|
+
totalRequests: phase.metrics?.totalRequests,
|
|
633
|
+
successfulRequests: phase.metrics?.successfulRequests,
|
|
634
|
+
requestSuccessRate: phase.metrics?.requestSuccessRate, // %
|
|
635
|
+
hasDecision: phase.metrics?.hasDecision,
|
|
636
|
+
decisionAction: phase.metrics?.decisionAction // CONTINUE | JUMP | REPLAY | etc.
|
|
637
|
+
});
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
// Branch metrics (for branched workflows)
|
|
641
|
+
result.branches?.forEach(branch => {
|
|
642
|
+
console.log(`Branch ${branch.branchId}:`, {
|
|
643
|
+
success: branch.metrics?.success,
|
|
644
|
+
executionTime: branch.metrics?.executionTime,
|
|
645
|
+
totalPhases: branch.metrics?.totalPhases,
|
|
646
|
+
completedPhases: branch.metrics?.completedPhases,
|
|
647
|
+
totalRequests: branch.metrics?.totalRequests,
|
|
648
|
+
requestSuccessRate: branch.metrics?.requestSuccessRate // %
|
|
649
|
+
});
|
|
650
|
+
});
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
### MetricsAggregator Utility
|
|
654
|
+
|
|
655
|
+
For custom metrics extraction and analysis:
|
|
656
|
+
|
|
657
|
+
```typescript
|
|
658
|
+
import { MetricsAggregator } from '@emmvish/stable-request';
|
|
659
|
+
|
|
660
|
+
// Extract workflow metrics
|
|
661
|
+
const workflowMetrics = MetricsAggregator.extractWorkflowMetrics(workflowResult);
|
|
662
|
+
|
|
663
|
+
// Extract phase metrics
|
|
664
|
+
const phaseMetrics = MetricsAggregator.extractPhaseMetrics(phaseResult);
|
|
665
|
+
|
|
666
|
+
// Extract branch metrics
|
|
667
|
+
const branchMetrics = MetricsAggregator.extractBranchMetrics(branchResult);
|
|
668
|
+
|
|
669
|
+
// Extract request group metrics
|
|
670
|
+
const requestGroups = MetricsAggregator.extractRequestGroupMetrics(responses);
|
|
671
|
+
|
|
672
|
+
// Extract individual request metrics
|
|
673
|
+
const requestMetrics = MetricsAggregator.extractRequestMetrics(responses);
|
|
674
|
+
|
|
675
|
+
// Extract circuit breaker metrics
|
|
676
|
+
const cbMetrics = MetricsAggregator.extractCircuitBreakerMetrics(circuitBreaker);
|
|
677
|
+
|
|
678
|
+
// Extract cache metrics
|
|
679
|
+
const cacheMetrics = MetricsAggregator.extractCacheMetrics(cacheManager);
|
|
680
|
+
|
|
681
|
+
// Extract rate limiter metrics
|
|
682
|
+
const rateLimiterMetrics = MetricsAggregator.extractRateLimiterMetrics(rateLimiter);
|
|
683
|
+
|
|
684
|
+
// Extract concurrency limiter metrics
|
|
685
|
+
const concurrencyMetrics = MetricsAggregator.extractConcurrencyLimiterMetrics(limiter);
|
|
686
|
+
|
|
687
|
+
// Aggregate all system metrics
|
|
688
|
+
const systemMetrics = MetricsAggregator.aggregateSystemMetrics(
|
|
689
|
+
workflowResult,
|
|
690
|
+
circuitBreaker,
|
|
691
|
+
cacheManager,
|
|
692
|
+
rateLimiter,
|
|
693
|
+
concurrencyLimiter
|
|
694
|
+
);
|
|
695
|
+
|
|
696
|
+
console.log('Complete System View:', {
|
|
697
|
+
workflow: systemMetrics.workflow,
|
|
698
|
+
phases: systemMetrics.phases,
|
|
699
|
+
branches: systemMetrics.branches,
|
|
700
|
+
requestGroups: systemMetrics.requestGroups,
|
|
701
|
+
requests: systemMetrics.requests,
|
|
702
|
+
circuitBreaker: systemMetrics.circuitBreaker,
|
|
703
|
+
cache: systemMetrics.cache,
|
|
704
|
+
rateLimiter: systemMetrics.rateLimiter,
|
|
705
|
+
concurrencyLimiter: systemMetrics.concurrencyLimiter
|
|
706
|
+
});
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
**Available Metrics Types:**
|
|
710
|
+
- `WorkflowMetrics`: Complete workflow statistics
|
|
711
|
+
- `BranchMetrics`: Branch execution metrics
|
|
712
|
+
- `PhaseMetrics`: Individual phase metrics
|
|
713
|
+
- `RequestGroupMetrics`: Grouped request statistics
|
|
714
|
+
- `RequestMetrics`: Individual request metrics
|
|
715
|
+
- `CircuitBreakerDashboardMetrics`: Circuit breaker state and performance
|
|
716
|
+
- `CacheDashboardMetrics`: Cache hit rates and efficiency
|
|
717
|
+
- `RateLimiterDashboardMetrics`: Throttling and rate limit statistics
|
|
718
|
+
- `ConcurrencyLimiterDashboardMetrics`: Concurrency and queue metrics
|
|
719
|
+
- `SystemMetrics`: Complete system-wide aggregation
|
|
720
|
+
|
|
386
721
|
## Workflow Execution Patterns
|
|
387
722
|
|
|
388
723
|
### Sequential and Concurrent Phases
|
|
@@ -659,7 +994,6 @@ const branches = [
|
|
|
659
994
|
if (branchResult.failedRequests > 0) {
|
|
660
995
|
return {
|
|
661
996
|
action: PHASE_DECISION_ACTIONS.TERMINATE,
|
|
662
|
-
terminateWorkflow: true, // Terminate entire workflow
|
|
663
997
|
metadata: { reason: 'Critical branch failed' }
|
|
664
998
|
};
|
|
665
999
|
}
|
|
@@ -1074,7 +1408,7 @@ await stableWorkflow(phases, {
|
|
|
1074
1408
|
},
|
|
1075
1409
|
|
|
1076
1410
|
// Monitor non-linear execution decisions
|
|
1077
|
-
handlePhaseDecision: async ({
|
|
1411
|
+
handlePhaseDecision: async ({ decision, phaseResult }) => {
|
|
1078
1412
|
console.log(`Phase decision: ${decision.action}`);
|
|
1079
1413
|
if (decision.targetPhaseId) {
|
|
1080
1414
|
console.log(`Target: ${decision.targetPhaseId}`);
|
|
@@ -1285,12 +1619,13 @@ await stableWorkflow(pollingPhases, {
|
|
|
1285
1619
|
### Webhook Retry with Circuit Breaker
|
|
1286
1620
|
|
|
1287
1621
|
```typescript
|
|
1288
|
-
import { CircuitBreaker, REQUEST_METHODS } from '@emmvish/stable-request';
|
|
1622
|
+
import { CircuitBreaker, REQUEST_METHODS, RETRY_STRATEGIES } from '@emmvish/stable-request';
|
|
1289
1623
|
|
|
1290
1624
|
const webhookBreaker = new CircuitBreaker({
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1625
|
+
failureThresholdPercentage: 60, // 60% failure rate triggers open
|
|
1626
|
+
minimumRequests: 5, // Minimum 5 requests before evaluation
|
|
1627
|
+
recoveryTimeoutMs: 30000, // 30s timeout in open state
|
|
1628
|
+
successThresholdPercentage: 40 // 40% success rate closes circuit
|
|
1294
1629
|
});
|
|
1295
1630
|
|
|
1296
1631
|
async function sendWebhook(eventData: any) {
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { API_GATEWAY_OPTIONS, API_GATEWAY_REQUEST } from '../types/index.js';
|
|
2
|
-
export declare function stableApiGateway<RequestDataType = any, ResponseDataType = any>(requests?: API_GATEWAY_REQUEST<RequestDataType, ResponseDataType>[], options?: API_GATEWAY_OPTIONS<RequestDataType, ResponseDataType>): Promise<
|
|
1
|
+
import { API_GATEWAY_OPTIONS, API_GATEWAY_REQUEST, API_GATEWAY_RESULT } from '../types/index.js';
|
|
2
|
+
export declare function stableApiGateway<RequestDataType = any, ResponseDataType = any>(requests?: API_GATEWAY_REQUEST<RequestDataType, ResponseDataType>[], options?: API_GATEWAY_OPTIONS<RequestDataType, ResponseDataType>): Promise<API_GATEWAY_RESULT<ResponseDataType>>;
|
|
3
3
|
//# sourceMappingURL=stable-api-gateway.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stable-api-gateway.d.ts","sourceRoot":"","sources":["../../src/core/stable-api-gateway.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,mBAAmB,EACnB,mBAAmB,
|
|
1
|
+
{"version":3,"file":"stable-api-gateway.d.ts","sourceRoot":"","sources":["../../src/core/stable-api-gateway.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,mBAAmB,EACnB,mBAAmB,EAEnB,kBAAkB,EAOrB,MAAM,mBAAmB,CAAC;AAY3B,wBAAsB,gBAAgB,CAAC,eAAe,GAAG,GAAG,EAAE,gBAAgB,GAAG,GAAG,EAChF,QAAQ,GAAE,mBAAmB,CAAC,eAAe,EAAE,gBAAgB,CAAC,EAAO,EACvE,OAAO,GAAE,mBAAmB,CAAC,eAAe,EAAE,gBAAgB,CAAM,GACrE,OAAO,CAAC,kBAAkB,CAAC,gBAAgB,CAAC,CAAC,CA6F/C"}
|
|
@@ -1,8 +1,20 @@
|
|
|
1
|
-
import { executeConcurrently, executeSequentially, extractCommonRequestConfigOptions as extractCommonOptions } from '../utilities/index.js';
|
|
1
|
+
import { executeConcurrently, executeSequentially, extractCommonRequestConfigOptions as extractCommonOptions, MetricsAggregator } from '../utilities/index.js';
|
|
2
|
+
import { CircuitBreaker, getGlobalCircuitBreaker } from '../utilities/circuit-breaker.js';
|
|
3
|
+
import { getGlobalCacheManager } from '../utilities/cache-manager.js';
|
|
4
|
+
import { getGlobalRateLimiter } from '../utilities/rate-limiter.js';
|
|
5
|
+
import { getGlobalConcurrencyLimiter } from '../utilities/concurrency-limiter.js';
|
|
2
6
|
export async function stableApiGateway(requests = [], options = {}) {
|
|
3
7
|
const { concurrentExecution = true, stopOnFirstError = false, requestGroups = [], maxConcurrentRequests, rateLimit, circuitBreaker, } = options;
|
|
4
8
|
if (!Array.isArray(requests) || requests.length === 0) {
|
|
5
|
-
|
|
9
|
+
const emptyResult = [];
|
|
10
|
+
emptyResult.metrics = {
|
|
11
|
+
totalRequests: 0,
|
|
12
|
+
successfulRequests: 0,
|
|
13
|
+
failedRequests: 0,
|
|
14
|
+
successRate: 0,
|
|
15
|
+
failureRate: 0
|
|
16
|
+
};
|
|
17
|
+
return emptyResult;
|
|
6
18
|
}
|
|
7
19
|
const requestExecutionOptions = {
|
|
8
20
|
stopOnFirstError,
|
|
@@ -13,11 +25,53 @@ export async function stableApiGateway(requests = [], options = {}) {
|
|
|
13
25
|
...(circuitBreaker !== undefined && { circuitBreaker }),
|
|
14
26
|
...extractCommonOptions(options)
|
|
15
27
|
};
|
|
28
|
+
let responses;
|
|
16
29
|
if (concurrentExecution) {
|
|
17
|
-
|
|
30
|
+
responses = await executeConcurrently(requests, requestExecutionOptions);
|
|
18
31
|
}
|
|
19
32
|
else {
|
|
20
|
-
|
|
33
|
+
responses = await executeSequentially(requests, requestExecutionOptions);
|
|
21
34
|
}
|
|
35
|
+
const successfulRequests = responses.filter(r => r.success).length;
|
|
36
|
+
const failedRequests = responses.filter(r => !r.success).length;
|
|
37
|
+
const totalRequests = responses.length;
|
|
38
|
+
const result = responses;
|
|
39
|
+
result.metrics = {
|
|
40
|
+
totalRequests,
|
|
41
|
+
successfulRequests,
|
|
42
|
+
failedRequests,
|
|
43
|
+
successRate: totalRequests > 0 ? (successfulRequests / totalRequests) * 100 : 0,
|
|
44
|
+
failureRate: totalRequests > 0 ? (failedRequests / totalRequests) * 100 : 0,
|
|
45
|
+
requestGroups: MetricsAggregator.extractRequestGroupMetrics(responses)
|
|
46
|
+
};
|
|
47
|
+
const infrastructureMetrics = {};
|
|
48
|
+
if (circuitBreaker) {
|
|
49
|
+
const cb = circuitBreaker instanceof CircuitBreaker ? circuitBreaker : getGlobalCircuitBreaker();
|
|
50
|
+
if (cb) {
|
|
51
|
+
infrastructureMetrics.circuitBreaker = MetricsAggregator.extractCircuitBreakerMetrics(cb);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (options.commonCache) {
|
|
55
|
+
const cache = getGlobalCacheManager();
|
|
56
|
+
if (cache) {
|
|
57
|
+
infrastructureMetrics.cache = MetricsAggregator.extractCacheMetrics(cache);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (rateLimit) {
|
|
61
|
+
const rateLimiter = getGlobalRateLimiter();
|
|
62
|
+
if (rateLimiter) {
|
|
63
|
+
infrastructureMetrics.rateLimiter = MetricsAggregator.extractRateLimiterMetrics(rateLimiter);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (maxConcurrentRequests) {
|
|
67
|
+
const concurrencyLimiter = getGlobalConcurrencyLimiter();
|
|
68
|
+
if (concurrencyLimiter) {
|
|
69
|
+
infrastructureMetrics.concurrencyLimiter = MetricsAggregator.extractConcurrencyLimiterMetrics(concurrencyLimiter);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (Object.keys(infrastructureMetrics).length > 0) {
|
|
73
|
+
result.metrics.infrastructureMetrics = infrastructureMetrics;
|
|
74
|
+
}
|
|
75
|
+
return result;
|
|
22
76
|
}
|
|
23
77
|
//# sourceMappingURL=stable-api-gateway.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stable-api-gateway.js","sourceRoot":"","sources":["../../src/core/stable-api-gateway.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"stable-api-gateway.js","sourceRoot":"","sources":["../../src/core/stable-api-gateway.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { STABLE_REQUEST } from '../types/index.js';
|
|
2
|
-
export declare function stableRequest<RequestDataType = any, ResponseDataType = any>(options: STABLE_REQUEST<RequestDataType, ResponseDataType>): Promise<ResponseDataType
|
|
1
|
+
import { STABLE_REQUEST, STABLE_REQUEST_RESULT } from '../types/index.js';
|
|
2
|
+
export declare function stableRequest<RequestDataType = any, ResponseDataType = any>(options: STABLE_REQUEST<RequestDataType, ResponseDataType>): Promise<STABLE_REQUEST_RESULT<ResponseDataType>>;
|
|
3
3
|
//# sourceMappingURL=stable-request.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stable-request.d.ts","sourceRoot":"","sources":["../../src/core/stable-request.ts"],"names":[],"mappings":"AAQA,OAAO,EAIL,cAAc,
|
|
1
|
+
{"version":3,"file":"stable-request.d.ts","sourceRoot":"","sources":["../../src/core/stable-request.ts"],"names":[],"mappings":"AAQA,OAAO,EAIL,cAAc,EACd,qBAAqB,EAEtB,MAAM,mBAAmB,CAAC;AAkB3B,wBAAsB,aAAa,CAAC,eAAe,GAAG,GAAG,EAAE,gBAAgB,GAAG,GAAG,EAC/E,OAAO,EAAE,cAAc,CAAC,eAAe,EAAE,gBAAgB,CAAC,GACzD,OAAO,CAAC,qBAAqB,CAAC,gBAAgB,CAAC,CAAC,CAqXlD"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { RETRY_STRATEGIES, RESPONSE_ERRORS, CircuitBreakerState } from '../enums/index.js';
|
|
2
|
-
import { CircuitBreaker, CircuitBreakerOpenError, executeWithPersistence, formatLogContext, generateAxiosRequestConfig, getNewDelayTime, delay, reqFn, safelyStringify, validateTrialModeProbabilities } from '../utilities/index.js';
|
|
2
|
+
import { CircuitBreaker, CircuitBreakerOpenError, executeWithPersistence, formatLogContext, generateAxiosRequestConfig, getNewDelayTime, getGlobalCacheManager, delay, reqFn, safelyStringify, validateTrialModeProbabilities, MetricsAggregator } from '../utilities/index.js';
|
|
3
3
|
export async function stableRequest(options) {
|
|
4
4
|
const { preExecution = {
|
|
5
5
|
preExecutionHook: ({ inputParams, commonBuffer }) => { },
|
|
@@ -30,6 +30,33 @@ export async function stableRequest(options) {
|
|
|
30
30
|
const { reqData: givenReqData, responseAnalyzer = ({ reqData, data, trialMode = { enabled: false } }) => true, resReq = false, attempts: givenAttempts = 1, performAllAttempts = false, wait = 1000, maxAllowedWait = 60000, retryStrategy = RETRY_STRATEGIES.FIXED, logAllErrors = false, handleErrors = ({ reqData, errorLog, maxSerializableChars = 1000, executionContext }) => console.error(`${formatLogContext(executionContext)}stable-request:\n`, 'Request data:\n', safelyStringify(reqData, maxSerializableChars), '\nError log:\n', safelyStringify(errorLog, maxSerializableChars)), logAllSuccessfulAttempts = false, handleSuccessfulAttemptData = ({ reqData, successfulAttemptData, maxSerializableChars = 1000, executionContext }) => console.info(`${formatLogContext(executionContext)}stable-request:\n`, 'Request data:\n', safelyStringify(reqData, maxSerializableChars), '\nSuccessful attempt:\n', safelyStringify(successfulAttemptData, maxSerializableChars)), maxSerializableChars = 1000, finalErrorAnalyzer = ({ reqData, error, trialMode = { enabled: false } }) => false, trialMode = { enabled: false }, hookParams = {}, cache, circuitBreaker, jitter = 0, statePersistence } = options;
|
|
31
31
|
let attempts = givenAttempts;
|
|
32
32
|
const reqData = generateAxiosRequestConfig(givenReqData);
|
|
33
|
+
const requestStartTime = Date.now();
|
|
34
|
+
const errorLogs = [];
|
|
35
|
+
const successfulAttemptsList = [];
|
|
36
|
+
let totalAttemptsMade = 0;
|
|
37
|
+
const buildResult = (success, data, error) => {
|
|
38
|
+
const totalExecutionTime = Date.now() - requestStartTime;
|
|
39
|
+
const successfulAttemptsCount = successfulAttemptsList.length;
|
|
40
|
+
const failedAttemptsCount = totalAttemptsMade - successfulAttemptsCount;
|
|
41
|
+
return {
|
|
42
|
+
success,
|
|
43
|
+
...(data !== undefined && { data }),
|
|
44
|
+
...(error && { error }),
|
|
45
|
+
...(errorLogs.length > 0 && { errorLogs }),
|
|
46
|
+
...(successfulAttemptsList.length > 0 && { successfulAttempts: successfulAttemptsList }),
|
|
47
|
+
metrics: {
|
|
48
|
+
totalAttempts: totalAttemptsMade,
|
|
49
|
+
successfulAttempts: successfulAttemptsCount,
|
|
50
|
+
failedAttempts: failedAttemptsCount,
|
|
51
|
+
totalExecutionTime,
|
|
52
|
+
averageAttemptTime: totalAttemptsMade > 0 ? totalExecutionTime / totalAttemptsMade : 0,
|
|
53
|
+
infrastructureMetrics: {
|
|
54
|
+
...(circuitBreakerInstance && { circuitBreaker: MetricsAggregator.extractCircuitBreakerMetrics(circuitBreakerInstance) }),
|
|
55
|
+
...(cache && getGlobalCacheManager() && { cache: MetricsAggregator.extractCacheMetrics(getGlobalCacheManager()) })
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
};
|
|
33
60
|
let circuitBreakerInstance = null;
|
|
34
61
|
if (circuitBreaker) {
|
|
35
62
|
circuitBreakerInstance = circuitBreaker instanceof CircuitBreaker
|
|
@@ -51,6 +78,7 @@ export async function stableRequest(options) {
|
|
|
51
78
|
do {
|
|
52
79
|
attempts--;
|
|
53
80
|
const currentAttempt = maxAttempts - attempts;
|
|
81
|
+
totalAttemptsMade = currentAttempt;
|
|
54
82
|
if (circuitBreakerInstance) {
|
|
55
83
|
const cbConfig = circuitBreakerInstance.getState().config;
|
|
56
84
|
if (cbConfig.trackIndividualAttempts || currentAttempt === 1) {
|
|
@@ -66,7 +94,7 @@ export async function stableRequest(options) {
|
|
|
66
94
|
if (trialMode.enabled) {
|
|
67
95
|
console.info(`${formatLogContext(executionContext)}stable-request: Response served from cache:\n`, safelyStringify(res?.data, maxSerializableChars));
|
|
68
96
|
}
|
|
69
|
-
return resReq ? res?.data : true;
|
|
97
|
+
return buildResult(true, resReq ? res?.data : true);
|
|
70
98
|
}
|
|
71
99
|
}
|
|
72
100
|
catch (attemptError) {
|
|
@@ -125,6 +153,7 @@ export async function stableRequest(options) {
|
|
|
125
153
|
executionTime: res.executionTime,
|
|
126
154
|
statusCode: res.statusCode
|
|
127
155
|
};
|
|
156
|
+
errorLogs.push(errorLog);
|
|
128
157
|
try {
|
|
129
158
|
await executeWithPersistence(handleErrors, {
|
|
130
159
|
reqData,
|
|
@@ -151,6 +180,7 @@ export async function stableRequest(options) {
|
|
|
151
180
|
executionTime: res.executionTime,
|
|
152
181
|
statusCode: res.statusCode
|
|
153
182
|
};
|
|
183
|
+
successfulAttemptsList.push(successfulAttemptLog);
|
|
154
184
|
try {
|
|
155
185
|
await executeWithPersistence(handleSuccessfulAttemptData, {
|
|
156
186
|
reqData,
|
|
@@ -182,14 +212,14 @@ export async function stableRequest(options) {
|
|
|
182
212
|
if (trialMode.enabled) {
|
|
183
213
|
console.info(`${formatLogContext(executionContext)}stable-request: Final response (performAllAttempts mode):\n`, safelyStringify(lastSuccessfulAttemptData, maxSerializableChars));
|
|
184
214
|
}
|
|
185
|
-
return resReq ? lastSuccessfulAttemptData : true;
|
|
215
|
+
return buildResult(true, resReq ? lastSuccessfulAttemptData : true);
|
|
186
216
|
}
|
|
187
217
|
else if (res.ok) {
|
|
188
218
|
if (trialMode.enabled) {
|
|
189
219
|
const finalResponse = res?.data ?? lastSuccessfulAttemptData;
|
|
190
220
|
console.info(`${formatLogContext(executionContext)}stable-request: Final response:\n`, safelyStringify(finalResponse, maxSerializableChars));
|
|
191
221
|
}
|
|
192
|
-
return resReq ? res?.data ?? lastSuccessfulAttemptData : true;
|
|
222
|
+
return buildResult(true, resReq ? (res?.data ?? lastSuccessfulAttemptData) : true);
|
|
193
223
|
}
|
|
194
224
|
else {
|
|
195
225
|
throw new Error(safelyStringify({
|
|
@@ -221,7 +251,7 @@ export async function stableRequest(options) {
|
|
|
221
251
|
throw e;
|
|
222
252
|
}
|
|
223
253
|
else {
|
|
224
|
-
return false;
|
|
254
|
+
return buildResult(false, undefined, e.message || 'Request failed');
|
|
225
255
|
}
|
|
226
256
|
}
|
|
227
257
|
}
|