Skip to content

Commit

Permalink
Ådd feature to blacklist some paths (#754)
Browse files Browse the repository at this point in the history
* add feature to blacklist some paths

* format with biome

* update exclude attr definition

* improvements

* style: formatted code

* fix: lint

---------

Co-authored-by: Arthur Fiorette <[email protected]>
  • Loading branch information
kcsujeet and arthurfiorette authored Dec 23, 2023
1 parent b9da1fe commit 0571298
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 5 deletions.
7 changes: 5 additions & 2 deletions docs/src/config/request-specifics.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ exceptions to the method rule.
_(These default status codes follows RFC 7231)_

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.

```ts{5,8,13}
Expand All @@ -201,7 +201,10 @@ axios.get<{ auth: { status: string } }>('url', {
responseMatch: ({ data }) => {
// Sample that only caches if the response is authenticated
return data.auth.status === 'authenticated';
}
},
// Ensures no request is cached if its url starts with "/api"
ignoreUrls: [/^\/api/]
}
}
});
Expand Down
31 changes: 31 additions & 0 deletions src/interceptors/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export function defaultRequestInterceptor(axios: AxiosCacheInstance) {
if (config.cache === false) {
if (__ACI_DEV__) {
axios.debug({
id: config.id,
msg: 'Ignoring cache because config.cache === false',
data: config
});
Expand All @@ -23,6 +24,35 @@ export function defaultRequestInterceptor(axios: AxiosCacheInstance) {
// merge defaults with per request configuration
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
//
// Its currently used before isMethodIn because if the isMethodIn returns false, the request
Expand All @@ -36,6 +66,7 @@ export function defaultRequestInterceptor(axios: AxiosCacheInstance) {
if (!isMethodIn(config.method, config.cache.methods)) {
if (__ACI_DEV__) {
axios.debug({
id: config.id,
msg: `Ignored because method (${config.method}) is not in cache.methods (${config.cache.methods})`
});
}
Expand Down
13 changes: 10 additions & 3 deletions src/util/types.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import type { CacheAxiosResponse, CacheRequestConfig } from '../cache/axios';
import type { CachedStorageValue, LoadingStorageValue, StorageValue } from '../storage/types';

export type CachePredicate<R = unknown, D = unknown> = Exclude<
CachePredicateObject<R, D> | CachePredicateObject<R, D>['responseMatch'],
undefined
export type CachePredicate<R = unknown, D = unknown> = NonNullable<
CachePredicateObject<R, D> | CachePredicateObject<R, D>['responseMatch']
>;

export interface CachePredicateObject<R = unknown, D = unknown> {
Expand All @@ -25,6 +24,14 @@ export interface CachePredicateObject<R = unknown, D = unknown> {

/** Check if the response matches this predicate. */
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)[];
}

/**
Expand Down
41 changes: 41 additions & 0 deletions test/interceptors/request.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -353,4 +353,45 @@ describe('Request Interceptor', () => {
assert.equal(headers2[Header.Pragma], 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);
});
});

0 comments on commit 0571298

Please sign in to comment.