Manual Reference Source Test

src/helpers/Helper.js

import { stringToCamel, stringToSnake } from '../utilities/FormatUtility';
import { fieldsToSnake, toJsonApiStub } from '../utilities/ClassUtility';
import Client from '../Client';
import { HelperFactory, helperFactory } from '../utilities/factories/HelperFactory';

import { flattenJsonApi } from '../utilities/JsonApiUtility';
/**
 * The base class for helper classes
 */
class Helper {
  /**
   * Initializer for the helper function
   */
  initializer() {
    /** @type {Object} */
    this._meta = {};
    this._relationships = {};
  }

  /** @type {Object} */
  get meta() {
    return this._meta;
  }

  /** @type {Object} */
  set meta(_meta) {
    this._meta = _meta;
  }

  /** @type {Object} - Placeholder for relationships */
  get relationships() {
    const relationships = {};
    Object.keys(this._relationships).forEach(k => (relationships[k] = { data: toJsonApiStub(this._relationships[k]) }));
    let _return;
    if (!(Object.keys(relationships).length === 0 && relationships.constructor === Object)) {
      _return = { relationships };
    }
    return _return;
  }

  /** @type {String} - Create an object that will be used for JSON.stringify.  Expect extending
   * functions to implement this appropriately.
   */
  static get type() {
    return 'none';
  }

  /** @type {String} - Create an object that will be used for JSON.stringify. */
  get type() {
    return this.constructor.type;
  }

  /**
   * Remove this object from storage
   */
  async clear() {
    return Client.instance.storageUtility.clear(this.type, this.id);
  }

  /**
   * Deserialies the relationship stubs prior to synching with database
   * @param {Object} json - Must be in JsonAPI Format
   */
  deserializeRelationshipStubs(json) {
    let relationships;
    if (json && (json.relationships || (json.data && json.data.relationships))) {
      // Create a clone as to not modify existing json
      // this._relationships = JSON.parse(JSON.stringify(json.relationships || json.data.relationships));
      relationships = JSON.parse(JSON.stringify(json.relationships || json.data.relationships));
    } else {
      return;
    }
    Object.keys(relationships).forEach((key) => {
      let data;
      if (relationships[key] && typeof relationships[key].data !== 'undefined') {
        // data can be an object or null
        ({ data } = relationships[key]);
      } else if (typeof relationships[key] !== 'undefined') {
        // An object is either already assigned
        data = relationships[key];
      }
      if (!this._relationships[key]) {
        if (Array.isArray(data)) {
          this._relationships[key] = [];
          data.forEach((obj) => {
            if (!HelperFactory.map[obj.type]) {
              // return a flattened version of the object as-is
              this._relationships[key].push(flattenJsonApi(obj));
            } else {
              this._relationships[key].push(new HelperFactory.map[obj.type](obj));
            }
          });
        } else if (data && data.type) {
          if (HelperFactory.map[data.type]) {
            this._relationships[key] = new HelperFactory.map[data.type](data);
          }
        } else if (typeof data !== 'undefined') {
          this._relationships[key] = data;
        }
      }
    });
  }

  /**
   * Get the relationship from storage or if it's already associated based on the type and id.
   *
   * @param  {Object} obj       - Get relationship based on json api relationship object
   * @param  {Number} obj.id    - The object id
   * @param  {String} obj.type  - The object type
   * @return {Promise}          - Returns a Promise with the object from the relationship
   */
  async getRelationship(obj) {
    const storage = Client.instance.storageUtility;
    return new Promise((resolve) => {
      if (this[obj.type] && this[obj.type].find(o => (o.id === obj.id))) {
        resolve(this[obj.type].find(o => (o.id === obj.id)));
      } else {
        storage.get(obj.type, obj)
          .then(s => this[`_${obj.type}`].push(s))
          .then(s => resolve(s));
      }
    });
  }

