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;