Files
webrequest/migration-v4.md
Juergen Kunz 54afcc46e2 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.
2025-10-20 09:59:24 +00:00

8.7 KiB

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

The v3 WebRequest class is still available but marked as deprecated. Your existing code will continue to work:

// 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

// 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

// 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

// 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

// 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

// 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

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

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

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

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

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

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):

await client.getJson(url, true); // Boolean for cache

After (v4):

await webrequest.getJson(url, {
  cacheStrategy: 'cache-first' // Explicit strategy
});

2. Multi-Endpoint API

Before (v3):

await client.requestMultiEndpoint([url1, url2], options);

After (v4):

await webrequest(url1, {
  fallbackUrls: [url2],
  retry: true
});

3. Constructor Options

Before (v3):

const client = new WebRequest({ logging: true });

After (v4):

// 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?