  /**
   * Synchronize the relationships from storage to the current object
   *
   * @return {Promise}
   */
  async syncRelationships(json) {
    let relationships;
    if (json && (json.relationships || (json.data && json.data.relationships))) {
      // Create a clone as to not modify existing json
      // this._relationships = JSON.parse(JSON.stringify(json.relationships || json.data.relationships));
      relationships = json.relationships || json.data.relationships;
    } else {
      return Promise.resolve();
    }
    // // Do relationships exist?
    // if (!this._relationships) {
    //   return Promise.resolve();
    // }

    // count relationships
    const storage = Client.instance.storageUtility;
    return Promise.all(
      Object.keys(relationships).map((key) => {
        let _promise = Promise.resolve(data);

        let data;
        if (relationships[key] && typeof relationships[key].data !== 'undefined') {
          // data can be an object or null
          ({ data } = relationships[key]);
        } else if (typeof relationships[key] !== 'undefined') {
          // An object is either already assigned
          data = relationships[key];
        }

        // Setup getters/setters dynamically
        /** @type {Any} - This is an attribute to link relationships */
        const self = this;
        if (self._relationships[key] && !self[stringToCamel(key)]) {
          // delete self._relationships[key];
          Object.defineProperty(self, stringToCamel(key), {
            get: () => self._relationships[key],
            set: v => (self._relationships[key] = v),
            configurable: true
          });
        }

        // Associate real data with relationships
        if (Array.isArray(data)) {
          /** @type {Object[]} */
          if (!this._relationships[key]) {
            this._relationships[key] = [];
          }
          _promise = Promise.all(
            data.map(obj => storage.get(obj.type).then((stor) => {
              // Point to the object in storage if it exists there
              const _obj = (Object.keys(stor).length !== 0 && obj.id && stor[obj.id]) ? stor[obj.id] : obj;

              const i = this._relationships[key].findIndex((o => _obj.id && o && o.id === _obj.id));
              if (i !== -1) {
                // If this already exists in _relationships, update it
                this._relationships[key][i] = _obj;
              } else {
                // Otherwise simply add it
                this._relationships[key].push(stor[_obj.id]);
              }
            }))
          );
        } else if (data && data.type) {
          _promise = storage.get(data.type, { id: data.id }).then((v) => {
            if (Object.keys(v).length !== 0) {
              if (data.id && v[data.id]) {
                this._relationships[key] = v[data.id];
              } else if (data.id && v.type) {
                this._relationships[key] = v;
              } else if (data.id && data.type) {
                helperFactory.get({ id: data.id, type: data.type }, data.type)
                  .then(obj => (this._relationships[key] = obj));
              } else {
                this._relationships[key] = v;
              }
            } else {
              this._relationships[key] = data;
            }
          });
        } else if (this._relationships[key] && this._relationships[key].id && this._relationships[key].type) {
          // make sure that if the server set this as null, we know the difference between this and it never being set
          this._relationships[key] = helperFactory.get(this._relationships[key], this._relationships[key].type);
          _promise = Promise.resolve(this._relationships[key]);
        } else {
          this._relationships[key] = data;
          _promise = Promise.resolve(this._relationships[key]);
        }
        return _promise;
      })
    );
  }

  /**
   * Load this object from storage.
   */
  async load() {
    return Client.instance.storageUtility.get(this.type, { id: this.id })
      .then(d => Object.assign(this, d));
  }

  /**
   * Store this object into storage
   */
  async store() {
    return Client.instance.storageUtility.set(this.type, this, { id: this.id });
  }

  /**
   * Default toJSON for json api format
   * @return {Object} - returns json useable for json api
   */
  toJSON() {
    const obj = {};
    if (this.id) {
      obj.id = this.id;
    }
    obj.type = this.type;
    obj.attributes = {};
    Object.keys(this).forEach((key) => {
      if (['id', 'type'].indexOf(key) === -1 && key.charAt(0) !== '_') {
        if (this[key] && typeof this[key].toJSON === 'function') {
          obj.attributes[stringToSnake(key)] = this[key].toJSON();
        } else {
          obj.attributes[stringToSnake(key)] = this[key];
        }
      }
    });
    Object.assign(obj, fieldsToSnake(this.relationships));
    return obj;
  }
}

export default Helper;