@yildizpay/http-adapter 3.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +186 -9
  2. package/README.tr.md +221 -44
  3. package/dist/contracts/http-client.contract.d.ts +0 -9
  4. package/dist/contracts/http-client.contract.js +0 -17
  5. package/dist/contracts/http-client.contract.js.map +1 -1
  6. package/dist/contracts/http-interceptor.contract.d.ts +2 -1
  7. package/dist/core/default-http-client.js +7 -6
  8. package/dist/core/default-http-client.js.map +1 -1
  9. package/dist/core/error.converter.d.ts +5 -0
  10. package/dist/core/error.converter.js +31 -0
  11. package/dist/core/error.converter.js.map +1 -0
  12. package/dist/core/http.adapter.js +11 -4
  13. package/dist/core/http.adapter.js.map +1 -1
  14. package/dist/exceptions/base-adapter.exception.d.ts +7 -0
  15. package/dist/exceptions/base-adapter.exception.js +34 -0
  16. package/dist/exceptions/base-adapter.exception.js.map +1 -0
  17. package/dist/exceptions/circuit-breaker-open.exception.d.ts +2 -2
  18. package/dist/exceptions/circuit-breaker-open.exception.js +3 -3
  19. package/dist/exceptions/circuit-breaker-open.exception.js.map +1 -1
  20. package/dist/exceptions/exception.guards.d.ts +15 -0
  21. package/dist/exceptions/exception.guards.js +48 -0
  22. package/dist/exceptions/exception.guards.js.map +1 -0
  23. package/dist/exceptions/http-exception.factory.d.ts +5 -0
  24. package/dist/exceptions/http-exception.factory.js +59 -0
  25. package/dist/exceptions/http-exception.factory.js.map +1 -0
  26. package/dist/exceptions/http-status.exceptions.d.ts +134 -0
  27. package/dist/exceptions/http-status.exceptions.js +382 -0
  28. package/dist/exceptions/http-status.exceptions.js.map +1 -0
  29. package/dist/exceptions/network-exception.factory.d.ts +6 -0
  30. package/dist/exceptions/network-exception.factory.js +35 -0
  31. package/dist/exceptions/network-exception.factory.js.map +1 -0
  32. package/dist/exceptions/network.exceptions.d.ts +25 -0
  33. package/dist/exceptions/network.exceptions.js +69 -0
  34. package/dist/exceptions/network.exceptions.js.map +1 -0
  35. package/dist/exceptions/unknown.exception.d.ts +7 -0
  36. package/dist/exceptions/unknown.exception.js +20 -0
  37. package/dist/exceptions/unknown.exception.js.map +1 -0
  38. package/dist/index.d.ts +9 -1
  39. package/dist/index.js +9 -1
  40. package/dist/index.js.map +1 -1
  41. package/dist/models/request-context.d.ts +5 -0
  42. package/dist/models/request-context.js +3 -0
  43. package/dist/models/request-context.js.map +1 -0
  44. package/dist/models/response.d.ts +8 -5
  45. package/dist/models/response.js +9 -5
  46. package/dist/models/response.js.map +1 -1
  47. package/dist/tsconfig.tsbuildinfo +1 -1
  48. package/package.json +1 -1
  49. package/dist/exceptions/http.exception.d.ts +0 -5
  50. package/dist/exceptions/http.exception.js +0 -12
  51. package/dist/exceptions/http.exception.js.map +0 -1
package/README.md CHANGED
@@ -11,6 +11,7 @@ A professional, robust, and highly configurable HTTP client adapter designed for
11
11
  ## Key Features
12
12
 
13
13
  - **Fluent Request Builder:** Construct complex HTTP requests with an intuitive, chainable API.
14
+ - **Structured Exception Hierarchy:** Every HTTP status code and network failure maps to a dedicated, named exception class with rich metadata, `isRetryable()` signals, and structured `toJSON()` serialization.
14
15
  - **Interceptor Architecture:** Easily implement middleware for logging, authentication, error handling, and data transformation.
15
16
  - **Resilience & Reliability:** Built-in support for retry policies (Exponential Backoff, etc.) and a generic **Circuit Breaker** to handle transient failures gracefully and prevent cascading failures in S2S communication.
16
17
  - **Type Safety:** Fully typed requests and responses using generics, ensuring type safety across your application.
@@ -53,7 +54,7 @@ import { HttpAdapter, RetryPolicies, CircuitBreaker } from '@yildizpay/http-adap
53
54
 
54
55
  const circuitBreaker = new CircuitBreaker({
55
56
  failureThreshold: 5,
56
- resetTimeoutMs: 60000,
57
+ resetTimeoutMs: 60000,
57
58
  });
58
59
 
59
60
  const adapter = HttpAdapter.create(
@@ -62,7 +63,7 @@ const adapter = HttpAdapter.create(
62
63
  ],
63
64
  RetryPolicies.exponential(3), // Retry up to 3 times with exponential backoff
64
65
  undefined, // Optional custom HTTP client
65
- circuitBreaker // Optional Circuit Breaker
66
+ circuitBreaker, // Optional Circuit Breaker
66
67
  );
67
68
  ```
68
69
 
@@ -84,6 +85,175 @@ try {
84
85
  }
85
86
  ```
86
87
 
