@fjell/registry 4.4.52 → 4.4.54
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/package.json +4 -4
- package/API.md +0 -62
- package/GETTING_STARTED.md +0 -69
- package/build.js +0 -38
- package/docs/README.md +0 -74
- package/docs/TIMING_NODE_OPTIMIZATION.md +0 -207
- package/docs/TIMING_README.md +0 -170
- package/docs/docs.config.ts +0 -114
- package/docs/index.html +0 -26
- package/docs/package-lock.json +0 -5129
- package/docs/package.json +0 -34
- package/docs/public/404.html +0 -53
- package/docs/public/GETTING_STARTED.md +0 -69
- package/docs/public/README.md +0 -623
- package/docs/public/TIMING_NODE_OPTIMIZATION.md +0 -207
- package/docs/public/api.md +0 -62
- package/docs/public/client-api-overview.md +0 -137
- package/docs/public/configuration.md +0 -759
- package/docs/public/error-handling/README.md +0 -356
- package/docs/public/error-handling/network-errors.md +0 -485
- package/docs/public/examples/coordinates-example.ts +0 -253
- package/docs/public/examples/multi-level-keys.ts +0 -374
- package/docs/public/examples/registry-hub-coordinates-example.ts +0 -370
- package/docs/public/examples/registry-hub-types.ts +0 -437
- package/docs/public/examples/simple-example.ts +0 -250
- package/docs/public/examples-README.md +0 -241
- package/docs/public/fjell-icon.svg +0 -1
- package/docs/public/icon.png +0 -0
- package/docs/public/icon2.png +0 -0
- package/docs/public/memory-overhead.svg +0 -120
- package/docs/public/memory.md +0 -430
- package/docs/public/operations/README.md +0 -121
- package/docs/public/operations/all.md +0 -325
- package/docs/public/operations/create.md +0 -415
- package/docs/public/operations/get.md +0 -389
- package/docs/public/package.json +0 -63
- package/docs/public/pano.png +0 -0
- package/docs/public/pano2.png +0 -0
- package/docs/public/timing-range.svg +0 -176
- package/docs/public/timing.md +0 -483
- package/docs/timing-range.svg +0 -174
- package/docs/vitest.config.ts +0 -14
- package/examples/README.md +0 -241
- package/examples/coordinates-example.ts +0 -253
- package/examples/multi-level-keys.ts +0 -382
- package/examples/registry-hub-coordinates-example.ts +0 -370
- package/examples/registry-hub-types.ts +0 -437
- package/examples/registry-statistics-example.ts +0 -264
- package/examples/simple-example.ts +0 -250
- package/tsconfig.docs.json +0 -28
- package/vitest.config.ts +0 -22
|
@@ -1,485 +0,0 @@
|
|
|
1
|
-
# Network Errors and Timeouts
|
|
2
|
-
|
|
3
|
-
Handle connection failures, DNS issues, and request timeouts with automatic retry logic.
|
|
4
|
-
|
|
5
|
-
## Overview
|
|
6
|
-
|
|
7
|
-
Network errors are among the most common issues in distributed systems. The Fjell Client API automatically handles these transient failures with intelligent retry logic and exponential backoff.
|
|
8
|
-
|
|
9
|
-
## Error Types
|
|
10
|
-
|
|
11
|
-
### NetworkError
|
|
12
|
-
- **Code**: `NETWORK_ERROR`
|
|
13
|
-
- **Retryable**: ✅ Yes
|
|
14
|
-
- **Common Causes**: Connection refused, DNS resolution failure, network unreachable
|
|
15
|
-
- **HTTP Equivalents**: Connection timeouts, DNS errors, network unavailable
|
|
16
|
-
|
|
17
|
-
### TimeoutError
|
|
18
|
-
- **Code**: `TIMEOUT_ERROR`
|
|
19
|
-
- **Retryable**: ✅ Yes
|
|
20
|
-
- **Common Causes**: Request takes longer than configured timeout
|
|
21
|
-
- **HTTP Equivalents**: Request timeout, gateway timeout
|
|
22
|
-
|
|
23
|
-
## Automatic Retry Behavior
|
|
24
|
-
|
|
25
|
-
Network errors are automatically retried with exponential backoff:
|
|
26
|
-
|
|
27
|
-
```typescript
|
|
28
|
-
// This will automatically retry on network failures
|
|
29
|
-
try {
|
|
30
|
-
const user = await userApi.get(userKey);
|
|
31
|
-
console.log('Success (possibly after retries):', user.name);
|
|
32
|
-
} catch (error) {
|
|
33
|
-
// Only reaches here after all retry attempts failed
|
|
34
|
-
console.error('Network completely unavailable:', error.message);
|
|
35
|
-
}
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
### Default Retry Configuration
|
|
39
|
-
|
|
40
|
-
```typescript
|
|
41
|
-
const defaultRetryConfig = {
|
|
42
|
-
maxRetries: 3, // Maximum retry attempts
|
|
43
|
-
initialDelayMs: 1000, // Initial delay: 1 second
|
|
44
|
-
maxDelayMs: 30000, // Maximum delay: 30 seconds
|
|
45
|
-
backoffMultiplier: 2, // Double delay each time
|
|
46
|
-
enableJitter: true // Add randomness to prevent thundering herd
|
|
47
|
-
};
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
### Retry Sequence Example
|
|
51
|
-
|
|
52
|
-
For a network error with default configuration:
|
|
53
|
-
|
|
54
|
-
1. **Initial attempt**: Fails with `ECONNREFUSED`
|
|
55
|
-
2. **Retry 1**: Wait ~1000ms (with jitter: 500-1000ms), retry
|
|
56
|
-
3. **Retry 2**: Wait ~2000ms (with jitter: 1000-2000ms), retry
|
|
57
|
-
4. **Retry 3**: Wait ~4000ms (with jitter: 2000-4000ms), retry
|
|
58
|
-
5. **Final failure**: If still failing, throw error
|
|
59
|
-
|
|
60
|
-
## Configuration
|
|
61
|
-
|
|
62
|
-
### Basic Network Resilience
|
|
63
|
-
|
|
64
|
-
```typescript
|
|
65
|
-
const config = {
|
|
66
|
-
baseUrl: 'https://api.example.com',
|
|
67
|
-
retryConfig: {
|
|
68
|
-
maxRetries: 5, // More retries for critical operations
|
|
69
|
-
initialDelayMs: 500, // Faster initial retry
|
|
70
|
-
maxDelayMs: 60000, // Allow longer delays
|
|
71
|
-
enableJitter: true // Prevent retry storms
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
const userApi = createPItemApi<User, 'user'>('user', ['users'], config);
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
### Custom Network Error Handling
|
|
79
|
-
|
|
80
|
-
```typescript
|
|
81
|
-
const networkResilientConfig = {
|
|
82
|
-
baseUrl: 'https://api.example.com',
|
|
83
|
-
retryConfig: {
|
|
84
|
-
maxRetries: 3,
|
|
85
|
-
initialDelayMs: 1000,
|
|
86
|
-
maxDelayMs: 30000,
|
|
87
|
-
backoffMultiplier: 2,
|
|
88
|
-
enableJitter: true,
|
|
89
|
-
|
|
90
|
-
// Custom retry logic for network errors
|
|
91
|
-
shouldRetry: (error, attemptNumber) => {
|
|
92
|
-
// Always retry network and timeout errors
|
|
93
|
-
if (error.code === 'NETWORK_ERROR' || error.code === 'TIMEOUT_ERROR') {
|
|
94
|
-
return attemptNumber < 5; // Allow up to 5 retries for network issues
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Default retry logic for other errors
|
|
98
|
-
return error.isRetryable && attemptNumber < 3;
|
|
99
|
-
},
|
|
100
|
-
|
|
101
|
-
// Monitor retry attempts
|
|
102
|
-
onRetry: (error, attemptNumber, delay) => {
|
|
103
|
-
console.log(`Network retry ${attemptNumber + 1}: ${error.message} (delay: ${delay}ms)`);
|
|
104
|
-
|
|
105
|
-
// Send metrics to monitoring service
|
|
106
|
-
metrics.increment('api.network.retry', {
|
|
107
|
-
error_code: error.code,
|
|
108
|
-
attempt: attemptNumber + 1
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
};
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
## Error Handling Patterns
|
|
116
|
-
|
|
117
|
-
### Basic Network Error Handling
|
|
118
|
-
|
|
119
|
-
```typescript
|
|
120
|
-
async function fetchUserSafely(userKey: PriKey<'user'>): Promise<User | null> {
|
|
121
|
-
try {
|
|
122
|
-
return await userApi.get(userKey);
|
|
123
|
-
} catch (error) {
|
|
124
|
-
if (error.code === 'NETWORK_ERROR') {
|
|
125
|
-
console.log('Network issue resolved automatically or exhausted retries');
|
|
126
|
-
|
|
127
|
-
// Could show user-friendly message
|
|
128
|
-
showNotification('Network connection issues. Please try again later.');
|
|
129
|
-
return null;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (error.code === 'TIMEOUT_ERROR') {
|
|
133
|
-
console.log('Request timed out after retries');
|
|
134
|
-
|
|
135
|
-
// Could suggest checking connection
|
|
136
|
-
showNotification('Request timed out. Please check your connection.');
|
|
137
|
-
return null;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Re-throw unexpected errors
|
|
141
|
-
throw error;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
### Graceful Degradation
|
|
147
|
-
|
|
148
|
-
```typescript
|
|
149
|
-
async function getUserWithFallback(userKey: PriKey<'user'>): Promise<User | null> {
|
|
150
|
-
try {
|
|
151
|
-
// Try primary API with retry
|
|
152
|
-
return await userApi.get(userKey);
|
|
153
|
-
|
|
154
|
-
} catch (error) {
|
|
155
|
-
if (error.code === 'NETWORK_ERROR' || error.code === 'TIMEOUT_ERROR') {
|
|
156
|
-
console.log('Primary API unavailable, trying alternatives...');
|
|
157
|
-
|
|
158
|
-
// Try cached data
|
|
159
|
-
try {
|
|
160
|
-
const cachedUser = await cache.get(`user:${userKey.pk}`);
|
|
161
|
-
if (cachedUser) {
|
|
162
|
-
console.log('Serving from cache');
|
|
163
|
-
return cachedUser;
|
|
164
|
-
}
|
|
165
|
-
} catch (cacheError) {
|
|
166
|
-
console.warn('Cache also unavailable:', cacheError.message);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Try backup API
|
|
170
|
-
try {
|
|
171
|
-
const backupUser = await backupUserApi.get(userKey);
|
|
172
|
-
console.log('Served from backup API');
|
|
173
|
-
return backupUser;
|
|
174
|
-
} catch (backupError) {
|
|
175
|
-
console.warn('Backup API also failed:', backupError.message);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// All sources failed
|
|
179
|
-
console.error('All data sources unavailable');
|
|
180
|
-
return null;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Non-network errors
|
|
184
|
-
throw error;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
### Circuit Breaker Pattern
|
|
190
|
-
|
|
191
|
-
```typescript
|
|
192
|
-
class CircuitBreaker {
|
|
193
|
-
private failures = 0;
|
|
194
|
-
private lastFailureTime = 0;
|
|
195
|
-
private state: 'CLOSED' | 'OPEN' | 'HALF_OPEN' = 'CLOSED';
|
|
196
|
-
|
|
197
|
-
constructor(
|
|
198
|
-
private failureThreshold = 5,
|
|
199
|
-
private recoveryTimeMs = 30000
|
|
200
|
-
) {}
|
|
201
|
-
|
|
202
|
-
async execute<T>(operation: () => Promise<T>): Promise<T> {
|
|
203
|
-
if (this.state === 'OPEN') {
|
|
204
|
-
if (Date.now() - this.lastFailureTime > this.recoveryTimeMs) {
|
|
205
|
-
this.state = 'HALF_OPEN';
|
|
206
|
-
} else {
|
|
207
|
-
throw new Error('Circuit breaker is OPEN');
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
try {
|
|
212
|
-
const result = await operation();
|
|
213
|
-
|
|
214
|
-
// Success - reset circuit breaker
|
|
215
|
-
if (this.state === 'HALF_OPEN') {
|
|
216
|
-
this.state = 'CLOSED';
|
|
217
|
-
this.failures = 0;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
return result;
|
|
221
|
-
|
|
222
|
-
} catch (error) {
|
|
223
|
-
this.failures++;
|
|
224
|
-
this.lastFailureTime = Date.now();
|
|
225
|
-
|
|
226
|
-
if (error.code === 'NETWORK_ERROR' || error.code === 'TIMEOUT_ERROR') {
|
|
227
|
-
if (this.failures >= this.failureThreshold) {
|
|
228
|
-
this.state = 'OPEN';
|
|
229
|
-
console.log('Circuit breaker opened due to network failures');
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
throw error;
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// Usage
|
|
239
|
-
const circuitBreaker = new CircuitBreaker(3, 10000); // 3 failures, 10s recovery
|
|
240
|
-
|
|
241
|
-
async function resilientApiCall(userKey: PriKey<'user'>): Promise<User | null> {
|
|
242
|
-
try {
|
|
243
|
-
return await circuitBreaker.execute(() => userApi.get(userKey));
|
|
244
|
-
} catch (error) {
|
|
245
|
-
if (error.message === 'Circuit breaker is OPEN') {
|
|
246
|
-
console.log('Circuit breaker preventing calls, using fallback');
|
|
247
|
-
return await getFallbackUser(userKey);
|
|
248
|
-
}
|
|
249
|
-
throw error;
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
## Monitoring and Observability
|
|
255
|
-
|
|
256
|
-
### Network Error Metrics
|
|
257
|
-
|
|
258
|
-
```typescript
|
|
259
|
-
const networkMetrics = {
|
|
260
|
-
// Track network error rates
|
|
261
|
-
recordNetworkError: (error: NetworkError, context: any) => {
|
|
262
|
-
metrics.increment('api.network.errors', {
|
|
263
|
-
error_type: error.code,
|
|
264
|
-
endpoint: context.url,
|
|
265
|
-
method: context.method
|
|
266
|
-
});
|
|
267
|
-
},
|
|
268
|
-
|
|
269
|
-
// Track retry success rates
|
|
270
|
-
recordRetrySuccess: (attemptNumber: number, context: any) => {
|
|
271
|
-
metrics.increment('api.network.retry_success', {
|
|
272
|
-
attempt_number: attemptNumber,
|
|
273
|
-
endpoint: context.url
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
metrics.timing('api.network.recovery_time', context.duration, {
|
|
277
|
-
attempts: attemptNumber
|
|
278
|
-
});
|
|
279
|
-
},
|
|
280
|
-
|
|
281
|
-
// Track connection health
|
|
282
|
-
recordConnectionHealth: (isHealthy: boolean) => {
|
|
283
|
-
metrics.gauge('api.network.health', isHealthy ? 1 : 0);
|
|
284
|
-
}
|
|
285
|
-
};
|
|
286
|
-
|
|
287
|
-
// Custom error handler with metrics
|
|
288
|
-
const config = {
|
|
289
|
-
errorHandler: (error, context) => {
|
|
290
|
-
if (error.code === 'NETWORK_ERROR' || error.code === 'TIMEOUT_ERROR') {
|
|
291
|
-
networkMetrics.recordNetworkError(error, context);
|
|
292
|
-
|
|
293
|
-
// Alert on high network error rates
|
|
294
|
-
if (context.totalAttempts >= 3) {
|
|
295
|
-
alerting.sendAlert({
|
|
296
|
-
severity: 'warning',
|
|
297
|
-
message: `High network error rate: ${error.message}`,
|
|
298
|
-
context
|
|
299
|
-
});
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
};
|
|
304
|
-
```
|
|
305
|
-
|
|
306
|
-
### Health Checks
|
|
307
|
-
|
|
308
|
-
```typescript
|
|
309
|
-
async function performHealthCheck(): Promise<boolean> {
|
|
310
|
-
try {
|
|
311
|
-
// Simple health check endpoint
|
|
312
|
-
await healthApi.get({ keyType: 'health', pk: 'status' });
|
|
313
|
-
networkMetrics.recordConnectionHealth(true);
|
|
314
|
-
return true;
|
|
315
|
-
} catch (error) {
|
|
316
|
-
if (error.code === 'NETWORK_ERROR' || error.code === 'TIMEOUT_ERROR') {
|
|
317
|
-
networkMetrics.recordConnectionHealth(false);
|
|
318
|
-
console.warn('Health check failed:', error.message);
|
|
319
|
-
return false;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
// API is reachable but returned an error
|
|
323
|
-
networkMetrics.recordConnectionHealth(true);
|
|
324
|
-
return true;
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
// Run health checks periodically
|
|
329
|
-
setInterval(performHealthCheck, 30000); // Every 30 seconds
|
|
330
|
-
```
|
|
331
|
-
|
|
332
|
-
## Testing Network Errors
|
|
333
|
-
|
|
334
|
-
### Unit Testing
|
|
335
|
-
|
|
336
|
-
```typescript
|
|
337
|
-
describe('Network Error Handling', () => {
|
|
338
|
-
it('should retry on network errors', async () => {
|
|
339
|
-
const mockApi = createMockApi();
|
|
340
|
-
|
|
341
|
-
// First call fails, second succeeds
|
|
342
|
-
mockApi.httpGet
|
|
343
|
-
.mockRejectedValueOnce(new NetworkError('ECONNREFUSED'))
|
|
344
|
-
.mockResolvedValueOnce({ id: 'user-123' });
|
|
345
|
-
|
|
346
|
-
const user = await userApi.get(userKey);
|
|
347
|
-
|
|
348
|
-
expect(user.id).toBe('user-123');
|
|
349
|
-
expect(mockApi.httpGet).toHaveBeenCalledTimes(2);
|
|
350
|
-
});
|
|
351
|
-
|
|
352
|
-
it('should respect max retries', async () => {
|
|
353
|
-
const mockApi = createMockApi();
|
|
354
|
-
mockApi.httpGet.mockRejectedValue(new NetworkError('ECONNREFUSED'));
|
|
355
|
-
|
|
356
|
-
const config = { retryConfig: { maxRetries: 2 } };
|
|
357
|
-
const api = createPItemApi<User, 'user'>('user', ['users'], config);
|
|
358
|
-
|
|
359
|
-
await expect(api.get(userKey)).rejects.toThrow('ECONNREFUSED');
|
|
360
|
-
expect(mockApi.httpGet).toHaveBeenCalledTimes(3); // 1 initial + 2 retries
|
|
361
|
-
});
|
|
362
|
-
|
|
363
|
-
it('should apply exponential backoff', async () => {
|
|
364
|
-
const delays: number[] = [];
|
|
365
|
-
const mockSetTimeout = jest.spyOn(global, 'setTimeout').mockImplementation((fn, delay) => {
|
|
366
|
-
delays.push(delay);
|
|
367
|
-
return setTimeout(fn, 0); // Execute immediately for testing
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
const mockApi = createMockApi();
|
|
371
|
-
mockApi.httpGet.mockRejectedValue(new NetworkError('ECONNREFUSED'));
|
|
372
|
-
|
|
373
|
-
try {
|
|
374
|
-
await userApi.get(userKey);
|
|
375
|
-
} catch (error) {
|
|
376
|
-
// Expected to fail after retries
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
expect(delays).toHaveLength(3);
|
|
380
|
-
expect(delays[1]).toBeGreaterThan(delays[0]); // Exponential increase
|
|
381
|
-
expect(delays[2]).toBeGreaterThan(delays[1]);
|
|
382
|
-
|
|
383
|
-
mockSetTimeout.mockRestore();
|
|
384
|
-
});
|
|
385
|
-
});
|
|
386
|
-
```
|
|
387
|
-
|
|
388
|
-
### Integration Testing
|
|
389
|
-
|
|
390
|
-
```typescript
|
|
391
|
-
describe('Network Resilience Integration', () => {
|
|
392
|
-
it('should handle intermittent network issues', async () => {
|
|
393
|
-
let callCount = 0;
|
|
394
|
-
|
|
395
|
-
server.use(
|
|
396
|
-
rest.get('/api/users/:id', (req, res, ctx) => {
|
|
397
|
-
callCount++;
|
|
398
|
-
|
|
399
|
-
// Fail first two calls, succeed on third
|
|
400
|
-
if (callCount <= 2) {
|
|
401
|
-
return res.networkError('Connection failed');
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
return res(ctx.json({ id: 'user-123' }));
|
|
405
|
-
})
|
|
406
|
-
);
|
|
407
|
-
|
|
408
|
-
const user = await userApi.get(userKey);
|
|
409
|
-
expect(user.id).toBe('user-123');
|
|
410
|
-
expect(callCount).toBe(3);
|
|
411
|
-
});
|
|
412
|
-
});
|
|
413
|
-
```
|
|
414
|
-
|
|
415
|
-
## Best Practices
|
|
416
|
-
|
|
417
|
-
### 1. **Configure Appropriate Timeouts**
|
|
418
|
-
|
|
419
|
-
```typescript
|
|
420
|
-
const config = {
|
|
421
|
-
baseUrl: 'https://api.example.com',
|
|
422
|
-
timeout: 10000, // 10 second timeout
|
|
423
|
-
retryConfig: {
|
|
424
|
-
maxRetries: 3,
|
|
425
|
-
initialDelayMs: 1000,
|
|
426
|
-
maxDelayMs: 15000
|
|
427
|
-
}
|
|
428
|
-
};
|
|
429
|
-
```
|
|
430
|
-
|
|
431
|
-
### 2. **Implement Progressive Degradation**
|
|
432
|
-
|
|
433
|
-
```typescript
|
|
434
|
-
async function getDataWithDegradation(key: string) {
|
|
435
|
-
try {
|
|
436
|
-
// Try full data
|
|
437
|
-
return await api.getFullData(key);
|
|
438
|
-
} catch (error) {
|
|
439
|
-
if (error.code === 'NETWORK_ERROR') {
|
|
440
|
-
try {
|
|
441
|
-
// Try summary data (smaller payload)
|
|
442
|
-
return await api.getSummaryData(key);
|
|
443
|
-
} catch (summaryError) {
|
|
444
|
-
// Use cached minimal data
|
|
445
|
-
return await cache.getMinimalData(key);
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
throw error;
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
```
|
|
452
|
-
|
|
453
|
-
### 3. **User Experience Considerations**
|
|
454
|
-
|
|
455
|
-
```typescript
|
|
456
|
-
async function fetchDataWithUI(key: string) {
|
|
457
|
-
const loadingIndicator = showLoadingSpinner();
|
|
458
|
-
|
|
459
|
-
try {
|
|
460
|
-
const data = await api.getData(key);
|
|
461
|
-
hideLoadingSpinner(loadingIndicator);
|
|
462
|
-
return data;
|
|
463
|
-
|
|
464
|
-
} catch (error) {
|
|
465
|
-
hideLoadingSpinner(loadingIndicator);
|
|
466
|
-
|
|
467
|
-
if (error.code === 'NETWORK_ERROR') {
|
|
468
|
-
showNotification('Connection issue. Retrying automatically...', 'info');
|
|
469
|
-
|
|
470
|
-
// Give user option to retry manually
|
|
471
|
-
showRetryButton(() => fetchDataWithUI(key));
|
|
472
|
-
} else if (error.code === 'TIMEOUT_ERROR') {
|
|
473
|
-
showNotification('Request timed out. Please try again.', 'warning');
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
throw error;
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
```
|
|
480
|
-
|
|
481
|
-
## Related Documentation
|
|
482
|
-
|
|
483
|
-
- [Error Handling Overview](./README.md) - Complete error handling guide
|
|
484
|
-
- [Server Errors](./server-errors.md) - Handle 5xx server errors
|
|
485
|
-
- [Configuration](../configuration.md) - Configure retry behavior
|