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.
This commit is contained in:
2025-10-20 09:59:24 +00:00
parent e228ed4ba0
commit 54afcc46e2
30 changed files with 18693 additions and 4031 deletions

View File

@@ -1,5 +1,5 @@
import { expect, tap } from '@push.rocks/tapbundle';
import * as webrequest from '../ts/index.js';
import { expect, tap } from '@git.zone/tstest/tapbundle';
import { webrequest } from '../ts/index.js';
// test dependencies
import * as typedserver from '@api.global/typedserver';
@@ -18,7 +18,7 @@ tap.test('setup test server', async () => {
new typedserver.servertools.Handler('GET', (req, res) => {
res.status(429);
res.end();
})
}),
);
testServer.addRoute(
@@ -26,7 +26,7 @@ tap.test('setup test server', async () => {
new typedserver.servertools.Handler('GET', (req, res) => {
res.status(500);
res.end();
})
}),
);
testServer.addRoute(
@@ -36,43 +36,62 @@ tap.test('setup test server', async () => {
res.send({
hithere: 'hi',
});
})
}),
);
await testServer.start();
});
tap.test('first test', async (tools) => {
const response = await (
await new webrequest.WebRequest().requestMultiEndpoint(
[
'http://localhost:2345/apiroute1',
tap.test('should handle fallback URLs', async () => {
const response = await webrequest(
'http://localhost:2345/apiroute1',
{
fallbackUrls: [
'http://localhost:2345/apiroute2',
'http://localhost:2345/apiroute4',
'http://localhost:2345/apiroute3',
],
{
method: 'GET',
}
)
).json();
retry: {
maxAttempts: 3,
backoff: 'constant',
initialDelay: 100,
},
}
);
const response2 = await new webrequest.WebRequest().getJson('http://localhost:2345/apiroute3');
const data = await response.json();
console.log('response with fallbacks: ' + JSON.stringify(data));
expect(data).toHaveProperty('hithere');
});
console.log('response 1: ' + JSON.stringify(response));
console.log('response 2: ' + JSON.stringify(response2));
expect(response).toHaveProperty('hithere'); //.to.equal('hi');
expect(response2).toHaveProperty('hithere'); //.to.equal('hi');
tap.test('should use getJson convenience method', async () => {
const data = await webrequest.getJson('http://localhost:2345/apiroute3');
console.log('getJson response: ' + JSON.stringify(data));
expect(data).toHaveProperty('hithere');
});
tap.test('should cache response', async () => {
const webrequestInstance = new webrequest.WebRequest();
const response = await webrequestInstance.getJson('http://localhost:2345/apiroute3', true);
expect(response).toHaveProperty('hithere');
// First request - goes to network
const response1 = await webrequest.getJson(
'http://localhost:2345/apiroute3',
{
cacheStrategy: 'cache-first',
}
);
expect(response1).toHaveProperty('hithere');
// Stop server
await testServer.stop();
const response2 = await webrequestInstance.getJson('http://localhost:2345/apiroute3', true);
// Second request - should use cache since server is down
const response2 = await webrequest.getJson(
'http://localhost:2345/apiroute3',
{
cacheStrategy: 'network-first', // Will fallback to cache on network error
}
);
expect(response2).toHaveProperty('hithere');
console.log('Cache fallback worked');
});
tap.start();
export default tap.start();