node-warp
Version:
Web-agnostic Rapid Prototyping tools
362 lines (302 loc) • 11.4 kB
JavaScript
'use strict';
var _createClass = require('babel-runtime/helpers/create-class')['default'];
var _classCallCheck = require('babel-runtime/helpers/class-call-check')['default'];
var _defineProperty = require('babel-runtime/helpers/define-property')['default'];
var _slicedToArray = require('babel-runtime/helpers/sliced-to-array')['default'];
var _toConsumableArray = require('babel-runtime/helpers/to-consumable-array')['default'];
var _Object$assign = require('babel-runtime/core-js/object/assign')['default'];
var _Map = require('babel-runtime/core-js/map')['default'];
var _Object$entries = require('babel-runtime/core-js/object/entries')['default'];
var _Promise = require('babel-runtime/core-js/promise')['default'];
var _interopRequireDefault = require('babel-runtime/helpers/interop-require-default')['default'];
Object.defineProperty(exports, '__esModule', {
value: true
});
var _debug = require('debug');
var _debug2 = _interopRequireDefault(_debug);
var _path2 = require('path');
var _assert = require('assert');
var _assert2 = _interopRequireDefault(_assert);
var _util = require('util');
var _bus = require('./bus');
var _bus2 = _interopRequireDefault(_bus);
var Functors = {
reduce: function reduce(prev, cur) {
return _Object$assign(prev, cur);
},
priorityAscending: function priorityAscending(a, b) {
return a.config.priority - b.config.priority;
},
priorityDescending: function priorityDescending(a, b) {
return b.config.priority - a.config.priority;
}
};
/**
* @desc Represents generic application skeleton,
* providing api for simple state and component management
*/
var Application = (function () {
_createClass(Application, null, [{
key: 'create',
/**
* Returns application instance
* @param {Object} config
* @param {String} config.name
* @return {Application}
*/
value: function create(config) {
return new Application(config);
}
}, {
key: 'defaults',
value: {
name: 'app',
componentsRoot: '.'
},
/**
* @param {Object} config
* @param {String} config.name
* @constructor
*/
enumerable: true
}]);
function Application(config) {
var _this = this;
_classCallCheck(this, Application);
(0, _assert2['default'])(config.name, 'application name should be a string');
this.bus = new _bus2['default']();
this._components = new _Map();
this.name = config.name;
this.config = _Object$assign({}, Application.defaults, config);
this.data = _Object$assign({ name: this.name }, config.data);
this.debug = (0, _debug2['default'])(this.name);
this.bus.onRequest('app.destroy', function* _destroy(data) {
this.debug('destroying application. reason: ' + data.reason);
return yield this.destroy();
}, this);
this.bus.on('app.data', function (data) {
_this.data = _Object$assign({}, _this.data, data);
});
this.bus.onRequest('app.query.data', function () {
return _Object$assign({}, _this.data);
});
this._setStatus('created');
}
/**
* @desc Starts an application:
* calls 'init' method on each registered component, then fires 'app.started' event
* @return {Promise}
*/
_createClass(Application, [{
key: 'start',
value: function start() {
var _this2 = this;
this.debug('starting');
this._instantiateComponents();
return this._initializeComponents().then(function () {
return _this2._setStatus('starting');
}).then(function () {
return _this2._forEachComponent('onStart', Functors.priorityAscending);
}).then(function () {
return _this2._setStatus('started', _this2.data);
}).then(function () {
return _this2._forEachComponent('onAfterStart', Functors.priorityAscending);
});
}
/**
* @desc Destroys an application:
* calls 'destroy' method on each registered component, then fires 'app.destroyed' event
* @return {Promise}
*/
}, {
key: 'destroy',
value: function destroy() {
var _this3 = this;
this.debug('destroying ' + this.name);
return this._forEachComponent('onBeforeDestroy', Functors.priorityDescending).then(function () {
return _this3._setStatus('destroying');
}).then(function () {
return _this3._forEachComponent('onDestroy', Functors.priorityDescending);
}).then(function () {
return _this3._setStatus('destroyed');
});
}
/**
* @desc Registers component, represented by given class or module path
* @param {Component|string} component
* @param {object} [componentConfig]
* @return {Promise}
*/
}, {
key: 'use',
value: function use(component) {
var componentConfig = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
if ((0, _util.isString)(component)) {
return this.useModule(component, componentConfig);
}
return this._addComponent(component, componentConfig);
}
}, {
key: 'useModule',
value: function useModule(path) {
var config = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
this.debug('loading component module: ' + path);
var component = require(path);
return this._addComponent(component, config);
}
/**
* @desc Loads whole bunch of components using configuration object.
* @param {object<string,object>} componentsConfig
*
* @example configuration object:
* let components = [{
* 'component-one': {
* imports: [],
* config: { someOption: 'someValue' }
* },
*
* 'component-two': {
* imports: ['component-one'], // dependency declaration (it overrides the defaults)
* config: { anotherOption: 'anotherValue' }
* },
*
* 'one-more-component': {
* path: './path/to/my-components', // explicit path declaration (overrides 'config.app.componentsRoot')
* imports: ['component-one', 'component-two'],
* config: {}
* },
* ... etc ...
* }];
*
* let warp = require('@hp/warp');
* let app = warp({ name: 'this-is-warp' });
*
* // loads stuff synchronously
* app.useConfig(components);
*
* // app is ready to start
* app.start().then( ... ).catch( ... );
*/
}, {
key: 'useConfig',
value: function useConfig() {
var _this4 = this;
var componentsConfig = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
_Object$entries(componentsConfig).forEach(function (_ref3) {
var _ref32 = _slicedToArray(_ref3, 2);
var key = _ref32[0];
var entry = _ref32[1];
var _path = entry.path || (0, _path2.resolve)(_this4.config.componentsRoot, key);
var _component = _this4.useModule(_path, entry.config);
_component.imports = entry.imports || _component.ComponentClass.imports || [];
});
}
}, {
key: '_addComponent',
value: function _addComponent(ComponentClass, config) {
var id = config.id || ComponentClass.id || ComponentClass.name;
(0, _assert2['default'])(!this._components.has(id), 'component with id \'' + id + '\' already registered');
var _entry = _Object$assign({}, { id: id, ComponentClass: ComponentClass, config: config });
this._components.set(id, _entry);
this.debug('component added: ' + JSON.stringify({
id: id, name: ComponentClass.name
}));
return _entry;
}
}, {
key: '_instantiateComponents',
value: function _instantiateComponents() {
var _this5 = this;
var _entries = [].concat(_toConsumableArray(this._components.keys())).map(function (id) {
return [id, _this5._createComponentInstance(id)];
});
this._components = new _Map(_entries);
}
}, {
key: '_createComponentInstance',
value: function _createComponentInstance(id) {
this.debug('creating instance of [' + id + ']');
var entry = this._components.get(id);
var instance = new entry.ComponentClass(id, this.bus);
return _Object$assign({}, entry, { instance: instance });
}
}, {
key: '_getImportsFor',
value: function _getImportsFor(_ref4) {
var id = _ref4.id;
var ComponentClass = _ref4.ComponentClass;
var _ref4$imports = _ref4.imports;
var imports = _ref4$imports === undefined ? ComponentClass.imports || [] : _ref4$imports;
return (function () {
var _this6 = this;
this.debug('getting imports of ' + id + ': [' + imports.join(', ') + ']');
return imports.map(function (importedID) {
var importEntry = _this6._components.get(importedID);
(0, _assert2['default'])(importEntry, 'Component \'' + importedID + '\' required by \'' + id + '\' is not registered');
return importEntry;
});
}).apply(this, arguments);
}
}, {
key: '_initializeComponents',
value: function _initializeComponents() {
var _this7 = this;
[].concat(_toConsumableArray(this._components.values())).forEach(function (entry) {
entry.imports = _this7._getImportsFor(entry);
});
var _promises = [].concat(_toConsumableArray(this._components.values())).map(function (_ref5) {
var instance = _ref5.instance;
var config = _ref5.config;
var imports = _ref5.imports;
var normalizedImports = _this7._normalizeImports(imports);
return instance.onInit.call(instance, config, normalizedImports);
});
return _Promise.all(_promises);
}
}, {
key: '_normalizeImports',
value: function _normalizeImports(imports) {
var _this8 = this;
return imports.map(function (importEntry) {
return _defineProperty({}, importEntry.id, _this8._getExportsFor(importEntry));
}).reduce(Functors.reduce, {});
}
}, {
key: '_getExportsFor',
value: function _getExportsFor(_ref6) {
var ComponentClass = _ref6.ComponentClass;
var instance = _ref6.instance;
this.debug('getting imports for [' + instance.id + ']');
var exports = ComponentClass.exports || [];
return exports.map(function (fnName) {
return _defineProperty({}, fnName, instance[fnName].bind(instance));
}).reduce(Functors.reduce, {});
}
}, {
key: '_forEachComponent',
value: function _forEachComponent(fnName, comparator) {
for (var _len = arguments.length, args = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
args[_key - 2] = arguments[_key];
}
var components = [].concat(_toConsumableArray(this._components.values()));
return components.sort(comparator).map(function (_ref7) {
var instance = _ref7.instance;
return instance;
}).reduce(function (prev, cur) {
return prev.then(function () {
var _cur$fnName;
return (_cur$fnName = cur[fnName]).call.apply(_cur$fnName, [cur].concat(args));
});
}, _Promise.resolve());
}
}, {
key: '_setStatus',
value: function _setStatus(status, data) {
return this.bus.emit('app.' + status, data);
}
}]);
return Application;
})();
exports['default'] = Application.create;
module.exports = exports['default'];
//# sourceMappingURL=application.js.map