jcgertig/betterstorage

View on GitHub
src/BetterStorageAsync.js

Summary

Maintainability
D
3 days
Test Coverage
import { autobind, decorate } from 'core-decorators';

function asPromsieResolve (val) {
  if (val instanceof Promise) {
    return val;
  }
  return new Promise(accept => accept(val));
}

@autobind
export default class BetterStorageAsync {
  constructor(prefix = '', kind = 'local', overrides = {}) {
    this.prefix = prefix;
    this.kind = kind;
    this.store = typeof kind === 'string' ? window[`${kind}Storage`] : kind;

    this.methods = Object.assign({
      doSet: this._doSet,
      doGet: this._doGet,
      doRemove: this._doRemove,
      doClear: this._doClear,
      doLength: this._doLength,
      doFullLength: this._doFullLength,
      doKey: this._doKey,
      doFullKey: this._doFullKey,
      doHas: this._doHas
    }, overrides);
  }

  _doSet(key, data) {
    return asPromsieResolve(this.store.setItem(key, JSON.stringify({ value: data })));
  }

  _doGet(key) {
    return new Promise((accept, reject) => {
      asPromsieResolve(this.store.getItem(key)).then((data) => {
        try {
          accept(JSON.parse(data).value)
        } catch (e) {
          accept(data);
        }
      }).catch(reject);
    });
  }

  _doRemove(key) {
    return asPromsieResolve(this.store.removeItem(key));
  }

  _doClear() {
    return asPromsieResolve(this.store.clear());
  }

  _doLength() {
    return new Promise((accept, reject) => {
      this.keys.then((keys) => accept(keys.length)).catch(reject);
    });
  }

  _doFullLength() {
    return new Promise((accept, reject) => {
      this.fullKeys.then((keys) => accept(keys.length)).catch(reject);
    });
  }

  _doKey(n) {
    return new Promise((accept, reject) => {
      this.keys.then((keys) => accept(keys[n])).catch(reject);
    });
  }

  _doFullKey(n) {
    return new Promise((accept, reject) => {
      this.fullKeys.then((keys) => accept(keys[n])).catch(reject);
    });
  }

  _doHas(key) {
    return new Promise((accept, reject) => {
      this.fullKeys.then((keys) => accept(keys.indexOf(key) > -1)).catch(reject);
    });
  }

  getStoreKey(key = '', prefix) {
    prefix = prefix || this.prefix;
    if (prefix === '') { return key; }
    return `${prefix}-${key}`;
  }

  setPrefix(prefix = '', changeExisting = false) {
    return new Promise((accept, reject) => {
      if (changeExisting && this.prefix !== '' && this.keys.length > 0) {
        this.keys
        .then((keys) => {
          Promise.all(keys.map((key) => {
            return new Promise((acceptInner, rejectInner) => {
              this.store.getItem(this.getStoreKey(key))
              .then((value) => acceptInner({ key, value }))
              .catch(rejectInner);
            })
          }))
          .then((pairs) => {
            Promise.all(pairs.reduce((promises, data) => {
              promises.push(this.store.removeItem(this.getStoreKey(data.key)));
              promises.push(this.store.setItem(this.getStoreKey(data.key, prefix), data.value));
              return promises;
            }, []))
            .then(() => {
              this.prefix = prefix;
              accept();
            })
            .catch(reject);
          })
          .catch(reject);
        })
        .catch(reject);
      } else {
        this.prefix = prefix;
        accept();
      }
    });
  }

  setKind(kind, migrateData = false) {
    return new Promise((accept, reject) => {
      const newStore = typeof kind === 'string' ? window[`${kind}Storage`] : kind;
      if (migrateData && this.prefix !== '' && this.keys.length > 0) {
        this.keys
        .then((keys) => {
          Promise.all(keys.map((key) => {
            return new Promise((acceptInner, rejectInner) => {
              this.store.getItem(this.getStoreKey(key))
              .then((value) => acceptInner({ key, value }))
              .catch(rejectInner);
            })
          }))
          .then((pairs) => {
            Promise.all(pairs.reduce((promises, data) => {
              promises.push(this.store.removeItem(this.getStoreKey(data.key)));
              promises.push(newStore.setItem(this.getStoreKey(data.key), data.value));
              return promises;
            }, []))
            .then(() => {
              this.kind = kind;
              this.store = newStore;
              accept();
            })
            .catch(reject);
          })
          .catch(reject);
        })
        .catch(reject);
      } else {
        this.kind = kind;
        this.store = newStore;
        accept();
      }
    });
  }

  /*
   * Set Item into Storage
   */
  set(key, value) {
    return this.methods.doSet(this.getStoreKey(key), value);
  }

  /*
   * Get Item from Storage
   */
  get(key) {
    return this.methods.doGet(this.getStoreKey(key));
  }

  /*
   * Removes an Item from Storage
   */
  remove(key) {
    return this.methods.doRemove(this.getStoreKey(key));
  }

  /*
   * If store has key
   */
  has(key) {
    return this.methods.doHas(this.getStoreKey(key));
  }

  /*
   * Clears the store
   */
  clear(full = false) {
    if (this.prefix === '' || full) {
      return this.methods.doClear();
    }
    return new Promise((accept, reject) => {
      this.keys.then((keys) => {
        Promise.all(keys.map((key) => this.methods.doRemove(this.getStoreKey(key))))
        .then(accept).catch(reject);
      }).catch(reject);
    });
  }

  /*
   * Number of items with prefix
   */
  getLength() {
    return this.methods.doLength();
  }

  /*
   * Number of items in the store
   */
  getFullLength() {
    return this.methods.doFullLength();
  }

  /*
   * Get prefix key at location
   */
  key(n) {
    return this.methods.doKey(n);
  }

  /*
   * Get key at location
   */
  fullKey(n) {
    return this.methods.doFullKey(n);
  }

  /*
   * List of prefixed keys in store
   */
  get keys() {
    if (this.prefix === '') { return this.fullKeys; }
    return new Promise((resolve, reject) => {
      this.fullKeys.then((keys) => {
        resolve(
          keys
          .filter((item) => item.indexOf(`${this.prefix}-`) === 0)
          .map((item) => item.replace(`${this.prefix}-`, ''))
        );
      }).catch(reject);
    });
  }

  /*
   * List of all keys in store
   */
  get fullKeys() {
    if (this.store.hasOwnProperty('getAllKeys')) {
      return this.store.getAllKeys();
    }
    return new Promise((accept) => accept(Object.keys(this.store)));
  }
}