@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.
- package/README.md +186 -9
- package/README.tr.md +221 -44
- package/dist/contracts/http-client.contract.d.ts +0 -9
- package/dist/contracts/http-client.contract.js +0 -17
- package/dist/contracts/http-client.contract.js.map +1 -1
- package/dist/contracts/http-interceptor.contract.d.ts +2 -1
- package/dist/core/default-http-client.js +7 -6
- package/dist/core/default-http-client.js.map +1 -1
- package/dist/core/error.converter.d.ts +5 -0
- package/dist/core/error.converter.js +31 -0
- package/dist/core/error.converter.js.map +1 -0
- package/dist/core/http.adapter.js +11 -4
- package/dist/core/http.adapter.js.map +1 -1
- package/dist/exceptions/base-adapter.exception.d.ts +7 -0
- package/dist/exceptions/base-adapter.exception.js +34 -0
- package/dist/exceptions/base-adapter.exception.js.map +1 -0
- package/dist/exceptions/circuit-breaker-open.exception.d.ts +2 -2
- package/dist/exceptions/circuit-breaker-open.exception.js +3 -3
- package/dist/exceptions/circuit-breaker-open.exception.js.map +1 -1
- package/dist/exceptions/exception.guards.d.ts +15 -0
- package/dist/exceptions/exception.guards.js +48 -0
- package/dist/exceptions/exception.guards.js.map +1 -0
- package/dist/exceptions/http-exception.factory.d.ts +5 -0
- package/dist/exceptions/http-exception.factory.js +59 -0
- package/dist/exceptions/http-exception.factory.js.map +1 -0
- package/dist/exceptions/http-status.exceptions.d.ts +134 -0
- package/dist/exceptions/http-status.exceptions.js +382 -0
- package/dist/exceptions/http-status.exceptions.js.map +1 -0
- package/dist/exceptions/network-exception.factory.d.ts +6 -0
- package/dist/exceptions/network-exception.factory.js +35 -0
- package/dist/exceptions/network-exception.factory.js.map +1 -0
- package/dist/exceptions/network.exceptions.d.ts +25 -0
- package/dist/exceptions/network.exceptions.js +69 -0
- package/dist/exceptions/network.exceptions.js.map +1 -0
- package/dist/exceptions/unknown.exception.d.ts +7 -0
- package/dist/exceptions/unknown.exception.js +20 -0
- package/dist/exceptions/unknown.exception.js.map +1 -0
- package/dist/index.d.ts +9 -1
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/dist/models/request-context.d.ts +5 -0
- package/dist/models/request-context.js +3 -0
- package/dist/models/request-context.js.map +1 -0
- package/dist/models/response.d.ts +8 -5
- package/dist/models/response.js +9 -5
- package/dist/models/response.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/dist/exceptions/http.exception.d.ts +0 -5
- package/dist/exceptions/http.exception.js +0 -12
- 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
|
|
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,
|
|
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
|
-
|
|
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 {
|
|
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:
|
|
158
|
-
if (error instanceof
|
|
159
|
-
|
|
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
|

|
|
7
7
|

|
|
8
8
|
|
|
9
|
-
Node.js tabanlı kurumsal
|
|
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
|
-
- **
|
|
14
|
-
-
|
|
15
|
-
- **
|
|
16
|
-
- **
|
|
17
|
-
- **
|
|
18
|
-
- **
|
|
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 ağ 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.
|
|
33
|
+
### 1. Request Oluşturma
|
|
33
34
|
|
|
34
|
-
|
|
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.
|
|
48
|
+
### 2. Adapter'ı Başlatma
|
|
48
49
|
|
|
49
|
-
İsteğe bağlı
|
|
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
|
|
62
|
+
/* interceptors */
|
|
62
63
|
],
|
|
63
|
-
RetryPolicies.exponential(3), //
|
|
64
|
-
undefined, //
|
|
65
|
-
circuitBreaker
|
|
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.
|
|
70
|
+
### 3. Request Gönderme
|
|
70
71
|
|
|
71
|
-
|
|
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('
|
|
84
|
+
console.error('Request başarısız oldu:', error);
|
|
84
85
|
}
|
|
85
86
|
```
|
|
86
87
|
|
|
87
|
-
##
|
|
88
|
+
## Hata Yönetimi (Error Handling)
|
|
88
89
|
|
|
89
|
-
|
|
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
|
-
###
|
|
92
|
+
### Exception Hiyerarşisi
|
|
92
93
|
|
|
93
|
-
|
|
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,
|
|
268
|
+
// 429, 502, 503, 504 ve ağ hatalarında retry yapar
|
|
99
269
|
const retryPolicy = RetryPolicies.exponential(5);
|
|
100
270
|
```
|
|
101
271
|
|
|
102
|
-
###
|
|
272
|
+
### Circuit Breaker
|
|
103
273
|
|
|
104
|
-
|
|
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
|
|
111
|
-
resetTimeoutMs: 30000, // 30 saniye sonra
|
|
112
|
-
successThreshold: 1, //
|
|
280
|
+
failureThreshold: 5, // 5 hatadan sonra circuit'i aç
|
|
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
|
-
##
|
|
286
|
+
## Interceptors
|
|
117
287
|
|
|
118
|
-
**
|
|
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.
|
|
121
|
-
|
|
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.
|
|
135
|
-
|
|
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
|
-
|
|
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.
|
|
151
|
-
|
|
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 {
|
|
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:
|
|
158
|
-
if (error instanceof
|
|
159
|
-
|
|
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
|
|
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":"
|
|
1
|
+
{"version":3,"file":"http-client.contract.js","sourceRoot":"","sources":["../../src/contracts/http-client.contract.ts"],"names":[],"mappings":""}
|