88
+ ## Error Handling
89
+
90
+ `@yildizpay/http-adapter` converts every raw error — HTTP failures, network-level OS errors, or totally unexpected exceptions — into a structured, typed exception class. This means your `catch` blocks never need to inspect raw status codes or error codes manually.
91
+
92
+ ### Exception Hierarchy
93
+
94
+ ```
95
+ BaseAdapterException
96
+ ├── HttpException (any HTTP response error)
97
+ │ ├── BadRequestException (400)
98
+ │ ├── UnauthorizedException (401)
99
+ │ ├── ForbiddenException (403)
100
+ │ ├── NotFoundException (404)
101
+ │ ├── ConflictException (409)
102
+ │ ├── UnprocessableEntityException (422)
103
+ │ ├── TooManyRequestsException (429) ← isRetryable() = true
104
+ │ ├── InternalServerErrorException (500)
105
+ │ ├── BadGatewayException (502) ← isRetryable() = true
106
+ │ ├── ServiceUnavailableException (503) ← isRetryable() = true
107
+ │ ├── GatewayTimeoutException (504) ← isRetryable() = true
108
+ │ └── ... (all 4xx / 5xx codes)
109
+ ├── NetworkException (OS-level connectivity failures)
110
+ │ ├── ConnectionRefusedException (ECONNREFUSED) ← isRetryable() = true
111
+ │ ├── TimeoutException (ETIMEDOUT / ECONNABORTED / AbortError) ← isRetryable() = true
112
+ │ ├── SocketResetException (ECONNRESET) ← isRetryable() = true
113
+ │ ├── DnsResolutionException (ENOTFOUND / EAI_AGAIN)
114
+ │ └── HostUnreachableException (EHOSTUNREACH / ENETUNREACH)
115
+ ├── UnknownException (any unclassifiable error)
116
+ └── CircuitBreakerOpenException (circuit is open, request not sent)
117
+ ```
118
+
119
+ ### Catching Exceptions by Type
120
+
121
+ ```typescript
122
+ import {
123
+ NotFoundException,
124
+ TooManyRequestsException,
125
+ TimeoutException,
126
+ ConnectionRefusedException,
127
+ CircuitBreakerOpenException,
128
+ UnknownException,
129
+ } from '@yildizpay/http-adapter';
130
+
131
+ try {
132
+ const response = await adapter.send<PaymentResponse>(request);
133
+ } catch (error) {
134
+ if (error instanceof NotFoundException) {
135
+ // HTTP 404 — resource does not exist
136
+ console.error('Resource not found:', error.response.data);
137
+ } else if (error instanceof TooManyRequestsException) {
138
+ // HTTP 429 — back off before retrying
139
+ const retryAfterMs = error.getRetryAfterMs();
140
+ console.warn(`Rate limited. Retry after ${retryAfterMs}ms`);
141
+ } else if (error instanceof TimeoutException) {
142
+ // ETIMEDOUT / AbortError — downstream service too slow
143
+ console.error('Request timed out:', error.code);
144
+ } else if (error instanceof ConnectionRefusedException) {
145
+ // ECONNREFUSED — downstream service is down
146
+ console.error('Service is down:', error.requestContext?.url);
147
+ } else if (error instanceof CircuitBreakerOpenException) {
148
+ // Circuit is open — fail fast without hitting the server
149
+ console.error('Circuit breaker is open. Not sending request.');
150
+ } else if (error instanceof UnknownException) {
151
+ // Something unexpected — log and investigate
152
+ console.error('Unhandled error:', error.toJSON());
153
+ }
154
+ }
155
+ ```
156
+
157
+ ### Type Guards
158
+
159
+ If you prefer narrowing without `instanceof` (useful in functional pipelines or when crossing module boundaries), every exception class has a corresponding type guard:
160
+
161
+ ```typescript
162
+ import {
163
+ isHttpException,
164
+ isTimeoutException,
165
+ isConnectionRefusedException,
166
+ isCircuitBreakerOpenException,
167
+ } from '@yildizpay/http-adapter';
168
+
169
+ function handleError(error: unknown): void {
170
+ if (isTimeoutException(error)) {
171
+ // TypeScript now knows: error is TimeoutException
172
+ scheduleRetry(error.requestContext?.url);
173
+ } else if (isHttpException(error)) {
174
+ // TypeScript now knows: error is HttpException
175
+ reportToMonitoring(error.response.status, error.response.data);
176
+ }
177
+ }
178
+ ```
179
+
180
+ ### `isRetryable()` Signal
181
+
182
+ Each exception exposes an `isRetryable(): boolean` method that reflects whether the failure is transient and worth retrying. This is useful when implementing custom retry decorators or deciding at the application layer whether to propagate or retry an error.
183
+
184
+ ```typescript
185
+ } catch (error) {
186
+ if (error instanceof BaseAdapterException && error.isRetryable()) {
187
+ return retryOperation();
188
+ }
189
+ throw error;
190
+ }
191
+ ```
192
+
193
+ Retryable exceptions: `TooManyRequestsException (429)`, `BadGatewayException (502)`, `ServiceUnavailableException (503)`, `GatewayTimeoutException (504)`, `TimeoutException`, `SocketResetException`, `ConnectionRefusedException`.
194
+
195
+ ### Structured Logging with `toJSON()`
196
+
197
+ All exceptions override `toJSON()`, making them compatible with structured loggers (Pino, Winston, etc.). `JSON.stringify(error)` produces a complete, nested log entry instead of an empty `{}`.
198
+
199
+ ```typescript
200
+ } catch (error) {
201
+ if (error instanceof BaseAdapterException) {
202
+ logger.error(error.toJSON());
203
+ // {
204
+ // name: 'NotFoundException',
205
+ // message: 'Not Found',
206
+ // code: 'ERR_NOT_FOUND',
207
+ // stack: '...',
208
+ // response: {
209
+ // status: 404,
210
+ // data: { detail: 'Payment record not found' },
211
+ // request: { method: 'GET', url: 'https://api.example.com/payments/123', correlationId: 'corr-abc' }
212
+ // }
213
+ // }
214
+ }
215
+ }
216
+ ```
217
+
218
+ ### `RequestContext` — Safe Request Metadata
219
+
220
+ Every exception automatically carries a `RequestContext` object (`method`, `url`, `correlationId`) sourced from the originating request. Headers and body are deliberately excluded to prevent accidental auth-token or PII leakage in logs.
221
+
222
+ ```typescript
223
+ } catch (error) {
224
+ if (error instanceof NetworkException) {
225
+ logger.warn({
226
+ event: 'network_failure',
227
+ exception: error.name,
228
+ request: error.requestContext, // { method, url, correlationId }
229
+ });
230
+ }
231
+ }
232
+ ```
233
+
234
+ ### Error Interceptor
235
+
236
+ You can also catch and transform exceptions at the interceptor layer before they reach your business logic.
237
+
238
+ ```typescript
239
+ import {
240
+ HttpErrorInterceptor,
241
+ Request,
242
+ BaseAdapterException,
243
+ UnauthorizedException,
244
+ } from '@yildizpay/http-adapter';
245
+
246
+ export class GlobalErrorInterceptor implements HttpErrorInterceptor {
247
+ async onError(error: BaseAdapterException, request: Request): Promise<never> {
248
+ if (error instanceof UnauthorizedException) {
249
+ await this.tokenService.refresh();
250
+ }
251
+ // Re-throw so the caller can handle it
252
+ throw error;
253
+ }
254
+ }
255
+ ```
256
+
87
257
  ## Resilience & Retries
