feat(WIP): structured code

This commit is contained in:
Hazork 2021-09-01 13:30:26 -03:00
parent 5d6b863d5b
commit 5ee2b0493b
13 changed files with 317 additions and 5 deletions

View File

@ -52,8 +52,24 @@ Axios Cache Interceptor</h1>
<br />
```sh
# TODO: Example
```ts
import axios from 'axios';
import { createCache, SessionCacheStorage } from 'axios-cache-interceptor';
// Any custom axios instance
const api = axios.create();
// Other axios instance with caching enabled
const cache = createCache(api, {
// Store values on window.sessionStorage
storage: new SessionCacheStorage(),
// Use the max-age header to determina the cache expiration time
interpretHeader: true
});
// Exactly the same as before
cache.get('http://example.com/');
```
<br />

19
docs/readme.ts Normal file
View File

@ -0,0 +1,19 @@
// Readme example
import axios from 'axios';
import { createCache, SessionCacheStorage } from '../src/index';
// Any custom axios instance
const api = axios.create();
// Other axios instance with caching enabled
const cache = createCache(api, {
// Store values on window.sessionStorage
storage: new SessionCacheStorage(),
// Use the max-age header to determina the cache expiration time
interpretHeader: true
});
// Exactly the same as before
cache.get('http://example.com/');

33
src/axios/cache.ts Normal file
View File

@ -0,0 +1,33 @@
import { AxiosInstance } from 'axios';
import { applyRequestInterceptor } from '../interceptors/request';
import { applyResponseInterceptor } from '../interceptors/response';
import { MemoryStorage } from '../storage/memory';
import { AxiosCacheInstance, CacheInstance, CacheRequestConfig } from './types';
type Options = CacheRequestConfig['cache'] & Partial<CacheInstance>;
export function createCache(
axios: AxiosInstance,
options: Options = {}
): AxiosCacheInstance {
const axiosCache = axios as AxiosCacheInstance;
axiosCache.storage = options.storage || new MemoryStorage();
// CacheRequestConfig values
axiosCache.defaults = {
...axios.defaults,
cache: {
maxAge: 1000 * 60 * 5,
interpretHeader: false,
methods: ['get'],
...options
}
};
// Apply interceptors
applyRequestInterceptor(axiosCache);
applyResponseInterceptor(axiosCache);
return axiosCache;
}

101
src/axios/types.ts Normal file
View File

@ -0,0 +1,101 @@
import type {
AxiosInstance,
AxiosInterceptorManager,
AxiosPromise,
AxiosRequestConfig,
AxiosResponse,
Method
} from 'axios';
import { CacheStorage } from '../storage/types';
/**
* Options that can be overridden per request
*/
export type CacheRequestConfig = AxiosRequestConfig & {
/**
* All cache options for the 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>[];
};
};
export interface CacheInstance {
/**
* The storage to save the cache data.
*
* @default new MemoryStorage()
*/
storage: CacheStorage;
}
export interface AxiosCacheInstance extends AxiosInstance, CacheInstance {
(config: CacheRequestConfig): AxiosPromise;
(url: string, config?: CacheRequestConfig): AxiosPromise;
defaults: CacheRequestConfig;
interceptors: {
request: AxiosInterceptorManager<CacheRequestConfig>;
response: AxiosInterceptorManager<
AxiosResponse & { config: CacheRequestConfig }
>;
};
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>;
}

View File

@ -1,3 +1,2 @@
export function sum(a: number, b: number): number {
return a + b;
}
export { createCache } from './axios/cache';
export * from './storage';

View File

@ -0,0 +1,7 @@
import { AxiosCacheInstance } from '../axios/types';
export function applyRequestInterceptor(axios: AxiosCacheInstance) {
axios.interceptors.request.use(async (config) => {
return config;
});
}

View File

@ -0,0 +1,7 @@
import { AxiosCacheInstance } from '../axios/types';
export function applyResponseInterceptor(axios: AxiosCacheInstance) {
axios.interceptors.response.use(async (config) => {
return config;
});
}

4
src/storage/index.ts Normal file
View File

@ -0,0 +1,4 @@
export * from './memory';
export * from './types';
export * from './web';
export * from './wrapper';

41
src/storage/memory.ts Normal file
View File

@ -0,0 +1,41 @@
import { CacheStorage, StorageValue } from './types';
const emptyValue: StorageValue = {
data: null,
expires: -1,
state: 'empty'
};
export class MemoryStorage implements CacheStorage {
readonly storage: Map<string, StorageValue> = new Map();
get = async (key: string): Promise<StorageValue> => {
const value = this.storage.get(key);
if (value) {
return value;
}
// Fresh copy to prevent code duplication
const empty = { ...emptyValue };
this.storage.set(key, empty);
return empty;
};
set = async (key: string, value: StorageValue): Promise<void> => {
this.storage.set(key, value);
};
remove = async (key: string): Promise<void> => {
this.storage.delete(key);
};
size = async (): Promise<number> => {
return this.storage.size;
};
clear = async (): Promise<void> => {
this.storage.clear();
};
}

32
src/storage/types.ts Normal file
View File

@ -0,0 +1,32 @@
export interface CacheStorage {
/**
* Returns the cached value for the given key or a new empty
*/
get: (key: string) => Promise<StorageValue>;
/**
* Sets a new value for the given key
*/
set: (key: string, value: StorageValue) => Promise<void>;
/**
* Removes the value for the given key
*/
remove: (key: string) => Promise<void>;
}
export interface StorageValue {
/**
* The value of the cached response
*/
data: any | null;
/**
* The time when the cached response expires
* -1 means not cached
*/
expires: number;
/**
* The status of this value.
*/
state: 'cached' | 'empty' | 'loading';
}

13
src/storage/web.ts Normal file
View File

@ -0,0 +1,13 @@
import { WindowStorageWrapper } from './wrapper';
export class LocalCacheStorage extends WindowStorageWrapper {
constructor(prefix?: string) {
super(window.localStorage, prefix);
}
}
export class SessionCacheStorage extends WindowStorageWrapper {
constructor(prefix?: string) {
super(window.sessionStorage, prefix);
}
}

24
src/storage/wrapper.ts Normal file
View File

@ -0,0 +1,24 @@
import { CacheStorage, StorageValue } from './types';
/**
* A storage that uses any {@link Storage} as his storage.
*/
export abstract class WindowStorageWrapper implements CacheStorage {
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) : null;
};
set = async (key: string, value: StorageValue): Promise<void> => {
const json = JSON.stringify(value);
this.storage.setItem(this.prefix + key, json);
};
remove = async (key: string): Promise<void> => {
this.storage.removeItem(this.prefix + key);
};
}

16
src/test.ts Normal file
View File

@ -0,0 +1,16 @@
import axios from 'axios';
import { createCache } from './';
import { SessionCacheStorage } from './storage';
// My own api
const api = axios.create();
const cache = createCache(api, {
// Store values on window.sessionStorage
storage: new SessionCacheStorage(),
// Use the max-age header to determina the cache expiration time
interpretHeader: true
});
cache.get('http://example.com/');