# 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()` - **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('https://api.example.com/user/1'); // user is typed as User const users = await webrequest.getJson('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