88
258
 
89
259
  Network instability is inevitable. This adapter allows you to define robust retry strategies.
@@ -95,7 +265,7 @@ The built-in `ExponentialBackoffPolicy` waits increasingly longer between retrie
95
265
  ```typescript
96
266
  import { RetryPolicies } from '@yildizpay/http-adapter';
97
267
 
98
- // Retries on 429, 500, 502, 503, 504 and network errors
268
+ // Retries on 429, 502, 503, 504 and network errors
99
269
  const retryPolicy = RetryPolicies.exponential(5);
100
270
  ```
101
271
 
@@ -118,6 +288,7 @@ const breaker = new CircuitBreaker({
118
288
  Thanks to the **Interface Segregation Principle (ISP)**, you aren't forced to implement massive interfaces. You can hook into the exact lifecycle event you need by implementing `HttpRequestInterceptor`, `HttpResponseInterceptor`, or `HttpErrorInterceptor`.
119
289
 
120
290
  ### 1. Request Interceptor (e.g., Auth Tokens)
291
+
121
292
  Add common headers like Authorization tokens before requests leave.
122
293
 
123
294
  ```typescript
@@ -132,6 +303,7 @@ export class AuthInterceptor implements HttpRequestInterceptor {
132
303
  ```
133
304
 
134
305
  ### 2. Response Interceptor (e.g., Data Transformation)
306
+
135
307
  Inspect or mutate payloads identically across all incoming responses.
136
308
 
137
309
  ```typescript
@@ -140,7 +312,7 @@ import { HttpResponseInterceptor, Response } from '@yildizpay/http-adapter';
140
312
  export class TransformResponseInterceptor implements HttpResponseInterceptor {
141
313
  async onResponse(response: Response): Promise<Response> {
142
314
  if (response.status === 201) {
143
- console.log('Resource successfully created!');
315
+ console.log('Resource successfully created!');
144
316
  }
145
317
  return response;
146
318
  }
@@ -148,17 +320,22 @@ export class TransformResponseInterceptor implements HttpResponseInterceptor {
148
320
  ```
149
321
 
150
322
  ### 3. Error Interceptor (e.g., Global Error Handling)
323
+
151
324
  Catch network failures or non-success HTTP statuses centrally.
152
325
 
153
326
  ```typescript
154
- import { HttpErrorInterceptor, Request, HttpClientException } from '@yildizpay/http-adapter';
327
+ import {
328
+ HttpErrorInterceptor,
329
+ Request,
330
+ BaseAdapterException,
331
+ UnauthorizedException,
332
+ } from '@yildizpay/http-adapter';
155
333
 
156
334
  export class GlobalErrorInterceptor implements HttpErrorInterceptor {
157
- async onError(error: unknown, request: Request): Promise<unknown> {
158
- if (error instanceof HttpClientException && error.response?.status === 401) {
159
- console.error(`Unauthorized access to ${request.endpoint}! Redirecting to login...`);
335
+ async onError(error: BaseAdapterException, request: Request): Promise<BadRequestException> {
336
+ if (error instanceof UnauthorizedException) {
337
+ console.error(`Unauthorized access to ${error.requestContext?.url}! Redirecting to login...`);
160
338
  }
161
- // You can throw a custom error or return a fallback payload
162
339
  throw error;
163
340
  }
164
341
  }
package/README.tr.md CHANGED
@@ -6,16 +6,17 @@
6
6
  ![NPM Version](https://img.shields.io/npm/v/@yildizpay/http-adapter)
7
7
  ![License](https://img.shields.io/npm/l/@yildizpay/http-adapter)
8
8
 
9
- Node.js tabanlı kurumsal seviye (enterprise-grade) uygulamalar için tasarlanmış profesyonel, dayanıklı (robust) ve yüksek oranda yapılandırılabilir bir HTTP istemci (client) adaptörü. Akıcı (fluent) bir API, yerleşik ağ direnci (resilience) desenleri ve güçlü bir önleyici (interceptor) sistemi sunar. Dış bağımlılık bulundurmayan (zero-dependency) paketin çekirdeği **Node.js Native Fetch API** kullanır ancak tercih edilen farklı özel HTTP istemcilerine de (Custom Clients) kolayca genişletilebilir.
9
+ Node.js tabanlı kurumsal uygulamalar için tasarlanmış profesyonel ve yüksek oranda yapılandırılabilir bir HTTP client adaptörü. Fluent API, built-in resilience pattern'ları, güçlü bir interceptor sistemi ve kapsamlı bir exception hiyerarşisi sunar. Zero-dependency olan paketin çekirdeği **Node.js Native Fetch API** kullanır; ancak istenen farklı custom HTTP client'lara da kolayca genişletilebilir.
10
10
 
11
11
  ## Temel Özellikler
12
12
 
13
- - **Akıcı İstek Oluşturucu (Fluent Request Builder):** Sezgisel ve zincirlenebilir (chainable) bir API ile karmaşık HTTP isteklerini kolayca oluşturun.
14
- - **Önleyici Mimarisi (Interceptor Architecture):** Loglama, kimlik doğrulama, hata yönetimi ve veri dönüşümü gibi ara yazılım (middleware) işlemlerini zahmetsizce entegre edin.
15
- - ** Direnci ve Güvenilirlik (Resilience & Reliability):** Sunucular arası entegrasyonlarda geçici arızaları zarif bir şekilde yönetmek ve zincirleme (cascading) hataları önlemek için üstel geri çekilme (Exponential Backoff vs.) gibi yeniden deneme (retry) politikaları ve yerleşik bir **Devre Kesici (Circuit Breaker)** içerir.
16
- - **Tip Güvenliği (Type Safety):** Jenerikleri (generics) kullanan tam tiplendirilmiş (fully typed) istek ve yanıtlar ile uygulamanız genelinde tip güvenliği sağlar.
17
- - **Test Edilebilirlik:** Bağımlılık enjeksiyonu (dependency injection) düşünülerek tasarlandığından, birim testleri (unit mock) yazmak oldukça kolaydır.
18
- - **Değişmez (Immutable) Tasarım:** Eşzamanlı (concurrent) ortamlarda yan etkileri (side effects) önlemek için çekirdek (core) bileşenler değiştirilemez (immutable) yapıda tasarlanmıştır.
13
+ - **Fluent Request Builder:** Sezgisel ve zincirlenebilir bir API ile karmaşık HTTP isteklerini kolayca oluşturun.
14
+ - **Structured Exception Hierarchy:** Her HTTP durum kodu ve hatası, zengin metadata, `isRetryable()` sinyali ve `toJSON()` desteğiyle ayrı bir exception sınıfına dönüştürülür.
15
+ - **Interceptor Mimarisi:** Loglama, kimlik doğrulama, hata yönetimi ve veri dönüşümü gibi middleware işlemlerini zahmetsizce entegre edin.
16
+ - **Resilience & Reliability:** S2S entegrasyonlarında geçici hataları zarif bir şekilde yönetmek için Exponential Backoff gibi retry policy'ler ve built-in **Circuit Breaker** içerir.
17
+ - **Type Safety:** Generic'ler kullanılarak tam olarak tiplendirilmiş request ve response'lar ile uygulama genelinde tip güvenliği sağlanır.
18
+ - **Test Edilebilirlik:** Dependency injection düşünülerek tasarlandığından mock yazmak oldukça kolaydır.
19
+ - **Immutable Tasarım:** Concurrent ortamlarda side effect'leri önlemek için core bileşenler immutable olarak tasarlanmıştır.
19
20
 
20
21
  ## Kurulum
21
22
 
@@ -29,9 +30,9 @@ pnpm add @yildizpay/http-adapter
29
30
 
30
31
  ## Kullanım
31
32
 
32
- ### 1. Temel İstek Oluşturma
33
+ ### 1. Request Oluşturma
33
34
 
34
- İstekleri temiz ve öz bir şekilde oluşturmak için `RequestBuilder` sınıfını kullanın.
35
+ `RequestBuilder` ile istekleri temiz ve öz bir şekilde oluşturun.
35
36
 
36
37
  ```typescript
37
38
  import { RequestBuilder, HttpMethod } from '@yildizpay/http-adapter';
@@ -44,31 +45,31 @@ const request = new RequestBuilder('https://api.example.com')
44
45
  .build();
45
46
  ```
46
47
 
47
- ### 2. Adaptörü Başlatma
48
+ ### 2. Adapter'ı Başlatma
48
49
 
49
- İsteğe bağlı önleyiciler (interceptors), yeniden deneme (retry) politikaları ve devre kesici (circuit breaker) ile `HttpAdapter` nesnesini oluşturun.
50
+ İsteğe bağlı interceptor'lar, retry policy ve circuit breaker ile `HttpAdapter` oluşturun.
50
51
 
51
52
  ```typescript
52
53
  import { HttpAdapter, RetryPolicies, CircuitBreaker } from '@yildizpay/http-adapter';
53
54
 
54
55
  const circuitBreaker = new CircuitBreaker({
55
56
  failureThreshold: 5,
56
- resetTimeoutMs: 60000,
57
+ resetTimeoutMs: 60000,
57
58
  });
58
59
 
59
60
  const adapter = HttpAdapter.create(
60
61
  [
61
- /* interceptors (önleyiciler) */
62
+ /* interceptors */
62
63
  ],
63
- RetryPolicies.exponential(3), // Üstel (exponential) geri çekilme ile 3 defaya kadar yeniden dene
64
- undefined, // İsteğe bağlı özel HTTP istemcisi (opsiyonel)
65
- circuitBreaker // İsteğe bağlı Devre Kesici (opsiyonel)
64
+ RetryPolicies.exponential(3), // Exponential backoff ile 3 defaya kadar retry
65
+ undefined, // Opsiyonel custom HTTP client
66
+ circuitBreaker, // Opsiyonel Circuit Breaker
66
67
  );
67
68
  ```
68
69
 
69
- ### 3. İstek Gönderme
70
+ ### 3. Request Gönderme
70
71
 
71
- İsteği yürütün ve kesin bir şekilde tiplendirilmiş (strongly-typed) yanıtı (response) alın.
72
+ Request'i çalıştırın ve strongly-typed response alın.
72
73
 
73
74
  ```typescript
74
75
  interface UserResponse {
@@ -80,45 +81,215 @@ try {
80
81
  const response = await adapter.send<UserResponse>(request);
81
82
  console.log('Kullanıcı oluşturuldu:', response.data);
82
83
  } catch (error) {
83
- console.error('İstek başarısız oldu:', error);
84
+ console.error('Request başarısız oldu:', error);
84
85
  }
85
86
  ```
86
87
 
87
- ## Direnç ve Yeniden Denemeler (Resilience & Retries)
88
+ ## Hata Yönetimi (Error Handling)
88
89
 
89
- kararsızlığı kaçınılmazdır. Bu adaptör, sağlam yeniden deneme stratejileri tanımlamanıza olanak tanır.
90
+ `@yildizpay/http-adapter`, her türlü ham hatayı — HTTP hataları, OS düzeyindeki ağ hataları veya tamamen beklenmedik exception'lar — yapılandırılmış ve tiplendirilmiş bir exception sınıfına dönüştürür. Bu sayede `catch` bloklarında ham durum kodlarını ya da hata kodlarını elle incelemenize gerek kalmaz.
90
91
 
91
- ### Üstel Geri Çekilme (Exponential Backoff)
92
+ ### Exception Hiyerarşisi
92
93
 
93
- Yerleşik `ExponentialBackoffPolicy`, denemeler arasında giderek daha uzun süreler (ör. 200ms, 400ms, 800ms) bekler ve "gürleyen sürü" (thundering herd) sorunlarını önlemek için gecikmelere rastgele bir sapma (jitter) ekler.
94
+ ```
95
+ BaseAdapterException
96
+ ├── HttpException (herhangi bir HTTP response hatası)
97
+ │ ├── BadRequestException (400)
98
+ │ ├── UnauthorizedException (401)
99
+ │ ├── ForbiddenException (403)
100
+ │ ├── NotFoundException (404)
101
+ │ ├── ConflictException (409)
102
+ │ ├── UnprocessableEntityException (422)
103
+ │ ├── TooManyRequestsException (429) ← isRetryable() = true
104
+ │ ├── InternalServerErrorException (500)
105
+ │ ├── BadGatewayException (502) ← isRetryable() = true
106
+ │ ├── ServiceUnavailableException (503) ← isRetryable() = true
107
+ │ ├── GatewayTimeoutException (504) ← isRetryable() = true
108
+ │ └── ... (tüm 4xx / 5xx kodları)
109
+ ├── NetworkException (OS düzeyindeki bağlantı hataları)
110
+ │ ├── ConnectionRefusedException (ECONNREFUSED) ← isRetryable() = true
111
+ │ ├── TimeoutException (ETIMEDOUT / ECONNABORTED / AbortError) ← isRetryable() = true
112
+ │ ├── SocketResetException (ECONNRESET) ← isRetryable() = true
113
+ │ ├── DnsResolutionException (ENOTFOUND / EAI_AGAIN)
114
+ │ └── HostUnreachableException (EHOSTUNREACH / ENETUNREACH)
115
+ ├── UnknownException (sınıflandırılamayan her türlü hata)
116
+ └── CircuitBreakerOpenException (circuit açık, request gönderilmedi)
117
+ ```
118
+
119
+ ### Exception Türüne Göre Yakalama
120
+
121
+ ```typescript
122
+ import {
123
+ NotFoundException,
124
+ TooManyRequestsException,
125
+ TimeoutException,
126
+ ConnectionRefusedException,
127
+ CircuitBreakerOpenException,
128
+ UnknownException,
129
+ } from '@yildizpay/http-adapter';
130
+
131
+ try {
132
+ const response = await adapter.send<PaymentResponse>(request);
133
+ } catch (error) {
134
+ if (error instanceof NotFoundException) {
135
+ // HTTP 404 — kaynak bulunamadı
136
+ console.error('Kaynak bulunamadı:', error.response.data);
137
+ } else if (error instanceof TooManyRequestsException) {
138
+ // HTTP 429 — retry'dan önce bekle
139
+ const retryAfterMs = error.getRetryAfterMs();
140
+ console.warn(`Rate limit aşıldı. ${retryAfterMs}ms sonra tekrar dene`);
141
+ } else if (error instanceof TimeoutException) {
142
+ // ETIMEDOUT / AbortError — downstream servis yavaş
143
+ console.error('Request timeout:', error.code);
144
+ } else if (error instanceof ConnectionRefusedException) {
145
+ // ECONNREFUSED — downstream servis kapalı
146
+ console.error('Servis kapalı:', error.requestContext?.url);
147
+ } else if (error instanceof CircuitBreakerOpenException) {
148
+ // Circuit açık — sunucuya istek gönderilmeden fail fast
149
+ console.error('Circuit breaker açık. Request gönderilmedi.');
150
+ } else if (error instanceof UnknownException) {
151
+ // Beklenmedik bir hata — logla ve araştır
152
+ console.error('Bilinmeyen hata:', error.toJSON());
153
+ }
154
+ }
155
+ ```
156
+
157
+ ### Type Guard'lar
158
+
159
+ `instanceof` kullanmadan type narrowing tercih ediyorsanız — fonksiyonel pipeline'larda veya modül sınırlarını geçerken kullanışlıdır — her exception sınıfının karşılık gelen bir type guard'ı mevcuttur:
160
+
161
+ ```typescript
162
+ import {
163
+ isHttpException,
164
+ isTimeoutException,
165
+ isConnectionRefusedException,
166
+ isCircuitBreakerOpenException,
167
+ } from '@yildizpay/http-adapter';
168
+
169
+ function handleError(error: unknown): void {
170
+ if (isTimeoutException(error)) {
171
+ // TypeScript artık biliyor: error, TimeoutException türünde
172
+ scheduleRetry(error.requestContext?.url);
173
+ } else if (isHttpException(error)) {
174
+ // TypeScript artık biliyor: error, HttpException türünde
175
+ reportToMonitoring(error.response.status, error.response.data);
176
+ }
177
+ }
178
+ ```
179
+
180
+ ### `isRetryable()` Sinyali
181
+
182
+ Her exception, hatanın geçici olup olmadığını ve retry'a değer olup olmadığını belirten bir `isRetryable(): boolean` metodu sunar. Custom retry decorator'lar yazarken ya da uygulama katmanında hatayı tekrar denemek isteyip istemediğinize karar verirken kullanışlıdır.
183
+
184
+ ```typescript
185
+ } catch (error) {
186
+ if (error instanceof BaseAdapterException && error.isRetryable()) {
187
+ return retryOperation();
188
+ }
189
+ throw error;
190
+ }
191
+ ```
192
+
193
+ Retry edilebilir exception'lar: `TooManyRequestsException (429)`, `BadGatewayException (502)`, `ServiceUnavailableException (503)`, `GatewayTimeoutException (504)`, `TimeoutException`, `SocketResetException`, `ConnectionRefusedException`.
194
+
195
+ ### `toJSON()` ile Structured Logging
196
+
197
+ Tüm exception'lar `toJSON()` metodunu override eder; bu sayede Pino, Winston gibi structured logger'larla tam uyumludur. `JSON.stringify(error)` çağrısı boş `{}` yerine eksiksiz bir log objesi üretir.
198
+
199
+ ```typescript
200
+ } catch (error) {
201
+ if (error instanceof BaseAdapterException) {
202
+ logger.error(error.toJSON());
203
+ // {
204
+ // name: 'NotFoundException',
205
+ // message: 'Not Found',
206
+ // code: 'ERR_NOT_FOUND',
207
+ // stack: '...',
208
+ // response: {
209
+ // status: 404,
210
+ // data: { detail: 'Ödeme kaydı bulunamadı' },
211
+ // request: { method: 'GET', url: 'https://api.example.com/payments/123', correlationId: 'corr-abc' }
212
+ // }
213
+ // }
214
+ }
215
+ }
216
+ ```
217
+
218
+ ### `RequestContext` — Güvenli Request Metadata
219
+
220
+ Her exception, kaynak request'ten alınan `RequestContext` objesini (`method`, `url`, `correlationId`) otomatik olarak taşır. Auth token'larının veya kişisel verilerin (PII) loglara sızmasını önlemek amacıyla header ve body bilgileri bu objeden kasıtlı olarak çıkarılmıştır.
221
+
222
+ ```typescript
223
+ } catch (error) {
224
+ if (error instanceof NetworkException) {
225
+ logger.warn({
226
+ event: 'network_failure',
227
+ exception: error.name,
228
+ request: error.requestContext, // { method, url, correlationId }
229
+ });
230
+ }
231
+ }
232
+ ```
233
+
234
+ ### Error Interceptor
235
+
236
+ Exception'lar business logic'e ulaşmadan önce interceptor seviyesinde yakalanabilir ve dönüştürülebilir.
237
+
238
+ ```typescript
239
+ import {
240
+ HttpErrorInterceptor,
241
+ Request,
242
+ BaseAdapterException,
243
+ UnauthorizedException,
244
+ } from '@yildizpay/http-adapter';
245
+
246
+ export class GlobalErrorInterceptor implements HttpErrorInterceptor {
247
+ async onError(error: BaseAdapterException, request: Request): Promise<never> {
248
+ if (error instanceof UnauthorizedException) {
249
+ await this.tokenService.refresh();
250
+ }
251
+ // Caller'ın handle edebilmesi için hatayı yeniden fırlat
252
+ throw error;
253
+ }
254
+ }
255
+ ```
256
+
257
+ ## Resilience & Retry
258
+
259
+ Ağ kararsızlığı kaçınılmazdır. Bu adaptör, sağlam retry stratejileri tanımlamanıza olanak tanır.
260
+
261
+ ### Exponential Backoff
262
+
263
+ Built-in `ExponentialBackoffPolicy`, denemeler arasında giderek artan süreler (ör. 200ms, 400ms, 800ms) bekler ve "thundering herd" sorununu önlemek için gecikmelere rastgele jitter ekler.
94
264
 
95
265
  ```typescript
