Source: entity/interface.js

const { UnknownEntityError } = require('../errors');
const logger = require('../logger')(__filename);
const objectPath = require('object-path');
const VError = require('verror');

/**
 * Interface Class for Entity instances (non-static methods)
 * Not meant to be used by itself. Use `Entity` instead.
 *
 * @class
 * @author Michael Hirn <michael.j.hirn+rlay[]gmail.com>
 */
class EntityInterface {

  /**
   * Create a new EntityInterface instance
   *
   * @param {Client} client - a `Client` instance
   * @param {object} payload - the `EntityObject` that the `CID` represents
   */
  constructor (client, payload) {
    this.client = client;
    this._cidRemote = payload.cid;
    this.payload = payload;
    this.type = this.payload.type;
    try {
      this.cid = client.getEntityCid(this.payload);
    } catch (e) {
      throw new VError(e, 'Invalid Payload: "%s"', JSON.stringify(this.payload));
    }
  }

  set payload (payload) {
    Reflect.deleteProperty(payload, 'cid');
    this._payload = payload;
  }

  get payload () {
    return this._payload;
  }

  /**
   * Create the class, data, object, and other assertions for an `Individual`
   *
   * @async
   * @param {Object} assertions - The non-inherent properties of the individual
   * @returns {String[]} - The `CID`s of the assertions
   */
  async assert (assertions = {}) {
    const assertionKeys = Object.keys(assertions);
    const assertionPromises = [];

    assertionKeys.forEach(propertyName => {
      if (this.client[propertyName]) {
        assertionPromises.push(
          this.client[propertyName].create({subject: this.cid})
        );
      } else {
        throw new UnknownEntityError(
          `No schema entity exists for: '${propertyName}'. Make sure you seeded it.`
        );
      }
    });

    const start = Date.now();
    logger.debug(`Storing Assertion(s) (${start}) ...`);

    const assertionEntities = await Promise.all(assertionPromises);
    const CIDs = assertionEntities.map(e => e.cid);

    logger.debug(`Stored Assertion(s) (${start} -> ${CIDs}) in ${Date.now() - start}ms`);
    return assertionEntities;
  }

  /**
   * Manages a payload key e.g. annotations, target, or subject etc. and
   * either finds (resolves) the CID or decodes the value/target.
   *
   * @return [Array<Array<string, Object>>] - Array of Array with path and Object
   */
  _schemaKeyHandler (schemaKey, schema, find, decodeValue) {
    if (schema[schemaKey] instanceof Array) {
      // is array of CIDs
      return schema[schemaKey].map((element, i) => {
        return [`${schemaKey}.${i}`, find(element)];
      });
    } else if(['value', 'target'].includes(schemaKey)) {
      // is encoded value
      return [schemaKey, decodeValue(schema[schemaKey])];
    }
    // is CID
    return [schemaKey, find(schema[schemaKey])];
  }

  /**
   * Given the payload of the entity it fetches the related entities and instantiates
   * proper `Entities` from them.
   *
   * @async
   */
  async fetch () {
    const { type, cid, ...schema } = this.payload;
    // get the schema jobs
    const schemaJobs = [...Object.keys(schema).map(schemaKey => {
      return this._schemaKeyHandler(
        schemaKey,
        schema,
        this.client.Individual.find.bind(this.client.Individual),
        this.client.rlay.decodeValue.bind(this.client.rlay)
      );
    })];
    // split the jobs
    const schemaPath = schemaJobs.map(job => job[0]);
    const schemaPromiseResults = await Promise.all(schemaJobs.map(job => job[1]));
    // attach the handled payload to the entity instance
    schemaPath.forEach((path, i) => {
      objectPath.set(this, path, schemaPromiseResults[i]);
    });
  }
}

module.exports = EntityInterface;