Home Reference Source Repository

lib/DB.js

/**
 * Created by AlexanderC on 6/15/15.
 */

'use strict';

import Kernel from 'deep-kernel';
import Vogels from 'vogels';
import {ExtendModel} from './Vogels/ExtendModel';
import {ModelNotFoundException} from './Exception/ModelNotFoundException';
import Validation from 'deep-validation';
import Utils from 'util';
import {FailedToCreateTableException} from './Exception/FailedToCreateTableException';
import {FailedToCreateTablesException} from './Exception/FailedToCreateTablesException';
import {AbstractDriver} from './Local/Driver/AbstractDriver';
import {AutoScaleDynamoDB} from './DynamoDB/AutoScaleDynamoDB';

/**
 * Vogels wrapper
 */
export class DB extends Kernel.ContainerAware {
  /**
   * @param {Array} models
   * @param {Object} tablesNames
   */
  constructor(models = [], tablesNames = {}) {
    super();

    this._tablesNames = tablesNames;
    this._validation = new Validation(models);
    this._models = this._rawModelsToVogels(models);
  }

  /**
   * @returns {Validation}
   */
  get validation() {
    return this._validation;
  }

  /**
   * @returns {Vogels[]}
   */
  get models() {
    return this._models;
  }

  /**
   * @param {String} modelName
   * @returns {Boolean}
   */
  has(modelName) {
    return typeof this._models[modelName] !== 'undefined';
  }

  /**
   * @param {String} modelName
   * @returns {Vogels}
   */
  get(modelName) {
    if (!this.has(modelName)) {
      throw new ModelNotFoundException(modelName);
    }

    let model = this._models[modelName];

    if (this.kernel && this.kernel.isRumEnabled) {
      // inject logService into extended model to log RUM events
      model.logService = this.kernel.get('log');
    }

    return model;
  }

  /**
   * @param {String} modelName
   * @param {Function} callback
   * @param {Object} options
   * @returns {DB}
   */
  assureTable(modelName, callback, options = {}) {
    if (!this.has(modelName)) {
      throw new ModelNotFoundException(modelName);
    }

    options = Utils._extend(DB.DEFAULT_TABLE_OPTIONS, options);
    options[modelName] = options;

    Vogels.createTables(options, (error) => {
      if (error) {
        throw new FailedToCreateTableException(modelName);
      }

      callback();
    });

    return this;
  }

  /**
   * @param {Function} callback
   * @param {Object} options
   * @returns {DB}
   */
  assureTables(callback, options = {}) {
    let allModelsOptions = {};
    let allModelNames = [];

    for (let modelName in this._models) {
      if (!this._models.hasOwnProperty(modelName)) {
        continue;
      }

      allModelsOptions[modelName] = Utils._extend(DB.DEFAULT_TABLE_OPTIONS, options);
      allModelNames.push(modelName);
    }

    Vogels.createTables(allModelsOptions, (error) => {
      if (error) {
        throw new FailedToCreateTablesException(allModelNames, error);
      }

      callback();
    });

    return this;
  }

  /**
   * Booting a certain service
   *
   * @param {Kernel} kernel
   * @param {Function} callback
   */
  boot(kernel, callback) {
    this._validation.boot(kernel, () => {
      this._tablesNames = kernel.config.tablesNames;
      this._models = this._rawModelsToVogels(kernel.config.models);

      if (this._localBackend) {
        this._enableLocalDB(callback);
      } else {
        if (!Vogels.documentClient().hasOwnProperty(AutoScaleDynamoDB.DEEP_DB_DECORATOR_FLAG)) {
          this._initVogelsAutoscale();
        }

        callback();
      }
    });
  }

  /**
   * @private
   */
  _initVogelsAutoscale() {
    Vogels.AWS.config.maxRetries = 3;

    Vogels.documentClient(
      new AutoScaleDynamoDB(
        Vogels.dynamoDriver(),
        Vogels.documentClient()
      ).extend()
    );
  }

  /**
   * @param {Object} driver
   * @returns {DB}
   * @private
   */
  _setVogelsDriver(driver) {
    Vogels.dynamoDriver(driver);

    return this;
  }

  /**
   * @param {Function} callback
   * @param {String} driver
   * @param {Number} tts
   * @returns {AbstractDriver}
   */
  static startLocalDynamoDBServer(callback, driver = 'LocalDynamo', tts = AbstractDriver.DEFAULT_TTS) {
    let LocalDBServer = require('./Local/DBServer').DBServer;

    let server = LocalDBServer.create(driver);

    server.start(callback, tts);

    return server;
  }

  /**
   * @returns {AWS.DynamoDB|VogelsMock.AWS.DynamoDB|*}
   * @private
   */
  get _localDynamoDb() {
    return new Vogels.AWS.DynamoDB({
      endpoint: new Vogels.AWS.Endpoint(`http://localhost:${DB.LOCAL_DB_PORT}`),
      accessKeyId: 'fake',
      secretAccessKey: 'fake',
      region: 'us-east-1',
    });
  }

  /**
   * @param {Function} callback
   * @private
   */
  _enableLocalDB(callback) {
    this._setVogelsDriver(this._localDynamoDb);

    this.assureTables(callback);
  }

  /**
   * @returns {Object}
   */
  static get DEFAULT_TABLE_OPTIONS() {
    return {
      readCapacity: 1,
      writeCapacity: 1,
    };
  }

  /**
   * @param {Array} rawModels
   * @returns {Object}
   */
  _rawModelsToVogels(rawModels) {
    let models = {};

    for (let modelKey in rawModels) {
      if (!rawModels.hasOwnProperty(modelKey)) {
        continue;
      }

      let backendModels = rawModels[modelKey];

      for (let modelName in backendModels) {
        if (!backendModels.hasOwnProperty(modelName)) {
          continue;
        }

        models[modelName] = new ExtendModel(Vogels.define(
          modelName,
          this._wrapModelSchema(modelName)
        )).inject();
      }
    }

    return models;
  }

  /**
   * @param {String} name
   * @returns {Object}
   * @private
   */
  _wrapModelSchema(name) {
    return {
      hashKey: 'Id',
      timestamps: true,
      tableName: this._tablesNames[name],
      schema: this._validation.getSchema(name),
    };
  }

  /**
   * @returns {Number}
   */
  static get LOCAL_DB_PORT() {
    return AbstractDriver.DEFAULT_PORT;
  }
}