@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 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;AAmBzC,MAAa,kBAAkB;IAA/B;QACmB,iBAAY,GAAsB,EAAE,CAAC;IA0FxD,CAAC;IA9EQ,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;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,CACzB,CAAC;IACJ,CAAC;CACF;AA3FD,gDA2FC"}
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
- if (!this.circuitBreaker) {
35
- return executePipeline();
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
- return this.circuitBreaker.execute(executePipeline);
38
- }
39
- async dispatch(request) {
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 = new URL(processedRequest.endpoint, processedRequest.baseUrl);
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
- const clientResponse = await this.httpClient.request({
61
- url: requestContext.url,
62
- method: processedRequest.method,
63
- data: processedRequest.body,
64
- headers: processedRequest.headers,
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
- let propagatedError = error_converter_1.ErrorConverter.toAdapterException(error, requestContext);
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":"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"}
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
- constructor(message?: string);
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;AAKhE,MAAa,2BAA4B,SAAQ,6CAAoB;IACnE,YAAY,UAAkB,4CAA4C;QACxE,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,6BAA6B,CAAC;QAC1C,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,2BAA2B,CAAC,SAAS,CAAC,CAAC;IACrE,CAAC;CACF;AAND,kEAMC"}
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,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=circuit-breaker-observer.js.map
@@ -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,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=http-adapter-observer.js.map
@@ -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;