@fjell/registry 4.4.23 → 4.4.24
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/dist/index.js.map +1 -1
- package/docs/docs.config.ts +52 -2
- package/docs/index.html +9 -0
- package/docs/public/404.html +53 -0
- package/docs/public/client-api-overview.md +137 -0
- package/docs/public/configuration.md +759 -0
- package/docs/public/error-handling/README.md +356 -0
- package/docs/public/error-handling/network-errors.md +485 -0
- package/docs/public/operations/README.md +121 -0
- package/docs/public/operations/all.md +325 -0
- package/docs/public/operations/create.md +415 -0
- package/docs/public/operations/get.md +389 -0
- package/docs/timing-range.svg +41 -39
- package/package.json +1 -1
- package/vitest.config.ts +0 -6
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
# Error Handling
|
|
2
|
+
|
|
3
|
+
Comprehensive error handling and resilience features for production-ready applications.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The Fjell Client API includes a robust error handling system designed for production environments. It provides automatic retry logic, custom error types, enhanced context, and configurable recovery strategies.
|
|
8
|
+
|
|
9
|
+
## Key Features
|
|
10
|
+
|
|
11
|
+
### 🔄 **Automatic Retry Logic**
|
|
12
|
+
- Exponential backoff with jitter
|
|
13
|
+
- Smart error classification (retryable vs non-retryable)
|
|
14
|
+
- Configurable retry strategies
|
|
15
|
+
- Rate limiting respect
|
|
16
|
+
|
|
17
|
+
### 🎯 **Custom Error Types**
|
|
18
|
+
- Specific error classes for different scenarios
|
|
19
|
+
- Enhanced error context and debugging information
|
|
20
|
+
- Structured error data for programmatic handling
|
|
21
|
+
- Business-friendly error messages
|
|
22
|
+
|
|
23
|
+
### 🛡️ **Production Resilience**
|
|
24
|
+
- Circuit breaker patterns
|
|
25
|
+
- Graceful degradation strategies
|
|
26
|
+
- Custom error handlers for monitoring integration
|
|
27
|
+
- Business workflow error recovery
|
|
28
|
+
|
|
29
|
+
## Error Types
|
|
30
|
+
|
|
31
|
+
| Error Type | Code | Retryable | Description |
|
|
32
|
+
|------------|------|-----------|-------------|
|
|
33
|
+
| **NetworkError** | `NETWORK_ERROR` | ✅ | Connection failures, DNS issues |
|
|
34
|
+
| **TimeoutError** | `TIMEOUT_ERROR` | ✅ | Request timeouts |
|
|
35
|
+
| **ServerError** | `SERVER_ERROR` | ✅ | 5xx HTTP status codes |
|
|
36
|
+
| **RateLimitError** | `RATE_LIMIT_ERROR` | ✅ | 429 Too Many Requests |
|
|
37
|
+
| **AuthenticationError** | `AUTHENTICATION_ERROR` | ❌ | 401 Unauthorized |
|
|
38
|
+
| **AuthorizationError** | `AUTHORIZATION_ERROR` | ❌ | 403 Forbidden |
|
|
39
|
+
| **NotFoundError** | `NOT_FOUND_ERROR` | ❌ | 404 Not Found |
|
|
40
|
+
| **ValidationError** | `VALIDATION_ERROR` | ❌ | 400 Bad Request |
|
|
41
|
+
| **ConflictError** | `CONFLICT_ERROR` | ❌ | 409 Conflict |
|
|
42
|
+
| **PayloadTooLargeError** | `PAYLOAD_TOO_LARGE_ERROR` | ❌ | 413 Request Too Large |
|
|
43
|
+
|
|
44
|
+
## Error Scenarios
|
|
45
|
+
|
|
46
|
+
### [Network Errors and Timeouts](./network-errors.md)
|
|
47
|
+
Handle connection failures, DNS issues, and request timeouts:
|
|
48
|
+
```typescript
|
|
49
|
+
try {
|
|
50
|
+
const user = await userApi.get(userKey);
|
|
51
|
+
} catch (error) {
|
|
52
|
+
if (error.code === 'NETWORK_ERROR') {
|
|
53
|
+
// Automatically retried with exponential backoff
|
|
54
|
+
console.log('Network recovered after retries');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### [Authentication and Authorization](./auth-errors.md)
|
|
60
|
+
Manage authentication failures and permission issues:
|
|
61
|
+
```typescript
|
|
62
|
+
try {
|
|
63
|
+
const data = await api.get(key);
|
|
64
|
+
} catch (error) {
|
|
65
|
+
if (error.code === 'AUTHENTICATION_ERROR') {
|
|
66
|
+
// Redirect to login
|
|
67
|
+
redirectToLogin();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### [Validation Errors](./validation-errors.md)
|
|
73
|
+
Handle request validation and data format issues:
|
|
74
|
+
```typescript
|
|
75
|
+
try {
|
|
76
|
+
const user = await userApi.create(userData);
|
|
77
|
+
} catch (error) {
|
|
78
|
+
if (error.code === 'VALIDATION_ERROR') {
|
|
79
|
+
// Display field-specific errors
|
|
80
|
+
error.validationErrors.forEach(err => {
|
|
81
|
+
showFieldError(err.field, err.message);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### [Server Errors and Retry Logic](./server-errors.md)
|
|
88
|
+
Automatic retry for server-side failures:
|
|
89
|
+
```typescript
|
|
90
|
+
// Server errors (5xx) are automatically retried
|
|
91
|
+
const user = await userApi.create(userData);
|
|
92
|
+
// Will retry up to configured maximum on 500, 502, 503 errors
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### [Rate Limiting](./rate-limiting.md)
|
|
96
|
+
Respect API rate limits with appropriate delays:
|
|
97
|
+
```typescript
|
|
98
|
+
try {
|
|
99
|
+
const results = await api.all(query);
|
|
100
|
+
} catch (error) {
|
|
101
|
+
if (error.code === 'RATE_LIMIT_ERROR') {
|
|
102
|
+
// Automatically retried after rate limit period
|
|
103
|
+
console.log('Rate limit recovered');
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### [Custom Error Handling](./custom-errors.md)
|
|
109
|
+
Implement custom error handlers for monitoring and business logic:
|
|
110
|
+
```typescript
|
|
111
|
+
const config = {
|
|
112
|
+
errorHandler: (error, context) => {
|
|
113
|
+
// Send to monitoring service
|
|
114
|
+
monitoring.recordError(error, context);
|
|
115
|
+
|
|
116
|
+
// Business-specific error handling
|
|
117
|
+
if (error.code === 'PAYMENT_FAILED') {
|
|
118
|
+
sendPaymentFailureNotification(context.customerId);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Configuration
|
|
125
|
+
|
|
126
|
+
### Basic Configuration
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
const config = {
|
|
130
|
+
baseUrl: 'https://api.example.com',
|
|
131
|
+
retryConfig: {
|
|
132
|
+
maxRetries: 3,
|
|
133
|
+
initialDelayMs: 1000,
|
|
134
|
+
maxDelayMs: 30000,
|
|
135
|
+
backoffMultiplier: 2,
|
|
136
|
+
enableJitter: true
|
|
137
|
+
},
|
|
138
|
+
enableErrorHandling: true
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const userApi = createPItemApi<User, 'user'>('user', ['users'], config);
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Advanced Configuration
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
const enterpriseConfig = {
|
|
148
|
+
baseUrl: 'https://api.enterprise.com',
|
|
149
|
+
retryConfig: {
|
|
150
|
+
maxRetries: 5,
|
|
151
|
+
initialDelayMs: 500,
|
|
152
|
+
maxDelayMs: 60000,
|
|
153
|
+
backoffMultiplier: 1.5,
|
|
154
|
+
enableJitter: true,
|
|
155
|
+
|
|
156
|
+
// Custom retry logic
|
|
157
|
+
shouldRetry: (error, attemptNumber) => {
|
|
158
|
+
if (error.code === 'PAYMENT_ERROR') {
|
|
159
|
+
return attemptNumber < 2; // Limited retries for payments
|
|
160
|
+
}
|
|
161
|
+
return error.isRetryable && attemptNumber < 5;
|
|
162
|
+
},
|
|
163
|
+
|
|
164
|
+
// Custom retry callback
|
|
165
|
+
onRetry: (error, attemptNumber, delay) => {
|
|
166
|
+
logger.warn(`Retrying API call (attempt ${attemptNumber + 1})`, {
|
|
167
|
+
errorCode: error.code,
|
|
168
|
+
delay
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
// Custom error handler
|
|
174
|
+
errorHandler: (error, context) => {
|
|
175
|
+
// Centralized error logging
|
|
176
|
+
logger.error('API Operation Failed', {
|
|
177
|
+
error: error.message,
|
|
178
|
+
code: error.code,
|
|
179
|
+
operation: context.operation,
|
|
180
|
+
duration: context.duration
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// Send to error tracking
|
|
184
|
+
errorTracking.captureException(error, { extra: context });
|
|
185
|
+
|
|
186
|
+
// Business logic
|
|
187
|
+
if (error.code === 'RATE_LIMIT_ERROR') {
|
|
188
|
+
circuitBreaker.recordFailure();
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Best Practices
|
|
195
|
+
|
|
196
|
+
### 1. **Handle Specific Error Types**
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
try {
|
|
200
|
+
const result = await api.operation();
|
|
201
|
+
} catch (error) {
|
|
202
|
+
switch (error.code) {
|
|
203
|
+
case 'VALIDATION_ERROR':
|
|
204
|
+
handleValidationErrors(error.validationErrors);
|
|
205
|
+
break;
|
|
206
|
+
case 'AUTHENTICATION_ERROR':
|
|
207
|
+
redirectToLogin();
|
|
208
|
+
break;
|
|
209
|
+
case 'NETWORK_ERROR':
|
|
210
|
+
showNetworkErrorMessage();
|
|
211
|
+
break;
|
|
212
|
+
default:
|
|
213
|
+
showGenericErrorMessage(error.message);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### 2. **Implement Graceful Degradation**
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
async function getUserWithFallback(userKey: PriKey<'user'>): Promise<User | null> {
|
|
222
|
+
try {
|
|
223
|
+
return await userApi.get(userKey);
|
|
224
|
+
} catch (error) {
|
|
225
|
+
if (error.code === 'NETWORK_ERROR') {
|
|
226
|
+
// Use cached data
|
|
227
|
+
return await getCachedUser(userKey);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (error.code === 'NOT_FOUND_ERROR') {
|
|
231
|
+
// Return null gracefully
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Re-throw unexpected errors
|
|
236
|
+
throw error;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### 3. **Monitor Error Patterns**
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
const errorMetrics = {
|
|
245
|
+
recordError: (error: ClientApiError, context: any) => {
|
|
246
|
+
metrics.increment('api.errors.total', {
|
|
247
|
+
error_code: error.code,
|
|
248
|
+
operation: context.operation
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
if (error.isRetryable) {
|
|
252
|
+
metrics.increment('api.errors.retryable');
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### 4. **Business Workflow Recovery**
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
async function createOrderWithRecovery(orderData: OrderData) {
|
|
262
|
+
const transaction = await beginTransaction();
|
|
263
|
+
|
|
264
|
+
try {
|
|
265
|
+
const order = await orderApi.create(orderData);
|
|
266
|
+
const payment = await paymentApi.create({ orderId: order.id });
|
|
267
|
+
|
|
268
|
+
await transaction.commit();
|
|
269
|
+
return order;
|
|
270
|
+
|
|
271
|
+
} catch (error) {
|
|
272
|
+
await transaction.rollback();
|
|
273
|
+
|
|
274
|
+
// Compensating actions
|
|
275
|
+
if (error.code === 'PAYMENT_FAILED') {
|
|
276
|
+
await notificationService.sendPaymentFailure(orderData.customerId);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
throw error;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## Testing Error Scenarios
|
|
285
|
+
|
|
286
|
+
### Unit Testing
|
|
287
|
+
|
|
288
|
+
```typescript
|
|
289
|
+
describe('Error Handling', () => {
|
|
290
|
+
it('should retry on network errors', async () => {
|
|
291
|
+
const mockApi = createMockApi();
|
|
292
|
+
mockApi.httpGet.mockRejectedValueOnce(new NetworkError('Connection failed'));
|
|
293
|
+
mockApi.httpGet.mockResolvedValueOnce({ id: 'user-123' });
|
|
294
|
+
|
|
295
|
+
const user = await userApi.get(userKey);
|
|
296
|
+
expect(user.id).toBe('user-123');
|
|
297
|
+
expect(mockApi.httpGet).toHaveBeenCalledTimes(2);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it('should not retry on validation errors', async () => {
|
|
301
|
+
const mockApi = createMockApi();
|
|
302
|
+
mockApi.httpPost.mockRejectedValue(new ValidationError('Invalid data'));
|
|
303
|
+
|
|
304
|
+
await expect(userApi.create({})).rejects.toThrow('Invalid data');
|
|
305
|
+
expect(mockApi.httpPost).toHaveBeenCalledTimes(1);
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### Integration Testing
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
describe('Error Recovery', () => {
|
|
314
|
+
it('should handle server downtime gracefully', async () => {
|
|
315
|
+
// Simulate server being down then recovering
|
|
316
|
+
server.use(
|
|
317
|
+
rest.get('/api/users/:id', (req, res, ctx) => {
|
|
318
|
+
return res.once(ctx.status(500));
|
|
319
|
+
}),
|
|
320
|
+
rest.get('/api/users/:id', (req, res, ctx) => {
|
|
321
|
+
return res(ctx.json({ id: 'user-123' }));
|
|
322
|
+
})
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
const user = await userApi.get(userKey);
|
|
326
|
+
expect(user.id).toBe('user-123');
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
## Production Checklist
|
|
332
|
+
|
|
333
|
+
- [ ] Configure appropriate retry strategies for your use case
|
|
334
|
+
- [ ] Implement custom error handlers for monitoring and alerting
|
|
335
|
+
- [ ] Set up error tracking (Sentry, Datadog, etc.)
|
|
336
|
+
- [ ] Add business-specific error recovery logic
|
|
337
|
+
- [ ] Implement graceful degradation for critical paths
|
|
338
|
+
- [ ] Monitor error rates and patterns
|
|
339
|
+
- [ ] Test error scenarios in staging environments
|
|
340
|
+
- [ ] Document error handling procedures for your team
|
|
341
|
+
- [ ] Set up alerts for critical error patterns
|
|
342
|
+
- [ ] Implement circuit breakers for external services
|
|
343
|
+
|
|
344
|
+
## Related Documentation
|
|
345
|
+
|
|
346
|
+
- [Configuration Guide](../configuration.md) - Configure error handling behavior
|
|
347
|
+
- [Operations](../operations/README.md) - Error handling in specific operations
|
|
348
|
+
- [Examples](../../examples-README.md) - Error handling examples and patterns
|
|
349
|
+
|
|
350
|
+
## Next Steps
|
|
351
|
+
|
|
352
|
+
1. Review specific error scenario documentation
|
|
353
|
+
2. Configure error handling for your environment
|
|
354
|
+
3. Implement custom error handlers for your monitoring stack
|
|
355
|
+
4. Test error scenarios in your application
|
|
356
|
+
5. Set up monitoring and alerting for production
|