Source: cache-manager/modules/cache-manager.js

/**
 * @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
};