96
266
  import { RetryPolicies } from '@yildizpay/http-adapter';
97
267
 
98
- // 429, 500, 502, 503, 504 durum kodlarında ve ağ hatalarında yeniden dener
268
+ // 429, 502, 503, 504 ve ağ hatalarında retry yapar
99
269
  const retryPolicy = RetryPolicies.exponential(5);
100
270
  ```
101
271
 
102
- ### Devre Kesici (Circuit Breaker)
272
+ ### Circuit Breaker
103
273
 
104
- Sisteminizi tamamen çökmüş olan bir sunucuyu beklemekten korumak için `CircuitBreaker` (Devre Kesici) yönteminden yararlanabilirsiniz. Konfigürasyonla belirlenmiş miktarda ardışık hata alındığında devre açılır ve yanıt vermeyen sunucuya gereksiz istek göndermeksizin anında `CircuitBreakerOpenException` fırlatarak cevap verir.
274
+ 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.
105
275
 
106
276
  ```typescript
107
277
  import { CircuitBreaker } from '@yildizpay/http-adapter';
108
278
 
109
279
  const breaker = new CircuitBreaker({
110
- failureThreshold: 5, // 5 hatadan sonra devreyi
111
- resetTimeoutMs: 30000, // 30 saniye sonra 'yarı-açık' (half-open) test isteği dene
112
- successThreshold: 1, // Yarı-açık durumda 1 başarılı istek sonrası devreyi kapat
280
+ failureThreshold: 5, // 5 hatadan sonra circuit'i
281
+ resetTimeoutMs: 30000, // 30 saniye sonra half-open test isteği gönder
282
+ successThreshold: 1, // 1 başarılı half-open request sonrası circuit'i kapat
113
283
  });
