- 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.
		
			
				
	
	
		
			340 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			340 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| # 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
 |