UNPKG

deep-kernel

Version:
863 lines (668 loc) 18.3 kB
/** * Created by mgoria on 5/26/15. */ /*eslint no-proto: 0*/ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.Kernel = undefined; var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _deepCore = require('deep-core'); var _deepCore2 = _interopRequireDefault(_deepCore); var _deepDi = require('deep-di'); var _deepDi2 = _interopRequireDefault(_deepDi); var _Exception = require('./Exception/Exception'); var _Instance = require('./Microservice/Instance'); var _MissingMicroserviceException = require('./Exception/MissingMicroserviceException'); var _Injectable = require('./Microservice/Injectable'); var _ContainerAware = require('./ContainerAware'); var _waitUntil = require('wait-until'); var _waitUntil2 = _interopRequireDefault(_waitUntil); var _util = require('util'); var _util2 = _interopRequireDefault(_util); var _Loader = require('./Config/Loader'); var _AsyncConfig = require('./Config/Driver/AsyncConfig'); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } /** * Deep application kernel */ let Kernel = exports.Kernel = function () { /** * @param {Array} deepServices * @param {String} context */ function Kernel(deepServices, context) { _classCallCheck(this, Kernel); if (Kernel.ALL_CONTEXTS.indexOf(context) === -1) { throw new _Exception.Exception(`Undefined context "${context}"`); } this._config = {}; this._services = deepServices; this._context = context; this._runtimeContext = {}; this._contextProvider = null; this._env = null; this._container = new _deepDi2.default(); this._isLoaded = false; this._asyncConfigCache = null; } /** * @returns {Boolean} */ _createClass(Kernel, [{ key: 'microservice', /** * @param {String|null} identifier * @returns {Microservice|*} */ value: function microservice() { let identifier = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; if (!identifier) { identifier = this._config.microserviceIdentifier; } for (let microserviceKey in this.microservices) { if (!this.microservices.hasOwnProperty(microserviceKey)) { continue; } let microservice = this.microservices[microserviceKey]; if (microservice.identifier === identifier) { return microservice; } } throw new _MissingMicroserviceException.MissingMicroserviceException(identifier); } /** * * @param {Function} cb * @returns {Kernel} */ }, { key: 'loadAsyncConfig', value: function loadAsyncConfig(cb) { if (this._asyncConfigCache) { cb(this._asyncConfigCache); return this; } let cache = this.get('cache').system; let cacheKey = Kernel.ASYNC_CONFIG_CACHE_KEY; cache.has(cacheKey, (error, exists) => { this._logErrorIfExistsAndNotProd(error); if (exists) { cache.get(cacheKey, (error, rawConfig) => { this._logErrorIfExistsAndNotProd(error); if (rawConfig) { try { this._asyncConfigCache = JSON.parse(rawConfig); cb(this._asyncConfigCache); return; } catch (error) { this._logErrorIfExistsAndNotProd(error); } } this._loadAsyncConfig(cache, cacheKey, cb); }); return; } this._loadAsyncConfig(cache, cacheKey, cb); }); return this; } /** * @param {Cache|*} cache * @param {String} cacheKey * @param {Function} cb * @private */ }, { key: '_loadAsyncConfig', value: function _loadAsyncConfig(cache, cacheKey, cb) { _Loader.Loader.asyncConfigLoader(this).load(config => { cache.set(cacheKey, JSON.stringify(config), 0, error => { this._logErrorIfExistsAndNotProd(error); this._asyncConfigCache = config; cb(config); }); }, error => { this._logErrorIfExistsAndNotProd(error); cb(null); }); } /** * @todo: get rid of this? * * @param {Error|String|*} error * @private */ }, { key: '_logErrorIfExistsAndNotProd', value: function _logErrorIfExistsAndNotProd(error) { if (error && this.env !== Kernel.PROD_ENVIRONMENT) { console.error(error); } } /** * @param {Function} callback * @returns {Kernel} * * @todo: put config file name into a constant? */ }, { key: 'bootstrap', value: function bootstrap(callback) { let rumEvent = { service: 'deep-kernel', resourceType: 'Lambda', eventName: 'KernelLoad', time: new Date().getTime() }; // @todo: remove AWS changes the way the things run // This is used because of AWS Lambda // context sharing after a cold start if (this._isLoaded) { if (this.isBackend) { rumEvent.eventName = 'KernelLoadFromCache'; rumEvent.resourceId = this.runtimeContext.invokedFunctionArn; rumEvent.payload = this.config; this.get('log').rumLog(rumEvent); } callback(this); return this; } _Loader.Loader.kernelLoader(this).load(config => { this.load(config, kernel => { if (this.isBackend) { // Log event 'start' time rumEvent.resourceId = this.runtimeContext.invokedFunctionArn; this.get('log').rumLog(rumEvent); // log event 'stop' time let event = _util2.default._extend({}, rumEvent); event.payload = kernel.config; event.time = new Date().getTime(); this.get('log').rumLog(event); } callback(kernel); }); }, error => { throw new _Exception.Exception(`Error loading kernel: ${error}`); }); return this; } /** * @returns {Kernel} */ }, { key: 'kernelCleanup', value: function kernelCleanup() { let serviceNames = Object.keys(this.services); for (let serviceName of serviceNames) { let serviceInstance = this.get(serviceName.toLowerCase()); serviceInstance.cleanup(); } return this; } /** * Loads all Kernel dependencies * * @param {Object} config * @param {Function} callback * * @returns {Kernel} */ }, { key: 'load', value: function load(config, callback) { // @todo: remove AWS changes the way the things run // This is used because of AWS Lambda // context sharing after a cold start if (this._isLoaded) { callback(this); return this; } let originalCallback = callback; callback = kernel => { this._isLoaded = true; originalCallback(kernel); }; this._config = config; this._buildContainer(callback); return this; } /** * @param {*} args * @returns {*} */ }, { key: 'get', value: function get() { var _container; return (_container = this._container).get.apply(_container, arguments); } /** * @param {Array} args * @returns {Boolean} */ }, { key: 'has', value: function has() { var _container2; return (_container2 = this._container).has.apply(_container2, arguments); } /** * @returns {Array} */ }, { key: 'getMsParam', /** * @param {String} msIdentifier * @param {String} paramPath * @param {*} defaultValue * * @returns {*} */ value: function getMsParam(msIdentifier, paramPath) { let defaultValue = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null; if (!this._config.microservices.hasOwnProperty(msIdentifier)) { throw new Error(`Unknown microservice identifier "${msIdentifier}".`); } return this._findParam(this._config.microservices[msIdentifier].parameters, paramPath, defaultValue); } /** * @param {String} paramPath * @param {*} defaultValue * * @returns {*} */ }, { key: 'getParam', value: function getParam(paramPath) { let defaultValue = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; return this._findParam(this._config.globals, paramPath, defaultValue); } /** * @param {*} params * @param {String} paramPath * @param {*} defaultValue * * @returns {*} * * @private */ }, { key: '_findParam', value: function _findParam(params, paramPath, defaultValue) { let paramParts = paramPath.split('|').map(x => x.trim()); let result = params; for (let i = 0; i < paramParts.length; i++) { let param = paramParts[i]; if (!result || typeof result !== 'object' && !result.hasOwnProperty(param)) { return defaultValue; } result = result[param]; } return result; } /** * @returns {Microservice[]} */ }, { key: '_buildContainer', /** * Loads all parameters and services into DI container * * @param {Function} callback */ value: function _buildContainer(callback) { this._env = this._config.env; this._container.addParameter(Kernel.KERNEL, this); this._container.addParameter(Kernel.CONTEXT, { environment: this._env, isFrontend: this.isFrontend, isBackend: this.isBackend }); this._container.addParameter(Kernel.MICROSERVICES, _Instance.Instance.createVector(this._config)); this._container.addParameter(Kernel.CONFIG, this._config); this._container.localBackend = _deepCore2.default.IS_DEV_SERVER; let bootingServices = 0; for (let serviceKey in this._services) { if (!this._services.hasOwnProperty(serviceKey)) { continue; } let serviceInstance = new this._services[serviceKey](); bootingServices++; serviceInstance.kernel = this; serviceInstance.localBackend = _deepCore2.default.IS_DEV_SERVER; serviceInstance.boot(this, () => { bootingServices--; }); this._container.addService(serviceInstance.name, Kernel._createProxyIfNeeded(serviceInstance)); } (0, _waitUntil2.default)().interval(10).times(999999 // @todo: get rid of magic here... ).condition(cb => { process.nextTick(() => { cb(bootingServices <= 0); }); }).done(() => { callback(this); }); } /** * @param {ContainerAware|Object} serviceObj * @returns {ContainerAware|Proxy|Object} * @private */ }, { key: 'isLoaded', get: function get() { return this._isLoaded; } /** * @returns {Object} */ }, { key: 'runtimeContext', get: function get() { return this._runtimeContext; } /** * @param {Object} runtimeContext */ , set: function set(runtimeContext) { this._runtimeContext = runtimeContext; } /** * @returns {Object} */ }, { key: 'contextProvider', get: function get() { return this._contextProvider; } /** * @param {Object} contextProvider */ , set: function set(contextProvider) { this._contextProvider = contextProvider; } /** * @returns {Microservice|*} */ }, { key: 'rootMicroservice', get: function get() { for (let microserviceKey in this.microservices) { if (!this.microservices.hasOwnProperty(microserviceKey)) { continue; } let microservice = this.microservices[microserviceKey]; if (microservice.isRoot) { return microservice; } } // this should never happen... throw new _MissingMicroserviceException.MissingMicroserviceException('ROOT'); } /** * @todo: add advanced criteria for account microservice * @returns {Microservice|null} */ }, { key: 'accountMicroservice', get: function get() { for (let microserviceKey in this.microservices) { if (!this.microservices.hasOwnProperty(microserviceKey)) { continue; } let microservice = this.microservices[microserviceKey]; if (microservice.identifier === Kernel.ACCOUNT_MICROSERVICE_IDENTIFIER) { return microservice; } } return null; } }, { key: 'services', get: function get() { return this._services; } /** * @returns {DI} */ }, { key: 'container', get: function get() { return this._container; } /** * @returns {Boolean} */ }, { key: 'isRumEnabled', get: function get() { return this.has('log') && this.get('log').isRumEnabled(); } /** * @returns {Boolean} */ }, { key: 'isFrontend', get: function get() { return this._context === Kernel.FRONTEND_CONTEXT; } /** * @returns {Boolean} */ }, { key: 'isLocalhost', get: function get() { return this.isFrontend && ['localhost', '127.0.0.1', '0.0.0.0', '::1'].indexOf(window.location.hostname) !== -1; } /** * @returns {Boolean} */ }, { key: 'isBackend', get: function get() { return this._context === Kernel.BACKEND_CONTEXT; } /** * @returns {String} */ }, { key: 'buildId', get: function get() { return this._config.deployId || ''; } /** * @returns {String} */ }, { key: 'context', get: function get() { return this._context; } /** * @returns {String} */ }, { key: 'env', get: function get() { return this._env; } /** * @returns {Object} */ }, { key: 'config', get: function get() { // @todo - create a class DeepConfig or smth, that will hold global config and expose shortcuts to different options return this._config; } }, { key: 'microservices', get: function get() { return this._container.get(Kernel.MICROSERVICES); } }], [{ key: '_createProxyIfNeeded', value: function _createProxyIfNeeded(serviceObj) { if (serviceObj === serviceObj.service) { return serviceObj; } else if (!serviceObj.hasOwnProperty('apply')) { return serviceObj.service; } let proxy = new Proxy(serviceObj, serviceObj.service); proxy.__proto__ = this.__proto__; proxy.constructor.prototype = this.constructor.prototype; return proxy; } /** * @returns {MicroserviceInjectable} */ }, { key: 'MicroserviceInjectable', get: function get() { return _Injectable.Injectable; } /** * @returns {ContainerAware} */ }, { key: 'ContainerAware', get: function get() { return _ContainerAware.ContainerAware; } /** * @returns {String} */ }, { key: 'FRONTEND_BOOTSTRAP_VECTOR', get: function get() { return 'deep_frontend_bootstrap_vector'; } /** * @returns {String} */ }, { key: 'CONFIG', get: function get() { return 'deep_config'; } /** * @returns {String} */ }, { key: 'KERNEL', get: function get() { return 'deep_kernel'; } /** * @returns {String} */ }, { key: 'CONTEXT', get: function get() { return 'deep_context'; } /** * @returns {String} */ }, { key: 'MICROSERVICES', get: function get() { return 'deep_microservices'; } /** * @returns {String} */ }, { key: 'FRONTEND_CONTEXT', get: function get() { return 'frontend-ctx'; } /** * @returns {String} */ }, { key: 'BACKEND_CONTEXT', get: function get() { return 'backend-ctx'; } /** * @returns {Array} */ }, { key: 'ALL_CONTEXTS', get: function get() { return [Kernel.FRONTEND_CONTEXT, Kernel.BACKEND_CONTEXT]; } /** * @returns {String} */ }, { key: 'PROD_ENVIRONMENT', get: function get() { return 'prod'; } /** * @returns {String} */ }, { key: 'STAGE_ENVIRONMENT', get: function get() { return 'stage'; } /** * @returns {String} */ }, { key: 'TEST_ENVIRONMENT', get: function get() { return 'test'; } /** * @returns {String} */ }, { key: 'DEV_ENVIRONMENT', get: function get() { return 'dev'; } /** * @returns {String} */ }, { key: 'ASYNC_CONFIG_FILE', get: function get() { return _AsyncConfig.AsyncConfig.DEFAULT_CONFIG_FILE; } /** * @returns {String} */ }, { key: 'ASYNC_CONFIG_CACHE_KEY', get: function get() { return 'asyncConfig'; } /** * @returns {String} */ }, { key: 'ACCOUNT_MICROSERVICE_IDENTIFIER', get: function get() { return 'deep-account'; } /** * @returns {Array} */ }, { key: 'ALL_ENVIRONMENTS', get: function get() { return [Kernel.PROD_ENVIRONMENT, Kernel.STAGE_ENVIRONMENT, Kernel.TEST_ENVIRONMENT, Kernel.DEV_ENVIRONMENT]; } }]); return Kernel; }();