feat: Implement comprehensive web request handling with caching, retry, and interceptors

- Added cache strategies: NetworkFirst, CacheFirst, StaleWhileRevalidate, NetworkOnly, and CacheOnly.
- Introduced InterceptorManager for managing request, response, and error interceptors.
- Developed RetryManager for handling request retries with customizable backoff strategies.
- Implemented RequestDeduplicator to prevent simultaneous identical requests.
- Created timeout utilities for handling request timeouts.
- Enhanced WebrequestClient to support global interceptors, caching, and retry logic.
- Added convenience methods for common HTTP methods (GET, POST, PUT, DELETE) with JSON handling.
- Established a fetch-compatible webrequest function for seamless integration.
- Defined core type structures for caching, retry options, interceptors, and web request configurations.
This commit is contained in:
2025-10-20 09:59:24 +00:00
parent e228ed4ba0
commit 54afcc46e2
30 changed files with 18693 additions and 4031 deletions

339
migration-v4.md Normal file
View File

@@ -0,0 +1,339 @@
# Migration Guide: v3 → v4
## Overview
Version 4.0 is a complete modernization of `@push.rocks/webrequest`, bringing it in line with modern web standards while maintaining backward compatibility through a deprecated API layer.
## What's New in v4
### Core Improvements
- **Fetch-Compatible API**: Drop-in replacement for native `fetch()` with enhanced features
- **Intelligent HTTP Caching**: Respects `Cache-Control`, `ETag`, `Last-Modified`, and `Expires` headers
- **Multiple Cache Strategies**: network-first, cache-first, stale-while-revalidate, network-only, cache-only
- **Advanced Retry System**: Configurable retry with exponential/linear/constant backoff
- **Request/Response Interceptors**: Middleware pattern for transforming requests and responses
- **Request Deduplication**: Automatically deduplicate simultaneous identical requests
- **TypeScript Generics**: Type-safe response parsing with `webrequest.getJson<T>()`
- **Better Fault Tolerance**: Enhanced multi-endpoint fallback with retry strategies
### Bug Fixes
- Fixed: `deleteJson()` now correctly uses `DELETE` method instead of `GET`
## Migration Path
### Option 1: Quick Migration (Recommended)
The v3 `WebRequest` class is still available but marked as deprecated. Your existing code will continue to work:
```typescript
// This still works in v4 (with deprecation warnings)
import { WebRequest } from '@push.rocks/webrequest';
const client = new WebRequest({ logging: true });
const data = await client.getJson('https://api.example.com/data', true);
```
### Option 2: Migrate to New API
#### Basic Fetch-Compatible Usage
```typescript
// v3
import { WebRequest } from '@push.rocks/webrequest';
const client = new WebRequest();
const response = await client.request('https://api.example.com/data', {
method: 'GET'
});
const data = await response.json();
// v4 - Fetch-compatible
import { webrequest } from '@push.rocks/webrequest';
const response = await webrequest('https://api.example.com/data');
const data = await response.json();
```
#### JSON Convenience Methods
```typescript
// v3
const client = new WebRequest();
const data = await client.getJson('https://api.example.com/data', true);
// v4 - Function API
import { webrequest } from '@push.rocks/webrequest';
const data = await webrequest.getJson('https://api.example.com/data', {
cacheStrategy: 'cache-first'
});
// v4 - Client API (similar to v3)
import { WebrequestClient } from '@push.rocks/webrequest';
const client = new WebrequestClient({ logging: true });
const data = await client.getJson('https://api.example.com/data', {
cacheStrategy: 'cache-first'
});
```
## Feature Comparison
### Caching
```typescript
// v3 - Boolean flag
const data = await client.getJson(url, true); // useCache = true
// v4 - Explicit strategies
const data = await webrequest.getJson(url, {
cacheStrategy: 'cache-first',
cacheMaxAge: 60000 // 60 seconds
});
// v4 - HTTP header-based caching (automatic)
const data = await webrequest.getJson(url, {
cacheStrategy: 'network-first' // Respects Cache-Control headers
});
// v4 - Stale-while-revalidate
const data = await webrequest.getJson(url, {
cacheStrategy: 'stale-while-revalidate' // Return cache, update in background
});
```
### Multi-Endpoint Fallback
```typescript
// v3
const client = new WebRequest();
const response = await client.requestMultiEndpoint(
['https://api1.example.com/data', 'https://api2.example.com/data'],
{ method: 'GET' }
);
// v4
import { webrequest } from '@push.rocks/webrequest';
const response = await webrequest('https://api1.example.com/data', {
fallbackUrls: ['https://api2.example.com/data'],
retry: {
maxAttempts: 3,
backoff: 'exponential'
}
});
```
### Timeout
```typescript
// v3
const response = await client.request(url, {
method: 'GET',
timeoutMs: 30000
});
// v4
const response = await webrequest(url, {
timeout: 30000 // milliseconds
});
```
## New Features in v4
### Retry Strategies
```typescript
import { webrequest } from '@push.rocks/webrequest';
const response = await webrequest('https://api.example.com/data', {
retry: {
maxAttempts: 3,
backoff: 'exponential', // or 'linear', 'constant'
initialDelay: 1000,
maxDelay: 30000,
retryOn: [408, 429, 500, 502, 503, 504], // Status codes to retry
onRetry: (attempt, error, nextDelay) => {
console.log(`Retry attempt ${attempt}, waiting ${nextDelay}ms`);
}
}
});
```
### Request/Response Interceptors
```typescript
import { webrequest } from '@push.rocks/webrequest';
// Add global request interceptor
webrequest.addRequestInterceptor((request) => {
// Add auth header
const headers = new Headers(request.headers);
headers.set('Authorization', `Bearer ${getToken()}`);
return new Request(request, { headers });
});
// Add global response interceptor
webrequest.addResponseInterceptor((response) => {
console.log(`Response: ${response.status} ${response.url}`);
return response;
});
// Per-request interceptors
const response = await webrequest('https://api.example.com/data', {
interceptors: {
request: [(req) => {
console.log('Sending request:', req.url);
return req;
}],
response: [(res) => {
console.log('Received response:', res.status);
return res;
}]
}
});
```
### Request Deduplication
```typescript
import { webrequest } from '@push.rocks/webrequest';
// Multiple simultaneous identical requests will be deduplicated
const [res1, res2, res3] = await Promise.all([
webrequest('https://api.example.com/data', { deduplicate: true }),
webrequest('https://api.example.com/data', { deduplicate: true }),
webrequest('https://api.example.com/data', { deduplicate: true }),
]);
// Only one actual network request is made
```
### TypeScript Generics
```typescript
import { webrequest } from '@push.rocks/webrequest';
interface User {
id: number;
name: string;
email: string;
}
// Type-safe response parsing
const user = await webrequest.getJson<User>('https://api.example.com/user/1');
// user is typed as User
const users = await webrequest.getJson<User[]>('https://api.example.com/users');
// users is typed as User[]
```
### Custom Cache Keys
```typescript
import { webrequest } from '@push.rocks/webrequest';
const response = await webrequest('https://api.example.com/search?q=test', {
cacheStrategy: 'cache-first',
cacheKey: (request) => {
// Custom cache key based on URL without query params
const url = new URL(request.url);
return `search:${url.searchParams.get('q')}`;
}
});
```
### Advanced Client Configuration
```typescript
import { WebrequestClient } from '@push.rocks/webrequest';
// Create a client with default options
const apiClient = new WebrequestClient({
logging: true,
timeout: 30000,
cacheStrategy: 'network-first',
retry: {
maxAttempts: 3,
backoff: 'exponential'
}
});
// Add global interceptors
apiClient.addRequestInterceptor((request) => {
const headers = new Headers(request.headers);
headers.set('X-API-Key', process.env.API_KEY);
return new Request(request, { headers });
});
// All requests through this client use the configured defaults
const data = await apiClient.getJson('https://api.example.com/data');
```
## Breaking Changes
### 1. Cache API Changes
**Before (v3):**
```typescript
await client.getJson(url, true); // Boolean for cache
```
**After (v4):**
```typescript
await webrequest.getJson(url, {
cacheStrategy: 'cache-first' // Explicit strategy
});
```
### 2. Multi-Endpoint API
**Before (v3):**
```typescript
await client.requestMultiEndpoint([url1, url2], options);
```
**After (v4):**
```typescript
await webrequest(url1, {
fallbackUrls: [url2],
retry: true
});
```
### 3. Constructor Options
**Before (v3):**
```typescript
const client = new WebRequest({ logging: true });
```
**After (v4):**
```typescript
// Function API (no constructor)
import { webrequest } from '@push.rocks/webrequest';
// Or client API
import { WebrequestClient } from '@push.rocks/webrequest';
const client = new WebrequestClient({ logging: true });
```
## Deprecation Timeline
- **v4.0**: `WebRequest` class marked as deprecated but fully functional
- **v4.x**: Continued support with deprecation warnings
- **v5.0**: `WebRequest` class will be removed
## Recommendation
We strongly recommend migrating to the new API to take advantage of:
- Standards-aligned fetch-compatible interface
- Intelligent HTTP caching with header support
- Advanced retry and fault tolerance
- Request/response interceptors
- Better TypeScript support
- Request deduplication
The migration is straightforward, and the new API is more powerful and flexible while being simpler to use.
## Need Help?
- Check the updated [README.md](./readme.md) for comprehensive examples
- Report issues at: https://code.foss.global/push.rocks/webrequest/issues