@emmvish/stable-request 1.7.3 → 1.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +457 -0
- 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 +23 -8
- package/dist/core/stable-workflow.js.map +1 -1
- package/dist/index.d.ts +2 -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 +258 -0
- 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 +38 -2
- 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 +115 -19
- 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 +47 -3
- package/dist/utilities/execute-non-linear-workflow.js.map +1 -1
- package/dist/utilities/execute-phase.d.ts +1 -1
- package/dist/utilities/execute-phase.d.ts.map +1 -1
- package/dist/utilities/execute-phase.js +45 -17
- 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)
|
|
@@ -383,6 +388,336 @@ console.log(state);
|
|
|
383
388
|
// { availableTokens: 1000, queueLength: 0, maxRequests: 1000, windowMs: 3600000 }
|
|
384
389
|
```
|
|
385
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
|
|
411
|
+
});
|
|
412
|
+
|
|
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
|
+
});
|
|
456
|
+
```
|
|
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
|
|
@@ -586,6 +921,85 @@ phaseDecisionHook: async ({
|
|
|
586
921
|
}
|
|
587
922
|
```
|
|
588
923
|
|
|
924
|
+
### Dynamic Phase and Branch Addition
|
|
925
|
+
|
|
926
|
+
Dynamically add phases or branches during workflow execution based on runtime conditions:
|
|
927
|
+
|
|
928
|
+
**Adding Phases Dynamically**:
|
|
929
|
+
```typescript
|
|
930
|
+
const phases = [
|
|
931
|
+
{
|
|
932
|
+
id: 'initial-phase',
|
|
933
|
+
requests: [...],
|
|
934
|
+
phaseDecisionHook: async ({ phaseResult }) => {
|
|
935
|
+
const needsExtraProcessing = phaseResult.responses[0]?.data?.requiresValidation;
|
|
936
|
+
|
|
937
|
+
if (needsExtraProcessing) {
|
|
938
|
+
return {
|
|
939
|
+
action: PHASE_DECISION_ACTIONS.CONTINUE,
|
|
940
|
+
addPhases: [
|
|
941
|
+
{
|
|
942
|
+
id: 'validation-phase',
|
|
943
|
+
requests: [{ id: 'validate', requestOptions: { reqData: { path: '/validate' }, resReq: true } }]
|
|
944
|
+
}
|
|
945
|
+
]
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
return { action: PHASE_DECISION_ACTIONS.CONTINUE };
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
];
|
|
952
|
+
|
|
953
|
+
await stableWorkflow(phases, {
|
|
954
|
+
enableNonLinearExecution: true,
|
|
955
|
+
commonRequestData: { hostname: 'api.example.com' }
|
|
956
|
+
});
|
|
957
|
+
```
|
|
958
|
+
|
|
959
|
+
**Adding Branches Dynamically**:
|
|
960
|
+
```typescript
|
|
961
|
+
const branches = [
|
|
962
|
+
{
|
|
963
|
+
id: 'main-branch',
|
|
964
|
+
phases: [...],
|
|
965
|
+
branchDecisionHook: async ({ branchResults }) => {
|
|
966
|
+
const requiresAudit = branchResults.some(p => p.responses[0]?.data?.flagged);
|
|
967
|
+
|
|
968
|
+
if (requiresAudit) {
|
|
969
|
+
return {
|
|
970
|
+
action: PHASE_DECISION_ACTIONS.CONTINUE,
|
|
971
|
+
addBranches: [
|
|
972
|
+
{
|
|
973
|
+
id: 'audit-branch',
|
|
974
|
+
phases: [{ id: 'audit', requests: [...] }]
|
|
975
|
+
}
|
|
976
|
+
]
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
return { action: PHASE_DECISION_ACTIONS.CONTINUE };
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
];
|
|
983
|
+
|
|
984
|
+
await stableWorkflow([], {
|
|
985
|
+
enableBranchExecution: true,
|
|
986
|
+
branches,
|
|
987
|
+
commonRequestData: { hostname: 'api.example.com' }
|
|
988
|
+
});
|
|
989
|
+
```
|
|
990
|
+
|
|
991
|
+
**Extending Current Branch**:
|
|
992
|
+
```typescript
|
|
993
|
+
branchDecisionHook: async ({ branchResults }) => {
|
|
994
|
+
return {
|
|
995
|
+
action: PHASE_DECISION_ACTIONS.CONTINUE,
|
|
996
|
+
addPhases: [
|
|
997
|
+
{ id: 'extra-phase', requests: [...] } // Branch re-executes with new phases
|
|
998
|
+
]
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
```
|
|
1002
|
+
|
|
589
1003
|
### Branched Workflows
|
|
590
1004
|
|
|
591
1005
|
Execute multiple independent workflow paths in parallel or sequentially:
|
|
@@ -853,6 +1267,49 @@ console.log(sharedBuffer); // Updated with data from workfl
|
|
|
853
1267
|
}
|
|
854
1268
|
```
|
|
855
1269
|
|
|
1270
|
+
**Pre-Phase Execution Hooks**:
|
|
1271
|
+
|
|
1272
|
+
Modify phase configuration before execution:
|
|
1273
|
+
|
|
1274
|
+
```typescript
|
|
1275
|
+
const phases = [
|
|
1276
|
+
{
|
|
1277
|
+
id: 'data-phase',
|
|
1278
|
+
requests: [...],
|
|
1279
|
+
prePhaseExecutionHook: async ({ phase, sharedBuffer, params }) => {
|
|
1280
|
+
// Dynamically modify phase based on shared state
|
|
1281
|
+
if (sharedBuffer.environment === 'production') {
|
|
1282
|
+
phase.commonConfig = { commonAttempts: 5, commonWait: 2000 };
|
|
1283
|
+
}
|
|
1284
|
+
return phase;
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
];
|
|
1288
|
+
```
|
|
1289
|
+
|
|
1290
|
+
**Pre-Branch Execution Hooks**:
|
|
1291
|
+
|
|
1292
|
+
Modify branch configuration before execution:
|
|
1293
|
+
|
|
1294
|
+
```typescript
|
|
1295
|
+
const branches = [
|
|
1296
|
+
{
|
|
1297
|
+
id: 'api-branch',
|
|
1298
|
+
phases: [...],
|
|
1299
|
+
preBranchExecutionHook: async ({ branch, sharedBuffer }) => {
|
|
1300
|
+
// Add authentication header dynamically
|
|
1301
|
+
branch.commonConfig = {
|
|
1302
|
+
...branch.commonConfig,
|
|
1303
|
+
commonRequestData: {
|
|
1304
|
+
headers: { 'Authorization': `Bearer ${sharedBuffer.token}` }
|
|
1305
|
+
}
|
|
1306
|
+
};
|
|
1307
|
+
return branch;
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
];
|
|
1311
|
+
```
|
|
1312
|
+
|
|
856
1313
|
### State Persistence and Recovery
|
|
857
1314
|
|
|
858
1315
|
Persist workflow state to external storage for recovery, distributed coordination, and long-running workflows.
|
|
@@ -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"}
|