189 lines
5.7 KiB
JavaScript
189 lines
5.7 KiB
JavaScript
import Cache from './Cache.js';
|
|
import PendingRequestStore from './PendingRequestStore.js';
|
|
|
|
/**
|
|
* @typedef {import('../types/types.js').CacheRequest} CacheRequest
|
|
* @typedef {import('../types/types.js').CacheOptions} CacheOptions
|
|
* @typedef {import('../types/types.js').ValidatedCacheOptions} ValidatedCacheOptions
|
|
*/
|
|
|
|
/**
|
|
* The id for the cache session
|
|
* @type {string | undefined}
|
|
*/
|
|
let cacheSessionId;
|
|
|
|
/**
|
|
* The ajax cache
|
|
* @type {Cache}
|
|
*/
|
|
export const ajaxCache = new Cache();
|
|
|
|
/**
|
|
* The pending request store
|
|
* @type {PendingRequestStore}
|
|
*/
|
|
export const pendingRequestStore = new PendingRequestStore();
|
|
|
|
/**
|
|
* Checks whether the given cacheSessionId matches the currently active id.
|
|
*
|
|
* @param {string|undefined} cacheId The cache id to check
|
|
*/
|
|
export const isCurrentSessionId = cacheId => cacheId === cacheSessionId;
|
|
|
|
/**
|
|
* Sets the current cache session ID.
|
|
*
|
|
* @param {string} id The id that will be tied to the current session
|
|
*/
|
|
export const setCacheSessionId = id => {
|
|
cacheSessionId = id;
|
|
};
|
|
|
|
/**
|
|
* Resets the cache session when the cacheId changes.
|
|
*
|
|
* There can be only 1 active session at all times.
|
|
* @param {string} cacheId The cache id that is tied to the current session
|
|
*/
|
|
export const resetCacheSession = cacheId => {
|
|
if (!cacheId) {
|
|
throw new Error('Invalid cache identifier');
|
|
}
|
|
if (!isCurrentSessionId(cacheId)) {
|
|
setCacheSessionId(cacheId);
|
|
ajaxCache.reset();
|
|
pendingRequestStore.reset();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Stringify URL search params
|
|
* @param {*} params query string parameters object
|
|
* @returns {string} of querystring parameters WITHOUT `?` or empty string ''
|
|
*/
|
|
const stringifySearchParams = (params = {}) =>
|
|
typeof params === 'object' && params !== null ? new URLSearchParams(params).toString() : '';
|
|
|
|
/**
|
|
* Returns request key string, which uniquely identifies a Request
|
|
* @param {Partial<CacheRequest>} request Request object
|
|
* @param {function} serializeSearchParams Function to serialize URL search params
|
|
* @returns {string} requestId to uniquely identify a request
|
|
*/
|
|
const DEFAULT_GET_REQUEST_ID = (
|
|
{ url = '', params },
|
|
serializeSearchParams = stringifySearchParams,
|
|
) => {
|
|
const serializedParams = serializeSearchParams(params);
|
|
return serializedParams ? `${url}?${serializedParams}` : url;
|
|
};
|
|
|
|
/**
|
|
* Defaults to 1 hour
|
|
*/
|
|
const DEFAULT_MAX_AGE = 1000 * 60 * 60;
|
|
|
|
/**
|
|
* @param {CacheOptions} options Cache options
|
|
* @returns {ValidatedCacheOptions}
|
|
*/
|
|
export const extendCacheOptions = ({
|
|
useCache = false,
|
|
methods = ['get'],
|
|
maxAge = DEFAULT_MAX_AGE,
|
|
requestIdFunction = DEFAULT_GET_REQUEST_ID,
|
|
invalidateUrls,
|
|
invalidateUrlsRegex,
|
|
contentTypes,
|
|
maxResponseSize,
|
|
maxCacheSize,
|
|
}) => ({
|
|
useCache,
|
|
methods,
|
|
maxAge,
|
|
requestIdFunction,
|
|
invalidateUrls,
|
|
invalidateUrlsRegex,
|
|
contentTypes,
|
|
maxResponseSize,
|
|
maxCacheSize,
|
|
});
|
|
|
|
/**
|
|
* @param {CacheOptions} options Cache options
|
|
*/
|
|
export const validateCacheOptions = ({
|
|
useCache,
|
|
methods,
|
|
maxAge,
|
|
requestIdFunction,
|
|
invalidateUrls,
|
|
invalidateUrlsRegex,
|
|
contentTypes,
|
|
maxResponseSize,
|
|
maxCacheSize,
|
|
} = {}) => {
|
|
if (useCache !== undefined && typeof useCache !== 'boolean') {
|
|
throw new Error('Property `useCache` must be a `boolean`');
|
|
}
|
|
if (methods !== undefined && JSON.stringify(methods) !== JSON.stringify(['get'])) {
|
|
throw new Error('Cache can only be utilized with `GET` method');
|
|
}
|
|
if (maxAge !== undefined && !Number.isFinite(maxAge)) {
|
|
throw new Error('Property `maxAge` must be a finite `number`');
|
|
}
|
|
if (invalidateUrls !== undefined && !Array.isArray(invalidateUrls)) {
|
|
throw new Error('Property `invalidateUrls` must be an `Array` or `undefined`');
|
|
}
|
|
if (invalidateUrlsRegex !== undefined && !(invalidateUrlsRegex instanceof RegExp)) {
|
|
throw new Error('Property `invalidateUrlsRegex` must be a `RegExp` or `undefined`');
|
|
}
|
|
if (requestIdFunction !== undefined && typeof requestIdFunction !== 'function') {
|
|
throw new Error('Property `requestIdFunction` must be a `function`');
|
|
}
|
|
if (contentTypes !== undefined && !Array.isArray(contentTypes)) {
|
|
throw new Error('Property `contentTypes` must be an `Array` or `undefined`');
|
|
}
|
|
if (maxResponseSize !== undefined && !Number.isFinite(maxResponseSize)) {
|
|
throw new Error('Property `maxResponseSize` must be a finite `number`');
|
|
}
|
|
if (maxCacheSize !== undefined && !Number.isFinite(maxCacheSize)) {
|
|
throw new Error('Property `maxCacheSize` must be a finite `number`');
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Invalidates matching requestIds in the cache and pendingRequestStore
|
|
*
|
|
* There are two kinds of invalidate rules:
|
|
* invalidateUrls (array of URL like strings)
|
|
* invalidateUrlsRegex (RegExp)
|
|
* If a non-GET method is fired, by default it only invalidates its own endpoint.
|
|
* Invalidating /api/users cache by doing a PATCH, will not invalidate /api/accounts cache.
|
|
* However, in the case of users and accounts, they may be very interconnected,
|
|
* so perhaps you do want to invalidate /api/accounts when invalidating /api/users.
|
|
* If it's NOT one of the config.methods, invalidate caches
|
|
*
|
|
* @param {string} requestId
|
|
* @param {CacheOptions} cacheOptions
|
|
*/
|
|
export const invalidateMatchingCache = (requestId, { invalidateUrls, invalidateUrlsRegex }) => {
|
|
// invalidate this request
|
|
ajaxCache.delete(requestId);
|
|
pendingRequestStore.resolve(requestId);
|
|
|
|
// also invalidate caches matching to invalidateUrls
|
|
if (Array.isArray(invalidateUrls)) {
|
|
invalidateUrls.forEach(url => {
|
|
ajaxCache.delete(url);
|
|
pendingRequestStore.resolve(url);
|
|
});
|
|
}
|
|
// also invalidate caches matching to invalidateUrlsRegex
|
|
if (invalidateUrlsRegex) {
|
|
ajaxCache.deleteMatching(invalidateUrlsRegex);
|
|
pendingRequestStore.resolveMatching(invalidateUrlsRegex);
|
|
}
|
|
};
|