Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

axios-cache-interceptor

Package Overview
Dependencies
Maintainers
1
Versions
80
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.4.0 to 0.4.1

dist/storage/util.js

149

dist/interceptors/request.js

@@ -6,86 +6,85 @@ "use strict";

class CacheRequestInterceptor {
axios;
constructor(axios) {
this.axios = axios;
}
use = () => {
this.axios.interceptors.request.use(this.onFulfilled);
};
onFulfilled = async (config) => {
// Skip cache
if (config.cache === false) {
return config;
}
// Only cache specified methods
const allowedMethods = config.cache?.methods || this.axios.defaults.cache.methods;
if (!allowedMethods.some((method) => (config.method || 'get').toLowerCase() == method)) {
return config;
}
const key = this.axios.generateKey(config);
// Assumes that the storage handled staled responses
let cache = await this.axios.storage.get(key);
// Not cached, continue the request, and mark it as fetching
emptyState: if (cache.state == 'empty') {
/**
* This checks for simultaneous access to a new key. The js
* event loop jumps on the first await statement, so the second
* (asynchronous call) request may have already started executing.
*/
if (this.axios.waiting[key]) {
cache = (await this.axios.storage.get(key));
break emptyState;
this.use = () => {
this.axios.interceptors.request.use(this.onFulfilled);
};
this.onFulfilled = async (config) => {
// Skip cache
if (config.cache === false) {
return config;
}
// Create a deferred to resolve other requests for the same key when it's completed
this.axios.waiting[key] = (0, deferred_1.deferred)();
// Only cache specified methods
const allowedMethods = config.cache?.methods || this.axios.defaults.cache.methods;
if (!allowedMethods.some((method) => (config.method || 'get').toLowerCase() == method)) {
return config;
}
const key = this.axios.generateKey(config);
// Assumes that the storage handled staled responses
let cache = await this.axios.storage.get(key);
// Not cached, continue the request, and mark it as fetching
emptyState: if (cache.state == 'empty') {
/**
* This checks for simultaneous access to a new key. The js
* event loop jumps on the first await statement, so the second
* (asynchronous call) request may have already started executing.
*/
if (this.axios.waiting[key]) {
cache = (await this.axios.storage.get(key));
break emptyState;
}
// Create a deferred to resolve other requests for the same key when it's completed
this.axios.waiting[key] = (0, deferred_1.deferred)();
/**
* Add a default reject handler to catch when the request is
* aborted without others waiting for it.
*/
this.axios.waiting[key]?.catch(() => undefined);
await this.axios.storage.set(key, {
state: 'loading',
ttl: config.cache?.ttl
});
return config;
}
let cachedResponse;
if (cache.state === 'loading') {
const deferred = this.axios.waiting[key];
/**
* If the deferred is undefined, means that the outside has
* removed that key from the waiting list
*/
if (!deferred) {
await this.axios.storage.remove(key);
return config;
}
try {
cachedResponse = await deferred;
}
catch (e) {
// The deferred is rejected when the request that we are waiting rejected cache.
return config;
}
}
else {
cachedResponse = cache.data;
}
config.adapter = () =>
/**
* Add a default reject handler to catch when the request is
* aborted without others waiting for it.
* Even though the response interceptor receives this one from
* here, it has been configured to ignore cached responses: true
*/
this.axios.waiting[key]?.catch(() => undefined);
await this.axios.storage.set(key, {
state: 'loading',
ttl: config.cache?.ttl
Promise.resolve({
config: config,
data: cachedResponse.data,
headers: cachedResponse.headers,
status: cachedResponse.status,
statusText: cachedResponse.statusText,
cached: true,
id: key
});
return config;
}
let cachedResponse;
if (cache.state === 'loading') {
const deferred = this.axios.waiting[key];
/**
* If the deferred is undefined, means that the outside has
* removed that key from the waiting list
*/
if (!deferred) {
await this.axios.storage.remove(key);
return config;
}
try {
cachedResponse = await deferred;
}
catch (e) {
// The deferred is rejected when the request that we are waiting rejected cache.
return config;
}
}
else {
cachedResponse = cache.data;
}
config.adapter = () =>
/**
* Even though the response interceptor receives this one from
* here, it has been configured to ignore cached responses: true
*/
Promise.resolve({
config: config,
data: cachedResponse.data,
headers: cachedResponse.headers,
status: cachedResponse.status,
statusText: cachedResponse.statusText,
cached: true,
id: key
});
return config;
};
};
}
}
exports.CacheRequestInterceptor = CacheRequestInterceptor;
//# sourceMappingURL=request.js.map

@@ -8,88 +8,89 @@ "use strict";

class CacheResponseInterceptor {
axios;
constructor(axios) {
this.axios = axios;
}
use = () => {
this.axios.interceptors.response.use(this.onFulfilled);
};
testCachePredicate = (response, cache) => {
const cachePredicate = cache?.cachePredicate || this.axios.defaults.cache.cachePredicate;
return ((typeof cachePredicate === 'function' && cachePredicate(response)) ||
(typeof cachePredicate === 'object' &&
(0, cache_predicate_1.checkPredicateObject)(response, cachePredicate)));
};
/**
* Rejects cache for this response. Also update the waiting list for
* this key by rejecting it.
*/
rejectResponse = async (key) => {
// Update the cache to empty to prevent infinite loading state
await this.axios.storage.remove(key);
// Reject the deferred if present
this.axios.waiting[key]?.reject();
delete this.axios.waiting[key];
};
onFulfilled = async (axiosResponse) => {
const key = this.axios.generateKey(axiosResponse.config);
const response = {
id: key,
// When the request interceptor override the request adapter, it means
// that the response.cached will be true and therefore, the request was cached.
cached: axiosResponse.cached || false,
...axiosResponse
this.use = () => {
this.axios.interceptors.response.use(this.onFulfilled);
};
// Skip cache
if (response.config.cache === false) {
return { ...response, cached: false };
}
// Response was marked as cached
if (response.cached) {
return response;
}
const cache = await this.axios.storage.get(key);
this.testCachePredicate = (response, cache) => {
const cachePredicate = cache?.cachePredicate || this.axios.defaults.cache.cachePredicate;
return ((typeof cachePredicate === 'function' && cachePredicate(response)) ||
(typeof cachePredicate === 'object' &&
(0, cache_predicate_1.checkPredicateObject)(response, cachePredicate)));
};
/**
* From now on, the cache and response represents the state of the
* first response to a request, which has not yet been cached or
* processed before.
* Rejects cache for this response. Also update the waiting list for
* this key by rejecting it.
*/
if (cache.state !== 'loading') {
return response;
}
// Config told that this response should be cached.
if (!this.testCachePredicate(response, response.config.cache)) {
await this.rejectResponse(key);
return response;
}
let ttl = response.config.cache?.ttl || this.axios.defaults.cache.ttl;
if (response.config.cache?.interpretHeader) {
const expirationTime = this.axios.headerInterpreter(response.headers);
// Cache should not be used
if (expirationTime === false) {
this.rejectResponse = async (key) => {
// Update the cache to empty to prevent infinite loading state
await this.axios.storage.remove(key);
// Reject the deferred if present
this.axios.waiting[key]?.reject();
delete this.axios.waiting[key];
};
this.onFulfilled = async (axiosResponse) => {
const key = this.axios.generateKey(axiosResponse.config);
const response = {
id: key,
/**
* The request interceptor response.cache will return true or
* undefined. And true only when the response was cached.
*/
cached: axiosResponse.cached || false,
...axiosResponse
};
// Skip cache
if (response.config.cache === false) {
return { ...response, cached: false };
}
// Response is already cached
if (response.cached) {
return response;
}
const cache = await this.axios.storage.get(key);
/**
* From now on, the cache and response represents the state of the
* first response to a request, which has not yet been cached or
* processed before.
*/
if (cache.state !== 'loading') {
return response;
}
// Config told that this response should be cached.
if (!this.testCachePredicate(response, response.config.cache)) {
await this.rejectResponse(key);
return response;
}
ttl = expirationTime ? expirationTime : ttl;
}
const newCache = {
state: 'cached',
ttl: ttl,
createdAt: Date.now(),
data: (0, object_1.extract)(response, ['data', 'headers', 'status', 'statusText'])
let ttl = response.config.cache?.ttl || this.axios.defaults.cache.ttl;
if (response.config.cache?.interpretHeader) {
const expirationTime = this.axios.headerInterpreter(response.headers);
// Cache should not be used
if (expirationTime === false) {
await this.rejectResponse(key);
return response;
}
ttl = expirationTime ? expirationTime : ttl;
}
const newCache = {
state: 'cached',
ttl: ttl,
createdAt: Date.now(),
data: (0, object_1.extract)(response, ['data', 'headers', 'status', 'statusText'])
};
// Update other entries before updating himself
if (response.config.cache?.update) {
(0, update_cache_1.updateCache)(this.axios.storage, response.data, response.config.cache.update);
}
const deferred = this.axios.waiting[key];
// Resolve all other requests waiting for this response
await deferred?.resolve(newCache.data);
delete this.axios.waiting[key];
// Define this key as cache on the storage
await this.axios.storage.set(key, newCache);
// Return the response with cached as false, because it was not cached at all
return response;
};
// Update other entries before updating himself
if (response.config.cache?.update) {
(0, update_cache_1.updateCache)(this.axios.storage, response.data, response.config.cache.update);
}
const deferred = this.axios.waiting[key];
// Resolve all other requests waiting for this response
await deferred?.resolve(newCache.data);
delete this.axios.waiting[key];
// Define this key as cache on the storage
await this.axios.storage.set(key, newCache);
// Return the response with cached as false, because it was not cached at all
return response;
};
}
}
exports.CacheResponseInterceptor = CacheResponseInterceptor;
//# sourceMappingURL=response.js.map
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MemoryStorage = void 0;
const util_1 = require("./util");
class MemoryStorage {
storage = new Map();
get = async (key) => {
const value = this.storage.get(key);
if (!value) {
return { state: 'empty' };
}
if (value.state === 'cached' && value.createdAt + value.ttl < Date.now()) {
this.remove(key);
return { state: 'empty' };
}
return value;
};
set = async (key, value) => {
this.storage.set(key, value);
};
remove = async (key) => {
this.storage.delete(key);
};
constructor() {
this.storage = new Map();
this.get = async (key) => {
const value = this.storage.get(key);
if (!value) {
return { state: 'empty' };
}
if ((0, util_1.isCacheValid)(value) === false) {
this.remove(key);
return { state: 'empty' };
}
return value;
};
this.set = async (key, value) => {
this.storage.set(key, value);
};
this.remove = async (key) => {
this.storage.delete(key);
};
}
}
exports.MemoryStorage = MemoryStorage;
//# sourceMappingURL=memory.js.map
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SessionCacheStorage = exports.LocalCacheStorage = exports.WindowStorageWrapper = void 0;
exports.SessionCacheStorage = exports.LocalCacheStorage = exports.WindowStorageWrapper = exports.DEFAULT_KEY_PREFIX = void 0;
const util_1 = require("./util");
/**
* The key prefix used in WindowStorageWrapper to prevent key
* collisions with other code.
*/
exports.DEFAULT_KEY_PREFIX = 'axios-cache-interceptor';
/**
* A storage that uses any {@link Storage} as his storage.
*
* **Note**: All storage keys used are prefixed with `prefix` value.
*/
class WindowStorageWrapper {
storage;
prefix;
constructor(storage, prefix = 'axios-cache:') {
/**
* Creates a new instance of WindowStorageWrapper
*
* @param storage The storage to interact
* @param prefix The prefix to use for all keys or
* `DEFAULT_KEY_PREFIX` if not provided.
* @see DEFAULT_KEY_PREFIX
*/
constructor(storage, prefix = exports.DEFAULT_KEY_PREFIX) {
this.storage = storage;
this.prefix = prefix;
this.get = async (key) => {
const prefixedKey = this.prefixKey(key);
const json = this.storage.getItem(prefixedKey);
if (!json) {
return { state: 'empty' };
}
const parsed = JSON.parse(json);
if ((0, util_1.isCacheValid)(parsed) === false) {
this.storage.removeItem(prefixedKey);
return { state: 'empty' };
}
return parsed;
};
this.set = async (key, value) => {
const json = JSON.stringify(value);
this.storage.setItem(this.prefixKey(key), json);
};
this.remove = async (key) => {
this.storage.removeItem(this.prefixKey(key));
};
this.prefixKey = (key) => `${this.prefix}:${key}`;
}
get = async (_key) => {
const key = this.prefix + _key;
const json = this.storage.getItem(key);
if (!json) {
return { state: 'empty' };
}
const parsed = JSON.parse(json);
if (parsed.state === 'cached' && parsed.createdAt + parsed.ttl < Date.now()) {
this.storage.removeItem(key);
return { state: 'empty' };
}
return parsed;
};
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);
};
}

@@ -35,0 +50,0 @@ exports.WindowStorageWrapper = WindowStorageWrapper;

{
"name": "axios-cache-interceptor",
"version": "0.4.0",
"version": "0.4.1",
"description": "Cache interceptor for axios",
"main": "dist/index.js",
"main": "./dist/index.js",
"types": "./types/index.d.ts",
"typings": "./types/index.d.ts",
"scripts": {

@@ -44,3 +46,3 @@ "build": "tsc --p tsconfig.build.json",

"devDependencies": {
"@arthurfiorette/prettier-config": "^1.0.6",
"@arthurfiorette/prettier-config": "*",
"@types/jest": "^27.0.2",

@@ -47,0 +49,0 @@ "@types/node": "^16.7.10",

@@ -52,4 +52,7 @@ import type { AxiosResponse } from 'axios';

id: key,
// When the request interceptor override the request adapter, it means
// that the response.cached will be true and therefore, the request was cached.
/**
* The request interceptor response.cache will return true or
* undefined. And true only when the response was cached.
*/
cached: (axiosResponse as CacheAxiosResponse<R, D>).cached || false,

@@ -64,3 +67,3 @@ ...axiosResponse

// Response was marked as cached
// Response is already cached
if (response.cached) {

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

import type { CacheStorage, StorageValue } from './types';
import { isCacheValid } from './util';

@@ -13,3 +14,3 @@ export class MemoryStorage implements CacheStorage {

if (value.state === 'cached' && value.createdAt + value.ttl < Date.now()) {
if (isCacheValid(value) === false) {
this.remove(key);

@@ -16,0 +17,0 @@ return { state: 'empty' };

@@ -35,2 +35,6 @@ export interface CacheStorage {

data: CachedResponse;
/**
* The number in milliseconds to wait after createdAt before the
* value is considered stale.
*/
ttl: number;

@@ -37,0 +41,0 @@ createdAt: number;

import type { CacheStorage, StorageValue } from './types';
import { isCacheValid } from './util';
/**
* The key prefix used in WindowStorageWrapper to prevent key
* collisions with other code.
*/
export const DEFAULT_KEY_PREFIX = 'axios-cache-interceptor';
/**
* A storage that uses any {@link Storage} as his storage.
*
* **Note**: All storage keys used are prefixed with `prefix` value.
*/
export abstract class WindowStorageWrapper implements CacheStorage {
constructor(readonly storage: Storage, readonly prefix: string = 'axios-cache:') {}
/**
* Creates a new instance of WindowStorageWrapper
*
* @param storage The storage to interact
* @param prefix The prefix to use for all keys or
* `DEFAULT_KEY_PREFIX` if not provided.
* @see DEFAULT_KEY_PREFIX
*/
constructor(readonly storage: Storage, readonly prefix: string = DEFAULT_KEY_PREFIX) {}
get = async (_key: string): Promise<StorageValue> => {
const key = this.prefix + _key;
const json = this.storage.getItem(key);
get = async (key: string): Promise<StorageValue> => {
const prefixedKey = this.prefixKey(key);
const json = this.storage.getItem(prefixedKey);

@@ -18,4 +36,4 @@ if (!json) {

if (parsed.state === 'cached' && parsed.createdAt + parsed.ttl < Date.now()) {
this.storage.removeItem(key);
if (isCacheValid(parsed) === false) {
this.storage.removeItem(prefixedKey);
return { state: 'empty' };

@@ -29,8 +47,10 @@ }

const json = JSON.stringify(value);
this.storage.setItem(this.prefix + key, json);
this.storage.setItem(this.prefixKey(key), json);
};
remove = async (key: string): Promise<void> => {
this.storage.removeItem(this.prefix + key);
this.storage.removeItem(this.prefixKey(key));
};
private prefixKey = (key: string): string => `${this.prefix}:${key}`;
}

@@ -37,0 +57,0 @@

@@ -14,3 +14,3 @@ {

/* Language and Environment */
"target": "ESNext" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
"target": "ES2021" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */

@@ -30,3 +30,3 @@ // "jsx": "preserve", /* Specify what JSX code is generated. */

// "rootDir": "./", /* Specify the root folder within your source files. */
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
"moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */

@@ -38,3 +38,3 @@ // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */

// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "resolveJsonModule": true, /* Enable importing .json files */
"resolveJsonModule": false, /* Enable importing .json files */
// "noResolve": true, /* Disallow `import`s, `require`s or `<reference>`s from expanding the number of files TypeScript should add to a project. */

@@ -69,9 +69,9 @@

// "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
"declarationDir": "./types", /* Specify the output directory for generated declaration files. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
"allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */,
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,

@@ -81,3 +81,3 @@

"strict": true /* Enable all strict type-checking options. */,
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
"noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
"strictNullChecks": true /* When type checking, take into account `null` and `undefined`. */,

@@ -102,5 +102,5 @@ "strictFunctionTypes": true /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */,

/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}

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