114
284
  ```
115
285
 
116
- ## Önleyiciler (Interceptors)
286
+ ## Interceptors
117
287
 
118
- **Arayüz Ayrımı Prensibi (ISP)** sayesinde gereksiz tüm metodları uygulamak zorunda kalmazsınız. Tam olarak araya girmek istediğiniz yaşam döngüsüne göre `HttpRequestInterceptor`, `HttpResponseInterceptor` veya `HttpErrorInterceptor` arayüzlerini uygulayabilirsiniz.
288
+ **Interface Segregation Principle (ISP)** sayesinde gereksiz metodları implement etmek zorunda kalmazsınız. Yalnızca ihtiyaç duyduğunuz lifecycle event'e göre `HttpRequestInterceptor`, `HttpResponseInterceptor` veya `HttpErrorInterceptor` interface'ini implement edebilirsiniz.
119
289
 
120
- ### 1. İstek Önleyici (Örn: Kimlik Doğrulama)
121
- İstekler yola çıkmadan önce `Authorization` (Yetkilendirme) gibi başlıkları otomatik ekleyebilirsiniz.
290
+ ### 1. Request Interceptor (Örn: Auth Token)
291
+
292
+ Request'ler gönderilmeden önce `Authorization` gibi header'ları otomatik ekleyebilirsiniz.
122
293
 
123
294
  ```typescript
