Skip to content

Commit ee3108a

Browse files
committed
docs: update
1 parent 7611598 commit ee3108a

File tree

1 file changed

+216
-1
lines changed

1 file changed

+216
-1
lines changed

README.md

Lines changed: 216 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,50 @@ const deviceAwareFetch = createFetch(await caches.open('content-cache'), {
286286
const response = await deviceAwareFetch('/api/content');
287287
```
288288

289+
### Custom Fetch with Authentication
290+
291+
```typescript
292+
// Production-ready example with automatic token refresh
293+
const createAuthenticatedFetch = (getToken) => {
294+
return async (input, init) => {
295+
const token = await getToken();
296+
const headers = new Headers(init?.headers);
297+
headers.set('Authorization', `Bearer ${token}`);
298+
299+
const response = await globalThis.fetch(input, {
300+
...init,
301+
headers
302+
});
303+
304+
// Handle token expiration
305+
if (response.status === 401) {
306+
// Token might be expired, retry once with fresh token
307+
const freshToken = await getToken(true); // force refresh
308+
headers.set('Authorization', `Bearer ${freshToken}`);
309+
310+
return globalThis.fetch(input, {
311+
...init,
312+
headers
313+
});
314+
}
315+
316+
return response;
317+
};
318+
};
319+
320+
const authFetch = createFetch(await caches.open('authenticated-api'), {
321+
fetch: createAuthenticatedFetch(() => getApiToken()),
322+
defaults: {
323+
cacheControlOverride: 's-maxage=300',
324+
cacheKeyRules: {
325+
header: { include: ['authorization'] } // Cache per token
326+
}
327+
}
328+
});
329+
330+
const userData = await authFetch('/api/user/profile');
331+
```
332+
289333
## 🌐 Global Setup
290334

291335
### Setting up Global Cache Storage
@@ -376,6 +420,71 @@ const response2 = await fetch('/api/data', {
376420
});
377421
```
378422

423+
### Custom Fetch Configuration
424+
425+
The `createFetch` function accepts a custom fetch implementation, allowing you to integrate with existing HTTP clients or add cross-cutting concerns:
426+
427+
```typescript
428+
// Example: Integration with axios
429+
import axios from 'axios';
430+
431+
const axiosFetch = async (input, init) => {
432+
const response = await axios({
433+
url: input.toString(),
434+
method: init?.method || 'GET',
435+
headers: init?.headers,
436+
data: init?.body,
437+
validateStatus: () => true, // Don't throw on 4xx/5xx
438+
});
439+
440+
return new Response(response.data, {
441+
status: response.status,
442+
statusText: response.statusText,
443+
headers: response.headers,
444+
});
445+
};
446+
447+
const fetch = createFetch(await caches.open('axios-cache'), {
448+
fetch: axiosFetch,
449+
defaults: {
450+
cacheControlOverride: 's-maxage=300'
451+
}
452+
});
453+
454+
// Example: Custom fetch with request/response transformation
455+
const transformFetch = async (input, init) => {
456+
// Transform request
457+
const url = new URL(input);
458+
url.searchParams.set('timestamp', Date.now().toString());
459+
460+
const response = await globalThis.fetch(url, init);
461+
462+
// Transform response
463+
if (response.headers.get('content-type')?.includes('application/json')) {
464+
const data = await response.json();
465+
const transformedData = {
466+
...data,
467+
fetchedAt: new Date().toISOString()
468+
};
469+
470+
return new Response(JSON.stringify(transformedData), {
471+
status: response.status,
472+
statusText: response.statusText,
473+
headers: response.headers,
474+
});
475+
}
476+
477+
return response;
478+
};
479+
480+
const transformedFetch = createFetch(await caches.open('transform-cache'), {
481+
fetch: transformFetch,
482+
defaults: {
483+
cacheControlOverride: 's-maxage=300'
484+
}
485+
});
486+
```
487+
379488
### Enhanced Fetch API
380489

381490
SharedCache extends the standard fetch API with caching options via the `sharedCache` parameter:
@@ -678,7 +787,7 @@ function createFetch(
678787
**Parameters:**
679788

680789
- `cache` - Optional SharedCache instance (auto-discovered from globalThis.caches if not provided)
681-
- `options.fetch` - Custom fetch implementation to use as the underlying fetcher
790+
- `options.fetch` - Custom fetch implementation to use as the underlying fetcher (defaults to globalThis.fetch)
682791
- `options.defaults` - Default shared cache options to apply to all requests
683792

684793
**Default Options:**
@@ -706,6 +815,112 @@ const fetch = createFetch(await caches.open('my-cache'), {
706815
});
707816
```
708817

818+
#### Custom Fetch Implementation
819+
820+
The `options.fetch` parameter allows you to provide a custom fetch implementation, enabling you to:
821+
822+
- **Add authentication**: Automatically include API keys or tokens
823+
- **Implement retries**: Add retry logic for failed requests
824+
- **Custom headers**: Add default headers to all requests
825+
- **Request/response transformation**: Modify requests or responses
826+
- **Logging and monitoring**: Add request/response logging
827+
828+
**Custom Fetch Examples:**
829+
830+
```typescript
831+
// Example 1: Fetch with automatic authentication
832+
const authenticatedFetch = async (input, init) => {
833+
const headers = new Headers(init?.headers);
834+
headers.set('Authorization', `Bearer ${getApiToken()}`);
835+
836+
return globalThis.fetch(input, {
837+
...init,
838+
headers
839+
});
840+
};
841+
842+
const fetch = createFetch(await caches.open('auth-cache'), {
843+
fetch: authenticatedFetch,
844+
defaults: {
845+
cacheControlOverride: 's-maxage=300'
846+
}
847+
});
848+
849+
// Example 2: Fetch with retry logic and logging
850+
const retryFetch = async (input, init, maxRetries = 3) => {
851+
let lastError;
852+
853+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
854+
try {
855+
console.log(`Attempt ${attempt}: ${init?.method || 'GET'} ${input}`);
856+
const response = await globalThis.fetch(input, init);
857+
858+
if (response.ok || attempt === maxRetries) {
859+
return response;
860+
}
861+
862+
lastError = new Error(`HTTP ${response.status}: ${response.statusText}`);
863+
} catch (error) {
864+
lastError = error;
865+
if (attempt === maxRetries) break;
866+
867+
// Exponential backoff
868+
await new Promise(resolve =>
869+
setTimeout(resolve, Math.pow(2, attempt - 1) * 1000)
870+
);
871+
}
872+
}
873+
874+
throw lastError;
875+
};
876+
877+
const resilientFetch = createFetch(await caches.open('resilient-cache'), {
878+
fetch: retryFetch,
879+
defaults: {
880+
cacheControlOverride: 's-maxage=600'
881+
}
882+
});
883+
884+
// Example 3: Fetch with custom base URL and headers
885+
const createApiFetch = (baseUrl, defaultHeaders = {}) => {
886+
return async (input, init) => {
887+
const url = new URL(input, baseUrl);
888+
const headers = new Headers(init?.headers);
889+
890+
// Add default headers
891+
Object.entries(defaultHeaders).forEach(([key, value]) => {
892+
if (!headers.has(key)) {
893+
headers.set(key, value);
894+
}
895+
});
896+
897+
return globalThis.fetch(url.toString(), {
898+
...init,
899+
headers
900+
});
901+
};
902+
};
903+
904+
const apiFetch = createFetch(await caches.open('api-cache'), {
905+
fetch: createApiFetch('https://api.example.com', {
906+
'Content-Type': 'application/json',
907+
'X-API-Version': '2024-01-01'
908+
}),
909+
defaults: {
910+
cacheControlOverride: 's-maxage=300'
911+
}
912+
});
913+
914+
// Usage: relative URLs are automatically resolved
915+
const userData = await apiFetch('/users/me'); // → https://api.example.com/users/me
916+
```
917+
918+
**Custom Fetch Requirements:**
919+
920+
- Must be compatible with the standard fetch API signature: `(input: RequestInfo | URL, init?: RequestInit) => Promise<Response>`
921+
- Should handle errors appropriately and return valid Response objects
922+
- Response objects should be consumable by SharedCache (cloneable for caching)
923+
709924
### Internal Implementation
710925

711926
The `createFetch` function is the primary API for creating cached fetch functions, but the package exports many additional utilities and classes for comprehensive cache management.

0 commit comments

Comments
 (0)