Manual Reference Source Test

src/utilities/StorageUtility.localStorage.js

import { validate, isA, isRequired } from './ValidationUtility';
import { helperFactory } from './factories/HelperFactory';

/** @type {Symbol} */
const localStorageSingleton = Symbol('localStorageSingleton');
/** @type {Symbol} */
const localStorageSingletonEnforcer = Symbol('localStorageSingletonEnforcer');

/**
 * Storage will store and retrieve data.  This is expected to have set and get methods
 * overwritten if necessary.
 *
 * @example
 * import { Storage } from 'clinical6';
 * Storage.set('kittens', 5);
 * Storage.get('kittens');
 */
class LocalStorageUtility {
  /**
   * Constructor
   *
   * @param {Symbol} enforcer
   */
  constructor(enforcer) {
    /** @type {Object} */

    if (!localStorage) {
      throw new Error('localStorage does not exist');
    }

    if (enforcer !== localStorageSingletonEnforcer) {
      throw new Error('Cannot construct singleton');
    }
  }

  /** @type {Object} */
  get data() {
    const obj = {};
    Object.keys(localStorage).forEach((key) => {
      try {
        obj[key] = JSON.parse(localStorage.getItem(key));
      } catch (e) {
        obj[key] = localStorage.getItem(key);
      }
    });
    return obj;
  }

  /**
   * Get the instance of the storage utility
   *
   * @type {Object} - instance
   */
  static get instance() {
    if (!this[localStorageSingleton]) {
      /** @type {Client} */
      this[localStorageSingleton] = new LocalStorageUtility(localStorageSingletonEnforcer);
    }
    return this[localStorageSingleton];
  }

  /**
   * Clear storage data.
   *
   * @throws {Clinical6Error} - If type is not a string
   * @param  {String} [type]  - Type/Key of item to clear
   * @param  {String} [id]    - The id, if you want to store a specific item
   * @return {Promise<any>}   - Promise to indicate the clear is finished
   *
   * @example
   * import { Storage } from 'clinical6';
   * Storage.clear('kittens');
   * Storage.clear();
   */
  clear(type = undefined, id = undefined) {
    validate('StorageUtility.clear',
      isA({ type }, 'string'));
    return new Promise((resolve) => {
      if (!type) {
        localStorage.clear();
      } else if (type && id) {
        const stor = localStorage.getItem(type) ? JSON.parse(localStorage.getItem(type)) : {};
        delete stor[id];
        localStorage.setItem(type, JSON.toString(stor));
      } else if (this.data[type]) {
        localStorage.removeItem(type);
      }
      resolve('removed');
    });
  }

  /**
   * Get storage data.
   *
   * @throws {Clinical6Error}             - If type is empty or not a string
   * @param  {!String}  type              - Type/Key of item to get - should be server's obj type
   * @param  {?Object}  [options]         - Options for getting storage information
   * @param  {String}   [options.id]      - The id, if you want to store a specific item
   * @param  {String}   [options.asArray] - Return as array
   * @return {Promise<any>}               - Promise with the stored data
   *
   * @example
   * import { Storage } from 'clinical6';
   * Storage.get('kittens');
   */
  get(type, options = undefined) {
    validate('StorageUtility.get',
      isRequired({ type }),
      isA({ type }, 'string'));
    return new Promise((resolve) => {
      let obj;
      // If type does not exist
      if (localStorage.getItem(type) === null) {
        obj = {};
      } else {
        // get storage object
        const storageObj = JSON.parse(localStorage.getItem(type));
        const keys = Object.keys(storageObj);
        if (keys[0] && keys[0].type) {
          // If the keys have a type, then it is a stored list.  This should be almost certain.
          keys.forEach((id) => {
            if (options && options.id) {
              // If the options.id exists, just assign deserialized object to obj
              if (parseInt(options.id, 10) === parseInt(id, 10)) {
                obj = helperFactory.get(storageObj[id]);
              }
            } else {
              // If the options.id doesn't exist, populate hashmap using id / deserialized object
              obj[id] = helperFactory.get(storageObj[id]);
            }
          });
        } else if (storageObj.type) {
          // If this is a simple stored object with type, deserialize
          obj = helperFactory.get(storageObj);
        } else {
          // Else, this is just something stored from the app - return the value
          obj = storageObj;
        }
      }
      resolve(obj);
    });
  }

  /**
   * Determines if the type and id exist in storage
   *
   * @param  {String} type  - The key of the storage location
   * @param  {Number} [id]  - The id belonging to the item in question
   * @return {Boolean}      - Whether or not the item exists
   */
  has(type, id = undefined) {
    let _return = false;
    if (id) {
      const obj = localStorage.getItem(type) ? JSON.parse(localStorage.getItem(type)) : {};
      _return = Boolean(obj[id]);
    } else {
      _return = Boolean(localStorage.getItem(type) === null);
    }
    return _return;
  }

  /**
   * Set storage data.
   *
   * @throws {Clinical6Error}         - If key/value is empty or not a string
   * @param  {!String}  type          - Type/Key of item to store - should be server's obj type
   * @param  {String}   value         - Value of item to store
   * @param  {?Object}  [options]     - Options for setting storage information
   * @param  {String}   [options.id]  - The id, if you want to store a specific item
   * @param  {String}   [options.key] - The key, if you want to store a specific key.  Only used
   *                                    if value is an array
   * @return {Promise<any>}           - Promise with the stored data
   *
   * @example
   * import { Storage } from 'clinical6';
   * Storage.set('kittens', 5);
   */
  set(type, value, options = undefined) {
    validate('StorageUtility.set',
      isRequired({ type, value }),
      isA({ type }, 'string'));
    return new Promise((resolve) => {
      const stor = localStorage.getItem(type) ? JSON.parse(localStorage.getItem(type)) : {};
      if (options && options.id) {
        stor[options.id] = value;
      } else if (Array.isArray(value)) {
        value.forEach((obj) => {
          const key = (options && options.key) ? obj[options.key] : obj.id;
          if (key !== undefined && key !== null) {
            stor[key] = obj;
          }
        });
        localStorage.setItem(type, JSON.toString(stor));
      } else {
        localStorage.setItem(type, JSON.toString(value));
      }
      resolve(value);
    });
  }
}

export default LocalStorageUtility;