baseEntity.js

const { validate, checker } = require("suma")
const validateValue = validate

/**
 * this class is the base of entities, every entity must to extends this class
 */
class BaseEntity {
  /**
   * Validate the entity and add the errors into "errors" property.
   * @returns {void}
  */
  validate() {
    const errors = {}
    for (const [name, definition] of Object.entries(this.meta.schema)) {
      const value = this[name]

      // ignore functions
      if (checker.isFunction(value)) continue

      // types validation
      const validation = definition.validation
      const retErrors = validateValue(value, validation)
      if (retErrors.errors && retErrors.errors.length > 0) {
        errors[name] = retErrors.errors
        continue
      }

      // for entity types (deep validation)
      if (value instanceof BaseEntity) {
        if (value.isValid()) continue
        errors[name] = value.errors
      }
      
      // for array of entity types
      if (Array.isArray(value) && definition.type[0] && definition.type[0].prototype instanceof BaseEntity) {
        const errorList = value.map((item) =>
          !item.isValid() ? item.errors : null
        )
        const errorFound = errorList.filter((error) => !!error)

        if (!errorFound || !errorFound.length) continue
        errors[name] = errorList
      }
    }

    this.__proto__.errors = errors
  }
  
  /**
   * Check if this entity is valid
   * @returns {boolean}
   */
  isValid() {
    this.validate()
    return Object.keys(this.errors).length === 0
  }

  /**
   * Transform this entity to JSON
   * @returns {JSON}
   */
  toJSON() {
    function deepCopy(obj) {
      const copy = {}
      for (const field in obj.meta.schema) {
        let value = obj[field]
        if (value instanceof BaseEntity) value = deepCopy(value)
        if (value instanceof Function) continue
        copy[field] = value
      }
      return copy
    }
    const obj = deepCopy(this)
    return obj
  }
  /**
   * Fill the entity with by a filled JSON object
   * @static
   * @param {JSON} json 
   */
  static fromJSON(json) {
    function parse(type, value) {
      if (value === undefined) return undefined
      if (value === null) return null

      if (type === Date) return new Date(value)
      if (type.prototype instanceof BaseEntity) {
        const entity = type.fromJSON(value)
        return entity
      }

      if (Array.isArray(type) && type[0] && type[0].prototype instanceof BaseEntity) {
        return value.map((item) => {
          const entity = type[0].fromJSON(item)
          return entity
        })
      }

      return value
    }

    let data = json
    if (typeof json === "string") data = JSON.parse(json)

    const instance = new this()

    for (const field in instance.meta.schema) {
      const fieldMeta = instance.meta.schema[field]
      if (!(fieldMeta.constructor.name === "Field")) continue
      instance[field] = parse(fieldMeta.type, data[field])
    }

    return instance
  }
}

module.exports = { BaseEntity }