diff --git a/docs/src/config/request-specifics.md b/docs/src/config/request-specifics.md index feb7c04..eeb774c 100644 --- a/docs/src/config/request-specifics.md +++ b/docs/src/config/request-specifics.md @@ -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} @@ -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/] } } }); diff --git a/src/interceptors/request.ts b/src/interceptors/request.ts index a2f818b..84c86bc 100644 --- a/src/interceptors/request.ts +++ b/src/interceptors/request.ts @@ -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 }); @@ -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 @@ -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})` }); } diff --git a/src/util/types.ts b/src/util/types.ts index 902d7dd..ae1ff84 100644 --- a/src/util/types.ts +++ b/src/util/types.ts @@ -1,9 +1,8 @@ import type { CacheAxiosResponse, CacheRequestConfig } from '../cache/axios'; import type { CachedStorageValue, LoadingStorageValue, StorageValue } from '../storage/types'; -export type CachePredicate = Exclude< - CachePredicateObject | CachePredicateObject['responseMatch'], - undefined +export type CachePredicate = NonNullable< + CachePredicateObject | CachePredicateObject['responseMatch'] >; export interface CachePredicateObject { @@ -25,6 +24,14 @@ export interface CachePredicateObject { /** Check if the response matches this predicate. */ responseMatch?: (res: CacheAxiosResponse) => MaybePromise; + + /** + * 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)[]; } /** diff --git a/test/interceptors/request.test.ts b/test/interceptors/request.test.ts index df1854c..7d466c8 100644 --- a/test/interceptors/request.test.ts +++ b/test/interceptors/request.test.ts @@ -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); + }); });