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.2.0 to 0.2.1

12

dist/axios/cache.d.ts

@@ -6,5 +6,5 @@ import { AxiosInstance, AxiosRequestConfig } from 'axios';

*
* @param axios the already created axios instance
* @param config the config for the caching interceptors
* @returns the same instance but with caching enabled
* @param axios The already created axios instance
* @param config The config for the caching interceptors
* @returns The same instance but with caching enabled
*/

@@ -15,7 +15,7 @@ export declare function applyCache(axios: AxiosInstance, { storage, generateKey, waiting, headerInterpreter, requestInterceptor, responseInterceptor, ...cacheOptions }?: Partial<CacheInstance> & Partial<CacheProperties>): AxiosCacheInstance;

*
* @param config the config for the caching interceptors
* @param axiosConfig the config for the created axios instance
* @returns the same instance but with caching enabled
* @param config The config for the caching interceptors
* @param axiosConfig The config for the created axios instance
* @returns The same instance but with caching enabled
*/
export declare function createCache(config?: Partial<CacheInstance> & Partial<CacheProperties>, axiosConfig?: AxiosRequestConfig): AxiosCacheInstance;
//# sourceMappingURL=cache.d.ts.map

@@ -16,5 +16,5 @@ "use strict";

*
* @param axios the already created axios instance
* @param config the config for the caching interceptors
* @returns the same instance but with caching enabled
* @param axios The already created axios instance
* @param config The config for the caching interceptors
* @returns The same instance but with caching enabled
*/

