Ådd feature to blacklist some paths (#754)

* add feature to blacklist some paths

* format with biome

* update exclude attr definition

* improvements

* style: formatted code

* fix: lint

---------

Co-authored-by: Arthur Fiorette <me@arthur.place>
This commit is contained in:
Sujeet Kc 2023-12-23 16:40:05 -04:00 committed by GitHub
parent b9da1fe1ed
commit 05712980df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 87 additions and 5 deletions

View File

@ -182,7 +182,7 @@ exceptions to the method rule.
_(These default status codes follows RFC 7231)_ _(These default status codes follows RFC 7231)_
An object or function that will be tested against the response to indicate if it can be An object or function that will be tested against the response to indicate if it can be
cached. You can use `statusCheck`, `containsHeader` and `responseMatch` to test against cached. You can use `statusCheck`, `containsHeader`, `ignoreUrls` and `responseMatch` to test against
the response. the response.
```ts{5,8,13} ```ts{5,8,13}
@ -201,7 +201,10 @@ axios.get<{ auth: { status: string } }>('url', {
responseMatch: ({ data }) => { responseMatch: ({ data }) => {
// Sample that only caches if the response is authenticated // Sample that only caches if the response is authenticated
return data.auth.status === 'authenticated'; return data.auth.status === 'authenticated';
} },
// Ensures no request is cached if its url starts with "/api"
ignoreUrls: [/^\/api/]
} }
} }
}); });

View File

@ -12,6 +12,7 @@ export function defaultRequestInterceptor(axios: AxiosCacheInstance) {
if (config.cache === false) { if (config.cache === false) {
if (__ACI_DEV__) { if (__ACI_DEV__) {
axios.debug({ axios.debug({
id: config.id,
msg: 'Ignoring cache because config.cache === false', msg: 'Ignoring cache because config.cache === false',
data: config data: config
}); });
@ -23,6 +24,35 @@ export function defaultRequestInterceptor(axios: AxiosCacheInstance) {
// merge defaults with per request configuration // merge defaults with per request configuration
config.cache = { ...axios.defaults.cache, ...config.cache }; config.cache = { ...axios.defaults.cache, ...config.cache };
if (
typeof config.cache.cachePredicate === 'object' &&
config.cache.cachePredicate.ignoreUrls &&
config.url
) {
for (const url of config.cache.cachePredicate.ignoreUrls) {
if (
url instanceof RegExp
? // Handles stateful regexes
// biome-ignore lint: reduces the number of checks
((url.lastIndex = 0), url.test(config.url))
: config.url.includes(url)
) {
if (__ACI_DEV__) {
axios.debug({
id: config.id,
msg: `Ignored because url (${config.url}) matches ignoreUrls (${config.cache.cachePredicate.ignoreUrls})`,
data: {
url: config.url,
cachePredicate: config.cache.cachePredicate
}
});
}
return config;
}
}
}
// Applies sufficient headers to prevent other cache systems to work along with this one // Applies sufficient headers to prevent other cache systems to work along with this one
// //
// Its currently used before isMethodIn because if the isMethodIn returns false, the request // Its currently used before isMethodIn because if the isMethodIn returns false, the request
@ -36,6 +66,7 @@ export function defaultRequestInterceptor(axios: AxiosCacheInstance) {
if (!isMethodIn(config.method, config.cache.methods)) { if (!isMethodIn(config.method, config.cache.methods)) {
if (__ACI_DEV__) { if (__ACI_DEV__) {
axios.debug({ axios.debug({
id: config.id,
msg: `Ignored because method (${config.method}) is not in cache.methods (${config.cache.methods})` msg: `Ignored because method (${config.method}) is not in cache.methods (${config.cache.methods})`
}); });
} }

View File

@ -1,9 +1,8 @@
import type { CacheAxiosResponse, CacheRequestConfig } from '../cache/axios'; import type { CacheAxiosResponse, CacheRequestConfig } from '../cache/axios';
import type { CachedStorageValue, LoadingStorageValue, StorageValue } from '../storage/types'; import type { CachedStorageValue, LoadingStorageValue, StorageValue } from '../storage/types';
export type CachePredicate<R = unknown, D = unknown> = Exclude< export type CachePredicate<R = unknown, D = unknown> = NonNullable<
CachePredicateObject<R, D> | CachePredicateObject<R, D>['responseMatch'], CachePredicateObject<R, D> | CachePredicateObject<R, D>['responseMatch']
undefined
>; >;
export interface CachePredicateObject<R = unknown, D = unknown> { export interface CachePredicateObject<R = unknown, D = unknown> {
@ -25,6 +24,14 @@ export interface CachePredicateObject<R = unknown, D = unknown> {
/** Check if the response matches this predicate. */ /** Check if the response matches this predicate. */
responseMatch?: (res: CacheAxiosResponse<R, D>) => MaybePromise<boolean>; responseMatch?: (res: CacheAxiosResponse<R, D>) => MaybePromise<boolean>;
/**
* Ignores the request if their url matches any provided urls and/or regexes.
*
* - It checks against the `request.url` property, `baseURL` is not considered.
* - When only `baseURL` is specified, this property is ignored.
*/
ignoreUrls?: (RegExp | string)[];
} }
/** /**

View File

@ -353,4 +353,45 @@ describe('Request Interceptor', () => {
assert.equal(headers2[Header.Pragma], undefined); assert.equal(headers2[Header.Pragma], undefined);
assert.equal(headers2[Header.Expires], undefined); assert.equal(headers2[Header.Expires], undefined);
}); });
it('ensures request with urls in exclude.paths are not cached', async () => {
const axios = mockAxios({
cachePredicate: {
ignoreUrls: ['url']
}
});
const [req0, req1] = await Promise.all([axios.get('url'), axios.get('url')]);
assert.equal(req0.cached, false);
assert.equal(req1.cached, false);
const [req2, req3] = await Promise.all([axios.get('some-other'), axios.get('some-other')]);
assert.equal(req2.cached, false);
assert.ok(req3.cached);
});
it('ensures request with urls in exclude.paths are not cached (regex)', async () => {
const axios = mockAxios({
cachePredicate: {
ignoreUrls: [/url/]
}
});
const [req0, req1] = await Promise.all([axios.get('my/url'), axios.get('my/url')]);
assert.equal(req0.cached, false);
assert.equal(req1.cached, false);
const [req2, req3] = await Promise.all([axios.get('some-other'), axios.get('some-other')]);
assert.equal(req2.cached, false);
assert.ok(req3.cached);
const [req4, req5] = await Promise.all([axios.get('other/url'), axios.get('other/url')]);
assert.equal(req4.cached, false);
assert.equal(req5.cached, false);
});
}); });