Socket
Socket
Sign inDemoInstall

axios-cache-interceptor

Package Overview
Dependencies
Maintainers
1
Versions
78
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

axios-cache-interceptor - npm Package Compare versions

Comparing version 0.0.3 to 0.0.5

.eslintignore

6

dist/axios/cache.d.ts
import { AxiosInstance } from 'axios';
import { AxiosCacheInstance, CacheInstance, CacheRequestConfig } from './types';
declare type Options = CacheRequestConfig['cache'] & Partial<CacheInstance>;
export declare function createCache(axios: AxiosInstance, options?: Options): AxiosCacheInstance;
export {};
import CacheInstance, { AxiosCacheInstance, CacheProperties } from './types';
export declare function createCache(axios: AxiosInstance, options?: Partial<CacheInstance & CacheProperties>): AxiosCacheInstance;
//# sourceMappingURL=cache.d.ts.map
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createCache = void 0;
const header_1 = require("../header");
const request_1 = require("../interceptors/request");
const response_1 = require("../interceptors/response");
const memory_1 = require("../storage/memory");
const key_generator_1 = require("../utils/key-generator");
const key_generator_1 = require("../util/key-generator");
function createCache(axios, options = {}) {
const axiosCache = axios;
axiosCache.storage = options.storage || new memory_1.MemoryStorage();
axiosCache.generateKey = key_generator_1.defaultKeyGenerator;
axiosCache.generateKey = options.generateKey || key_generator_1.defaultKeyGenerator;
axiosCache.waiting = options.waiting || {};
axiosCache.interpretHeader = options.interpretHeader || header_1.defaultHeaderInterpreter;
// CacheRequestConfig values
axiosCache.defaults = Object.assign(Object.assign({}, axios.defaults), { cache: Object.assign({ maxAge: 1000 * 60 * 5, interpretHeader: false, methods: ['get'], shouldCache: ({ status }) => status >= 200 && status < 300, update: {} }, options) });
axiosCache.defaults = {
...axios.defaults,
cache: {
maxAge: 1000 * 60 * 5,
interpretHeader: false,
methods: ['get'],
cachePredicate: ({ status }) => status >= 200 && status < 300,
update: {},
...options
}
};
// Apply interceptors

@@ -15,0 +28,0 @@ (0, request_1.applyRequestInterceptor)(axiosCache);

import type { AxiosInstance, AxiosInterceptorManager, AxiosPromise, AxiosRequestConfig, AxiosResponse, Method } from 'axios';
import { CacheStorage } from '../storage/types';
import { Deferred } from 'src/util/deferred';
import { KeyGenerator } from 'src/util/key-generator';
import { HeaderInterpreter } from '../header';
import { CachedResponse, CacheStorage } from '../storage/types';
import { CachePredicate } from '../util/cache-predicate';
export declare type DefaultCacheRequestConfig = AxiosRequestConfig & {
cache: CacheProperties;
};
export declare type CacheProperties = {
/**
* The time until the cached value is expired in milliseconds.
*
* @default 1000 * 60 * 5
*/
maxAge: number;
/**
* If this interceptor should configure the cache from the request cache header
* When used, the maxAge property is ignored
*
* @default false
*/
interpretHeader: boolean;
/**
* All methods that should be cached.
*
* @default ['get']
*/
methods: Lowercase<Method>[];
/**
* The function to check if the response code permit being cached.
*
* @default ({ status }) => status >= 200 && status < 300
*/
cachePredicate: CachePredicate;
/**
* Once the request is resolved, this specifies what requests should we change the cache.
* Can be used to update the request or delete other caches.
*
* If the function returns void, the entry is deleted
*
* This is independent if the request made was cached or not.
*
* The id used is the same as the id on `CacheRequestConfig['id']`, auto-generated or not.
*
* @default {}
*/
update: {
[id: string]: 'delete' | ((oldValue: any, atual: any) => any | undefined);
};
};
/**

@@ -17,46 +66,5 @@ * Options that can be overridden per request

*/
cache?: {
/**
* The time until the cached value is expired in milliseconds.
*
* @default 1000 * 60 * 5
*/
maxAge?: number;
/**
* If this interceptor should configure the cache from the request cache header
* When used, the maxAge property is ignored
*
* @default false
*/
interpretHeader?: boolean;
/**
* All methods that should be cached.
*
* @default ['get']
*/
methods?: Lowercase<Method>[];
/**
* The function to check if the response code permit being cached.
*
* @default ({ status }) => status >= 200 && status < 300
*/
shouldCache?: (response: AxiosResponse) => boolean;
/**
* Once the request is resolved, this specifies what requests should we change the cache.
* Can be used to update the request or delete other caches.
*
* If the function returns void, the entry is deleted
*
* This is independent if the request made was cached or not.
*
* The id used is the same as the id on `CacheRequestConfig['id']`, auto-generated or not.
*
* @default {}
*/
update?: {
[id: string]: 'delete' | ((oldValue: any, atual: any) => any | void);
};
};
cache?: Partial<CacheProperties>;
};
export interface CacheInstance {
export default interface CacheInstance {
/**

@@ -73,8 +81,24 @@ * The storage to save the cache data.

*/
generateKey: (options: CacheRequestConfig) => string;
generateKey: KeyGenerator;
/**
* A simple object that holds all deferred objects until it is resolved.
*/
waiting: Record<string, Deferred<CachedResponse>>;
/**
* The function to parse and interpret response headers.
* Only used if cache.interpretHeader is true.
*/
interpretHeader: HeaderInterpreter;
}
/**
* Same as the AxiosInstance but with CacheRequestConfig as a config type.
*
* @see AxiosInstance
* @see CacheRequestConfig
* @see CacheInstance
*/
export interface AxiosCacheInstance extends AxiosInstance, CacheInstance {
(config: CacheRequestConfig): AxiosPromise;
(url: string, config?: CacheRequestConfig): AxiosPromise;
defaults: CacheRequestConfig;
defaults: DefaultCacheRequestConfig;
interceptors: {

@@ -81,0 +105,0 @@ request: AxiosInterceptorManager<CacheRequestConfig>;

@@ -1,4 +0,4 @@

export { createCache } from './axios/cache';
export * from './constants';
export * from './axios';
export * from './storage';
export * as StatusCodes from './util/status-codes';
//# sourceMappingURL=index.d.ts.map

@@ -9,11 +9,22 @@ "use strict";

}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createCache = void 0;
var cache_1 = require("./axios/cache");
Object.defineProperty(exports, "createCache", { enumerable: true, get: function () { return cache_1.createCache; } });
__exportStar(require("./constants"), exports);
exports.StatusCodes = void 0;
__exportStar(require("./axios"), exports);
__exportStar(require("./storage"), exports);
exports.StatusCodes = __importStar(require("./util/status-codes"));
//# sourceMappingURL=index.js.map
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.applyRequestInterceptor = void 0;
const constants_1 = require("../constants");
const deferred_1 = require("../utils/deferred");
const deferred_1 = require("../util/deferred");
const status_codes_1 = require("../util/status-codes");
function applyRequestInterceptor(axios) {
axios.interceptors.request.use((config) => __awaiter(this, void 0, void 0, function* () {
var _a, _b, _c, _d;
axios.interceptors.request.use(async (config) => {
// Only cache specified methods
if ((_b = (_a = config.cache) === null || _a === void 0 ? void 0 : _a.methods) === null || _b === void 0 ? void 0 : _b.some((method) => (config.method || 'get').toLowerCase() == method)) {
if (config.cache?.methods?.some((method) => (config.method || 'get').toLowerCase() == method)) {
return config;
}
const key = axios.generateKey(config);
const cache = yield axios.storage.get(key);
const cache = await axios.storage.get(key);
// Not cached, continue the request, and mark it as fetching
if (cache.state == 'empty') {
yield axios.storage.set(key, {
// Create a deferred to resolve other requests for the same key when it's completed
axios.waiting[key] = new deferred_1.Deferred();
await axios.storage.set(key, {
state: 'loading',
data: new deferred_1.Deferred(),
data: null,
// The cache header will be set after the response has been read, until that time, the expiration will be -1
expiration: ((_c = config.cache) === null || _c === void 0 ? void 0 : _c.interpretHeader)
expiration: config.cache?.interpretHeader
? -1
: ((_d = config.cache) === null || _d === void 0 ? void 0 : _d.maxAge) || axios.defaults.cache.maxAge
: config.cache?.maxAge || axios.defaults.cache.maxAge
});

@@ -38,17 +30,29 @@ return config;

if (cache.state === 'cached' && cache.expiration < Date.now()) {
yield axios.storage.remove(key);
await axios.storage.remove(key);
return config;
}
const { body, headers } = yield cache.data;
let data = {};
if (cache.state === 'loading') {
const deferred = axios.waiting[key];
// If the deferred is undefined, means that the
// outside has removed that key from the waiting list
if (!deferred) {
return config;
}
data = await deferred;
}
else {
data = cache.data;
}
config.adapter = () => Promise.resolve({
data: body,
config,
headers,
status: constants_1.CACHED_RESPONSE_STATUS,
statusText: constants_1.CACHED_RESPONSE_STATUS_TEXT
data: data.body,
headers: data.headers,
status: status_codes_1.CACHED_RESPONSE_STATUS,
statusText: status_codes_1.CACHED_RESPONSE_STATUS_TEXT
});
return config;
}));
});
}
exports.applyRequestInterceptor = applyRequestInterceptor;
//# sourceMappingURL=request.js.map
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.applyResponseInterceptor = void 0;
const cache_control_1 = require("@tusbar/cache-control");
const cache_predicate_1 = require("../util/cache-predicate");
const update_cache_1 = require("../util/update-cache");
function applyResponseInterceptor(axios) {
axios.interceptors.response.use((response) => __awaiter(this, void 0, void 0, function* () {
var _a, _b, _c, _d, _e;
axios.interceptors.response.use(async (response) => {
// Update other entries before updating himself
for (const [cacheKey, value] of Object.entries(((_a = response.config.cache) === null || _a === void 0 ? void 0 : _a.update) || {})) {
if (value == 'delete') {
yield axios.storage.remove(cacheKey);
continue;
if (response.config.cache?.update) {
(0, update_cache_1.updateCache)(axios, response.data, response.config.cache.update);
}
const cachePredicate = response.config.cache?.cachePredicate || axios.defaults.cache.cachePredicate;
// Config told that this response should be cached.
if (typeof cachePredicate === 'function') {
if (!cachePredicate(response)) {
return response;
}
const oldValue = yield axios.storage.get(cacheKey);
const newValue = value(oldValue, response.data);
if (newValue !== undefined) {
yield axios.storage.set(cacheKey, newValue);
}
else {
if (!(0, cache_predicate_1.checkPredicateObject)(response, cachePredicate)) {
return response;
}
else {
yield axios.storage.remove(cacheKey);
}
}
// Config told that this response should be cached.
if (!((_b = response.config.cache) === null || _b === void 0 ? void 0 : _b.shouldCache(response))) {
return response;
}
const key = axios.generateKey(response.config);
const cache = yield axios.storage.get(key);
if (
// Response already is in cache.
cache.state === 'cached' ||
// Received response without being intercepted in the response
cache.state === 'empty') {
const cache = await axios.storage.get(key);
// Response already is in cache or received without
// being intercepted in the response
if (cache.state === 'cached' || cache.state === 'empty') {
return response;
}
if ((_c = response.config.cache) === null || _c === void 0 ? void 0 : _c.interpretHeader) {
const cacheControl = response.headers['cache-control'] || '';
const { noCache, noStore, maxAge } = (0, cache_control_1.parse)(cacheControl);
const defaultMaxAge = response.config.cache?.maxAge || axios.defaults.cache.maxAge;
cache.expiration = cache.expiration || defaultMaxAge;
let saveCache = true;
if (response.config.cache?.interpretHeader) {
const expirationTime = axios.interpretHeader(response.headers['cache-control']);
// Header told that this response should not be cached.
if (noCache || noStore) {
return response;
if (expirationTime === false) {
saveCache = false;
}
const expirationTime = maxAge
? // Header max age in seconds
Date.now() + maxAge * 1000
: ((_d = response.config.cache) === null || _d === void 0 ? void 0 : _d.maxAge) || axios.defaults.cache.maxAge;
cache.expiration = expirationTime;
else {
cache.expiration = expirationTime ? expirationTime : defaultMaxAge;
}
}
else {
// If the cache expiration has not been set, use the default expiration.
cache.expiration =
cache.expiration ||
((_e = response.config.cache) === null || _e === void 0 ? void 0 : _e.maxAge) ||
axios.defaults.cache.maxAge;
const data = { body: response.data, headers: response.headers };
const deferred = axios.waiting[key];
// Resolve all other requests waiting for this response
if (deferred) {
deferred.resolve(data);
}
const data = { body: response.data, headers: response.headers };
// Resolve this deferred to update the cache after it
cache.data.resolve(data);
yield axios.storage.set(key, {
data,
expiration: cache.expiration,
state: 'cached'
});
if (saveCache) {
await axios.storage.set(key, {
data,
expiration: cache.expiration,
state: 'cached'
});
}
return response;
}));
});
}
exports.applyResponseInterceptor = applyResponseInterceptor;
//# sourceMappingURL=response.js.map
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.MemoryStorage = void 0;
class MemoryStorage {
constructor() {
this.storage = new Map();
this.get = (key) => __awaiter(this, void 0, void 0, function* () {
const value = this.storage.get(key);
if (value) {
return value;
}
// Fresh copy to prevent code duplication
const empty = { data: null, expiration: -1, state: 'empty' };
this.storage.set(key, empty);
return empty;
});
this.set = (key, value) => __awaiter(this, void 0, void 0, function* () {
this.storage.set(key, value);
});
this.remove = (key) => __awaiter(this, void 0, void 0, function* () {
this.storage.delete(key);
});
this.size = () => __awaiter(this, void 0, void 0, function* () {
return this.storage.size;
});
this.clear = () => __awaiter(this, void 0, void 0, function* () {
this.storage.clear();
});
}
storage = new Map();
get = async (key) => {
const value = this.storage.get(key);
if (value) {
return value;
}
const empty = { data: null, expiration: -1, state: 'empty' };
this.storage.set(key, empty);
return empty;
};
set = async (key, value) => {
this.storage.set(key, value);
};
remove = async (key) => {
this.storage.delete(key);
};
size = async () => {
return this.storage.size;
};
clear = async () => {
this.storage.clear();
};
}
exports.MemoryStorage = MemoryStorage;
//# sourceMappingURL=memory.js.map

@@ -1,2 +0,1 @@

import { Deferred } from '../utils/deferred';
export interface CacheStorage {

@@ -29,3 +28,3 @@ /**

} | {
data: Deferred<CachedResponse>;
data: null;
/**

@@ -32,0 +31,0 @@ * If interpretHeader is used, this value will be `-1`until the response is received

"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });

@@ -17,19 +8,19 @@ exports.SessionCacheStorage = exports.LocalCacheStorage = exports.WindowStorageWrapper = void 0;

class WindowStorageWrapper {
storage;
prefix;
constructor(storage, prefix = 'axios-cache:') {
this.storage = storage;
this.prefix = prefix;
this.get = (key) => __awaiter(this, void 0, void 0, function* () {
const json = this.storage.getItem(this.prefix + key);
return json
? JSON.parse(json)
: { data: null, expiration: -1, state: 'empty' };
});
this.set = (key, value) => __awaiter(this, void 0, void 0, function* () {
const json = JSON.stringify(value);
this.storage.setItem(this.prefix + key, json);
});
this.remove = (key) => __awaiter(this, void 0, void 0, function* () {
this.storage.removeItem(this.prefix + key);
});
}
get = async (key) => {
const json = this.storage.getItem(this.prefix + key);
return json ? JSON.parse(json) : { data: null, expiration: -1, state: 'empty' };
};
set = async (key, value) => {
const json = JSON.stringify(value);
this.storage.setItem(this.prefix + key, json);
};
remove = async (key) => {
this.storage.removeItem(this.prefix + key);
};
}

@@ -36,0 +27,0 @@ exports.WindowStorageWrapper = WindowStorageWrapper;

{
"name": "axios-cache-interceptor",
"version": "0.0.3",
"version": "0.0.5",
"description": "Cache interceptor for axios",

@@ -25,3 +25,7 @@ "main": "dist/index.js",

],
"author": "Hazork",
"author": {
"name": "Arthur Fiorette",
"email": "arthur.fiorette@gmail.com",
"url": "https://arthurfiorette.com.br"
},
"license": "MIT",

@@ -32,3 +36,7 @@ "bugs": {

"homepage": "https://github.com/ArthurFiorette/axios-cache-interceptor#readme",
"dependencies": {
"@tusbar/cache-control": "^0.6.0"
},
"devDependencies": {
"@arthurfiorette/prettier-config": "^1.0.5",
"@types/node": "^16.7.10",

@@ -44,7 +52,4 @@ "@typescript-eslint/eslint-plugin": "^4.30.0",

"prettier-plugin-organize-imports": "^2.3.3",
"typescript": "^4.4.2"
},
"dependencies": {
"@tusbar/cache-control": "^0.6.0"
"typescript": "^4.4.3"
}
}

@@ -39,2 +39,9 @@ <br />

></code>
<code
><a href="https://www.npmjs.com/package/axios-cache-interceptor"
><img
src="https://img.shields.io/npm/v/axios-cache-interceptor?color=CB3837&logo=npm&label=Npm"
target="_blank"
alt="Npm" /></a
></code>
</div>

@@ -81,8 +88,10 @@

> Axios is a peer dependency and must be installed separately.
```sh
# Npm
npm install --save axios-cache-interceptor
npm install --save axios axios-cache-interceptor
# Yarn
yarn add axios-cache-interceptor
yarn add axios axios-cache-interceptor
```

@@ -94,5 +103,6 @@

This project is highly inspired by several projects, written entirely in typescript, supporting https headers and much more,
This project is highly inspired by several projects, written entirely in typescript, supporting
https headers and much more.
Take a look at some projects:
Take a look at some similar projects:

@@ -113,4 +123,5 @@ - [axios-cache-adapter](https://github.com/RasCarlito/axios-cache-adapter)

See my contact information on my [github profile](https://github.com/ArthurFiorette) or open a new issue.
See my contact information on my [github profile](https://github.com/ArthurFiorette) or open a new
issue.
<br />
import { AxiosInstance } from 'axios';
import { defaultHeaderInterpreter } from '../header';
import { applyRequestInterceptor } from '../interceptors/request';
import { applyResponseInterceptor } from '../interceptors/response';
import { MemoryStorage } from '../storage/memory';
import { defaultKeyGenerator } from '../utils/key-generator';
import { AxiosCacheInstance, CacheInstance, CacheRequestConfig } from './types';
import { defaultKeyGenerator } from '../util/key-generator';
import CacheInstance, { AxiosCacheInstance, CacheProperties } from './types';
type Options = CacheRequestConfig['cache'] & Partial<CacheInstance>;
export function createCache(
axios: AxiosInstance,
options: Options = {}
options: Partial<CacheInstance & CacheProperties> = {}
): AxiosCacheInstance {

@@ -17,3 +16,5 @@ const axiosCache = axios as AxiosCacheInstance;

axiosCache.storage = options.storage || new MemoryStorage();
axiosCache.generateKey = defaultKeyGenerator;
axiosCache.generateKey = options.generateKey || defaultKeyGenerator;
axiosCache.waiting = options.waiting || {};
axiosCache.interpretHeader = options.interpretHeader || defaultHeaderInterpreter;

@@ -27,3 +28,3 @@ // CacheRequestConfig values

methods: ['get'],
shouldCache: ({ status }) => status >= 200 && status < 300,
cachePredicate: ({ status }) => status >= 200 && status < 300,
update: {},

@@ -30,0 +31,0 @@ ...options

@@ -9,4 +9,59 @@ import type {

} from 'axios';
import { CacheStorage } from '../storage/types';
import { Deferred } from 'src/util/deferred';
import { KeyGenerator } from 'src/util/key-generator';
import { HeaderInterpreter } from '../header';
import { CachedResponse, CacheStorage } from '../storage/types';
import { CachePredicate } from '../util/cache-predicate';
export type DefaultCacheRequestConfig = AxiosRequestConfig & {
cache: CacheProperties;
};
export type CacheProperties = {
/**
* The time until the cached value is expired in milliseconds.
*
* @default 1000 * 60 * 5
*/
maxAge: number;
/**
* If this interceptor should configure the cache from the request cache header
* When used, the maxAge property is ignored
*
* @default false
*/
interpretHeader: boolean;
/**
* All methods that should be cached.
*
* @default ['get']
*/
methods: Lowercase<Method>[];
/**
* The function to check if the response code permit being cached.
*
* @default ({ status }) => status >= 200 && status < 300
*/
cachePredicate: CachePredicate;
/**
* Once the request is resolved, this specifies what requests should we change the cache.
* Can be used to update the request or delete other caches.
*
* If the function returns void, the entry is deleted
*
* This is independent if the request made was cached or not.
*
* The id used is the same as the id on `CacheRequestConfig['id']`, auto-generated or not.
*
* @default {}
*/
update: {
[id: string]: 'delete' | ((oldValue: any, atual: any) => any | undefined);
};
};
/**

@@ -27,51 +82,6 @@ * Options that can be overridden per request

*/
cache?: {
/**
* The time until the cached value is expired in milliseconds.
*
* @default 1000 * 60 * 5
*/
maxAge?: number;
/**
* If this interceptor should configure the cache from the request cache header
* When used, the maxAge property is ignored
*
* @default false
*/
interpretHeader?: boolean;
/**
* All methods that should be cached.
*
* @default ['get']
*/
methods?: Lowercase<Method>[];
/**
* The function to check if the response code permit being cached.
*
* @default ({ status }) => status >= 200 && status < 300
*/
shouldCache?: (response: AxiosResponse) => boolean;
/**
* Once the request is resolved, this specifies what requests should we change the cache.
* Can be used to update the request or delete other caches.
*
* If the function returns void, the entry is deleted
*
* This is independent if the request made was cached or not.
*
* The id used is the same as the id on `CacheRequestConfig['id']`, auto-generated or not.
*
* @default {}
*/
update?: {
[id: string]: 'delete' | ((oldValue: any, atual: any) => any | void);
};
};
cache?: Partial<CacheProperties>;
};
export interface CacheInstance {
export default interface CacheInstance {
/**

@@ -89,5 +99,23 @@ * The storage to save the cache data.

*/
generateKey: (options: CacheRequestConfig) => string;
generateKey: KeyGenerator;
/**
* A simple object that holds all deferred objects until it is resolved.
*/
waiting: Record<string, Deferred<CachedResponse>>;
/**
* The function to parse and interpret response headers.
* Only used if cache.interpretHeader is true.
*/
interpretHeader: HeaderInterpreter;
}
/**
* Same as the AxiosInstance but with CacheRequestConfig as a config type.
*
* @see AxiosInstance
* @see CacheRequestConfig
* @see CacheInstance
*/
export interface AxiosCacheInstance extends AxiosInstance, CacheInstance {

@@ -97,8 +125,7 @@ (config: CacheRequestConfig): AxiosPromise;

defaults: CacheRequestConfig;
defaults: DefaultCacheRequestConfig;
interceptors: {
request: AxiosInterceptorManager<CacheRequestConfig>;
response: AxiosInterceptorManager<
AxiosResponse & { config: CacheRequestConfig }
>;
response: AxiosInterceptorManager<AxiosResponse & { config: CacheRequestConfig }>;
};

@@ -108,22 +135,8 @@

request<T = any, R = AxiosResponse<T>>(
config: CacheRequestConfig
): Promise<R>;
request<T = any, R = AxiosResponse<T>>(config: CacheRequestConfig): Promise<R>;
get<T = any, R = AxiosResponse<T>>(
url: string,
config?: CacheRequestConfig
): Promise<R>;
delete<T = any, R = AxiosResponse<T>>(
url: string,
config?: CacheRequestConfig
): Promise<R>;
head<T = any, R = AxiosResponse<T>>(
url: string,
config?: CacheRequestConfig
): Promise<R>;
options<T = any, R = AxiosResponse<T>>(
url: string,
config?: CacheRequestConfig
): Promise<R>;
get<T = any, R = AxiosResponse<T>>(url: string, config?: CacheRequestConfig): Promise<R>;
delete<T = any, R = AxiosResponse<T>>(url: string, config?: CacheRequestConfig): Promise<R>;
head<T = any, R = AxiosResponse<T>>(url: string, config?: CacheRequestConfig): Promise<R>;
options<T = any, R = AxiosResponse<T>>(url: string, config?: CacheRequestConfig): Promise<R>;
post<T = any, R = AxiosResponse<T>>(

@@ -130,0 +143,0 @@ url: string,

@@ -1,3 +0,3 @@

export { createCache } from './axios/cache';
export * from './constants';
export * from './axios';
export * from './storage';
export * as StatusCodes from './util/status-codes';

@@ -0,16 +1,10 @@

import { CachedResponse } from 'src/storage/types';
import { AxiosCacheInstance } from '../axios/types';
import {
CACHED_RESPONSE_STATUS,
CACHED_RESPONSE_STATUS_TEXT
} from '../constants';
import { Deferred } from '../utils/deferred';
import { Deferred } from '../util/deferred';
import { CACHED_RESPONSE_STATUS, CACHED_RESPONSE_STATUS_TEXT } from '../util/status-codes';
export function applyRequestInterceptor(axios: AxiosCacheInstance) {
export function applyRequestInterceptor(axios: AxiosCacheInstance): void {
axios.interceptors.request.use(async (config) => {
// Only cache specified methods
if (
config.cache?.methods?.some(
(method) => (config.method || 'get').toLowerCase() == method
)
) {
if (config.cache?.methods?.some((method) => (config.method || 'get').toLowerCase() == method)) {
return config;

@@ -24,10 +18,14 @@ }

if (cache.state == 'empty') {
// Create a deferred to resolve other requests for the same key when it's completed
axios.waiting[key] = new Deferred();
await axios.storage.set(key, {
state: 'loading',
data: new Deferred(),
data: null,
// The cache header will be set after the response has been read, until that time, the expiration will be -1
expiration: config.cache?.interpretHeader
? -1
: config.cache?.maxAge || axios.defaults.cache!.maxAge!
: config.cache?.maxAge || axios.defaults.cache.maxAge
});
return config;

@@ -42,9 +40,22 @@ }

const { body, headers } = await cache.data;
let data = {} as CachedResponse;
if (cache.state === 'loading') {
const deferred = axios.waiting[key];
// If the deferred is undefined, means that the
// outside has removed that key from the waiting list
if (!deferred) {
return config;
}
data = await deferred;
} else {
data = cache.data;
}
config.adapter = () =>
Promise.resolve({
data: body,
config,
headers,
data: data.body,
headers: data.headers,
status: CACHED_RESPONSE_STATUS,

@@ -51,0 +62,0 @@ statusText: CACHED_RESPONSE_STATUS_TEXT

@@ -1,27 +0,24 @@

import { parse } from '@tusbar/cache-control';
import { AxiosCacheInstance } from '../axios/types';
import { checkPredicateObject } from '../util/cache-predicate';
import { updateCache } from '../util/update-cache';
export function applyResponseInterceptor(axios: AxiosCacheInstance) {
export function applyResponseInterceptor(axios: AxiosCacheInstance): void {
axios.interceptors.response.use(async (response) => {
// Update other entries before updating himself
for (const [cacheKey, value] of Object.entries(
response.config.cache?.update || {}
)) {
if (value == 'delete') {
await axios.storage.remove(cacheKey);
continue;
}
const oldValue = await axios.storage.get(cacheKey);
const newValue = value(oldValue, response.data);
if (newValue !== undefined) {
await axios.storage.set(cacheKey, newValue);
} else {
await axios.storage.remove(cacheKey);
}
if (response.config.cache?.update) {
updateCache(axios, response.data, response.config.cache.update);
}
const cachePredicate =
response.config.cache?.cachePredicate || axios.defaults.cache.cachePredicate;
// Config told that this response should be cached.
if (!response.config.cache?.shouldCache!(response)) {
return response;
if (typeof cachePredicate === 'function') {
if (!cachePredicate(response)) {
return response;
}
} else {
if (!checkPredicateObject(response, cachePredicate)) {
return response;
}
}

@@ -32,44 +29,38 @@

if (
// Response already is in cache.
cache.state === 'cached' ||
// Received response without being intercepted in the response
cache.state === 'empty'
) {
// Response already is in cache or received without
// being intercepted in the response
if (cache.state === 'cached' || cache.state === 'empty') {
return response;
}
const defaultMaxAge = response.config.cache?.maxAge || axios.defaults.cache.maxAge;
cache.expiration = cache.expiration || defaultMaxAge;
let saveCache = true;
if (response.config.cache?.interpretHeader) {
const cacheControl = response.headers['cache-control'] || '';
const { noCache, noStore, maxAge } = parse(cacheControl);
const expirationTime = axios.interpretHeader(response.headers['cache-control']);
// Header told that this response should not be cached.
if (noCache || noStore) {
return response;
if (expirationTime === false) {
saveCache = false;
} else {
cache.expiration = expirationTime ? expirationTime : defaultMaxAge;
}
const expirationTime = maxAge
? // Header max age in seconds
Date.now() + maxAge * 1000
: response.config.cache?.maxAge || axios.defaults.cache!.maxAge!;
cache.expiration = expirationTime;
} else {
// If the cache expiration has not been set, use the default expiration.
cache.expiration =
cache.expiration ||
response.config.cache?.maxAge ||
axios.defaults.cache!.maxAge!;
}
const data = { body: response.data, headers: response.headers };
const deferred = axios.waiting[key];
// Resolve this deferred to update the cache after it
cache.data.resolve(data);
// Resolve all other requests waiting for this response
if (deferred) {
deferred.resolve(data);
}
await axios.storage.set(key, {
data,
expiration: cache.expiration,
state: 'cached'
});
if (saveCache) {
await axios.storage.set(key, {
data,
expiration: cache.expiration,
state: 'cached'
});
}

@@ -76,0 +67,0 @@ return response;

@@ -13,3 +13,2 @@ import { CacheStorage, StorageValue } from './types';

// Fresh copy to prevent code duplication
const empty = { data: null, expiration: -1, state: 'empty' } as const;

@@ -16,0 +15,0 @@ this.storage.set(key, empty);

@@ -1,3 +0,1 @@

import { Deferred } from '../utils/deferred';
export interface CacheStorage {

@@ -34,3 +32,3 @@ /**

| {
data: Deferred<CachedResponse>;
data: null;
/**

@@ -37,0 +35,0 @@ * If interpretHeader is used, this value will be `-1`until the response is received

@@ -6,12 +6,7 @@ import { CacheStorage, StorageValue } from './types';

export abstract class WindowStorageWrapper implements CacheStorage {
constructor(
readonly storage: Storage,
readonly prefix: string = 'axios-cache:'
) {}
constructor(readonly storage: Storage, readonly prefix: string = 'axios-cache:') {}
get = async (key: string): Promise<StorageValue> => {
const json = this.storage.getItem(this.prefix + key);
return json
? JSON.parse(json)
: { data: null, expiration: -1, state: 'empty' };
return json ? JSON.parse(json) : { data: null, expiration: -1, state: 'empty' };
};

@@ -18,0 +13,0 @@

@@ -7,3 +7,3 @@ {

"incremental": true /* Enable incremental compilation */,
"target": "ES6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
"target": "ESNext" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
"module": "CommonJS" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,

@@ -10,0 +10,0 @@ // "lib": [], /* Specify library files to be included in the compilation. */

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc