/** * @summary cache-manager * @version 1.0.0 * @author Arian Khosravi <arian.khosravi@aofl.com> */ import {cacheTypeEnumerate} from './cache-type-enumerate'; import {MemoryStorage} from './memory-storage'; import md5 from 'tiny-js-md5'; const STORAGE = { [cacheTypeEnumerate.LOCAL]: localStorage, [cacheTypeEnumerate.SESSION]: sessionStorage, [cacheTypeEnumerate.MEMORY]: MemoryStorage }; const MAX_SIGNED_INT = 2147483647; const EXPIRE_SYMBOL = Symbol('expire'); /** * Provides a unified class for storing objects in Storage-like Objects. You can choose from * localStorage, sessionStorage and memoryStorage. * @memberof module:@aofl/cache-manager */ class CacheManager { /** * Creates an instance of CacheManager. * @param {*} namespace used to group keys controlled by each instance since the starage objects are shared * @param {*} [storageType=cacheTypeEnumerate.MEMORY] memory, local or session * @param {number} [expire=3600000] cache expiration in ms */ constructor(namespace, storageType = cacheTypeEnumerate.MEMORY, expire = 3600000) { this.storage = STORAGE[storageType]; /** @type {cacheTypeEnumerate} */ this.storageType = storageType; /** @type {String} */ this.namespace = namespace; this.storedKeys = this.getStoredKeys(); /** @type {Number} */ this.expire = expire; } get expire() { return this[EXPIRE_SYMBOL]; } set expire(value) { this[EXPIRE_SYMBOL] = value; if (this.interval !== null) { clearInterval(this.interval); } if (value > 0 && value < MAX_SIGNED_INT) { this.interval = setInterval(() => this.removeExpired(), value); } } /** * The size read-only property of the Storage interface returns an integer representing the * number of data items stored in the Storage object. * * @readonly * @type {Number} */ get size() { const collection = this.getCollection(); return Object.keys(collection).length; } /** * Returns keys controlled by the instance of CacheManager * * @return {Array} */ getStoredKeys() { const keys = []; for (const key in this.storage) { if (!Object.hasOwnProperty.call(this.storage, key)) continue; if (key.indexOf(this.namespace + '_') === 0) { keys.push(key); } } return keys; } /** * Returns the serialized key based on namespace * * @param {String} key * @return {String} */ getNamespaceKey(key) { if (this.storedKeys.indexOf(key) > -1) { return key; } return this.namespace + '_' + md5(key); } /** * The setItem() method of the CacheManager interface, when passed a key name and value, will add * that key to the storage, or update that key's value if it already exists * * @param {String} key A String containing the name of the key you want to create/update. * @param {*} value The value you want to give the key you are creating/updating. */ setItem(key, value) { let obj = { t: Date.now(), v: value }; if (this.storageType === cacheTypeEnumerate.LOCAL || this.storageType === cacheTypeEnumerate.SESSION) { obj = JSON.stringify(obj); } const namespaceKey = this.getNamespaceKey(key); this.storage.setItem(namespaceKey, obj); this.storedKeys.push(namespaceKey); } /** * The getItem() method of the CacheManager interface, when passed a key name, will return that * key's value or null if the key does not exist. * * @param {String} key The name of the key you want to retrieve the value of * @return {*} */ getItem(key) { let obj = this.storage.getItem(this.getNamespaceKey(key)); if (this.storageType === cacheTypeEnumerate.LOCAL || this.storageType === cacheTypeEnumerate.SESSION) { obj = JSON.parse(obj); } if (obj !== null && Object.hasOwnProperty.call(obj, 't')) { if (this.isExpired(key)) { // expired this.removeItem(key); return null; } return obj.v; } return obj; } /** * The getItem() method of the CacheManager interface, wen invoked, will return the key/value * pairs that that instance of CacheManager tracks. * * @return {Object} */ getCollection() { const collection = {}; this.storedKeys = this.getStoredKeys(); for (let i = 0; i < this.storedKeys.length; i++) { if (this.isExpired(this.storedKeys[i])) continue; collection[this.storedKeys[i]] = this.getItem(this.storedKeys[i]); } return collection; } /** * The removeItem() method of the CacheManager interface, when passed a key name, will remove that * key from the storage if it exists. If there is no item associated with the given key, this * method will do nothing. * * @param {String} key The name of the key you want to remove */ removeItem(key) { const namespaceKey = this.getNamespaceKey(key); const index = this.storedKeys.indexOf(namespaceKey); if (index > -1) { this.storage.removeItem(namespaceKey); this.storedKeys.splice(index, 1); } } /** * The clear() method of the CacheManager interface, when invoked, clears all stored keys */ clear() { for (let i = 0; i < this.storedKeys.length; i++) { this.storage.removeItem(this.storedKeys[i]); } this.storedKeys = []; } /** * The isExpired() method of the CacheManager interface, when invoked, checks if the given key * is expired. * * @param {String} key The name of the key you want to check if is expired. * @return {Boolean} */ isExpired(key) { if (typeof this.expire !== 'number' || this.expire <= 0) { return false; } const namespaceKey = this.getNamespaceKey(key); let obj = this.storage.getItem(namespaceKey); if (this.storageType === cacheTypeEnumerate.LOCAL || this.storageType === cacheTypeEnumerate.SESSION) { obj = JSON.parse(obj); } if (obj.t < (Date.now() - this.expire)) { // expired return true; } return false; } /** * The removeExpired() method of the CacheManager interface, when invoked, Removes all expired * keys. */ removeExpired() { for (let i = 0; i < this.storedKeys.length; i++) { if (this.isExpired(this.storedKeys[i])) { this.removeItem(this.storedKeys[i]); } } } /** * The destruct() method of the CacheManager interface, when invoked, resets all instance * variables and clears expire interval. */ destruct() { clearInterval(this.interval); this.namespace = null; this.storage = null; this.storageType = null; this.expire = null; this.storedKeys = null; this.interval = null; } } export { CacheManager };