src/application.js

Summary

Maintainability
A
2 hrs
Test Coverage
"use strict";
/**
 * @file
 * The Waigo application object.
 */

const debug = require('debug')('waigo_application'),
  koa = require('koa'),
  log4js = require('log4js'),
  EventEmitter = require('eventemitter3'),
  path = require('path');


const waigo = global.waigo,
  _ = waigo._,
  logger = waigo.load('support/logger'),
  errors = waigo.load('support/errors');


// underscore mixins
waigo.load('support/lodashMixins')(_);


const AppError = errors.define('AppError');


/** 
 * The application object.
 * 
 * This is the main entry point for all Waigo applications. It is responsible 
 * for constructing the Koa `app` object, loading in the app 
 * configuration, initialising logging and running all startup steps.
 */
class Application extends EventEmitter {
  constructor () {
    super();

    this._initKoa();

    this._onError = this._onError.bind(this);
  }


  /**
   * Initialize koa.
   *
   * @protected
   */
  _initKoa () {
    var self = this;

    this.koa = koa();

    /**
     * Default middleware!
     */
    this.koa.use(function*(next) {
      this.App = self;

      yield next;
    });
  }



  /**
   * Load in the application configuration.
   *
   * This gets called automatically during startup.
   *
   * @param {Object} [options] Additional options.
   * @param {Function} [options.postConfig] Function to pass configuration object to once loaded.
   * Should have signature `function (config: Object)`.
   * 
   * @protected
   */
  * _loadConfig (options) {
    options = options || {};

    debug('Loading configuration');

    this.config = waigo.load('config/index')();

    if (options.postConfig) {
      debug('Executing dynamic configuration');

      options.postConfig.call(null, this.config);
    }
  }


  /**
   * Handle an error.
   *
   * @param {Error} err The error.
   * 
   * @protected
   */
  _onError (err) {
    this.logger.error(err.stack ? err.stack : err);
  };



  /**
   * Setup application logger.
   *
   * Upon completion `this.logger` will be set.
   *
   * @param {Object} cfg Logger configuration.
   *
   * @see support/logger.js
   * @protected
   */
  * _setupLogger (cfg) {
    debug('Setup logging');

    this.logger = logger.init(cfg);

    process.on('uncaughtException', this._onError);
    this.koa.on('error', this._onError);
    this.on('error', this._onError);
  }



  /**
   * Start the application.
   *
   * @param {Object} [options] Additional options.
   * @param {Function} [options.postConfig] Function to pass configuration object to once loaded.
   * Should have signature `function (config: Object)`.
   */
  * start (options) {
    if (this.config) {
      throw new AppError('Application already started');
    }

    // load config
    yield this._loadConfig(options);

    // logging
    yield this._setupLogger(this.config.logging);

    for (let stepName of this.config.startupSteps) {
      this.logger.debug(`Running startup step: ${stepName}`);
      
      /*jshint -W030 */
      yield waigo.load(`support/startup/${stepName}`)(this);
    }

    this.logger.info('Startup complete');
  }



  /**
   * Stop the application.
   *
   * This will reset the internal Koa `app` object.
   */
  * shutdown () {
    if (!this.config) {
      throw new AppError('Application not started');
    }

    // remove uncaught exception handler
    process.removeListener('uncaughtException', this._onError);

    // run shutdown steps
    for (let stepName of this.config.shutdownSteps) {
      this.logger.debug(`Running shutdown step: ${stepName}`);

      /*jshint -W030 */
      yield waigo.load(`support/shutdown/${stepName}`)(this);
    }

    this.logger.info('Shutdown complete');

    delete this.config;

    // reset koa
    this._initKoa();
  };


}



/**
 * @type {Class}
 */
module.exports = Application;