Home Intro Source Mebo GitHub

src/Utils/LruCache.js

const assert = require('assert');
const TypeCheck = require('js-typecheck');
const LruCacheBase = require('lru-cache');
const sizeof = require('object-sizeof');

// symbols used for private members to avoid any potential clashing
// caused by re-implementations
const _size = Symbol('size');
const _lifespan = Symbol('lifespan');
const _cache = Symbol('cache');


/**
 * A cache that discards the least recently used items first.
 *
 * This feature is used by the {@link Session} to store the result of
 * cacheable actions ({@link Action.isCacheable}).
 */
class LruCache{

  /**
   * Creates an LruCache
   *
   * @param {number} size - Sets in bytes the maximum size of the cache
   * @param {number} lifespan - Lifespan in milliseconds about amount of time that
   * an item under LRU cache should be kept alive
   */
  constructor(size, lifespan){

    assert(TypeCheck.isNumber(size), 'size requires a number value');
    assert(TypeCheck.isNumber(lifespan), 'lifespan requires a number value');

    this[_size] = size;
    this[_lifespan] = lifespan;

    // lazy creation
    this[_cache] = null;
  }

  /**
   * Returns the size of the cache in bytes
   *
   * @type {number}
   */
  get size(){
    return this[_size];
  }

  /**
   * Returns the lifespan for the values held by the cache in seconds
   *
   * @type {number}
   */
  get lifespan(){
    return this[_lifespan];
  }

  /**
   * Resets the cache by flushing it
   *
   * @param {string} [key] - id associated with the value otherwise flushes the whole
   * cache
   */
  flush(key=null){

    if (this[_cache] === null){
      return;
    }

    if (key){
      this[_cache].del(key);
    }
    else{
      this[_cache].reset();
    }
  }

  /**
   * Returns a list containing the keys of the values held by the cache
   *
   * @return {Array<string>}
   */
  keys(){
    return (this[_cache] === null) ? [] : this[_cache].keys();
  }

  /**
   * Returns the number of items held by the cache
   *
   * @type {number}
   */
  get length(){
    return this.keys().length;
  }

  /**
   * Returns a boolean telling if the input key is under the cache
   *
   * @param {string} key - id associated with the value
   * @return {boolean}
   */
  has(key){
    return (this[_cache] !== null && this[_cache].has(key));
  }

  /**
   * Returns the value from the lru cache based on the input {@link Action.id},
   * otherwise if the id is not found under the cache then returns undefined
   *
   * @param {string} key - id associated with the value
   * @param {*} [defaultValue=undefined] - value returned in case the key does not exist
   * @return {*}
   */
  get(key, defaultValue=undefined){
    assert(TypeCheck.isString(key), 'Invalid action key!');

    return (this.has(key)) ? this[_cache].get(key).data : defaultValue;
  }

  /**
   * Sets a result value to the lru cache, this value is associated with the {@link Action.id}
   *
   * @param {string} key - id associated with the value
   * @param {*} value - value that should be store by the cache
   */
  set(key, value){
    assert(TypeCheck.isString(key), 'Invalid action key!');

    if (this[_cache] === null){
      this[_cache] = new LruCacheBase({
        max: this[_size],
        maxAge: this[_lifespan],
        length: (n, k) => {
          return n.memorySize;
        },
      });
    }

    const resultHolder = {};
    resultHolder.memorySize = sizeof(value);
    resultHolder.data = value;

    this[_cache].set(key, resultHolder);
  }
}

module.exports = LruCache;