Source: lib/util/Code.js

'use strict';

const message = require('../message');
const StatusCode = require('../connector/Message').StatusCode;
const deprecated = require('./deprecated');

/**
 * Representation of a Code which runs on Baqend.
 *
 * @alias util.Code
 */
class Code {
  /**
   * @param {metamodel.Metamodel} metamodel
   * @param {EntityManagerFactory} entityManagerFactory
   */
  constructor(metamodel, entityManagerFactory) {
    /**
     * @type metamodel.Metamodel
     */
    this.metamodel = metamodel;
    /** @type EntityManagerFactory */
    this.entityManagerFactory = entityManagerFactory;
  }

  /**
   * Converts the given function to a string
   * @param {Function} fn The JavaScript function to serialize
   * @return {string} The serialized function
   */
  functionToString(fn) {
    if (!fn) {
      return '';
    }

    let str = fn.toString();
    str = str.substring(str.indexOf('{') + 1, str.lastIndexOf('}'));
    if (str.charAt(0) === '\n') {
      str = str.substring(1);
    }

    if (str.charAt(str.length - 1) === '\n') {
      str = str.substring(0, str.length - 1);
    }

    return str;
  }

  /**
   * Converts the given string to a module wrapper function
   * @param {Array<string>} signature The expected parameters of the function
   * @param {string} code The JavaScript function to deserialize
   * @return {Function} The deserialized function
   */
  stringToFunction(signature, code) {
    return new Function(signature, code); // eslint-disable-line no-new-func
  }

  /**
   * Loads a list of all available modules without handlers
   *
   * @return {Promise<Array<string>>}
   */
  loadModules() {
    const msg = new message.GetAllModules();
    return this.entityManagerFactory.send(msg)
      .then(response => response.entity);
  }

  /**
   * Loads Baqend code which will be identified by the given bucket and code type
   *
   * @param {metamodel.ManagedType|string} type The entity type for the handler or the Name of the
   * Baqend code
   * @param {string} codeType The type of the code
   * @param {true} asFunction set it to <code>true</code>, to parse the code as a function and return it
   * instead of a string
   * @return {Promise<Function>} The code as parsed function
   *
   * @function
   * @name loadCode
   * @memberOf util.Code.prototype
   */

  /**
   * Loads Baqend code which will be identified by the given bucket and code type
   *
   * @param {metamodel.ManagedType|string} type The entity type for the handler or the Name of the
   * Baqend code
   * @param {string} codeType The type of the code
   * @param {false} [asFunction=false] set it to <code>true</code>, to parse the code as a function and return it
   * instead of a string
   * @return {Promise<string>} The code as string
   */
  loadCode(type, codeType, asFunction) {
    const bucket = Object(type) instanceof String ? type : type.name;
    const msg = new message.GetBaqendCode(bucket, codeType)
      .responseType('text');

    return this.entityManagerFactory.send(msg)
      .then(response => this.parseCode(bucket, codeType, asFunction, response.entity), (e) => {
        if (e.status === StatusCode.OBJECT_NOT_FOUND) {
          return null;
        }

        throw e;
      });
  }

  /**
   * Saves Baqend code which will be identified by the given bucket and code type
   *
   * @param {metamodel.ManagedType|string} type The entity type for the handler or the Name of the
   * Baqend code
   * @param {string} codeType The type of the code
   * @param {string} fn Baqend code as a string
   * @return {Promise<string>} The stored code as a string
   *
   * @function
   * @name saveCode
   * @memberOf util.Code.prototype
   */

  /**
   * Saves Baqend code which will be identified by the given bucket and code type
   *
   * @param {metamodel.ManagedType|string} type The entity type for the handler or the Name of the
   * Baqend code
   * @param {string} codeType The type of the code
   * @param {Function} fn Baqend code as a function
   * @return {Promise<Function>} The stored code as a parsed function
   */
  saveCode(type, codeType, fn) {
    const bucket = Object(type) instanceof String ? type : type.name;
    const asFunction = fn instanceof Function;

    const msg = new message.SetBaqendCode(bucket, codeType)
      .entity(asFunction ? this.functionToString(fn) : fn, 'text')
      .responseType('text');

    return this.entityManagerFactory.send(msg)
      .then(response => this.parseCode(bucket, codeType, asFunction, response.entity));
  }

  /**
   * Deletes Baqend code identified by the given bucket and code type
   *
   * @param {metamodel.ManagedType|string} type The entity type for the handler or the Name of the
   * Baqend code
   * @param {string} codeType The type of the code
   * @return {Promise<*>} succeed if the code was deleted
   */
  deleteCode(type, codeType) {
    const bucket = Object(type) instanceof String ? type : type.name;
    const msg = new message.DeleteBaqendCode(bucket, codeType);
    return this.entityManagerFactory.send(msg)
      .then(() => this.parseCode(bucket, codeType, false, null));
  }

  /**
   * @param {string} bucket
   * @param {string} codeType
   * @param {boolean} [asFunction=false]
   * @param {string} code
   * @return {string|Function}
   * @private
   */
  parseCode(bucket, codeType, asFunction, code) {
    if (codeType === 'validate') {
      const type = this.metamodel.entity(bucket);
      type.validationCode = code;
      return asFunction ? type.validationCode : code;
    }

    return asFunction ? this.stringToFunction(['module', 'exports'], code) : code;
  }
}

deprecated(Code.prototype, '_metamodel', 'metamodel');
deprecated(Code.prototype, '_parseCode', 'parseCode');

module.exports = Code;