@@ -52,5 +52,5 @@ function applyCache(axios, { storage, generateKey, waiting, headerInterpreter, requestInterceptor, responseInterceptor, ...cacheOptions } = {}) {

*
* @param config the config for the caching interceptors
* @param axiosConfig the config for the created axios instance
* @returns the same instance but with caching enabled
* @param config The config for the caching interceptors
* @param axiosConfig The config for the created axios instance
* @returns The same instance but with caching enabled
*/

@@ -57,0 +57,0 @@ function createCache(config = {}, axiosConfig = {}) {

@@ -8,3 +8,3 @@ import type { AxiosInstance, AxiosInterceptorManager, AxiosPromise, AxiosRequestConfig, AxiosResponse, Method } from 'axios';

import { KeyGenerator } from '../util/key-generator';
export declare type CacheUpdater = ((cached: EmptyStorageValue | CachedStorageValue, newData: any) => CachedStorageValue | void) | 'delete';
export declare type CacheUpdater = 'delete' | ((cached: EmptyStorageValue | CachedStorageValue, newData: any) => CachedStorageValue | void);
export declare type DefaultCacheRequestConfig = AxiosRequestConfig & {

@@ -23,4 +23,4 @@ cache: CacheProperties;

/**
* If this interceptor should configure the cache from the request cache header
* When used, the ttl property is ignored
* If this interceptor should configure the cache from the request
* cache header When used, the ttl property is ignored
*

@@ -39,8 +39,9 @@ * @default false

*
* @default { statusCheck: [200, 399] }
* @default {statusCheck: [200, 399]}
*/
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.
* 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.
*

@@ -51,10 +52,16 @@ * If the function returns nothing, the entry is deleted

*
* The id used is the same as the id on `CacheRequestConfig['id']`, auto-generated or not.
* The id used is the same as the id on `CacheRequestConfig['id']`,
* auto-generated or not.
*
* @default {}
* @default
*/
update: Record<string, CacheUpdater>;
};
export declare type CacheAxiosResponse = AxiosResponse & {
export declare type CacheAxiosResponse<T = any> = AxiosResponse<T> & {
config: CacheRequestConfig;
/**
* The id used for this request. if config specified an id, the id
* will be returned
*/
id: string;
};

@@ -66,8 +73,8 @@ /**

/**
* An id for this request, if this request is used in cache, only the last request made with this id will be returned.
* An id for this request, if this request is used in cache, only
* the last request made with this id will be returned.
*
* @see cacheKey
* @default undefined
*/
id?: string | symbol;
id?: string;
/**

@@ -89,13 +96,17 @@ * All cache options for the request.

* The function used to create different keys for each request.
* Defaults to a function that priorizes the id, and if not specified,
* a string is generated using the method, baseUrl, params, and url
* Defaults to a function that priorizes the id, and if not
* specified, a string is generated using the method, baseUrl,
* params, and url
*/
generateKey: KeyGenerator;
/**
* A simple object that holds all deferred objects until it is resolved.
* A simple object that holds all deferred objects until it is
* resolved or rejected.
*
* Can be used to listen when a request is cached or not.
*/
waiting: Record<string, Deferred<CachedResponse>>;
waiting: Record<string, Deferred<CachedResponse, void>>;
/**
* The function to parse and interpret response headers.
* Only used if cache.interpretHeader is true.
* The function to parse and interpret response headers. Only used
* if cache.interpretHeader is true.
*/

@@ -113,3 +124,4 @@ headerInterpreter: HeaderInterpreter;

/**
* Same as the AxiosInstance but with CacheRequestConfig as a config type.
* Same as the AxiosInstance but with CacheRequestConfig as a config
* type and CacheAxiosResponse as response type.
*

@@ -129,11 +141,11 @@ * @see AxiosInstance

getUri(config?: CacheRequestConfig): string;
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>;
post<T = any, R = AxiosResponse<T>>(url: string, data?: any, config?: CacheRequestConfig): Promise<R>;
put<T = any, R = AxiosResponse<T>>(url: string, data?: any, config?: CacheRequestConfig): Promise<R>;
patch<T = any, R = AxiosResponse<T>>(url: string, data?: any, config?: CacheRequestConfig): Promise<R>;
request<T = any, R = CacheAxiosResponse<T>>(config: CacheRequestConfig): Promise<R>;
get<T = any, R = CacheAxiosResponse<T>>(url: string, config?: CacheRequestConfig): Promise<R>;
delete<T = any, R = CacheAxiosResponse<T>>(url: string, config?: CacheRequestConfig): Promise<R>;
head<T = any, R = CacheAxiosResponse<T>>(url: string, config?: CacheRequestConfig): Promise<R>;
options<T = any, R = CacheAxiosResponse<T>>(url: string, config?: CacheRequestConfig): Promise<R>;
post<T = any, R = CacheAxiosResponse<T>>(url: string, data?: any, config?: CacheRequestConfig): Promise<R>;
put<T = any, R = CacheAxiosResponse<T>>(url: string, data?: any, config?: CacheRequestConfig): Promise<R>;
patch<T = any, R = CacheAxiosResponse<T>>(url: string, data?: any, config?: CacheRequestConfig): Promise<R>;
}
//# sourceMappingURL=types.d.ts.map
/**
* Interpret the cache control header, if present.
*
* @param header the header object to interpret.
*
* @returns `false` if cache should not be used. `undefined` when provided
* headers was not enough to determine a valid value. Or a `number` containing
* the number of **milliseconds** to cache the response.
* @param header The header object to interpret.
* @returns `false` if cache should not be used. `undefined` when
* provided headers was not enough to determine a valid value. Or a
* `number` containing the number of **milliseconds** to cache the response.
*/
export declare type HeaderInterpreter = (headers?: Record<Lowercase<string>, string>) => false | undefined | number;
//# sourceMappingURL=types.d.ts.map

@@ -15,3 +15,3 @@ "use strict";

onFulfilled = async (config) => {
// Ignore caching
// Skip cache
if (config.cache === false) {

@@ -30,6 +30,7 @@ return config;

emptyState: if (cache.state == 'empty') {
// This if catches concurrent access to a new key.
// The js event loop skips in the first await statement,
// so the next code block will be executed both if called
// from two places asynchronously.
/**
* 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]) {

@@ -40,3 +41,8 @@ cache = (await this.axios.storage.get(key));

// Create a deferred to resolve other requests for the same key when it's completed
this.axios.waiting[key] = new deferred_1.Deferred();
this.axios.waiting[key] = (0, deferred_1.deferred)();
/**
* Add a default reject handler to detect when the request is
* aborted without others waiting
*/
this.axios.waiting[key]?.catch(() => { });
await this.axios.storage.set(key, {

@@ -51,4 +57,6 @@ 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 the deferred is undefined, means that the outside has
* removed that key from the waiting list
*/
if (!deferred) {

@@ -58,3 +66,9 @@ await this.axios.storage.remove(key);

}
data = await deferred;
try {
data = await deferred;
}
catch (e) {
// The deferred is rejected when the request that we are waiting rejected cache.
return config;
}
}

@@ -61,0 +75,0 @@ else {

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

import { AxiosResponse } from 'axios';
import { AxiosCacheInstance, CacheAxiosResponse, CacheProperties, CacheRequestConfig } from '../axios/types';
import { AxiosCacheInstance, CacheAxiosResponse } from '../axios/types';
import { AxiosInterceptor } from './types';
declare type CacheConfig = CacheRequestConfig & {
cache?: Partial<CacheProperties>;
};
export declare class CacheResponseInterceptor implements AxiosInterceptor<CacheAxiosResponse> {

@@ -11,6 +7,10 @@ readonly axios: AxiosCacheInstance;

apply: () => void;
testCachePredicate: (response: AxiosResponse, { cache }: CacheConfig) => boolean;
private testCachePredicate;
/**
* Rejects cache for this response. Also update the waiting list for
* this key by rejecting it.
*/
private rejectResponse;
onFulfilled: (response: CacheAxiosResponse) => Promise<CacheAxiosResponse>;
}
export {};
//# sourceMappingURL=response.d.ts.map

@@ -19,8 +19,20 @@ "use strict";

};
/**
* 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 (response) => {
// Ignore caching
const key = this.axios.generateKey(response.config);
response.id = key;
// Skip cache
if (response.config.cache === false) {
return response;
}
const key = this.axios.generateKey(response.config);
const cache = await this.axios.storage.get(key);

@@ -33,4 +45,3 @@ // Response shouldn't be cached or was already cached

if (!this.testCachePredicate(response, response.config)) {
// Update the cache to empty to prevent infinite loading state
await this.axios.storage.remove(key);
await this.rejectResponse(key);
return response;

@@ -43,4 +54,3 @@ }

if (expirationTime === false) {
// Update the cache to empty to prevent infinite loading state
await this.axios.storage.remove(key);
await this.rejectResponse(key);
return response;

@@ -62,5 +72,3 @@ }

// Resolve all other requests waiting for this response
if (deferred) {
await deferred.resolve(newCache.data);
}
await deferred?.resolve(newCache.data);
await this.axios.storage.set(key, newCache);

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

export interface CacheStorage {
/**
* Returns the cached value for the given key.
* Must handle cache miss and staling by returning a new `StorageValue` with `empty` state.
* Returns the cached value for the given key. Must handle cache
* miss and staling by returning a new `StorageValue` with `empty` state.
*/

@@ -6,0 +6,0 @@ get: (key: string) => Promise<StorageValue>;

@@ -5,11 +5,10 @@ import { AxiosResponse } from 'axios';

/**
* The status predicate, if a tuple is returned,
* the first and seconds value means the interval (inclusive) accepted.
* Can also be a function.
* The status predicate, if a tuple is returned, the first and
* seconds value means the interval (inclusive) accepted. Can also
* be a function.
*/
statusCheck?: [start: number, end: number] | ((status: number) => boolean);
/**
* Matches if the response header container all keys.
* A tuple also checks for values.
* Can also be a predicate.
* Matches if the response header container all keys. A tuple also
* checks for values. Can also be a predicate.
*/

@@ -16,0 +15,0 @@ containsHeaders?: Record<string, true | string | ((header: string) => boolean)>;

export declare type MaybePromise<T> = T | PromiseLike<T>;
export interface Deferred<T, E> extends Promise<T> {
resolve(value: MaybePromise<T>): void;
reject(reason: E): void;
}
/**
* Represents the completion of an asynchronous operation that can be completed later.
* Returns a promise that can be resolved or reject later
*
* @returns The deferred promise
*/
export declare class Deferred<T = any> implements PromiseLike<T> {
private readonly promise;
private _resolve;
private _reject;
constructor();
/**
* Resolve this deferred promise with the given value.
* @param the value to resolve
*/
readonly resolve: (value: MaybePromise<T>) => void;
/**
* Reject this deferred promise with the given reason.
* @param reason the reason to reject this deferred promise
*/
readonly reject: (reason?: any) => void;
/**
* Attaches callbacks for the resolution and/or rejection of the Promise.
* @param onfulfilled The callback to execute when the Promise is resolved.
* @param onrejected The callback to execute when the Promise is rejected.
* @returns A Promise for the completion of which ever callback is executed.
*/
readonly then: <TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => MaybePromise<TResult1>) | null | undefined, onrejected?: ((reason: any) => MaybePromise<TResult2>) | null | undefined) => Promise<TResult1 | TResult2>;
/**
* Attaches a callback for only the rejection of the Promise.
* @param onrejected The callback to execute when the Promise is rejected.
* @returns A Promise for the completion of the callback.
*/
readonly catch: <TResult = never>(onrejected?: ((reason: any) => MaybePromise<TResult>) | null | undefined) => Promise<T | TResult>;
/**
* Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The
* resolved value cannot be modified from the callback.
* @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected).
* @returns A Promise for the completion of the callback.
*/
readonly finally: (onfinally?: (() => void) | undefined | null) => Promise<T>;
}
export declare function deferred<T, E>(): Deferred<T, E>;
//# sourceMappingURL=deferred.d.ts.map
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Deferred = void 0;
exports.deferred = void 0;
/**
* Represents the completion of an asynchronous operation that can be completed later.
* Returns a promise that can be resolved or reject later
*
* @returns The deferred promise
*/
class Deferred {
promise;
_resolve = () => { };
_reject = () => { };
constructor() {
this.promise = new Promise((resolve, reject) => {
this._resolve = resolve;
this._reject = reject;
});
}
/**
* Resolve this deferred promise with the given value.
* @param the value to resolve
*/
resolve = (value) => {
this._resolve(value);
};
/**
* Reject this deferred promise with the given reason.
* @param reason the reason to reject this deferred promise
*/
reject = (reason) => {
this._reject(reason);
};
/**
* Attaches callbacks for the resolution and/or rejection of the Promise.
* @param onfulfilled The callback to execute when the Promise is resolved.
* @param onrejected The callback to execute when the Promise is rejected.
* @returns A Promise for the completion of which ever callback is executed.
*/
then = (onfulfilled, onrejected) => {
return this.promise.then(onfulfilled, onrejected);
};
/**
* Attaches a callback for only the rejection of the Promise.
* @param onrejected The callback to execute when the Promise is rejected.
* @returns A Promise for the completion of the callback.
*/
catch = (onrejected) => {
return this.promise.catch(onrejected);
};
/**
* Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The
* resolved value cannot be modified from the callback.
* @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected).
* @returns A Promise for the completion of the callback.
*/
finally = (onfinally) => {
return this.promise.finally(onfinally);
};
function deferred() {
let reject = () => { };
let resolve = () => { };
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
promise.resolve = resolve;
promise.reject = reject;
return promise;
}
exports.Deferred = Deferred;
exports.deferred = deferred;
//# sourceMappingURL=deferred.js.map
{
"name": "axios-cache-interceptor",
"version": "0.2.0",
"version": "0.2.1",
"description": "Cache interceptor for axios",

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

"test:coverage": "jest --coverage",
"prettify": "prettier . --write",
"prettify": "prettier --write .",
"lint": "tsc --noEmit && eslint . --ext .ts",

@@ -14,0 +14,0 @@ "lint:fix": "eslint . --ext .ts --fix",

@@ -12,5 +12,5 @@ import Axios, { AxiosInstance, AxiosRequestConfig } from 'axios';

*
* @param axios the already created axios instance
* @param config the config for the caching interceptors
* @returns the same instance but with caching enabled
* @param axios The already created axios instance
* @param config The config for the caching interceptors
* @returns The same instance but with caching enabled
*/

@@ -63,5 +63,5 @@ export function applyCache(

*
* @param config the config for the caching interceptors
* @param axiosConfig the config for the created axios instance
* @returns the same instance but with caching enabled
* @param config The config for the caching interceptors
* @param axiosConfig The config for the created axios instance
* @returns The same instance but with caching enabled
*/

@@ -68,0 +68,0 @@ export function createCache(

@@ -22,4 +22,4 @@ import type {

export type CacheUpdater =
| ((cached: EmptyStorageValue | CachedStorageValue, newData: any) => CachedStorageValue | void)
| 'delete';
| 'delete'
| ((cached: EmptyStorageValue | CachedStorageValue, newData: any) => CachedStorageValue | void);

@@ -41,4 +41,4 @@ export type DefaultCacheRequestConfig = AxiosRequestConfig & {

/**
* If this interceptor should configure the cache from the request cache header
* When used, the ttl property is ignored
* If this interceptor should configure the cache from the request
* cache header When used, the ttl property is ignored
*

@@ -59,3 +59,3 @@ * @default false

*
* @default { statusCheck: [200, 399] }
* @default {statusCheck: [200, 399]}
*/

@@ -65,4 +65,5 @@ 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.
* 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.
*

@@ -73,5 +74,6 @@ * If the function returns nothing, the entry is deleted

*
* The id used is the same as the id on `CacheRequestConfig['id']`, auto-generated or not.
* The id used is the same as the id on `CacheRequestConfig['id']`,
* auto-generated or not.
*
* @default {}
* @default
*/

@@ -81,4 +83,10 @@ update: Record<string, CacheUpdater>;

export type CacheAxiosResponse = AxiosResponse & {
export type CacheAxiosResponse<T = any> = AxiosResponse<T> & {
config: CacheRequestConfig;
/**
* The id used for this request. if config specified an id, the id
* will be returned
*/
id: string;
};

@@ -91,8 +99,8 @@

/**
* An id for this request, if this request is used in cache, only the last request made with this id will be returned.
* An id for this request, if this request is used in cache, only
* the last request made with this id will be returned.
*
* @see cacheKey
* @default undefined
*/
id?: string | symbol;
id?: string;

@@ -117,4 +125,5 @@ /**

* The function used to create different keys for each request.
* Defaults to a function that priorizes the id, and if not specified,
* a string is generated using the method, baseUrl, params, and url
* Defaults to a function that priorizes the id, and if not
* specified, a string is generated using the method, baseUrl,
* params, and url
*/

@@ -124,9 +133,12 @@ generateKey: KeyGenerator;

/**
* A simple object that holds all deferred objects until it is resolved.
* A simple object that holds all deferred objects until it is
* resolved or rejected.
*
* Can be used to listen when a request is cached or not.
*/
waiting: Record<string, Deferred<CachedResponse>>;
waiting: Record<string, Deferred<CachedResponse, void>>;
/**
* The function to parse and interpret response headers.
* Only used if cache.interpretHeader is true.
* The function to parse and interpret response headers. Only used
* if cache.interpretHeader is true.
*/

@@ -147,3 +159,4 @@ headerInterpreter: HeaderInterpreter;

/**
* Same as the AxiosInstance but with CacheRequestConfig as a config type.
* Same as the AxiosInstance but with CacheRequestConfig as a config
* type and CacheAxiosResponse as response type.
*

@@ -167,9 +180,9 @@ * @see AxiosInstance

request<T = any, R = AxiosResponse<T>>(config: CacheRequestConfig): Promise<R>;
request<T = any, R = CacheAxiosResponse<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>;
post<T = any, R = AxiosResponse<T>>(
get<T = any, R = CacheAxiosResponse<T>>(url: string, config?: CacheRequestConfig): Promise<R>;
delete<T = any, R = CacheAxiosResponse<T>>(url: string, config?: CacheRequestConfig): Promise<R>;
head<T = any, R = CacheAxiosResponse<T>>(url: string, config?: CacheRequestConfig): Promise<R>;
options<T = any, R = CacheAxiosResponse<T>>(url: string, config?: CacheRequestConfig): Promise<R>;
post<T = any, R = CacheAxiosResponse<T>>(
url: string,

@@ -179,3 +192,3 @@ data?: any,

): Promise<R>;
put<T = any, R = AxiosResponse<T>>(
put<T = any, R = CacheAxiosResponse<T>>(
url: string,

@@ -185,3 +198,3 @@ data?: any,

): Promise<R>;
patch<T = any, R = AxiosResponse<T>>(
patch<T = any, R = CacheAxiosResponse<T>>(
url: string,

@@ -188,0 +201,0 @@ data?: any,

/**
* Interpret the cache control header, if present.
*
* @param header the header object to interpret.
*
* @returns `false` if cache should not be used. `undefined` when provided
* headers was not enough to determine a valid value. Or a `number` containing
* the number of **milliseconds** to cache the response.
* @param header The header object to interpret.
* @returns `false` if cache should not be used. `undefined` when
* provided headers was not enough to determine a valid value. Or a
* `number` containing the number of **milliseconds** to cache the response.
*/

@@ -10,0 +9,0 @@ export type HeaderInterpreter = (

import { AxiosCacheInstance, CacheRequestConfig } from '../axios/types';
import { CachedResponse, CachedStorageValue, LoadingStorageValue } from '../storage/types';
import { Deferred } from '../util/deferred';
import { deferred } from '../util/deferred';
import { CACHED_STATUS_CODE, CACHED_STATUS_TEXT } from '../util/status-codes';

@@ -15,3 +15,3 @@ import { AxiosInterceptor } from './types';

onFulfilled = async (config: CacheRequestConfig): Promise<CacheRequestConfig> => {
// Ignore caching
// Skip cache
if (config.cache === false) {

@@ -35,6 +35,7 @@ return config;

emptyState: if (cache.state == 'empty') {
// This if catches concurrent access to a new key.
// The js event loop skips in the first await statement,
// so the next code block will be executed both if called
// from two places asynchronously.
/**
* 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]) {

@@ -46,4 +47,10 @@ cache = (await this.axios.storage.get(key)) as CachedStorageValue | LoadingStorageValue;

// Create a deferred to resolve other requests for the same key when it's completed
this.axios.waiting[key] = new Deferred();
this.axios.waiting[key] = deferred();
/**
* Add a default reject handler to detect when the request is
* aborted without others waiting
*/
this.axios.waiting[key]?.catch(() => {});
await this.axios.storage.set(key, {

@@ -62,4 +69,6 @@ state: 'loading',

// If the deferred is undefined, means that the
// outside has removed that key from the waiting list
/**
* If the deferred is undefined, means that the outside has
* removed that key from the waiting list
*/
if (!deferred) {

@@ -70,3 +79,8 @@ await this.axios.storage.remove(key);

data = await deferred;
try {
data = await deferred;
} catch (e) {
// The deferred is rejected when the request that we are waiting rejected cache.
return config;
}
} else {

@@ -73,0 +87,0 @@ data = cache.data;

@@ -22,3 +22,3 @@ import { AxiosResponse } from 'axios';

testCachePredicate = (response: AxiosResponse, { cache }: CacheConfig): boolean => {
private testCachePredicate = (response: AxiosResponse, { cache }: CacheConfig): boolean => {
const cachePredicate = cache?.cachePredicate || this.axios.defaults.cache.cachePredicate;

@@ -32,4 +32,19 @@

/**
* Rejects cache for this response. Also update the waiting list for
* this key by rejecting it.
*/
private rejectResponse = async (key: string) => {
// 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 (response: CacheAxiosResponse): Promise<CacheAxiosResponse> => {
// Ignore caching
const key = this.axios.generateKey(response.config);
response.id = key;
// Skip cache
if (response.config.cache === false) {

@@ -39,3 +54,2 @@ return response;

const key = this.axios.generateKey(response.config);
const cache = await this.axios.storage.get(key);

@@ -50,4 +64,3 @@

if (!this.testCachePredicate(response, response.config as CacheConfig)) {
// Update the cache to empty to prevent infinite loading state
await this.axios.storage.remove(key);
await this.rejectResponse(key);
return response;

@@ -63,4 +76,3 @@ }

if (expirationTime === false) {
// Update the cache to empty to prevent infinite loading state
await this.axios.storage.remove(key);
await this.rejectResponse(key);
return response;

@@ -87,5 +99,3 @@ }

// Resolve all other requests waiting for this response
if (deferred) {
await deferred.resolve(newCache.data);
}
await deferred?.resolve(newCache.data);

@@ -92,0 +102,0 @@ await this.axios.storage.set(key, newCache);

export interface CacheStorage {
/**
* Returns the cached value for the given key.
* Must handle cache miss and staling by returning a new `StorageValue` with `empty` state.
* Returns the cached value for the given key. Must handle cache
* miss and staling by returning a new `StorageValue` with `empty` state.
*/

@@ -6,0 +6,0 @@ get: (key: string) => Promise<StorageValue>;

@@ -7,5 +7,5 @@ import { AxiosResponse } from 'axios';

/**
* The status predicate, if a tuple is returned,
* the first and seconds value means the interval (inclusive) accepted.
* Can also be a function.
* The status predicate, if a tuple is returned, the first and
* seconds value means the interval (inclusive) accepted. Can also
* be a function.
*/

@@ -15,5 +15,4 @@ statusCheck?: [start: number, end: number] | ((status: number) => boolean);

/**
* Matches if the response header container all keys.
* A tuple also checks for values.
* Can also be a predicate.
* Matches if the response header container all keys. A tuple also
* checks for values. Can also be a predicate.
*/

@@ -20,0 +19,0 @@ containsHeaders?: Record<string, true | string | ((header: string) => boolean)>;

export type MaybePromise<T> = T | PromiseLike<T>;
export interface Deferred<T, E> extends Promise<T> {
resolve(value: MaybePromise<T>): void;
reject(reason: E): void;
}
/**
* Represents the completion of an asynchronous operation that can be completed later.
* Returns a promise that can be resolved or reject later
*
* @returns The deferred promise
*/
export class Deferred<T = any> implements PromiseLike<T> {
private readonly promise: Promise<T>;
private _resolve: (value: MaybePromise<T>) => void = () => {};
private _reject: (reason?: any) => void = () => {};
export function deferred<T, E>(): Deferred<T, E> {
let reject: Deferred<T, E>['reject'] = () => {};
let resolve: Deferred<T, E>['resolve'] = () => {};
constructor() {
this.promise = new Promise((resolve, reject) => {
this._resolve = resolve;
this._reject = reject;
});
}
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
}) as Deferred<T, E>;
/**
* Resolve this deferred promise with the given value.
* @param the value to resolve
*/
public readonly resolve = (value: MaybePromise<T>): void => {
this._resolve(value);
};
promise.resolve = resolve;
promise.reject = reject;
/**
* Reject this deferred promise with the given reason.
* @param reason the reason to reject this deferred promise
*/
public readonly reject = (reason?: any): void => {
this._reject(reason);
};
/**
* Attaches callbacks for the resolution and/or rejection of the Promise.
* @param onfulfilled The callback to execute when the Promise is resolved.
* @param onrejected The callback to execute when the Promise is rejected.
* @returns A Promise for the completion of which ever callback is executed.
*/
public readonly then = <TResult1 = T, TResult2 = never>(
onfulfilled?: ((value: T) => MaybePromise<TResult1>) | undefined | null,
onrejected?: ((reason: any) => MaybePromise<TResult2>) | undefined | null
): Promise<TResult1 | TResult2> => {
return this.promise.then(onfulfilled, onrejected);
};
/**
* Attaches a callback for only the rejection of the Promise.
* @param onrejected The callback to execute when the Promise is rejected.
* @returns A Promise for the completion of the callback.
*/
public readonly catch = <TResult = never>(
onrejected?: ((reason: any) => MaybePromise<TResult>) | undefined | null
): Promise<T | TResult> => {
return this.promise.catch(onrejected);
};
/**
* Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The
* resolved value cannot be modified from the callback.
* @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected).
* @returns A Promise for the completion of the callback.
*/
public readonly finally = (onfinally?: (() => void) | undefined | null): Promise<T> => {
return this.promise.finally(onfinally);
};
return promise;
}

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