GillesRasigade/pattern

View on GitHub
src/Entity/Entity.js

Summary

Maintainability
A
35 mins
Test Coverage
'use strict';

const assert = require('assert');
const defaults = require('json-schema-defaults');
const _ = require('lodash');

const EventSourcing = require('../EventSourcing/EventSourcing');
const Validator = require('../Validator/Validator');
const Commands = require('../Command/Commands');
const Errors = require('./Errors');

const entityValidator = new Validator();

/**
 * @class Entity
 * @extends {EventSourcing}
 *
 * @description
 * The entity class aims to ease the use of instances use in combination of
 * commands pattern and Event Sourcing.
 *
 * @constructor
 * @param {any} [data={}] The data to bind on
 * @param {Validator} [validator=entityValidator] Entity validator which is
 * responsible to answer yes or no to the question: isValid.
 */
class Entity extends EventSourcing {
  constructor(data = {}, validator = entityValidator) {
    assert(new.target && new.target.name !== 'Entity',
      'Cannot construct Entity instance directly');

    if (new.target.initialized !== true) {
      Entity.initConstructor(new.target);
    }

    assert(new.target.initialized === true,
      'Cannot construct Entity instance without initializing it');

    // Build data based on the entity schema:
    const _data = _.merge({}, defaults(new.target.SCHEMA), data);

    super(_data);

    this.validator = validator;
    this.commands = new Commands(this);

    this.on('execute', this.push.bind(this));

    // Attach the entityName:
    this._constructor = new.target;
    this._entityName = new.target.name;
  }
  /**
   * Initialize the Entity with the
   */
  static initConstructor(constructor) {
    if (!constructor.SCHEMA) {
      // eslint-disable-next-line no-param-reassign
      constructor.SCHEMA = {};
    }
    // Add Entity schema to the validator:
    entityValidator.addSchema(constructor, constructor.SCHEMA);

    // Expose getter and setter to schema properties:
    Entity._defineGettersSetters(constructor);

    // Set constructor as initialized:
    // eslint-disable-next-line no-param-reassign
    constructor.initialized = true;
  }
  /**
   * Check whether the instance data is valid against schema.
   *
   * @returns {boolean}
   */
  isValid() {
    return this.validator.isValid(this);
  }
  /**
   * Define default getters and setters to exposed first level schema properties.
   *
   * @returns {void}
   */
  static _defineGettersSetters(constructor) {
    const schema = constructor.SCHEMA;
    if (schema && schema.properties) {
       // eslint-disable-next-line no-restricted-syntax
      for (const key in schema.properties) {
        if (!constructor.prototype.hasOwnProperty(key)) {
          Object.defineProperty(constructor.prototype, key, {
            // eslint-disable-next-line object-shorthand
            get: function get() {
              return this.data[key];
            },
            // eslint-disable-next-line object-shorthand
            set: function set(value) {
              this.data[key] = value;
            },
            enumerable: false,
            configurable: false,
          });
        }
      }
    }
  }
  mutator() {
    throw new Errors.MutatorError('Must be implemented');
  }
  accessor() {
    throw new Errors.AccessorError('Must be implemented');
  }
}

module.exports = Entity;