Skip to content

Commit 0571298

Browse files
Å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 <[email protected]>
1 parent b9da1fe commit 0571298

File tree

4 files changed

+87
-5
lines changed

4 files changed

+87
-5
lines changed

docs/src/config/request-specifics.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ exceptions to the method rule.
182182
_(These default status codes follows RFC 7231)_
183183

184184
An object or function that will be tested against the response to indicate if it can be
185-
cached. You can use `statusCheck`, `containsHeader` and `responseMatch` to test against
185+
cached. You can use `statusCheck`, `containsHeader`, `ignoreUrls` and `responseMatch` to test against
186186
the response.
187187

188188
```ts{5,8,13}
@@ -201,7 +201,10 @@ axios.get<{ auth: { status: string } }>('url', {
201201
responseMatch: ({ data }) => {
202202
// Sample that only caches if the response is authenticated
203203
return data.auth.status === 'authenticated';
204-
}
204+
},
205+
206+
// Ensures no request is cached if its url starts with "/api"
207+
ignoreUrls: [/^\/api/]
205208
}
206209
}
207210
});

src/interceptors/request.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export function defaultRequestInterceptor(axios: AxiosCacheInstance) {
1212
if (config.cache === false) {
1313
if (__ACI_DEV__) {
1414
axios.debug({
15+
id: config.id,
1516
msg: 'Ignoring cache because config.cache === false',
1617
data: config
1718
});
@@ -23,6 +24,35 @@ export function defaultRequestInterceptor(axios: AxiosCacheInstance) {
2324
// merge defaults with per request configuration
2425
config.cache = { ...axios.defaults.cache, ...config.cache };
2526

27+
if (
28+
typeof config.cache.cachePredicate === 'object' &&
29+
config.cache.cachePredicate.ignoreUrls &&
30+
config.url
31+
) {
32+
for (const url of config.cache.cachePredicate.ignoreUrls) {
33+
if (
34+
url instanceof RegExp
35+
? // Handles stateful regexes
36+
// biome-ignore lint: reduces the number of checks
37+
((url.lastIndex = 0), url.test(config.url))
38+
: config.url.includes(url)
39+
) {
40+
if (__ACI_DEV__) {
41+
axios.debug({
42+
id: config.id,
43+
msg: `Ignored because url (${config.url}) matches ignoreUrls (${config.cache.cachePredicate.ignoreUrls})`,
44+
data: {
45+
url: config.url,
46+
cachePredicate: config.cache.cachePredicate
47+
}
48+
});
49+
}
50+
51+
return config;
52+
}
53+
}
54+
}
55+
2656
// Applies sufficient headers to prevent other cache systems to work along with this one
2757
//
2858
// Its currently used before isMethodIn because if the isMethodIn returns false, the request
@@ -36,6 +66,7 @@ export function defaultRequestInterceptor(axios: AxiosCacheInstance) {
3666
if (!isMethodIn(config.method, config.cache.methods)) {
3767
if (__ACI_DEV__) {
3868
axios.debug({
69+
id: config.id,
3970
msg: `Ignored because method (${config.method}) is not in cache.methods (${config.cache.methods})`
4071
});
4172
}

src/util/types.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import type { CacheAxiosResponse, CacheRequestConfig } from '../cache/axios';
22
import type { CachedStorageValue, LoadingStorageValue, StorageValue } from '../storage/types';
33

4-
export type CachePredicate<R = unknown, D = unknown> = Exclude<
5-
CachePredicateObject<R, D> | CachePredicateObject<R, D>['responseMatch'],
6-
undefined
4+
export type CachePredicate<R = unknown, D = unknown> = NonNullable<
5+
CachePredicateObject<R, D> | CachePredicateObject<R, D>['responseMatch']
76
>;
87

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

2625
/** Check if the response matches this predicate. */
2726
responseMatch?: (res: CacheAxiosResponse<R, D>) => MaybePromise<boolean>;
27+
28+
/**
29+
* Ignores the request if their url matches any provided urls and/or regexes.
30+
*
31+
* - It checks against the `request.url` property, `baseURL` is not considered.
32+
* - When only `baseURL` is specified, this property is ignored.
33+
*/
34+
ignoreUrls?: (RegExp | string)[];
2835
}
2936

3037
/**

test/interceptors/request.test.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,4 +353,45 @@ describe('Request Interceptor', () => {
353353
assert.equal(headers2[Header.Pragma], undefined);
354354
assert.equal(headers2[Header.Expires], undefined);
355355
});
356+
357+
it('ensures request with urls in exclude.paths are not cached', async () => {
358+
const axios = mockAxios({
359+
cachePredicate: {
360+
ignoreUrls: ['url']
361+
}
362+
});
363+
364+
const [req0, req1] = await Promise.all([axios.get('url'), axios.get('url')]);
365+
366+
assert.equal(req0.cached, false);
367+
assert.equal(req1.cached, false);
368+
369+
const [req2, req3] = await Promise.all([axios.get('some-other'), axios.get('some-other')]);
370+
371+
assert.equal(req2.cached, false);
372+
assert.ok(req3.cached);
373+
});
374+
375+
it('ensures request with urls in exclude.paths are not cached (regex)', async () => {
376+
const axios = mockAxios({
377+
cachePredicate: {
378+
ignoreUrls: [/url/]
379+
}
380+
});
381+
382+
const [req0, req1] = await Promise.all([axios.get('my/url'), axios.get('my/url')]);
383+
384+
assert.equal(req0.cached, false);
385+
assert.equal(req1.cached, false);
386+
387+
const [req2, req3] = await Promise.all([axios.get('some-other'), axios.get('some-other')]);
388+
389+
assert.equal(req2.cached, false);
390+
assert.ok(req3.cached);
391+
392+
const [req4, req5] = await Promise.all([axios.get('other/url'), axios.get('other/url')]);
393+
394+
assert.equal(req4.cached, false);
395+
assert.equal(req5.cached, false);
396+
});
356397
});

0 commit comments

Comments
 (0)