party_document-factory.js

const Ajv = require('ajv')
const debug = require('debug')('dataparty.document-factory')
const IDocument = require('./idocument')

const reach = require('../utils/reach')

const DocumentValidationError = require('../errors/document-validation-error')



class DocumentFactory {
  /**
 * Represents document schemas mapped to contructor claasses which extend IDocument
 * @class module:Party.DocumentFactory
 * @extends EventEmitter
 * @link module.Party
 */
  constructor({model, factories, party, documentClass}){
    this.factories = factories || {}
    this.party = party || null
    this.ajv = new Ajv()
    //this.model = model
    this.documentClass = documentClass || IDocument
    this.validators = {}

    if(model){

      this.schemas = reach(model, 'schemas', model)

      for(let schema of this.schemas.JSONSchema){
        const v = this.ajv.compile(schema)
        this.validators[schema.title] = v
        debug(schema.title)
      }
    }
  }

  /**
   * @async
   * @method module:Party.DocumentFactory.hydrate
   * @param {*} found 
   */
  async hydrate(found){
    let documents = []
    for(let doc of found){
      let id = doc.$meta.id
      let type = doc.$meta.type
      let document = this.getDocument(type, id, doc)

      documents.push(document)
    }

    return documents
  }


  /**
   * @method module:Party.DocumentFactory.addFactory
   *
   * @param {*} type 
   * @param {*} factory 
   */
  addFactory(type, factory){
    this.factories[type] = factory
  }

  /**
   @method module:Party.DocumentFactory.getFactory
   * 
   * @param {*} type 
   */
  getFactory(type){
    if(this.factories && this.factories[type]){
      return this.factories[type]
    }

    return this.documentClass
  }

  /**
   * @method module:Party.DocumentFactory.getTypes
   */
  getTypes(){
    let types = ['document']
    return types.concat(Object.keys(this.factories))
  }

  /**
   * @method module:Party.DocumentFactory.getValidators
   */
  getValidators(){
    return Object.keys(this.validators)
  }


  /**
   * @method module:Party.DocumentFactory.getFactories
   */
  getFactories(){
    let factories = { document: this.documentClass }

    for(let name of this.getTypes()){
      factories[name] = this.getFactory(name)
    }

    return factories
  }

  /**
   * @method module:Party.DocumentFactory.getDocument
   * 
   * @param {string} type 
   * @param {string} id 
   * @param {Object} data 
   * @returns {module:Party.IDocument}
   */
  getDocument(type, id, data){
    let TypeFactory = this.getFactory(type)
    let instance = new TypeFactory({type, id, data, party: this.party})

    return instance
  }


  /**
   * @async
   * @method module:Party.DocumentFactory.validate
   * 
   * @param {*} type 
   * @param {*} data 
   */
  async validate(type, data){
    debug('validate',type)
    return new Promise((resolve, reject)=>{

      if(!this.validators[type]){
        debug('WARNING - validate with no such model type[', type, ']')
        return reject(new DocumentValidationError('no such validator'))
        //return resolve(data)
      }

      let valid = this.validators[type](data)

      if(!valid){
        let errors = this.validators[type].errors

        return reject(new DocumentValidationError(errors))
      }

      return resolve(data)
    })
  }

  /**
   * @member module:Party.IDocument.Document
   * @type {IDocument} */
  get Document(){
    return this.documentClass
  }
}

module.exports = DocumentFactory