Add enabled flag for opt-in cache control (#1141)

* Initial plan

* Add enabled flag to CacheProperties with full implementation and tests

Co-authored-by: arthurfiorette <47537704+arthurfiorette@users.noreply.github.com>

* Fix deprecated annotation to only apply to cache: false, add documentation for enabled flag

Co-authored-by: arthurfiorette <47537704+arthurfiorette@users.noreply.github.com>

* Improve JSDoc comment for DeprecatedFalse type

Co-authored-by: arthurfiorette <47537704+arthurfiorette@users.noreply.github.com>

* Remove deprecation warnings from docs, keep cache: false for backward compatibility

Co-authored-by: arthurfiorette <47537704+arthurfiorette@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: arthurfiorette <47537704+arthurfiorette@users.noreply.github.com>
This commit is contained in:
Copilot 2025-12-08 12:34:00 -03:00 committed by GitHub
parent 47bb5b0c6a
commit 6bda1baae0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 290 additions and 9 deletions

View File

@ -30,7 +30,7 @@ or a custom one provided by [`config.id`](./request-specifics.md#id)
<Badge text="optional" type="warning"/>
- Type: `false` or `Partial<CacheProperties<R, D>>`.
- Type: `Partial<CacheProperties<R, D>>`.
- Default: `{}` _(Inherits from global configuration)_
::: tip
@ -43,19 +43,77 @@ configuration
The cache option available through the request config is where all the cache customization
happens.
Setting the `cache` property to `false` will disable the cache for this request.
You can pass an object with cache properties to customize cache behavior.
This does not mean that the cache will be excluded from the storage, in which case, you
can do that by deleting the storage entry:
To disable caching for a specific request, use `cache: { enabled: false }`:
```ts
// Make a request with cache disabled.
const { id: requestId } = await axios.get('url', { cache: false });
// Make a request with cache disabled
const { id: requestId } = await axios.get('url', { cache: { enabled: false } });
// Delete the cache entry for this request.
// Delete the cache entry for this request if needed
await axios.storage.remove(requestId);
```
## cache.enabled
<Badge text="optional" type="warning"/>
- Type: `boolean`
- Default: `true`
Whether the cache is enabled for this request.
When set to `false`, the cache will be completely disabled for this request.
This is useful for **opt-in cache** scenarios where you want to disable cache globally
but enable it for specific requests.
### Example: Opt-in Cache Pattern
You can disable cache by default and enable it only for specific endpoints:
```ts
import { setupCache } from 'axios-cache-interceptor';
// Setup axios with cache disabled by default
const axios = setupCache(axiosInstance, {
enabled: false // Disable cache globally
});
// Most requests won't use cache
await axios.get('/api/realtime-data'); // Not cached
// Enable cache for specific heavy/expensive requests
await axios.get('/api/heavy-computation', {
cache: {
enabled: true,
ttl: 1000 * 60 * 10 // Cache for 10 minutes
}
}); // Cached
```
### Example: Traditional Pattern (Opt-out)
The traditional pattern where cache is enabled by default:
```ts
import { setupCache } from 'axios-cache-interceptor';
// Setup axios with cache enabled by default (this is the default behavior)
const axios = setupCache(axiosInstance, {
enabled: true // or omit this as true is the default
});
// Most requests will use cache
await axios.get('/api/user-profile'); // Cached
// Disable cache for specific real-time endpoints
await axios.get('/api/live-stock-prices', {
cache: { enabled: false }
}); // Not cached
```
## cache.ttl
<Badge text="optional" type="warning"/>

7
src/cache/axios.ts vendored
View File

@ -79,14 +79,17 @@ export interface CacheRequestConfig<R = any, D = any> extends AxiosRequestConfig
* The cache option available through the request config is where all the cache
* customization happens.
*
* Setting the `cache` property to `false` will disable the cache for this request.
* You can pass an object with cache properties to customize cache behavior.
*
* **Note:** Setting `cache: false` is still supported for backward compatibility, but
* will be removed in the next major release. Use `cache: { enabled: false }` instead.
*
* This does not mean that the current cache will be excluded from the storage.
*
* @default 'inherits from global configuration'
* @see https://axios-cache-interceptor.js.org/config/response-object#cache
*/
cache?: false | Partial<CacheProperties<R, D>>;
cache?: Partial<CacheProperties<R, D>> | false;
}
/** Cached version of type {@link InternalAxiosRequestConfig} */

14
src/cache/cache.ts vendored
View File

@ -22,6 +22,20 @@ import type { CacheAxiosResponse, InternalCacheRequestConfig } from './axios.js'
* @template D The type for the request body
*/
export interface CacheProperties<R = unknown, D = unknown> {
/**
* Whether the cache is enabled for this request.
*
* When set to `false`, the cache will be completely disabled for this request,
* similar to setting `cache: false` in the request config.
*
* This is useful for opt-in cache scenarios where you want to disable cache globally
* but enable it for specific requests by setting `cache: { enabled: true }`.
*
* @default true
* @see https://axios-cache-interceptor.js.org/config/request-specifics#cache-enabled
*/
enabled: boolean;
/**
* The time until the cached value is expired in milliseconds.
*

2
src/cache/create.ts vendored
View File

@ -57,6 +57,8 @@ export function setupCache(axios: AxiosInstance, options: CacheOptions = {}): Ax
// CacheRequestConfig values
axiosCache.defaults.cache = {
enabled: options.enabled ?? true,
update: options.update || {},
ttl: options.ttl ?? 1000 * 60 * 5,

View File

@ -30,6 +30,19 @@ export function defaultRequestInterceptor(axios: AxiosCacheInstance): RequestInt
// merge defaults with per request configuration
config.cache = { ...axios.defaults.cache, ...config.cache };
// Check if cache is disabled via enabled flag
if (config.cache.enabled === false) {
if (__ACI_DEV__) {
axios.debug({
id: config.id,
msg: 'Ignoring cache because config.cache.enabled === false',
data: config
});
}
return config;
}
// ignoreUrls (blacklist)
if (
typeof config.cache.cachePredicate === 'object' &&

View File

@ -0,0 +1,191 @@
import assert from 'node:assert';
import { describe, it } from 'node:test';
import { mockAxios } from '../mocks/axios.js';
describe('Cache Enabled Flag', () => {
it('Cache enabled by default (enabled: true)', async () => {
const axios = mockAxios();
const response1 = await axios.get('http://test.com');
assert.equal(response1.cached, false);
assert.equal(response1.stale, undefined);
const response2 = await axios.get('http://test.com');
assert.ok(response2.cached);
assert.equal(response2.stale, false);
});
it('Global cache disabled (enabled: false)', async () => {
const axios = mockAxios({ enabled: false });
const response1 = await axios.get('http://test.com');
const response2 = await axios.get('http://test.com');
assert.equal(response1.cached, false);
assert.equal(response1.stale, undefined);
assert.equal(response2.cached, false);
assert.equal(response2.stale, undefined);
// Verify cache is empty
const cacheKey = axios.generateKey(response1.config);
const cache = await axios.storage.get(cacheKey);
assert.equal(cache.state, 'empty');
});
it('Global cache disabled, per-request enabled', async () => {
const axios = mockAxios({ enabled: false });
// First request with cache enabled
const response1 = await axios.get('http://test.com', {
cache: { enabled: true }
});
assert.equal(response1.cached, false);
assert.equal(response1.stale, undefined);
// Second request with cache enabled - should be cached
const response2 = await axios.get('http://test.com', {
cache: { enabled: true }
});
assert.ok(response2.cached);
assert.equal(response2.stale, false);
// Third request without cache config - should not be cached (global default)
const response3 = await axios.get('http://test.com/other');
const response4 = await axios.get('http://test.com/other');
assert.equal(response3.cached, false);
assert.equal(response3.stale, undefined);
assert.equal(response4.cached, false);
assert.equal(response4.stale, undefined);
});
it('Global cache enabled, per-request disabled', async () => {
const axios = mockAxios({ enabled: true });
// First request with cache disabled
const response1 = await axios.get('http://test.com', {
cache: { enabled: false }
});
assert.equal(response1.cached, false);
assert.equal(response1.stale, undefined);
// Second request with cache disabled - should not be cached
const response2 = await axios.get('http://test.com', {
cache: { enabled: false }
});
assert.equal(response2.cached, false);
assert.equal(response2.stale, undefined);
// Third request without cache config - should be cached (global default)
const response3 = await axios.get('http://test.com/other');
const response4 = await axios.get('http://test.com/other');
assert.equal(response3.cached, false);
assert.equal(response3.stale, undefined);
assert.ok(response4.cached);
assert.equal(response4.stale, false);
});
it('Backward compatibility: cache: false still works', async () => {
const axios = mockAxios();
const response1 = await axios.get('http://test.com', { cache: false });
const response2 = await axios.get('http://test.com', { cache: false });
assert.equal(response1.cached, false);
assert.equal(response1.stale, undefined);
assert.equal(response2.cached, false);
assert.equal(response2.stale, undefined);
// Verify cache is empty
const cacheKey = axios.generateKey(response1.config);
const cache = await axios.storage.get(cacheKey);
assert.equal(cache.state, 'empty');
});
it('Enabled flag works with other cache options', async () => {
const axios = mockAxios({ enabled: false });
const response1 = await axios.get('http://test.com', {
cache: {
enabled: true,
ttl: 1000 * 60 * 10 // 10 minutes
}
});
assert.equal(response1.cached, false);
assert.equal(response1.stale, undefined);
const response2 = await axios.get('http://test.com', {
cache: {
enabled: true,
ttl: 1000 * 60 * 10
}
});
assert.ok(response2.cached);
assert.equal(response2.stale, false);
// Verify cache config is applied
assert.equal(response2.config.cache?.ttl, 1000 * 60 * 10);
});
it('Enabled flag overrides in request after global enabled', async () => {
const axios = mockAxios({ enabled: true });
// Request 1 with enabled: true (explicit)
const response1 = await axios.get('http://test.com', {
cache: { enabled: true }
});
assert.equal(response1.cached, false);
assert.equal(response1.stale, undefined);
// Request 2 - should be cached
const response2 = await axios.get('http://test.com');
assert.ok(response2.cached);
assert.equal(response2.stale, false);
// Request 3 with enabled: false - should not be cached even though cache exists
const response3 = await axios.get('http://test.com', {
cache: { enabled: false }
});
assert.equal(response3.cached, false);
assert.equal(response3.stale, undefined);
});
it('Concurrent requests respect enabled flag', async () => {
const axios = mockAxios({ enabled: false });
const [resp1, resp2] = await Promise.all([
axios.get('http://test.com', { cache: { enabled: true } }),
axios.get('http://test.com', { cache: { enabled: true } })
]);
assert.equal(resp1.cached, false);
assert.equal(resp1.stale, undefined);
assert.ok(resp2.cached);
assert.equal(resp2.stale, false);
});
it('Mixed enabled and disabled requests do not interfere', async () => {
const axios = mockAxios({ enabled: false });
// Disabled request
const resp1 = await axios.get('http://test.com');
assert.equal(resp1.cached, false);
// Enabled request - creates cache
const resp2 = await axios.get('http://test.com', {
cache: { enabled: true }
});
assert.equal(resp2.cached, false);
// Disabled request - does not use cache
const resp3 = await axios.get('http://test.com');
assert.equal(resp3.cached, false);
// Enabled request - uses cache
const resp4 = await axios.get('http://test.com', {
cache: { enabled: true }
});
assert.ok(resp4.cached);
});
});