124
295
  import { HttpRequestInterceptor, Request } from '@yildizpay/http-adapter';
@@ -131,8 +302,9 @@ export class AuthInterceptor implements HttpRequestInterceptor {
131
302
  }
132
303
  ```
133
304
 
134
- ### 2. Yanıt Önleyici (Örn: Veri İzleme ve Dönüşüm)
135
- Projeye giren tüm başarılı verileri merkezi olarak şekillendirebilir veya loglayabilirsiniz.
305
+ ### 2. Response Interceptor (Örn: Veri Dönüşümü)
306
+
307
+ Gelen tüm response'ları merkezi olarak şekillendirebilir veya loglayabilirsiniz.
136
308
 
137
309
  ```typescript
138
310
  import { HttpResponseInterceptor, Response } from '@yildizpay/http-adapter';
@@ -140,25 +312,30 @@ import { HttpResponseInterceptor, Response } from '@yildizpay/http-adapter';
140
312
  export class TransformResponseInterceptor implements HttpResponseInterceptor {
141
313
  async onResponse(response: Response): Promise<Response> {
142
314
  if (response.status === 201) {
143
- console.log('Kaynak başarıyla oluşturuldu!');
315
+ console.log('Kaynak başarıyla oluşturuldu!');
144
316
  }
145
317
  return response;
146
318
  }
147
319
  }
