fix(tests): Fix tests and documentation: adjust test server routes and expectations, add timeout/fallback routes, and refresh README
This commit is contained in:
		
							
								
								
									
										686
									
								
								readme.md
									
									
									
									
									
								
							
							
						
						
									
										686
									
								
								readme.md
									
									
									
									
									
								
							| @@ -1,159 +1,617 @@ | ||||
| # @push.rocks/webrequest | ||||
|  | ||||
| securely request from browsers | ||||
| Modern, fetch-compatible web request library with intelligent HTTP caching, retry strategies, and advanced fault tolerance. | ||||
|  | ||||
| ## Install | ||||
| ## Features | ||||
|  | ||||
| To use `@push.rocks/webrequest` in your project, install it using npm or yarn: | ||||
| - 🌐 **Fetch-Compatible API** - Drop-in replacement for native `fetch()` with enhanced features | ||||
| - 💾 **Intelligent HTTP Caching** - Respects `Cache-Control`, `ETag`, `Last-Modified`, and `Expires` headers (RFC 7234) | ||||
| - 🔄 **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** - Multi-endpoint fallback with retry strategies | ||||
| - ⏱️ **Timeout Support** - Configurable request timeouts with AbortController | ||||
|  | ||||
| ## Installation | ||||
|  | ||||
| ```bash | ||||
| npm install @push.rocks/webrequest --save | ||||
| # or with yarn | ||||
| yarn add @push.rocks/webrequest | ||||
| pnpm install @push.rocks/webrequest | ||||
| # or | ||||
| npm install @push.rocks/webrequest | ||||
| ``` | ||||
|  | ||||
| This package is designed to be used in an environment where ECMAScript Modules (ESM) and TypeScript are supported. | ||||
| This package requires a modern JavaScript environment with ESM and TypeScript support. | ||||
|  | ||||
| ## Usage | ||||
| ## Quick Start | ||||
|  | ||||
| `@push.rocks/webrequest` is a powerful module designed to simplify making web requests securely from browsers. It leverages modern JavaScript features and TypeScript for a type-safe development experience. Below are comprehensive examples demonstrating how to utilize the module effectively: | ||||
|  | ||||
| ### Setting Up | ||||
|  | ||||
| First, import `WebRequest` from the module: | ||||
| ### Basic Fetch-Compatible Usage | ||||
|  | ||||
| ```typescript | ||||
| import { WebRequest } from '@push.rocks/webrequest'; | ||||
| ``` | ||||
| import { webrequest } from '@push.rocks/webrequest'; | ||||
|  | ||||
| Create an instance of `WebRequest`. You can optionally pass configuration options: | ||||
| // Use exactly like fetch() | ||||
| const response = await webrequest('https://api.example.com/data'); | ||||
| const data = await response.json(); | ||||
|  | ||||
| ```typescript | ||||
| const webRequest = new WebRequest({ | ||||
|   logging: true, // Optional: enables logging, defaults to true | ||||
| // With options (fetch-compatible + enhanced) | ||||
| const response = await webrequest('https://api.example.com/data', { | ||||
|   method: 'POST', | ||||
|   headers: { 'Content-Type': 'application/json' }, | ||||
|   body: JSON.stringify({ key: 'value' }), | ||||
|   timeout: 30000, | ||||
|   retry: true | ||||
| }); | ||||
| ``` | ||||
|  | ||||
| ### Making GET Requests | ||||
|  | ||||
| To fetch JSON data: | ||||
| ### JSON Convenience Methods | ||||
|  | ||||
| ```typescript | ||||
| // Fetch JSON data using GET request | ||||
| async function fetchJsonData() { | ||||
|   const url = 'https://api.example.com/data'; | ||||
|   try { | ||||
|     const jsonData = await webRequest.getJson(url); | ||||
|     console.log(jsonData); | ||||
|   } catch (error) { | ||||
|     console.error(error); | ||||
|   } | ||||
| import { webrequest } from '@push.rocks/webrequest'; | ||||
|  | ||||
| // GET JSON with type safety | ||||
| interface User { | ||||
|   id: number; | ||||
|   name: string; | ||||
|   email: string; | ||||
| } | ||||
|  | ||||
| fetchJsonData(); | ||||
| const user = await webrequest.getJson<User>('https://api.example.com/user/1'); | ||||
| // user is typed as User | ||||
|  | ||||
| // POST JSON | ||||
| const result = await webrequest.postJson('https://api.example.com/users', { | ||||
|   name: 'John Doe', | ||||
|   email: 'john@example.com' | ||||
| }); | ||||
|  | ||||
| // Other convenience methods | ||||
| await webrequest.putJson(url, data); | ||||
| await webrequest.patchJson(url, data); | ||||
| await webrequest.deleteJson(url); | ||||
| ``` | ||||
|  | ||||
| ### POST, PUT, and DELETE Requests | ||||
| ## Cache Strategies | ||||
|  | ||||
| Similarly, you can make POST, PUT, and DELETE requests to send or manipulate data: | ||||
| ### Network-First (Default) | ||||
|  | ||||
| Always fetch from network, fall back to cache on failure. Respects HTTP caching headers. | ||||
|  | ||||
| ```typescript | ||||
| // Example POST request to submit JSON data | ||||
| async function postJsonData() { | ||||
|   const url = 'https://api.example.com/submit'; | ||||
|   const data = { key: 'value' }; | ||||
| const data = await webrequest.getJson('https://api.example.com/data', { | ||||
|   cacheStrategy: 'network-first' | ||||
| }); | ||||
| ``` | ||||
|  | ||||
|   try { | ||||
|     const result = await webRequest.postJson(url, data); | ||||
|     console.log(result); | ||||
|   } catch (error) { | ||||
|     console.error(error); | ||||
| ### Cache-First | ||||
|  | ||||
| Check cache first, only fetch from network if not cached or stale. | ||||
|  | ||||
| ```typescript | ||||
| const data = await webrequest.getJson('https://api.example.com/data', { | ||||
|   cacheStrategy: 'cache-first', | ||||
|   cacheMaxAge: 60000 // 60 seconds | ||||
| }); | ||||
| ``` | ||||
|  | ||||
| ### Stale-While-Revalidate | ||||
|  | ||||
| Return cached data immediately, update in background. | ||||
|  | ||||
| ```typescript | ||||
| const data = await webrequest.getJson('https://api.example.com/data', { | ||||
|   cacheStrategy: 'stale-while-revalidate' | ||||
| }); | ||||
| ``` | ||||
|  | ||||
| ### Network-Only and Cache-Only | ||||
|  | ||||
| ```typescript | ||||
| // Always fetch from network, never cache | ||||
| const data = await webrequest.getJson(url, { | ||||
|   cacheStrategy: 'network-only' | ||||
| }); | ||||
|  | ||||
| // Only use cache, never fetch from network | ||||
| const data = await webrequest.getJson(url, { | ||||
|   cacheStrategy: 'cache-only' | ||||
| }); | ||||
| ``` | ||||
|  | ||||
| ### HTTP Header-Based Caching | ||||
|  | ||||
| The library automatically respects HTTP caching headers: | ||||
|  | ||||
| ```typescript | ||||
| // Server returns: Cache-Control: max-age=3600, ETag: "abc123" | ||||
| const response = await webrequest('https://api.example.com/data', { | ||||
|   cacheStrategy: 'network-first' | ||||
| }); | ||||
|  | ||||
| // Subsequent requests automatically send: | ||||
| // If-None-Match: "abc123" | ||||
| // Server returns 304 Not Modified - cache is used | ||||
| ``` | ||||
|  | ||||
| ### Custom Cache Keys | ||||
|  | ||||
| ```typescript | ||||
| const response = await webrequest('https://api.example.com/search?q=test', { | ||||
|   cacheStrategy: 'cache-first', | ||||
|   cacheKey: (request) => { | ||||
|     const url = new URL(request.url); | ||||
|     return `search:${url.searchParams.get('q')}`; | ||||
|   } | ||||
| } | ||||
|  | ||||
| postJsonData(); | ||||
|  | ||||
| // PUT and DELETE can be similarly used | ||||
| }); | ||||
| ``` | ||||
|  | ||||
| ### Using Caches | ||||
| ## Retry Strategies | ||||
|  | ||||
| The library provides mechanisms to cache responses, which is useful for reducing network load and improving performance. Here’s how to fetch data with caching: | ||||
| ### Basic Retry | ||||
|  | ||||
| ```typescript | ||||
| // Fetch with caching enabled | ||||
| async function fetchDataWithCache() { | ||||
|   const url = 'https://api.example.com/data'; | ||||
|   try { | ||||
|     // The second parameter enables caching | ||||
|     const jsonData = await webRequest.getJson(url, true); | ||||
|     console.log(jsonData); | ||||
|   } catch (error) { | ||||
|     console.error(error); | ||||
|   } | ||||
| } | ||||
|  | ||||
| fetchDataWithCache(); | ||||
| const response = await webrequest('https://api.example.com/data', { | ||||
|   retry: true // Uses defaults: 3 attempts, exponential backoff | ||||
| }); | ||||
| ``` | ||||
|  | ||||
| ### Handling Multiple Endpoints | ||||
|  | ||||
| `@push.rocks/webrequest` supports querying multiple endpoints with fallbacks to handle the situation where some endpoints may fail or be unavailable: | ||||
| ### Advanced Retry Configuration | ||||
|  | ||||
| ```typescript | ||||
| // Attempt to request from multiple endpoints | ||||
| async function requestFromMultipleEndpoints() { | ||||
|   const endpoints = [ | ||||
|     'https://api.primary-example.com/data', | ||||
|     'https://api.backup-example.com/data', | ||||
|   ]; | ||||
|   try { | ||||
|     const response = await webRequest.requestMultiEndpoint(endpoints, { | ||||
|       method: 'GET', | ||||
|     }); | ||||
|     const data = await response.json(); | ||||
|     console.log(data); | ||||
|   } catch (error) { | ||||
|     console.error('Failed to retrieve data from any endpoint', error); | ||||
|   } | ||||
| } | ||||
|  | ||||
| requestFromMultipleEndpoints(); | ||||
| ``` | ||||
|  | ||||
| ### Advanced Usage | ||||
|  | ||||
| For advanced scenarios, you can directly use the `request` method to fully customize the request options including headers, request method, and body (for POST/PUT requests): | ||||
|  | ||||
| ```typescript | ||||
| // Custom request with timeout | ||||
| async function customRequest() { | ||||
|   const url = 'https://api.example.com/advanced'; | ||||
|   try { | ||||
|     const response = await webRequest.request(url, { | ||||
|       method: 'POST', | ||||
|       headers: { 'Content-Type': 'application/json' }, | ||||
|       body: JSON.stringify({ custom: 'data' }), | ||||
|       timeoutMs: 10000, // Timeout in milliseconds | ||||
|     }); | ||||
|     if (response.ok) { | ||||
|       const result = await response.json(); | ||||
|       console.log(result); | ||||
|     } else { | ||||
|       console.error('Response error:', response.status); | ||||
| const response = await webrequest('https://api.example.com/data', { | ||||
|   retry: { | ||||
|     maxAttempts: 5, | ||||
|     backoff: 'exponential', // or 'linear', 'constant' | ||||
|     initialDelay: 1000,      // 1 second | ||||
|     maxDelay: 30000,         // 30 seconds | ||||
|     retryOn: [408, 429, 500, 502, 503, 504], // Status codes to retry | ||||
|     onRetry: (attempt, error, nextDelay) => { | ||||
|       console.log(`Retry attempt ${attempt}, waiting ${nextDelay}ms`); | ||||
|     } | ||||
|   } catch (error) { | ||||
|     console.error('Request failed:', error); | ||||
|   } | ||||
| }); | ||||
| ``` | ||||
|  | ||||
| ### Multi-Endpoint Fallback | ||||
|  | ||||
| ```typescript | ||||
| const response = await webrequest('https://api1.example.com/data', { | ||||
|   fallbackUrls: [ | ||||
|     'https://api2.example.com/data', | ||||
|     'https://api3.example.com/data' | ||||
|   ], | ||||
|   retry: { | ||||
|     maxAttempts: 3, | ||||
|     backoff: 'exponential' | ||||
|   } | ||||
| }); | ||||
| ``` | ||||
|  | ||||
| ## Request/Response Interceptors | ||||
|  | ||||
| ### Global Interceptors | ||||
|  | ||||
| ```typescript | ||||
| import { webrequest } from '@push.rocks/webrequest'; | ||||
|  | ||||
| // Add authentication to all requests | ||||
| webrequest.addRequestInterceptor((request) => { | ||||
|   const headers = new Headers(request.headers); | ||||
|   headers.set('Authorization', `Bearer ${getToken()}`); | ||||
|   return new Request(request, { headers }); | ||||
| }); | ||||
|  | ||||
| // Log all responses | ||||
| webrequest.addResponseInterceptor((response) => { | ||||
|   console.log(`${response.status} ${response.url}`); | ||||
|   return response; | ||||
| }); | ||||
|  | ||||
| // Handle errors globally | ||||
| webrequest.addErrorInterceptor((error) => { | ||||
|   console.error('Request failed:', error); | ||||
|   throw error; | ||||
| }); | ||||
| ``` | ||||
|  | ||||
| ### Per-Request Interceptors | ||||
|  | ||||
| ```typescript | ||||
| const response = await webrequest('https://api.example.com/data', { | ||||
|   interceptors: { | ||||
|     request: [(req) => { | ||||
|       console.log('Sending:', req.url); | ||||
|       return req; | ||||
|     }], | ||||
|     response: [(res) => { | ||||
|       console.log('Received:', res.status); | ||||
|       return res; | ||||
|     }], | ||||
|     error: [(err) => { | ||||
|       console.error('Error:', err); | ||||
|       throw err; | ||||
|     }] | ||||
|   } | ||||
| }); | ||||
| ``` | ||||
|  | ||||
| ## Request Deduplication | ||||
|  | ||||
| Automatically prevent duplicate simultaneous requests: | ||||
|  | ||||
| ```typescript | ||||
| // Only one actual network request is made | ||||
| 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 }), | ||||
| ]); | ||||
|  | ||||
| // All three get the same response | ||||
| ``` | ||||
|  | ||||
| ## Client API with Default Options | ||||
|  | ||||
| For more control, use `WebrequestClient` to set default options: | ||||
|  | ||||
| ```typescript | ||||
| import { WebrequestClient } from '@push.rocks/webrequest'; | ||||
|  | ||||
| const apiClient = new WebrequestClient({ | ||||
|   logging: true, | ||||
|   timeout: 30000, | ||||
|   cacheStrategy: 'network-first', | ||||
|   retry: { | ||||
|     maxAttempts: 3, | ||||
|     backoff: 'exponential' | ||||
|   } | ||||
| }); | ||||
|  | ||||
| // Add global interceptors to this client | ||||
| 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'); | ||||
|  | ||||
| // Standard fetch-compatible API also available | ||||
| const response = await apiClient.request('https://api.example.com/data'); | ||||
| ``` | ||||
|  | ||||
| ## Advanced Features | ||||
|  | ||||
| ### Timeout | ||||
|  | ||||
| ```typescript | ||||
| const response = await webrequest('https://api.example.com/data', { | ||||
|   timeout: 5000 // 5 seconds | ||||
| }); | ||||
| ``` | ||||
|  | ||||
| ### Custom Headers | ||||
|  | ||||
| ```typescript | ||||
| const response = await webrequest('https://api.example.com/data', { | ||||
|   headers: { | ||||
|     'Authorization': 'Bearer token123', | ||||
|     'X-Custom-Header': 'value' | ||||
|   } | ||||
| }); | ||||
| ``` | ||||
|  | ||||
| ### Cache Management | ||||
|  | ||||
| ```typescript | ||||
| // Clear all cached responses | ||||
| await webrequest.clearCache(); | ||||
|  | ||||
| // Clear specific cache entry | ||||
| await webrequest.clearCache('https://api.example.com/data'); | ||||
| ``` | ||||
|  | ||||
| ## TypeScript Support | ||||
|  | ||||
| Full TypeScript support with generics for type-safe responses: | ||||
|  | ||||
| ```typescript | ||||
| interface ApiResponse<T> { | ||||
|   data: T; | ||||
|   status: 'success' | 'error'; | ||||
|   message?: string; | ||||
| } | ||||
|  | ||||
| interface User { | ||||
|   id: number; | ||||
|   name: string; | ||||
|   email: string; | ||||
| } | ||||
|  | ||||
| // Fully typed response | ||||
| const response = await webrequest.getJson<ApiResponse<User>>( | ||||
|   'https://api.example.com/user/1' | ||||
| ); | ||||
|  | ||||
| // response.data.id, response.data.name, response.data.email are all typed | ||||
| ``` | ||||
|  | ||||
| ## API Reference | ||||
|  | ||||
| ### Main Function | ||||
|  | ||||
| ```typescript | ||||
| webrequest(input: string | Request | URL, options?: IWebrequestOptions): Promise<Response> | ||||
| ``` | ||||
|  | ||||
| ### Convenience Methods | ||||
|  | ||||
| ```typescript | ||||
| webrequest.getJson<T>(url: string, options?: IWebrequestOptions): Promise<T> | ||||
| webrequest.postJson<T>(url: string, body: any, options?: IWebrequestOptions): Promise<T> | ||||
| webrequest.putJson<T>(url: string, body: any, options?: IWebrequestOptions): Promise<T> | ||||
| webrequest.patchJson<T>(url: string, body: any, options?: IWebrequestOptions): Promise<T> | ||||
| webrequest.deleteJson<T>(url: string, options?: IWebrequestOptions): Promise<T> | ||||
| ``` | ||||
|  | ||||
| ### Global Methods | ||||
|  | ||||
| ```typescript | ||||
| webrequest.addRequestInterceptor(interceptor: TRequestInterceptor): void | ||||
| webrequest.addResponseInterceptor(interceptor: TResponseInterceptor): void | ||||
| webrequest.addErrorInterceptor(interceptor: TErrorInterceptor): void | ||||
| webrequest.clearCache(url?: string): Promise<void> | ||||
| ``` | ||||
|  | ||||
| ### Options Interface | ||||
|  | ||||
| ```typescript | ||||
| interface IWebrequestOptions extends Omit<RequestInit, 'cache'> { | ||||
|   // Standard fetch options | ||||
|   method?: string; | ||||
|   headers?: HeadersInit; | ||||
|   body?: BodyInit; | ||||
|  | ||||
|   // Enhanced options | ||||
|   cache?: 'default' | 'no-store' | 'reload' | 'no-cache' | 'force-cache' | 'only-if-cached'; | ||||
|   cacheStrategy?: 'network-first' | 'cache-first' | 'stale-while-revalidate' | 'network-only' | 'cache-only'; | ||||
|   cacheMaxAge?: number; // milliseconds | ||||
|   cacheKey?: (request: Request) => string; | ||||
|  | ||||
|   retry?: boolean | IRetryOptions; | ||||
|   fallbackUrls?: string[]; | ||||
|   timeout?: number; // milliseconds | ||||
|  | ||||
|   interceptors?: { | ||||
|     request?: TRequestInterceptor[]; | ||||
|     response?: TResponseInterceptor[]; | ||||
|     error?: TErrorInterceptor[]; | ||||
|   }; | ||||
|  | ||||
|   deduplicate?: boolean; | ||||
|   logging?: boolean; | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ## Migration from v3 | ||||
|  | ||||
| Version 4.0 is a **complete rewrite** of `@push.rocks/webrequest` with breaking changes. **The v3 API has been completely removed** - all v3 code must be migrated to v4. | ||||
|  | ||||
| ### What's New in v4 | ||||
|  | ||||
| - **Fetch-Compatible API**: Drop-in replacement for native `fetch()` with enhanced features | ||||
| - **Intelligent HTTP Caching**: Respects `Cache-Control`, `ETag`, `Last-Modified`, and `Expires` headers (RFC 7234) | ||||
| - **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 | ||||
|  | ||||
| ### Breaking Changes | ||||
|  | ||||
| 1. **Removed `WebRequest` class** - Use `webrequest` function or `WebrequestClient` class instead | ||||
| 2. **Cache API changed** - Boolean `useCache` replaced with explicit `cacheStrategy` options | ||||
| 3. **Multi-endpoint API changed** - `requestMultiEndpoint()` replaced with `fallbackUrls` option | ||||
|  | ||||
| ### Migration Examples | ||||
|  | ||||
| #### 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' | ||||
| }); | ||||
| ``` | ||||
|  | ||||
| #### 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 | ||||
| }); | ||||
| ``` | ||||
|  | ||||
| #### 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 | ||||
| }); | ||||
| ``` | ||||
|  | ||||
| ## Examples | ||||
|  | ||||
| ### Complete Example with All Features | ||||
|  | ||||
| ```typescript | ||||
| import { webrequest } from '@push.rocks/webrequest'; | ||||
|  | ||||
| async function fetchUserData(userId: string) { | ||||
|   interface User { | ||||
|     id: string; | ||||
|     name: string; | ||||
|     email: string; | ||||
|   } | ||||
|  | ||||
|   const user = await webrequest.getJson<User>( | ||||
|     `https://api.example.com/users/${userId}`, | ||||
|     { | ||||
|       // Caching | ||||
|       cacheStrategy: 'stale-while-revalidate', | ||||
|       cacheMaxAge: 300000, // 5 minutes | ||||
|  | ||||
|       // Retry | ||||
|       retry: { | ||||
|         maxAttempts: 3, | ||||
|         backoff: 'exponential', | ||||
|         retryOn: [500, 502, 503, 504] | ||||
|       }, | ||||
|  | ||||
|       // Fallback | ||||
|       fallbackUrls: [ | ||||
|         'https://api-backup.example.com/users/${userId}' | ||||
|       ], | ||||
|  | ||||
|       // Timeout | ||||
|       timeout: 10000, // 10 seconds | ||||
|  | ||||
|       // Deduplication | ||||
|       deduplicate: true, | ||||
|  | ||||
|       // Per-request interceptor | ||||
|       interceptors: { | ||||
|         request: [(req) => { | ||||
|           console.log(`Fetching user ${userId}`); | ||||
|           return req; | ||||
|         }] | ||||
|       } | ||||
|     } | ||||
|   ); | ||||
|  | ||||
|   return user; | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### Building a Typed API Client | ||||
|  | ||||
| ```typescript | ||||
| import { WebrequestClient } from '@push.rocks/webrequest'; | ||||
|  | ||||
| class ApiClient { | ||||
|   private client: WebrequestClient; | ||||
|  | ||||
|   constructor(private baseUrl: string, private apiKey: string) { | ||||
|     this.client = new WebrequestClient({ | ||||
|       timeout: 30000, | ||||
|       cacheStrategy: 'network-first', | ||||
|       retry: { | ||||
|         maxAttempts: 3, | ||||
|         backoff: 'exponential' | ||||
|       } | ||||
|     }); | ||||
|  | ||||
|     // Add auth interceptor | ||||
|     this.client.addRequestInterceptor((request) => { | ||||
|       const headers = new Headers(request.headers); | ||||
|       headers.set('Authorization', `Bearer ${this.apiKey}`); | ||||
|       headers.set('Content-Type', 'application/json'); | ||||
|       return new Request(request, { headers }); | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   async getUser(id: string): Promise<User> { | ||||
|     return this.client.getJson<User>(`${this.baseUrl}/users/${id}`); | ||||
|   } | ||||
|  | ||||
|   async createUser(data: CreateUserData): Promise<User> { | ||||
|     return this.client.postJson<User>(`${this.baseUrl}/users`, data); | ||||
|   } | ||||
|  | ||||
|   async updateUser(id: string, data: UpdateUserData): Promise<User> { | ||||
|     return this.client.putJson<User>(`${this.baseUrl}/users/${id}`, data); | ||||
|   } | ||||
|  | ||||
|   async deleteUser(id: string): Promise<void> { | ||||
|     await this.client.deleteJson(`${this.baseUrl}/users/${id}`); | ||||
|   } | ||||
| } | ||||
|  | ||||
| customRequest(); | ||||
| // Usage | ||||
| const api = new ApiClient('https://api.example.com', process.env.API_KEY); | ||||
| const user = await api.getUser('123'); | ||||
| ``` | ||||
|  | ||||
| ### Conclusion | ||||
|  | ||||
| `@push.rocks/webrequest` offers a streamlined, secure way to handle web requests from browsers, supporting various HTTP methods, response caching, and requests to multiple endpoints with fault tolerance. Its TypeScript integration ensures type safety and enhances developer productivity by enabling IntelliSense in supported IDEs. | ||||
|  | ||||
| ## License and Legal Information | ||||
|  | ||||
| This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository. | ||||
| @@ -166,7 +624,7 @@ This project is owned and maintained by Task Venture Capital GmbH. The names and | ||||
|  | ||||
| ### Company Information | ||||
|  | ||||
| Task Venture Capital GmbH   | ||||
| Task Venture Capital GmbH | ||||
| Registered at District court Bremen HRB 35230 HB, Germany | ||||
|  | ||||
| For any legal inquiries or if you require further information, please contact us via email at hello@task.vc. | ||||
|   | ||||
		Reference in New Issue
	
	Block a user