@yildizpay/http-adapter 3.6.1 → 3.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 +98 -1
- package/README.tr.md +98 -1
- package/dist/builders/http-adapter.builder.d.ts +3 -0
- package/dist/builders/http-adapter.builder.js +5 -1
- package/dist/builders/http-adapter.builder.js.map +1 -1
- package/dist/core/http.adapter.d.ts +14 -2
- package/dist/core/http.adapter.js +88 -54
- package/dist/core/http.adapter.js.map +1 -1
- package/dist/exceptions/circuit-breaker-open.exception.d.ts +3 -1
- package/dist/exceptions/circuit-breaker-open.exception.js +5 -1
- package/dist/exceptions/circuit-breaker-open.exception.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/observability/circuit-breaker-observer.d.ts +7 -0
- package/dist/observability/circuit-breaker-observer.js +3 -0
- package/dist/observability/circuit-breaker-observer.js.map +1 -0
- package/dist/observability/http-adapter-observer.d.ts +9 -0
- package/dist/observability/http-adapter-observer.js +3 -0
- package/dist/observability/http-adapter-observer.js.map +1 -0
- package/dist/resilience/circuit-breaker/circuit-breaker.d.ts +4 -0
- package/dist/resilience/circuit-breaker/circuit-breaker.js +28 -19
- package/dist/resilience/circuit-breaker/circuit-breaker.js.map +1 -1
- package/dist/resilience/retry-executor.d.ts +3 -1
- package/dist/resilience/retry-executor.js +6 -1
- package/dist/resilience/retry-executor.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -381,13 +381,110 @@ const policy = RetryPolicies.fullJitter(3).retryIf(new BusinessRetryPredicate())
|
|
|
381
381
|
To protect your system from waiting for a completely down downstream service, you can employ the `CircuitBreaker`. It opens the circuit after a configured amount of consecutive failures and replies instantaneously with `CircuitBreakerOpenException` without hitting the unresponsive server.
|
|
382
382
|
|
|
383
383
|
```typescript
|
|
384
|
-
import { CircuitBreaker } from '@yildizpay/http-adapter';
|
|
384
|
+
import { CircuitBreaker, CircuitBreakerOpenException } from '@yildizpay/http-adapter';
|
|
385
385
|
|
|
386
386
|
const breaker = new CircuitBreaker({
|
|
387
387
|
failureThreshold: 5, // Trip after 5 failures
|
|
388
388
|
resetTimeoutMs: 30000, // Try a 'half-open' request after 30 seconds
|
|
389
389
|
successThreshold: 1, // Close circuit after 1 successful half-open request
|
|
390
390
|
});
|
|
391
|
+
|
|
392
|
+
// CircuitBreakerOpenException carries when the circuit will allow the next probe
|
|
393
|
+
try {
|
|
394
|
+
await adapter.send(request);
|
|
395
|
+
} catch (err) {
|
|
396
|
+
if (err instanceof CircuitBreakerOpenException) {
|
|
397
|
+
console.warn(`Circuit is open. Retry after ${err.retryAfterMs()}ms`);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
#### State machine
|
|
403
|
+
|
|
404
|
+
```
|
|
405
|
+
[CLOSED] ──(failureThreshold reached)──▶ [OPEN]
|
|
406
|
+
▲ │
|
|
407
|
+
│ (resetTimeoutMs)
|
|
408
|
+
│ │
|
|
409
|
+
└──(successThreshold met)──── [HALF_OPEN] ──(failure)──▶ [OPEN]
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
#### Why only one probe in HALF_OPEN?
|
|
413
|
+
|
|
414
|
+
Node.js runs on a single-threaded event loop, but `async/await` introduces cooperative multitasking: while one coroutine is suspended at an `await`, the event loop is free to start other coroutines. Without a guard, every request arriving during `HALF_OPEN` would read the same state and proceed concurrently — potentially overwhelming a service that has only just started to recover.
|
|
415
|
+
|
|
416
|
+
To prevent this, the circuit breaker uses a **probe flag**: only the first caller gets the probe slot; all subsequent concurrent callers receive `CircuitBreakerOpenException` until the probe resolves. This is a deliberate trade-off — a few requests are rejected in exchange for a controlled, safe recovery test.
|
|
417
|
+
|
|
418
|
+
## Observability
|
|
419
|
+
|
|
420
|
+
The adapter ships with a two-tier observability system. **Observers are read-only** — they cannot modify requests or responses. Use them for metrics, structured logging, and distributed tracing. Use interceptors when you need to mutate the pipeline.
|
|
421
|
+
|
|
422
|
+
### `HttpAdapterObserver`
|
|
423
|
+
|
|
424
|
+
Attach a single observer to the adapter via `.withObserver()` on the builder.
|
|
425
|
+
|
|
426
|
+
| Hook | When it fires |
|
|
427
|
+
|---|---|
|
|
428
|
+
| `onRequestStart(request)` | After all request interceptors, immediately before the HTTP call |
|
|
429
|
+
| `onRequestSuccess(response, durationMs)` | After a successful response (includes retry time if retries occurred) |
|
|
430
|
+
| `onRequestFailure(error, durationMs)` | When the final error is propagated to the caller |
|
|
431
|
+
| `onRetry(attempt, error, delayMs)` | Each time a retry is scheduled, before the backoff delay |
|
|
432
|
+
|
|
433
|
+
```typescript
|
|
434
|
+
import { HttpAdapterObserver, HttpAdapter, RetryPolicies } from '@yildizpay/http-adapter';
|
|
435
|
+
|
|
436
|
+
class MetricsObserver implements HttpAdapterObserver {
|
|
437
|
+
onRequestSuccess(_response: Response, durationMs: number): void {
|
|
438
|
+
metrics.histogram('http.request.duration', durationMs);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
onRequestFailure(error: BaseAdapterException, durationMs: number): void {
|
|
442
|
+
metrics.increment('http.request.error', { type: error.name });
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
onRetry(attempt: number, _error: BaseAdapterException, delayMs: number): void {
|
|
446
|
+
logger.warn(`Retry attempt ${attempt} in ${delayMs}ms`);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const adapter = HttpAdapter.builder()
|
|
451
|
+
.withRetryPolicy(RetryPolicies.exponential(3))
|
|
452
|
+
.withObserver(new MetricsObserver())
|
|
453
|
+
.build();
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
### `CircuitBreakerObserver`
|
|
457
|
+
|
|
458
|
+
Attach an observer to a `CircuitBreaker` instance via the fluent `.observe()` method.
|
|
459
|
+
|
|
460
|
+
| Hook | When it fires |
|
|
461
|
+
|---|---|
|
|
462
|
+
| `onStateChange(from, to)` | On every state transition (CLOSED↔OPEN↔HALF_OPEN) |
|
|
463
|
+
| `onSuccess()` | After every successful execution |
|
|
464
|
+
| `onFailure(error)` | When a failure is counted (i.e. `isFailure` predicate returned `true`) |
|
|
465
|
+
| `onProbeRejected()` | When a concurrent caller is turned away in HALF_OPEN |
|
|
466
|
+
|
|
467
|
+
```typescript
|
|
468
|
+
import { CircuitBreaker, CircuitBreakerObserver, CircuitState } from '@yildizpay/http-adapter';
|
|
469
|
+
|
|
470
|
+
class CircuitMetricsObserver implements CircuitBreakerObserver {
|
|
471
|
+
onStateChange(from: CircuitState, to: CircuitState): void {
|
|
472
|
+
logger.warn(`Circuit breaker: ${from} → ${to}`);
|
|
473
|
+
metrics.increment('circuit_breaker.state_change', { from, to });
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
onProbeRejected(): void {
|
|
477
|
+
metrics.increment('circuit_breaker.probe_rejected');
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
const adapter = HttpAdapter.builder()
|
|
482
|
+
.withCircuitBreaker(
|
|
483
|
+
new CircuitBreaker({ failureThreshold: 5 })
|
|
484
|
+
.observe(new CircuitMetricsObserver()),
|
|
485
|
+
)
|
|
486
|
+
.withObserver(new MetricsObserver())
|
|
487
|
+
.build();
|
|
391
488
|
```
|
|
392
489
|
|
|
393
490
|
## Interceptors
|
package/README.tr.md
CHANGED
|
@@ -381,13 +381,110 @@ const policy = RetryPolicies.fullJitter(3).retryIf(new BusinessRetryPredicate())
|
|
|
381
381
|
Tamamen çökmüş bir downstream servisi beklemeye karşı sisteminizi korumak için `CircuitBreaker` kullanabilirsiniz. Belirli sayıda ardışık hata alındığında circuit açılır ve yanıt vermeyen servise gereksiz istek göndermeksizin anında `CircuitBreakerOpenException` fırlatır.
|
|
382
382
|
|
|
383
383
|
```typescript
|
|
384
|
-
import { CircuitBreaker } from '@yildizpay/http-adapter';
|
|
384
|
+
import { CircuitBreaker, CircuitBreakerOpenException } from '@yildizpay/http-adapter';
|
|
385
385
|
|
|
386
386
|
const breaker = new CircuitBreaker({
|
|
387
387
|
failureThreshold: 5, // 5 hatadan sonra circuit'i aç
|
|
388
388
|
resetTimeoutMs: 30000, // 30 saniye sonra half-open test isteği gönder
|
|
389
389
|
successThreshold: 1, // 1 başarılı half-open request sonrası circuit'i kapat
|
|
390
390
|
});
|
|
391
|
+
|
|
392
|
+
// CircuitBreakerOpenException bir sonraki deneme zamanını taşır
|
|
393
|
+
try {
|
|
394
|
+
await adapter.send(request);
|
|
395
|
+
} catch (err) {
|
|
396
|
+
if (err instanceof CircuitBreakerOpenException) {
|
|
397
|
+
console.warn(`Circuit açık. ${err.retryAfterMs()}ms sonra tekrar dene`);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
#### Durum makinesi
|
|
403
|
+
|
|
404
|
+
```
|
|
405
|
+
[CLOSED] ──(failureThreshold aşıldı)──▶ [OPEN]
|
|
406
|
+
▲ │
|
|
407
|
+
│ (resetTimeoutMs)
|
|
408
|
+
│ │
|
|
409
|
+
└──(successThreshold karşılandı)── [HALF_OPEN] ──(hata)──▶ [OPEN]
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
#### HALF_OPEN'da neden sadece bir probe isteği geçer?
|
|
413
|
+
|
|
414
|
+
Node.js, tek iş parçacıklı (single-threaded) bir event loop üzerinde çalışır; ancak `async/await` yapısı **kooperatif çoklu görev** (cooperative multitasking) modelini hayata geçirir: bir coroutine `await` noktasında askıya alındığında, event loop diğer coroutine'leri çalıştırmaya devam edebilir. Bu davranış, `HALF_OPEN` durumunda kritik bir risk yaratır: herhangi bir guard mekanizması olmaksızın, o anda gelen tüm istekler aynı `HALF_OPEN` durumunu okuyup eş zamanlı olarak ilerleyebilir — yeni toparlanmaya başlayan bir servisi bir anda çok sayıda istekle boğabilir.
|
|
415
|
+
|
|
416
|
+
Bunu önlemek için circuit breaker bir **probe flag** kullanır: yalnızca ilk çağıran probe slotunu alır; probe tamamlanana kadar diğer tüm eş zamanlı çağrılar `CircuitBreakerOpenException` ile reddedilir. Bu bilinçli bir tasarım kararıdır — birkaç isteği feda ederek servisin gerçekten sağlıklı olup olmadığı güvenli biçimde doğrulanır.
|
|
417
|
+
|
|
418
|
+
## Observability
|
|
419
|
+
|
|
420
|
+
Adaptör, iki katmanlı bir observability sistemiyle birlikte gelir. **Observer'lar salt okunurdur** — request veya response'u değiştiremezler. Metrics, yapılandırılmış loglama ve dağıtık izleme için observer'ları, pipeline'ı değiştirmeniz gerektiğinde ise interceptor'ları kullanın.
|
|
421
|
+
|
|
422
|
+
### `HttpAdapterObserver`
|
|
423
|
+
|
|
424
|
+
Builder üzerindeki `.withObserver()` metoduyla adaptöre tek bir observer bağlanır.
|
|
425
|
+
|
|
426
|
+
| Hook | Ne zaman tetiklenir |
|
|
427
|
+
|---|---|
|
|
428
|
+
| `onRequestStart(request)` | Tüm request interceptor'larından sonra, HTTP çağrısından hemen önce |
|
|
429
|
+
| `onRequestSuccess(response, durationMs)` | Başarılı yanıt sonrasında (retry süresi dahil) |
|
|
430
|
+
| `onRequestFailure(error, durationMs)` | Son hata çağırana iletildiğinde |
|
|
431
|
+
| `onRetry(attempt, error, delayMs)` | Her retry planlandığında, backoff gecikmesinden önce |
|
|
432
|
+
|
|
433
|
+
```typescript
|
|
434
|
+
import { HttpAdapterObserver, HttpAdapter, RetryPolicies } from '@yildizpay/http-adapter';
|
|
435
|
+
|
|
436
|
+
class MetricsObserver implements HttpAdapterObserver {
|
|
437
|
+
onRequestSuccess(_response: Response, durationMs: number): void {
|
|
438
|
+
metrics.histogram('http.request.duration', durationMs);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
onRequestFailure(error: BaseAdapterException, durationMs: number): void {
|
|
442
|
+
metrics.increment('http.request.error', { type: error.name });
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
onRetry(attempt: number, _error: BaseAdapterException, delayMs: number): void {
|
|
446
|
+
logger.warn(`Retry denemesi ${attempt}, ${delayMs}ms sonra`);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const adapter = HttpAdapter.builder()
|
|
451
|
+
.withRetryPolicy(RetryPolicies.exponential(3))
|
|
452
|
+
.withObserver(new MetricsObserver())
|
|
453
|
+
.build();
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
### `CircuitBreakerObserver`
|
|
457
|
+
|
|
458
|
+
Fluent `.observe()` metodu aracılığıyla bir `CircuitBreaker` instance'ına observer bağlanır.
|
|
459
|
+
|
|
460
|
+
| Hook | Ne zaman tetiklenir |
|
|
461
|
+
|---|---|
|
|
462
|
+
| `onStateChange(from, to)` | Her state geçişinde (CLOSED↔OPEN↔HALF_OPEN) |
|
|
463
|
+
| `onSuccess()` | Her başarılı çalıştırma sonrasında |
|
|
464
|
+
| `onFailure(error)` | Bir hata sayıldığında (`isFailure` predicate `true` döndürdüğünde) |
|
|
465
|
+
| `onProbeRejected()` | HALF_OPEN'da eş zamanlı bir çağıran reddedildiğinde |
|
|
466
|
+
|
|
467
|
+
```typescript
|
|
468
|
+
import { CircuitBreaker, CircuitBreakerObserver, CircuitState } from '@yildizpay/http-adapter';
|
|
469
|
+
|
|
470
|
+
class CircuitMetricsObserver implements CircuitBreakerObserver {
|
|
471
|
+
onStateChange(from: CircuitState, to: CircuitState): void {
|
|
472
|
+
logger.warn(`Circuit breaker: ${from} → ${to}`);
|
|
473
|
+
metrics.increment('circuit_breaker.state_change', { from, to });
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
onProbeRejected(): void {
|
|
477
|
+
metrics.increment('circuit_breaker.probe_rejected');
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
const adapter = HttpAdapter.builder()
|
|
482
|
+
.withCircuitBreaker(
|
|
483
|
+
new CircuitBreaker({ failureThreshold: 5 })
|
|
484
|
+
.observe(new CircuitMetricsObserver()),
|
|
485
|
+
)
|
|
486
|
+
.withObserver(new MetricsObserver())
|
|
487
|
+
.build();
|
|
391
488
|
```
|
|
392
489
|
|
|
393
490
|
## Interceptors
|
|
@@ -4,16 +4,19 @@ import { RetryPolicy } from '../contracts/retry-policy.contract';
|
|
|
4
4
|
import { HttpClientContract } from '../contracts/http-client.contract';
|
|
5
5
|
import { CircuitBreaker } from '../resilience/circuit-breaker/circuit-breaker';
|
|
6
6
|
import { CircuitBreakerOptions } from '../resilience/circuit-breaker/circuit-breaker-options';
|
|
7
|
+
import { HttpAdapterObserver } from '../observability/http-adapter-observer';
|
|
7
8
|
export declare class HttpAdapterBuilder {
|
|
8
9
|
private readonly interceptors;
|
|
9
10
|
private retryPolicy;
|
|
10
11
|
private httpClient;
|
|
11
12
|
private circuitBreaker;
|
|
12
13
|
private correlationIdConfig;
|
|
14
|
+
private observer;
|
|
13
15
|
withInterceptor(...interceptors: HttpInterceptor[]): this;
|
|
14
16
|
withRetryPolicy(policy: RetryPolicy): this;
|
|
15
17
|
withCircuitBreaker(breaker: CircuitBreaker | CircuitBreakerOptions): this;
|
|
16
18
|
withHttpClient(client: HttpClientContract): this;
|
|
17
19
|
withCorrelationId(header?: string): this;
|
|
20
|
+
withObserver(observer: HttpAdapterObserver): this;
|
|
18
21
|
build(): HttpAdapter;
|
|
19
22
|
}
|
|
@@ -28,8 +28,12 @@ class HttpAdapterBuilder {
|
|
|
28
28
|
this.correlationIdConfig = { enabled: true, header: header ?? correlation_id_config_1.DEFAULT_CORRELATION_ID_HEADER };
|
|
29
29
|
return this;
|
|
30
30
|
}
|
|
31
|
+
withObserver(observer) {
|
|
32
|
+
this.observer = observer;
|
|
33
|
+
return this;
|
|
34
|
+
}
|
|
31
35
|
build() {
|
|
32
|
-
return http_adapter_1.HttpAdapter.create(this.interceptors, this.retryPolicy, this.httpClient, this.circuitBreaker, this.correlationIdConfig);
|
|
36
|
+
return http_adapter_1.HttpAdapter.create(this.interceptors, this.retryPolicy, this.httpClient, this.circuitBreaker, this.correlationIdConfig, this.observer);
|
|
33
37
|
}
|
|
34
38
|
}
|
|
35
39
|
exports.HttpAdapterBuilder = HttpAdapterBuilder;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http-adapter.builder.js","sourceRoot":"","sources":["../../src/builders/http-adapter.builder.ts"],"names":[],"mappings":";;;AAAA,uDAAmD;AAInD,mFAA+E;AAE/E,2EAGyC;
|
|
1
|
+
{"version":3,"file":"http-adapter.builder.js","sourceRoot":"","sources":["../../src/builders/http-adapter.builder.ts"],"names":[],"mappings":";;;AAAA,uDAAmD;AAInD,mFAA+E;AAE/E,2EAGyC;AAoBzC,MAAa,kBAAkB;IAA/B;QACmB,iBAAY,GAAsB,EAAE,CAAC;IA+GxD,CAAC;IAlGQ,eAAe,CAAC,GAAG,YAA+B;QACvD,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,GAAG,YAAY,CAAC,CAAC;QACxC,OAAO,IAAI,CAAC;IACd,CAAC;IAOM,eAAe,CAAC,MAAmB;QACxC,IAAI,CAAC,WAAW,GAAG,MAAM,CAAC;QAC1B,OAAO,IAAI,CAAC;IACd,CAAC;IAWM,kBAAkB,CAAC,OAA+C;QACvE,IAAI,CAAC,cAAc,GAAG,OAAO,YAAY,gCAAc,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,gCAAc,CAAC,OAAO,CAAC,CAAC;QAChG,OAAO,IAAI,CAAC;IACd,CAAC;IASM,cAAc,CAAC,MAA0B;QAC9C,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC;QACzB,OAAO,IAAI,CAAC;IACd,CAAC;IAoBM,iBAAiB,CAAC,MAAe;QACtC,IAAI,CAAC,mBAAmB,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,IAAI,qDAA6B,EAAE,CAAC;QAC9F,OAAO,IAAI,CAAC;IACd,CAAC;IAgBM,YAAY,CAAC,QAA6B;QAC/C,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,OAAO,IAAI,CAAC;IACd,CAAC;IAOM,KAAK;QACV,OAAO,0BAAW,CAAC,MAAM,CACvB,IAAI,CAAC,YAAY,EACjB,IAAI,CAAC,WAAW,EAChB,IAAI,CAAC,UAAU,EACf,IAAI,CAAC,cAAc,EACnB,IAAI,CAAC,mBAAmB,EACxB,IAAI,CAAC,QAAQ,CACd,CAAC;IACJ,CAAC;CACF;AAhHD,gDAgHC"}
|
|
@@ -6,16 +6,28 @@ import { CircuitBreaker } from '../resilience/circuit-breaker/circuit-breaker';
|
|
|
6
6
|
import { HttpClientContract } from '../contracts/http-client.contract';
|
|
7
7
|
import { HttpAdapterBuilder } from '../builders/http-adapter.builder';
|
|
8
8
|
import { CorrelationIdConfig } from '../models/correlation-id-config';
|
|
9
|
+
import { HttpAdapterObserver } from '../observability/http-adapter-observer';
|
|
9
10
|
export declare class HttpAdapter {
|
|
10
|
-
private readonly interceptors;
|
|
11
11
|
private readonly httpClient;
|
|
12
12
|
private readonly retryPolicy?;
|
|
13
13
|
private readonly circuitBreaker?;
|
|
14
14
|
private readonly correlationIdConfig?;
|
|
15
|
+
private readonly observer?;
|
|
16
|
+
private readonly requestInterceptors;
|
|
17
|
+
private readonly responseInterceptors;
|
|
18
|
+
private readonly validatedResponseInterceptors;
|
|
19
|
+
private readonly errorInterceptors;
|
|
15
20
|
private constructor();
|
|
16
|
-
static create(interceptors: HttpInterceptor[], retryPolicy?: RetryPolicy, httpClient?: HttpClientContract, circuitBreaker?: CircuitBreaker, correlationIdConfig?: CorrelationIdConfig): HttpAdapter;
|
|
21
|
+
static create(interceptors: HttpInterceptor[], retryPolicy?: RetryPolicy, httpClient?: HttpClientContract, circuitBreaker?: CircuitBreaker, correlationIdConfig?: CorrelationIdConfig, observer?: HttpAdapterObserver): HttpAdapter;
|
|
17
22
|
static builder(): HttpAdapterBuilder;
|
|
18
23
|
send<T = unknown>(request: Request): Promise<Response<T>>;
|
|
19
24
|
private dispatch;
|
|
25
|
+
private runRequestInterceptors;
|
|
26
|
+
private buildRequestUrl;
|
|
27
|
+
private executeHttpCall;
|
|
28
|
+
private runResponseInterceptors;
|
|
29
|
+
private runValidators;
|
|
30
|
+
private runPostValidationInterceptors;
|
|
31
|
+
private runErrorInterceptors;
|
|
20
32
|
private applyCorrelationIdHeader;
|
|
21
33
|
}
|
|
@@ -10,46 +10,51 @@ const validation_exception_1 = require("../exceptions/validation.exception");
|
|
|
10
10
|
const http_adapter_builder_1 = require("../builders/http-adapter.builder");
|
|
11
11
|
const correlation_id_config_1 = require("../models/correlation-id-config");
|
|
12
12
|
class HttpAdapter {
|
|
13
|
-
constructor(interceptors, httpClient, retryPolicy, circuitBreaker, correlationIdConfig) {
|
|
14
|
-
this.interceptors = interceptors;
|
|
13
|
+
constructor(interceptors, httpClient, retryPolicy, circuitBreaker, correlationIdConfig, observer) {
|
|
15
14
|
this.httpClient = httpClient;
|
|
16
15
|
this.retryPolicy = retryPolicy;
|
|
17
16
|
this.circuitBreaker = circuitBreaker;
|
|
18
17
|
this.correlationIdConfig = correlationIdConfig;
|
|
18
|
+
this.observer = observer;
|
|
19
|
+
this.requestInterceptors = interceptors.filter((i) => typeof i.onRequest === 'function');
|
|
20
|
+
this.responseInterceptors = interceptors.filter((i) => typeof i.onResponse === 'function');
|
|
21
|
+
this.validatedResponseInterceptors = interceptors.filter((i) => typeof i.onResponseValidated === 'function');
|
|
22
|
+
this.errorInterceptors = interceptors.filter((i) => typeof i.onError === 'function');
|
|
19
23
|
}
|
|
20
|
-
static create(interceptors, retryPolicy, httpClient, circuitBreaker, correlationIdConfig) {
|
|
21
|
-
return new HttpAdapter(interceptors, httpClient ?? default_http_client_1.defaultHttpClient, retryPolicy, circuitBreaker, correlationIdConfig);
|
|
24
|
+
static create(interceptors, retryPolicy, httpClient, circuitBreaker, correlationIdConfig, observer) {
|
|
25
|
+
return new HttpAdapter(interceptors, httpClient ?? default_http_client_1.defaultHttpClient, retryPolicy, circuitBreaker, correlationIdConfig, observer);
|
|
22
26
|
}
|
|
23
27
|
static builder() {
|
|
24
28
|
return new http_adapter_builder_1.HttpAdapterBuilder();
|
|
25
29
|
}
|
|
26
30
|
async send(request) {
|
|
31
|
+
const startTime = Date.now();
|
|
27
32
|
const executePipeline = () => {
|
|
28
33
|
if (!this.retryPolicy) {
|
|
29
34
|
return this.dispatch(request);
|
|
30
35
|
}
|
|
31
|
-
const executor = new retry_executor_1.RetryExecutor(this.retryPolicy);
|
|
36
|
+
const executor = new retry_executor_1.RetryExecutor(this.retryPolicy, this.observer);
|
|
32
37
|
return executor.execute(() => this.dispatch(request));
|
|
33
38
|
};
|
|
34
|
-
|
|
35
|
-
|
|
39
|
+
try {
|
|
40
|
+
const response = await (this.circuitBreaker
|
|
41
|
+
? this.circuitBreaker.execute(executePipeline)
|
|
42
|
+
: executePipeline());
|
|
43
|
+
this.observer?.onRequestSuccess?.(response, Date.now() - startTime);
|
|
44
|
+
return response;
|
|
36
45
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
let processedRequest = request;
|
|
41
|
-
for (const interceptor of this.interceptors) {
|
|
42
|
-
if (interceptor.onRequest) {
|
|
43
|
-
processedRequest = await interceptor.onRequest(processedRequest);
|
|
46
|
+
catch (error) {
|
|
47
|
+
if (error instanceof base_adapter_exception_1.BaseAdapterException) {
|
|
48
|
+
this.observer?.onRequestFailure?.(error, Date.now() - startTime);
|
|
44
49
|
}
|
|
50
|
+
throw error;
|
|
45
51
|
}
|
|
52
|
+
}
|
|
53
|
+
async dispatch(request) {
|
|
54
|
+
const processedRequest = await this.runRequestInterceptors(request);
|
|
46
55
|
let requestContext;
|
|
47
56
|
try {
|
|
48
|
-
const url =
|
|
49
|
-
const searchParams = new URLSearchParams(processedRequest.queryParams);
|
|
50
|
-
if (searchParams.toString()) {
|
|
51
|
-
url.search = searchParams.toString();
|
|
52
|
-
}
|
|
57
|
+
const url = this.buildRequestUrl(processedRequest);
|
|
53
58
|
requestContext = {
|
|
54
59
|
method: processedRequest.method,
|
|
55
60
|
url: url.toString(),
|
|
@@ -57,46 +62,75 @@ class HttpAdapter {
|
|
|
57
62
|
};
|
|
58
63
|
processedRequest.setTimestamp(new Date());
|
|
59
64
|
this.applyCorrelationIdHeader(processedRequest);
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
timeout: processedRequest.options?.timeout,
|
|
66
|
-
});
|
|
67
|
-
let response = response_1.Response.create(clientResponse.data, clientResponse.status, clientResponse.headers ?? null, requestContext);
|
|
68
|
-
for (const interceptor of this.interceptors) {
|
|
69
|
-
if (interceptor.onResponse) {
|
|
70
|
-
response = (await interceptor.onResponse(response));
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
for (const validator of request.validators) {
|
|
74
|
-
try {
|
|
75
|
-
await validator.validate(response);
|
|
76
|
-
}
|
|
77
|
-
catch (err) {
|
|
78
|
-
if (err instanceof base_adapter_exception_1.BaseAdapterException)
|
|
79
|
-
throw err;
|
|
80
|
-
throw new validation_exception_1.ValidationException('Response validation failed', response, err);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
for (const interceptor of this.interceptors) {
|
|
84
|
-
if (interceptor.onResponseValidated) {
|
|
85
|
-
response = (await interceptor.onResponseValidated(response));
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
return response;
|
|
65
|
+
this.observer?.onRequestStart?.(processedRequest);
|
|
66
|
+
const response = await this.executeHttpCall(processedRequest, url, requestContext);
|
|
67
|
+
const interceptedResponse = await this.runResponseInterceptors(response);
|
|
68
|
+
await this.runValidators(interceptedResponse, request);
|
|
69
|
+
return await this.runPostValidationInterceptors(interceptedResponse);
|
|
89
70
|
}
|
|
90
71
|
catch (error) {
|
|
91
|
-
|
|
92
|
-
for (const interceptor of this.interceptors) {
|
|
93
|
-
if (interceptor.onError) {
|
|
94
|
-
propagatedError = await interceptor.onError(propagatedError, processedRequest);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
72
|
+
const propagatedError = await this.runErrorInterceptors(error_converter_1.ErrorConverter.toAdapterException(error, requestContext), processedRequest);
|
|
97
73
|
throw propagatedError;
|
|
98
74
|
}
|
|
99
75
|
}
|
|
76
|
+
async runRequestInterceptors(request) {
|
|
77
|
+
let processedRequest = request;
|
|
78
|
+
for (const interceptor of this.requestInterceptors) {
|
|
79
|
+
processedRequest = await interceptor.onRequest(processedRequest);
|
|
80
|
+
}
|
|
81
|
+
return processedRequest;
|
|
82
|
+
}
|
|
83
|
+
buildRequestUrl(request) {
|
|
84
|
+
const url = new URL(request.endpoint, request.baseUrl);
|
|
85
|
+
const searchParams = new URLSearchParams(request.queryParams);
|
|
86
|
+
if (searchParams.toString()) {
|
|
87
|
+
url.search = searchParams.toString();
|
|
88
|
+
}
|
|
89
|
+
return url;
|
|
90
|
+
}
|
|
91
|
+
async executeHttpCall(request, url, requestContext) {
|
|
92
|
+
const clientResponse = await this.httpClient.request({
|
|
93
|
+
url: url.toString(),
|
|
94
|
+
method: request.method,
|
|
95
|
+
data: request.body,
|
|
96
|
+
headers: request.headers,
|
|
97
|
+
timeout: request.options?.timeout,
|
|
98
|
+
});
|
|
99
|
+
return response_1.Response.create(clientResponse.data, clientResponse.status, clientResponse.headers ?? null, requestContext);
|
|
100
|
+
}
|
|
101
|
+
async runResponseInterceptors(response) {
|
|
102
|
+
let result = response;
|
|
103
|
+
for (const interceptor of this.responseInterceptors) {
|
|
104
|
+
result = (await interceptor.onResponse(result));
|
|
105
|
+
}
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
async runValidators(response, request) {
|
|
109
|
+
for (const validator of request.validators) {
|
|
110
|
+
try {
|
|
111
|
+
await validator.validate(response);
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
if (err instanceof base_adapter_exception_1.BaseAdapterException)
|
|
115
|
+
throw err;
|
|
116
|
+
throw new validation_exception_1.ValidationException('Response validation failed', response, err);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
async runPostValidationInterceptors(response) {
|
|
121
|
+
let result = response;
|
|
122
|
+
for (const interceptor of this.validatedResponseInterceptors) {
|
|
123
|
+
result = (await interceptor.onResponseValidated(result));
|
|
124
|
+
}
|
|
125
|
+
return result;
|
|
126
|
+
}
|
|
127
|
+
async runErrorInterceptors(error, request) {
|
|
128
|
+
let propagatedError = error;
|
|
129
|
+
for (const interceptor of this.errorInterceptors) {
|
|
130
|
+
propagatedError = await interceptor.onError(propagatedError, request);
|
|
131
|
+
}
|
|
132
|
+
return propagatedError;
|
|
133
|
+
}
|
|
100
134
|
applyCorrelationIdHeader(request) {
|
|
101
135
|
const effectiveConfig = request.correlationIdConfig ?? this.correlationIdConfig;
|
|
102
136
|
if (!effectiveConfig?.enabled)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.adapter.js","sourceRoot":"","sources":["../../src/core/http.adapter.ts"],"names":[],"mappings":";;;AAAA,+DAA0D;AAE1D,iDAA8C;
|
|
1
|
+
{"version":3,"file":"http.adapter.js","sourceRoot":"","sources":["../../src/core/http.adapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { BaseAdapterException } from './base-adapter.exception';
|
|
2
2
|
export declare class CircuitBreakerOpenException extends BaseAdapterException {
|
|
3
|
-
|
|
3
|
+
readonly nextAttemptAt: number;
|
|
4
|
+
constructor(nextAttemptAt?: number, message?: string);
|
|
5
|
+
retryAfterMs(): number;
|
|
4
6
|
}
|
|
@@ -3,11 +3,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.CircuitBreakerOpenException = void 0;
|
|
4
4
|
const base_adapter_exception_1 = require("./base-adapter.exception");
|
|
5
5
|
class CircuitBreakerOpenException extends base_adapter_exception_1.BaseAdapterException {
|
|
6
|
-
constructor(message = 'Circuit Breaker is OPEN. Execution denied.') {
|
|
6
|
+
constructor(nextAttemptAt = 0, message = 'Circuit Breaker is OPEN. Execution denied.') {
|
|
7
7
|
super(message);
|
|
8
8
|
this.name = 'CircuitBreakerOpenException';
|
|
9
|
+
this.nextAttemptAt = nextAttemptAt;
|
|
9
10
|
Object.setPrototypeOf(this, CircuitBreakerOpenException.prototype);
|
|
10
11
|
}
|
|
12
|
+
retryAfterMs() {
|
|
13
|
+
return Math.max(0, this.nextAttemptAt - Date.now());
|
|
14
|
+
}
|
|
11
15
|
}
|
|
12
16
|
exports.CircuitBreakerOpenException = CircuitBreakerOpenException;
|
|
13
17
|
//# sourceMappingURL=circuit-breaker-open.exception.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"circuit-breaker-open.exception.js","sourceRoot":"","sources":["../../src/exceptions/circuit-breaker-open.exception.ts"],"names":[],"mappings":";;;AAAA,qEAAgE;
|
|
1
|
+
{"version":3,"file":"circuit-breaker-open.exception.js","sourceRoot":"","sources":["../../src/exceptions/circuit-breaker-open.exception.ts"],"names":[],"mappings":";;;AAAA,qEAAgE;AAahE,MAAa,2BAA4B,SAAQ,6CAAoB;IAOnE,YACE,gBAAwB,CAAC,EACzB,UAAkB,4CAA4C;QAE9D,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,6BAA6B,CAAC;QAC1C,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;QACnC,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,2BAA2B,CAAC,SAAS,CAAC,CAAC;IACrE,CAAC;IAMM,YAAY;QACjB,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IACtD,CAAC;CACF;AAxBD,kEAwBC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -20,6 +20,8 @@ export * from './resilience/policies/decorrelated-jitter.retry-policy';
|
|
|
20
20
|
export * from './resilience/circuit-breaker/circuit-state.enum';
|
|
21
21
|
export * from './resilience/circuit-breaker/circuit-breaker-options';
|
|
22
22
|
export * from './resilience/circuit-breaker/circuit-breaker';
|
|
23
|
+
export * from './observability/http-adapter-observer';
|
|
24
|
+
export * from './observability/circuit-breaker-observer';
|
|
23
25
|
export * from './auth/token-provider';
|
|
24
26
|
export * from './auth/bearer-auth.interceptor';
|
|
25
27
|
export * from './auth/basic-auth.interceptor';
|
package/dist/index.js
CHANGED
|
@@ -36,6 +36,8 @@ __exportStar(require("./resilience/policies/decorrelated-jitter.retry-policy"),
|
|
|
36
36
|
__exportStar(require("./resilience/circuit-breaker/circuit-state.enum"), exports);
|
|
37
37
|
__exportStar(require("./resilience/circuit-breaker/circuit-breaker-options"), exports);
|
|
38
38
|
__exportStar(require("./resilience/circuit-breaker/circuit-breaker"), exports);
|
|
39
|
+
__exportStar(require("./observability/http-adapter-observer"), exports);
|
|
40
|
+
__exportStar(require("./observability/circuit-breaker-observer"), exports);
|
|
39
41
|
__exportStar(require("./auth/token-provider"), exports);
|
|
40
42
|
__exportStar(require("./auth/bearer-auth.interceptor"), exports);
|
|
41
43
|
__exportStar(require("./auth/basic-auth.interceptor"), exports);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AACA,sDAAoC;AACpC,6DAA2C;AAG3C,6DAA2C;AAC3C,kEAAgD;AAGhD,mDAAiC;AACjC,oDAAkC;AAClC,2DAAyC;AACzC,2DAAyC;AACzC,iEAA+C;AAG/C,wEAAsD;AACtD,oEAAkD;AAClD,uEAAqD;AACrD,0EAAwD;AAGxD,8DAA4C;AAC5C,yFAAuE;AACvE,iFAA+D;AAC/D,oFAAkE;AAClE,iFAA+D;AAC/D,yFAAuE;AACvE,kFAAgE;AAChE,uFAAqE;AACrE,+EAA6D;AAG7D,wDAAsC;AACtC,iEAA+C;AAC/C,gEAA8C;AAC9C,6DAA2C;AAG3C,kEAAgD;AAChD,4DAA0C;AAG1C,8EAA4D;AAC5D,sEAAoD;AACpD,sEAAoD;AACpD,sEAAoD;AACpD,kEAAgD;AAChD,yEAAuD;AACvD,iEAA+C;AAC/C,oEAAkD;AAClD,gEAA8C;AAC9C,yDAAuC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AACA,sDAAoC;AACpC,6DAA2C;AAG3C,6DAA2C;AAC3C,kEAAgD;AAGhD,mDAAiC;AACjC,oDAAkC;AAClC,2DAAyC;AACzC,2DAAyC;AACzC,iEAA+C;AAG/C,wEAAsD;AACtD,oEAAkD;AAClD,uEAAqD;AACrD,0EAAwD;AAGxD,8DAA4C;AAC5C,yFAAuE;AACvE,iFAA+D;AAC/D,oFAAkE;AAClE,iFAA+D;AAC/D,yFAAuE;AACvE,kFAAgE;AAChE,uFAAqE;AACrE,+EAA6D;AAG7D,wEAAsD;AACtD,2EAAyD;AAGzD,wDAAsC;AACtC,iEAA+C;AAC/C,gEAA8C;AAC9C,6DAA2C;AAG3C,kEAAgD;AAChD,4DAA0C;AAG1C,8EAA4D;AAC5D,sEAAoD;AACpD,sEAAoD;AACpD,sEAAoD;AACpD,kEAAgD;AAChD,yEAAuD;AACvD,iEAA+C;AAC/C,oEAAkD;AAClD,gEAA8C;AAC9C,yDAAuC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { CircuitState } from '../resilience/circuit-breaker/circuit-state.enum';
|
|
2
|
+
export interface CircuitBreakerObserver {
|
|
3
|
+
onStateChange?(from: CircuitState, to: CircuitState): void;
|
|
4
|
+
onSuccess?(): void;
|
|
5
|
+
onFailure?(error: unknown): void;
|
|
6
|
+
onProbeRejected?(): void;
|
|
7
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"circuit-breaker-observer.js","sourceRoot":"","sources":["../../src/observability/circuit-breaker-observer.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Request } from '../models/request';
|
|
2
|
+
import { Response } from '../models/response';
|
|
3
|
+
import { BaseAdapterException } from '../exceptions/base-adapter.exception';
|
|
4
|
+
export interface HttpAdapterObserver {
|
|
5
|
+
onRequestStart?(request: Request): void;
|
|
6
|
+
onRequestSuccess?(response: Response, durationMs: number): void;
|
|
7
|
+
onRequestFailure?(error: BaseAdapterException, durationMs: number): void;
|
|
8
|
+
onRetry?(attempt: number, error: BaseAdapterException, delayMs: number): void;
|
|
9
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-adapter-observer.js","sourceRoot":"","sources":["../../src/observability/http-adapter-observer.ts"],"names":[],"mappings":""}
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import { CircuitState } from './circuit-state.enum';
|
|
2
2
|
import { CircuitBreakerOptions } from './circuit-breaker-options';
|
|
3
|
+
import { CircuitBreakerObserver } from '../../observability/circuit-breaker-observer';
|
|
3
4
|
export declare class CircuitBreaker {
|
|
4
5
|
private state;
|
|
5
6
|
private failureCount;
|
|
6
7
|
private successCount;
|
|
7
8
|
private nextAttemptAt;
|
|
9
|
+
private halfOpenProbeInFlight;
|
|
10
|
+
private observer;
|
|
8
11
|
private readonly failureThreshold;
|
|
9
12
|
private readonly resetTimeoutMs;
|
|
10
13
|
private readonly successThreshold;
|
|
11
14
|
private readonly isFailurePredicate;
|
|
12
15
|
constructor(options?: CircuitBreakerOptions);
|
|
16
|
+
observe(observer: CircuitBreakerObserver): this;
|
|
13
17
|
getState(): CircuitState;
|
|
14
18
|
execute<T>(operation: () => Promise<T>): Promise<T>;
|
|
15
19
|
private recordSuccess;
|