148
320
  ```
149
321
 
150
- ### 3. Hata Önleyici (Örn: Evrensel Hata Yönetimi)
151
- Sunucudan gelen hatalı HTTP kodlarını (4xx, 5xx) veya ağ kopmalarını tek bir yerden yakalayıp yönetebilirsiniz.
322
+ ### 3. Error Interceptor (Örn: Global Hata Yönetimi)
323
+
324
+ Sunucudan gelen hatalı HTTP kodlarını (4xx, 5xx) veya ağ hatalarını tek bir yerden yakalayıp yönetebilirsiniz.
152
325
 
153
326
  ```typescript
154
- import { HttpErrorInterceptor, Request, HttpClientException } from '@yildizpay/http-adapter';
327
+ import {
328
+ HttpErrorInterceptor,
329
+ Request,
330
+ BaseAdapterException,
331
+ UnauthorizedException,
332
+ } from '@yildizpay/http-adapter';
155
333
 
156
334
  export class GlobalErrorInterceptor implements HttpErrorInterceptor {
157
- async onError(error: unknown, request: Request): Promise<unknown> {
158
- if (error instanceof HttpClientException && error.response?.status === 401) {
159
- console.error(`${request.endpoint} noktasına yetkisiz erişim! Login sayfasına yönlendiriliyor...`);
335
+ async onError(error: BaseAdapterException, request: Request): Promise<never> {
336
+ if (error instanceof UnauthorizedException) {
337
+ console.error(`${error.requestContext?.url} endpoint'ine yetkisiz erişim!`);
160
338
  }
161
- // Hatayı fırlatmaya devam edebilir veya varsayılan bir veri (fallback) dönebilirsiniz
162
339
  throw error;
163
340
  }
164
341
  }
@@ -166,7 +343,7 @@ export class GlobalErrorInterceptor implements HttpErrorInterceptor {
166
343
 
167
344
  ## Katkıda Bulunma
168
345
 
169
- Katkılarınızı her zaman bekliyoruz! Lütfen bir "Pull Request" göndermekten çekinmeyin.
346
+ Katkılarınızı her zaman bekliyoruz! Lütfen bir Pull Request göndermekten çekinmeyin.
170
347
 
171
348
  ## Lisans
172
349
 
@@ -15,12 +15,3 @@ export interface HttpClientResponse<T = unknown> {
15
15
  export interface HttpClientContract {
16
16
  request<T = unknown>(config: HttpClientRequestConfig): Promise<HttpClientResponse<T>>;
17
17
  }
18
- export declare class HttpClientException<T = unknown> extends Error {
19
- readonly response?: {
20
- status: number;
21
- data: T;
22
- headers: Record<string, string>;
23
- };
24
- readonly code?: string;
25
- constructor(message: string, status?: number, data?: T, headers?: Record<string, string>, code?: string);
26
- }
@@ -1,20 +1,3 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.HttpClientException = void 0;
4
- class HttpClientException extends Error {
5
- constructor(message, status, data, headers, code) {
6
- super(message);
7
- this.name = 'HttpClientException';
8
- Object.setPrototypeOf(this, HttpClientException.prototype);
9
- if (status !== undefined) {
10
- this.response = {
11
- status,
12
- data: data,
13
- headers: headers ?? {},
14
- };
15
- }
16
- this.code = code;
17
- }
18
- }
19
- exports.HttpClientException = HttpClientException;
20
3
  //# sourceMappingURL=http-client.contract.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"http-client.contract.js","sourceRoot":"","sources":["../../src/contracts/http-client.contract.ts"],"names":[],"mappings":";;;AAkCA,MAAa,mBAAiC,SAAQ,KAAK;IAQzD,YACE,OAAe,EACf,MAAe,EACf,IAAQ,EACR,OAAgC,EAChC,IAAa;QAEb,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;QAClC,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,mBAAmB,CAAC,SAAS,CAAC,CAAC;QAE3D,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,IAAI,CAAC,QAAQ,GAAG;gBACd,MAAM;gBACN,IAAI,EAAE,IAAS;gBACf,OAAO,EAAE,OAAO,IAAI,EAAE;aACvB,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF;AA5BD,kDA4BC"}
1
+ {"version":3,"file":"http-client.contract.js","sourceRoot":"","sources":["../../src/contracts/http-client.contract.ts"],"names":[],"mappings":""}