From c4ad7eeaffbda2bc968e53e9d20c147036d14607 Mon Sep 17 00:00:00 2001 From: Ilya Frolov Date: Tue, 19 Sep 2017 12:37:07 +0300 Subject: [PATCH 01/26] bug: non-js-file is tried to load --- core/application/ControllersBuilder.js | 16 +++++++++++----- .../controllers/protected/controllers/.gitignore | 1 + 2 files changed, 12 insertions(+), 5 deletions(-) create mode 100644 examples/controllers/protected/controllers/.gitignore diff --git a/core/application/ControllersBuilder.js b/core/application/ControllersBuilder.js index a82f3d4..b84a36c 100644 --- a/core/application/ControllersBuilder.js +++ b/core/application/ControllersBuilder.js @@ -162,12 +162,18 @@ ControllersBuilder.prototype._read_controllers = function _read_controllers(main list.forEach(function(file_name) { if(is_directory(directory_path, file_name)) { regularized.directories.push(file_name); - } else if(FIRST_LOADED_FILE === pathWithoutExtension(Path.basename(file_name))) { - regularized.start = file_name; - } else if(LAST_LOADED_FILE === pathWithoutExtension(Path.basename(file_name))) { - regularized.end = file_name; } else { - regularized.files.push(file_name); + if(Path.extname(file_name) !== '.js') { + return; + } + + if(FIRST_LOADED_FILE === pathWithoutExtension(Path.basename(file_name))) { + regularized.start = file_name; + } else if(LAST_LOADED_FILE === pathWithoutExtension(Path.basename(file_name))) { + regularized.end = file_name; + } else { + regularized.files.push(file_name); + } } }); diff --git a/examples/controllers/protected/controllers/.gitignore b/examples/controllers/protected/controllers/.gitignore new file mode 100644 index 0000000..7857f00 --- /dev/null +++ b/examples/controllers/protected/controllers/.gitignore @@ -0,0 +1 @@ +some-non-js.file From 931d27d7b6e3977d956905b234acb72ce904b7e6 Mon Sep 17 00:00:00 2001 From: Ilya Frolov Date: Tue, 19 Sep 2017 12:42:29 +0300 Subject: [PATCH 02/26] v1.7.1 release notes: Bugs: - Non-JS-files are tried to load in controllers folder --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1ab757c..a5f56cb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ifnode", - "version": "1.7.0", + "version": "1.7.1", "description": "Node.js MVC Framework", "main": "index.js", "scripts": { From 5a839bd62aaab7cdf2b0483195124b2022a358ad Mon Sep 17 00:00:00 2001 From: Ilya Frolov Date: Mon, 2 Oct 2017 16:43:07 +0300 Subject: [PATCH 03/26] bug: plugin components aren't loaded before rest --- core/Application.js | 45 ++++++++------ core/application/ComponentsBuilder.js | 58 ++++++++++++++----- .../components/ApplicationComponent.js | 28 +++++++++ .../internal-component-class/index.js | 23 ++++++++ .../extensions/internal-component/index.js | 4 ++ package.json | 9 ++- test/plugins.js | 30 +++++++++- 7 files changed, 160 insertions(+), 37 deletions(-) create mode 100644 examples/plugins/protected/components/ApplicationComponent.js create mode 100644 examples/plugins/protected/extensions/internal-component-class/index.js diff --git a/core/Application.js b/core/Application.js index 39ccf72..6e422b3 100644 --- a/core/Application.js +++ b/core/Application.js @@ -53,7 +53,6 @@ function Application(options) { IFNodeVirtualSchema ]; this._models_builder = null; - this._components_builder = null; this._controllers_builder = null; this.id = UUID.v4(); @@ -63,7 +62,7 @@ function Application(options) { this.config = this._initialize_config(options.env || options.environment); deepFreeze(this.config); - + this.listener = this._initialize_listener(); this.connection = this._initialize_connection_server(); @@ -75,6 +74,7 @@ function Application(options) { this.models = {}; this.components = {}; + this._components_builder = new ComponentsBuilder(this.components, this.config.components); this.controllers = {}; } @@ -133,7 +133,7 @@ Application.prototype.load = function() { this.models = this._initialize_models(); Object.freeze(this.models); - this.components = this._initialize_components(); + this._initialize_components(); // Object.freeze(this.components); this.controllers = this._initialize_controllers(); @@ -151,7 +151,7 @@ Application.prototype.load = function() { */ Application.prototype.extension = function(id) { var cache = this._extensions_cache; - + if(!(id in cache)) { var custom_folder = this.config.application.folders.extensions; var custom_full_path = Path.resolve(this._project_folder, custom_folder); @@ -184,14 +184,21 @@ Application.prototype.component = function(id) { } var full_path = Path.resolve(this.config.application.folders.components, id); + var components_configs = this.config.components; + var components_builder = this._components_builder; + var component = components_builder.read_and_build_component(full_path, { + name: id, + config: (components_configs && components_configs[id]) || {} + }); - if(!(full_path in this.components)) { - this.components[full_path] = this._components_builder.read_and_build_component(full_path, { - name: id - }); + components_builder.compile(this, full_path); + + if (!this.components[id]) { + components_builder.save_component(component, id); + components_builder.components_compiled[id] = true; } - return this.components[full_path]; + return component; }; /** @@ -214,7 +221,7 @@ Application.prototype.Component = function(custom_component_config) { var builder = this._components_builder; return builder.make( - builder.build_component_config(custom_component_config, this.config.components) + builder.build_component_config(custom_component_config) ); }; @@ -374,7 +381,8 @@ Application.prototype._initialize_models = function _initialize_models() { * @private */ Application.prototype._initialize_components = function _initialize_components() { - var components_builder = this._components_builder = new ComponentsBuilder; + var self = this; + var components_builder = this._components_builder; var Component = this.Component.bind(this); var modules = this._modules; @@ -382,12 +390,16 @@ Application.prototype._initialize_components = function _initialize_components() var module = modules[i][PLUGIN_TYPES.COMPONENT]; if(module) { - module(this, Component); + var component = module(this, Component); + + if(component) { + components_builder.build_component(component, {}); + } + + components_builder.compile(this); } } - var components_config = this.config.components; - Diread({ src: this.config.application.folders.components, directories: true, @@ -402,8 +414,9 @@ Application.prototype._initialize_components = function _initialize_components() try { components_builder.read_and_build_component( component_path, - components_builder.build_component_config({}, components_config) + components_builder.build_component_config() ); + components_builder.compile(self, component_path); } catch(error) { /** * Errors inside component will not catch by this handle @@ -418,8 +431,6 @@ Application.prototype._initialize_components = function _initialize_components() } } }); - - return components_builder.compile(this); }; /** diff --git a/core/application/ComponentsBuilder.js b/core/application/ComponentsBuilder.js index 514bd1e..e0fe783 100644 --- a/core/application/ComponentsBuilder.js +++ b/core/application/ComponentsBuilder.js @@ -11,25 +11,30 @@ var Component = require('./../Component'); /** * * @class ComponentsBuilder + * + * @param {Object} components + * @param {Object} [components_configs] */ -function ComponentsBuilder() { +function ComponentsBuilder(components, components_configs) { /** * * @type {Object.} */ - this.components = {}; + this.components = components; + this.components_compiled = {}; + + this._components_configs = components_configs || {}; this._autoformed_config = null; } /** * - * @param {Object} custom_config - * @param {Object} [components_configs] + * @param {Object} [custom_config] * @returns {Object} */ -ComponentsBuilder.prototype.build_component_config = function build_component_config(custom_config, components_configs) { +ComponentsBuilder.prototype.build_component_config = function build_component_config(custom_config) { custom_config = _defaults(custom_config || {}, this._autoformed_config); - custom_config.config = (components_configs && components_configs[custom_config.name]) || {}; + custom_config.config = (this._components_configs[custom_config.name]) || {}; return custom_config; }; @@ -49,14 +54,22 @@ ComponentsBuilder.prototype.build_and_memorize_config = function build_and_memor return this._autoformed_config; }; + /** * * @param {string} component_path * @param {Object} component_config */ ComponentsBuilder.prototype.read_and_build_component = function read_and_build_component(component_path, component_config) { - var component = require(component_path); + return this.build_component(require(component_path), component_config); +}; +/** + * + * @param {*} component + * @param {Object} component_config + */ +ComponentsBuilder.prototype.build_component = function build_component(component, component_config) { if(typeof component === 'function' && isInheritsFrom(component, Component)) { var component_name = component_config.name; var saved_component = this.components[component_name]; @@ -69,11 +82,13 @@ ComponentsBuilder.prototype.read_and_build_component = function read_and_build_c return saved_component; } - component = new component(component_config); + component_config.name = component_config.name || component.name; + + component = new component(_defaults(component_config, this._components_configs[component_config.name])); } return component instanceof Component ? - this._save_component(component, component.name) : + this.save_component(component, component.name) : component; }; @@ -83,32 +98,41 @@ ComponentsBuilder.prototype.read_and_build_component = function read_and_build_c * @returns {Component} */ ComponentsBuilder.prototype.make = function make(component_config) { - return this._save_component(new Component(component_config), component_config.name); + return this.save_component(new Component(component_config), component_config.name); }; /** * * @param {Application} app + * @param {string} [component_path] * @returns {Object.} */ -ComponentsBuilder.prototype.compile = function compile(app) { +ComponentsBuilder.prototype.compile = function compile(app, component_path) { var self = this; var components = this.components; + var components_compiled = this.components_compiled; Object.keys(components).forEach(function(unique_name) { + if (components_compiled[unique_name]) { + return; + } + if(unique_name in app) { Log.error('application', 'Alias [' + unique_name + '] already busy in application instance.'); } var component = components[unique_name]; - app[unique_name] = component; if(component.initialize) { component.initialize(component.config); } + components_compiled[unique_name] = true; + app[unique_name] = component; + component.alias.forEach(function(alias) { - self._save_component(component, alias); + self.save_component(component, alias); + components_compiled[alias] = true; if(alias in app) { Log.error('application', 'Alias [' + alias + '] already busy in application instance.'); @@ -116,6 +140,11 @@ ComponentsBuilder.prototype.compile = function compile(app) { app[alias] = component; }); + + if(component_path) { + self.save_component(component, component_path); + components_compiled[component_path] = true; + } }); return components; @@ -123,12 +152,11 @@ ComponentsBuilder.prototype.compile = function compile(app) { /** * - * @private * @param {Component} component * @param {string} key * @returns {Component} */ -ComponentsBuilder.prototype._save_component = function(component, key) { +ComponentsBuilder.prototype.save_component = function(component, key) { var saved_component = this.components[key]; if(!saved_component) { diff --git a/examples/plugins/protected/components/ApplicationComponent.js b/examples/plugins/protected/components/ApplicationComponent.js new file mode 100644 index 0000000..a2695b8 --- /dev/null +++ b/examples/plugins/protected/components/ApplicationComponent.js @@ -0,0 +1,28 @@ +'use strict'; + +var Util = require('util'); +var Component = require('./../../../../core/Component'); +/** + * + * @type {Application} + */ +var app = require('./../../../..')('plugins'); +var PluginComponent = app.component('PluginComponent'); + +/** + * + * @class + * @extends Component + * + * @param options + */ +function ApplicationComponent(options) { + Component.call(this, options); +} +Util.inherits(ApplicationComponent, Component); + +ApplicationComponent.prototype.get_plugin_component = function() { + return PluginComponent; +}; + +module.exports = ApplicationComponent; diff --git a/examples/plugins/protected/extensions/internal-component-class/index.js b/examples/plugins/protected/extensions/internal-component-class/index.js new file mode 100644 index 0000000..4369abc --- /dev/null +++ b/examples/plugins/protected/extensions/internal-component-class/index.js @@ -0,0 +1,23 @@ +var _defaults = require('lodash/defaults'); +var Util = require('util'); +var Component = require('../../../../../core/Component'); + +module.exports = { + component: function() { + /** + * + * @class + * @extends Component + * + * @param {Object} options + */ + function PluginClassComponent(options) { + Component.call(this, _defaults(options, { + alias: 'plugin_class_component' + })); + } + Util.inherits(PluginClassComponent, Component); + + return PluginClassComponent; + } +}; diff --git a/examples/plugins/protected/extensions/internal-component/index.js b/examples/plugins/protected/extensions/internal-component/index.js index 0d955c1..6207501 100644 --- a/examples/plugins/protected/extensions/internal-component/index.js +++ b/examples/plugins/protected/extensions/internal-component/index.js @@ -1,4 +1,8 @@ module.exports = { component: function(app, Component) { + Component({ + name: 'PluginComponent', + alias: 'plugin_component' + }); } }; diff --git a/package.json b/package.json index a5f56cb..2f91c24 100644 --- a/package.json +++ b/package.json @@ -19,9 +19,9 @@ "author": "Ilya Frolov ", "license": "MIT", "dependencies": { - "debug": "3.0.0", + "debug": "3.1.0", "diread": "0.2.0", - "express": "4.15.4", + "express": "4.16.1", "lodash": "4.17.4", "uuid": "3.1.0" }, @@ -35,6 +35,9 @@ "serve-static": "1.12.4", "setprototypeof": "1.0.3", "should": "11.2.1", - "supertest": "3.0.0" + "supertest": "2.0.1" + }, + "engines": { + "node": ">=0.10.0" } } diff --git a/test/plugins.js b/test/plugins.js index 2f274db..639061d 100644 --- a/test/plugins.js +++ b/test/plugins.js @@ -2,8 +2,9 @@ require('should'); var Path = require('path'); +var Should = require('should'); var SuperTest = require('supertest'); -var IFNode = require('../'); +var IFNode = require('..'); var app = IFNode({ project_folder: Path.resolve(__dirname, '../examples/plugins'), @@ -11,6 +12,8 @@ var app = IFNode({ }); app.register([ + 'internal-component', + 'internal-component-class', 'defined-controller-plugin' ]); app.load(); @@ -20,5 +23,28 @@ describe('Plugins', function() { SuperTest(app.listener) .get('/defined-controller-plugin') .expect('controller_plugin_option', done); - }) + }); + + it('should has loaded plugin component', function() { + Should.ok( + app.component('PluginComponent') === app.PluginComponent && + app.PluginComponent === app.component('plugin_component') && + app.plugin_component === app.component('PluginComponent') + ); + }); + + it('should has loaded plugin class component', function() { + Should.ok( + app.component('PluginClassComponent') === app.plugin_class_component && + app.PluginClassComponent === app.component('plugin_class_component') && + app.plugin_class_component === app.component('PluginClassComponent') + ); + }); + + it('should load and compile components before application ones', function() { + Should.equal( + app.component('ApplicationComponent').get_plugin_component(), + app.component('PluginComponent') + ); + }); }); From 722ab85a89d52458746827b00bdc8a542a28b4aa Mon Sep 17 00:00:00 2001 From: Ilya Frolov Date: Tue, 3 Oct 2017 16:51:16 +0300 Subject: [PATCH 04/26] v1.7.2 release notes: Bugs: - Plugin components aren't loaded before rest --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2f91c24..b4f8c93 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ifnode", - "version": "1.7.1", + "version": "1.7.2", "description": "Node.js MVC Framework", "main": "index.js", "scripts": { From a4f1ee31c11e179ef7f4624810ab06798aef0cdc Mon Sep 17 00:00:00 2001 From: Ilya Frolov Date: Thu, 23 Nov 2017 15:39:54 +0300 Subject: [PATCH 05/26] bug: incorrect "this" statement for configuration's db "config" method --- core/application/DAOList.js | 6 +++++- examples/models_custom_schema/config/custom-schema.js | 4 ++++ .../protected/extensions/custom-schema.js | 3 ++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/core/application/DAOList.js b/core/application/DAOList.js index 385608b..ae8493a 100644 --- a/core/application/DAOList.js +++ b/core/application/DAOList.js @@ -64,7 +64,11 @@ DAOList.prototype._initialize_schemas = function _initialize_schemas(db) { } if(schema_driver.driver) { - var driver = schema_driver.driver(db_config.config); + var config = db_config.config; + var driver = schema_driver.driver(typeof config === 'function' ? + config.bind(db_config) : + config + ); if(driver) { schema_driver.fn._driver = driver; diff --git a/examples/models_custom_schema/config/custom-schema.js b/examples/models_custom_schema/config/custom-schema.js index ef044eb..736dafd 100644 --- a/examples/models_custom_schema/config/custom-schema.js +++ b/examples/models_custom_schema/config/custom-schema.js @@ -2,6 +2,10 @@ module.exports = { db: { first_database: { schema: 'custom-schema', + correct_this_test: function correct_this_test() {}, + config: function config() { + return this.correct_this_test(); + }, default: true }, second_database: { diff --git a/examples/models_custom_schema/protected/extensions/custom-schema.js b/examples/models_custom_schema/protected/extensions/custom-schema.js index f71764d..473d32d 100644 --- a/examples/models_custom_schema/protected/extensions/custom-schema.js +++ b/examples/models_custom_schema/protected/extensions/custom-schema.js @@ -2,7 +2,8 @@ var SCHEMA = require('./../../../../core/PLUGIN_TYPES').SCHEMA; module.exports[SCHEMA] = function(app, CustomSchema) { CustomSchema.schema = 'custom-schema'; - CustomSchema.driver = function driver() { + CustomSchema.driver = function driver(config) { + config(); return {}; }; From 496231d4b53aecbcebe16c4c714f22d7d33c91a1 Mon Sep 17 00:00:00 2001 From: Ilya Frolov Date: Thu, 23 Nov 2017 16:09:12 +0300 Subject: [PATCH 06/26] v1.7.3 release notes: Bugs: - Incorrect "this" statement for configuration's db "config" method --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b4f8c93..ffbe541 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ifnode", - "version": "1.7.2", + "version": "1.7.3", "description": "Node.js MVC Framework", "main": "index.js", "scripts": { From a9bd549bb72228c7de9538ff642984fa895294a8 Mon Sep 17 00:00:00 2001 From: Ilya Frolov Date: Thu, 21 Dec 2017 19:09:18 +0300 Subject: [PATCH 07/26] bug: plugin components configuration incorrectly pass into plugin --- core/application/ComponentsBuilder.js | 6 +++++- examples/plugins/config/plugins.js | 10 ++++++++++ test/plugins.js | 8 +++++++- 3 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 examples/plugins/config/plugins.js diff --git a/core/application/ComponentsBuilder.js b/core/application/ComponentsBuilder.js index e0fe783..12c6a9f 100644 --- a/core/application/ComponentsBuilder.js +++ b/core/application/ComponentsBuilder.js @@ -83,8 +83,12 @@ ComponentsBuilder.prototype.build_component = function build_component(component } component_config.name = component_config.name || component.name; + component_config.config = _defaults( + component_config.config || {}, + (this._components_configs[component_config.name]) + ); - component = new component(_defaults(component_config, this._components_configs[component_config.name])); + component = new component(component_config); } return component instanceof Component ? diff --git a/examples/plugins/config/plugins.js b/examples/plugins/config/plugins.js new file mode 100644 index 0000000..bb46a1a --- /dev/null +++ b/examples/plugins/config/plugins.js @@ -0,0 +1,10 @@ +module.exports = { + components: { + PluginComponent: { + key1: 'value1' + }, + PluginClassComponent: { + key2: 'value2' + } + } +}; diff --git a/test/plugins.js b/test/plugins.js index 639061d..ac0b4c6 100644 --- a/test/plugins.js +++ b/test/plugins.js @@ -8,7 +8,8 @@ var IFNode = require('..'); var app = IFNode({ project_folder: Path.resolve(__dirname, '../examples/plugins'), - alias: 'plugins' + alias: 'plugins', + environment: 'plugins' }); app.register([ @@ -47,4 +48,9 @@ describe('Plugins', function() { app.component('PluginComponent') ); }); + + it('should has correct configurations for plugins', function() { + Should.deepEqual(app.component('PluginComponent').config, app.config.components.PluginComponent); + Should.deepEqual(app.component('PluginClassComponent').config, app.config.components.PluginClassComponent); + }); }); From 29f085171712a8db164d729e036868c0286d77ee Mon Sep 17 00:00:00 2001 From: Ilya Frolov Date: Thu, 21 Dec 2017 19:18:56 +0300 Subject: [PATCH 08/26] v1.7.4 release notes: Bugs: - Plugin components configuration incorrectly pass into plugin --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ffbe541..a1de11f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ifnode", - "version": "1.7.3", + "version": "1.7.4", "description": "Node.js MVC Framework", "main": "index.js", "scripts": { From 850e24ab6f3574e1a0180abf8a0f2aa9b9356ac2 Mon Sep 17 00:00:00 2001 From: Ilya Frolov Date: Fri, 22 Dec 2017 22:34:35 +0300 Subject: [PATCH 09/26] general: added special marker for Component class for checking ones from different IFNode modules --- core/Component.js | 46 +++++++++++++++++-- core/application/ComponentsBuilder.js | 7 ++- core/helper/isIFNodeItem.js | 24 ++++++++++ core/helper/isIFNodeItemInstance.js | 19 ++++++++ .../protected/components/ClassES5.js | 24 +++++----- .../components/ClassES5DoubleInherits.js | 21 +++++++++ .../Component.js | 26 +++++++++++ .../index.js | 28 +++++++++++ .../index.js | 32 +++++++++++++ package.json | 1 - test/03. components.js | 6 +++ test/plugins.js | 15 ++++++ 12 files changed, 229 insertions(+), 20 deletions(-) create mode 100644 core/helper/isIFNodeItem.js create mode 100644 core/helper/isIFNodeItemInstance.js create mode 100644 examples/components/protected/components/ClassES5DoubleInherits.js create mode 100644 examples/plugins/protected/extensions/internal-another-ifnode-component-class/Component.js create mode 100644 examples/plugins/protected/extensions/internal-another-ifnode-component-class/index.js create mode 100644 examples/plugins/protected/extensions/internal-another-ifnode-component-es6-class/index.js diff --git a/core/Component.js b/core/Component.js index 2f312f1..55c6a28 100644 --- a/core/Component.js +++ b/core/Component.js @@ -1,16 +1,26 @@ 'use strict'; var UUID = require('uuid'); +var _isInheritsFrom = require('./helper/isInheritsFrom'); +var isIFNodeItem = require('./helper/isIFNodeItem'); +var isIFNodeItemInstance = require('./helper/isIFNodeItemInstance'); var toArray = require('./helper/toArray'); +var PLUGIN_TYPES = require('./PLUGIN_TYPES'); + +/** + * + * @typedef {Object} ComponentOptions + * + * @property {string} [name] + * @property {Array.} [alias=[]] + * @property {Object} [config={}] + */ /** * * @class Component * - * @param {Object} [options={}] - * @param {string} [options.name] - * @param {Array.} [options.alias=[]] - * @param {Object} [options.config={}] + * @param {ComponentOptions} [options={}] */ function Component(options) { options = options || {}; @@ -21,4 +31,32 @@ function Component(options) { this.alias = toArray(options.alias); } +Object.defineProperties(Component, { + __IFNODE_ITEM: { + value: PLUGIN_TYPES.COMPONENT + }, + + isInstanceOf: { + /** + * + * @param {*} object + * @returns {boolean} + */ + value: function isInstanceOf(object) { + return !!object && (object instanceof this || isIFNodeItemInstance(object, this.__IFNODE_ITEM)); + } + }, + + isInheritsFrom: { + /** + * + * @param {*} Base + * @returns {boolean} + */ + value: function isInheritsFrom(Base) { + return _isInheritsFrom(Base, this) || isIFNodeItem(Base, this.__IFNODE_ITEM); + } + } +}); + module.exports = Component; diff --git a/core/application/ComponentsBuilder.js b/core/application/ComponentsBuilder.js index 12c6a9f..3ca2f16 100644 --- a/core/application/ComponentsBuilder.js +++ b/core/application/ComponentsBuilder.js @@ -2,7 +2,6 @@ var _defaults = require('lodash/defaults'); var Path = require('path'); -var isInheritsFrom = require('./../helper/isInheritsFrom'); var pathWithoutExtension = require('./../helper/pathWithoutExtension'); var Log = require('./../Log'); @@ -70,7 +69,7 @@ ComponentsBuilder.prototype.read_and_build_component = function read_and_build_c * @param {Object} component_config */ ComponentsBuilder.prototype.build_component = function build_component(component, component_config) { - if(typeof component === 'function' && isInheritsFrom(component, Component)) { + if(typeof component === 'function' && Component.isInheritsFrom(component)) { var component_name = component_config.name; var saved_component = this.components[component_name]; @@ -91,14 +90,14 @@ ComponentsBuilder.prototype.build_component = function build_component(component component = new component(component_config); } - return component instanceof Component ? + return Component.isInstanceOf(component) ? this.save_component(component, component.name) : component; }; /** * - * @param {Object} component_config + * @param {ComponentOptions} component_config * @returns {Component} */ ComponentsBuilder.prototype.make = function make(component_config) { diff --git a/core/helper/isIFNodeItem.js b/core/helper/isIFNodeItem.js new file mode 100644 index 0000000..2e22cf1 --- /dev/null +++ b/core/helper/isIFNodeItem.js @@ -0,0 +1,24 @@ +/** + * + * @param {Function} Base + * @param {string} item_type + * @returns {boolean} + */ +function isIFNodeItem(Base, item_type) { + if (Base.__IFNODE_ITEM === item_type) { + return true; + } + + /** + * Check of "util.inherits" classes + */ + for (var proto = Base.super_; proto; proto = proto.super_) { + if (proto.__IFNODE_ITEM === item_type) { + return true; + } + } + + return false; +} + +module.exports = isIFNodeItem; diff --git a/core/helper/isIFNodeItemInstance.js b/core/helper/isIFNodeItemInstance.js new file mode 100644 index 0000000..48a4473 --- /dev/null +++ b/core/helper/isIFNodeItemInstance.js @@ -0,0 +1,19 @@ +var isIFNodeItem = require('./isIFNodeItem'); + +/** + * + * @param {Object} object + * @param {string} item_type + * @returns {boolean} + */ +function isIFNodeItemInstance(object, item_type) { + for (var constructor = object.constructor; constructor !== Function; constructor = constructor.constructor) { + if (isIFNodeItem(constructor, item_type)) { + return true; + } + } + + return false; +} + +module.exports = isIFNodeItemInstance; diff --git a/examples/components/protected/components/ClassES5.js b/examples/components/protected/components/ClassES5.js index c38d60f..f4b3652 100644 --- a/examples/components/protected/components/ClassES5.js +++ b/examples/components/protected/components/ClassES5.js @@ -1,7 +1,5 @@ 'use strict'; -var setPrototypeOf = require('setprototypeof'); - /** * * @class @@ -10,17 +8,21 @@ function ClassES5() { this._plain = 'ClassES5#plain'; } -var ClassES5Statics = { - STATIC: 'ClassES5.STATIC', - getStatic: function() { - return this.STATIC; +Object.defineProperties(ClassES5, { + STATIC: { + value: 'ClassES5.STATIC' + }, + getStatic: { + value: function() { + return this.STATIC; + } }, - getName: function() { - return this.name; + getName: { + value: function() { + return this.name; + } } -}; - -setPrototypeOf(ClassES5, ClassES5Statics); +}); ClassES5.prototype.plain = function() { return this._plain; diff --git a/examples/components/protected/components/ClassES5DoubleInherits.js b/examples/components/protected/components/ClassES5DoubleInherits.js new file mode 100644 index 0000000..bbdfa35 --- /dev/null +++ b/examples/components/protected/components/ClassES5DoubleInherits.js @@ -0,0 +1,21 @@ +'use strict'; +var Util = require('util'); +var ClassES5 = require('./ClassES5'); + +/** + * + * @class + */ +function ClassES5DoubleInherits() { + ClassES5.call(this); + + this._plain2 = 'ClassES5DoubleInherits#plain'; +} + +Util.inherits(ClassES5DoubleInherits, ClassES5); + +ClassES5DoubleInherits.prototype.plain2 = function() { + return this._plain2; +}; + +module.exports = ClassES5DoubleInherits; diff --git a/examples/plugins/protected/extensions/internal-another-ifnode-component-class/Component.js b/examples/plugins/protected/extensions/internal-another-ifnode-component-class/Component.js new file mode 100644 index 0000000..41ecbc5 --- /dev/null +++ b/examples/plugins/protected/extensions/internal-another-ifnode-component-class/Component.js @@ -0,0 +1,26 @@ +'use strict'; + +var UUID = require('uuid'); +var toArray = require('./../../../../../core/helper/toArray'); +var PLUGIN_TYPES = require('./../../../../../core/PLUGIN_TYPES'); + +/** + * + * @class Component + * + * @param {ComponentOptions} options + */ +function Component(options) { + this.id = UUID.v4(); + this.name = options.name; + this.config = options.config; + this.alias = toArray(options.alias); +} + +Object.defineProperties(Component, { + __IFNODE_ITEM: { + value: PLUGIN_TYPES.COMPONENT + } +}); + +module.exports = Component; diff --git a/examples/plugins/protected/extensions/internal-another-ifnode-component-class/index.js b/examples/plugins/protected/extensions/internal-another-ifnode-component-class/index.js new file mode 100644 index 0000000..18703ec --- /dev/null +++ b/examples/plugins/protected/extensions/internal-another-ifnode-component-class/index.js @@ -0,0 +1,28 @@ +var _defaults = require('lodash/defaults'); +var Util = require('util'); +var PLUGIN_TYPES = require('./../../../../../core/PLUGIN_TYPES'); + +/** + * Copy of "core/Component". Simulates situation when component inherits another ifnode's Component class + * + * @class {Component} + */ +var Component = require('./Component'); + +module.exports[PLUGIN_TYPES.COMPONENT] = function() { + /** + * + * @class + * @extends Component + * + * @param {Object} options + */ + function PluginClassAnotherIFNodeComponent(options) { + Component.call(this, _defaults(options, { + alias: 'plugin_class_another_ifnode_component' + })); + } + Util.inherits(PluginClassAnotherIFNodeComponent, Component); + + return PluginClassAnotherIFNodeComponent; +}; diff --git a/examples/plugins/protected/extensions/internal-another-ifnode-component-es6-class/index.js b/examples/plugins/protected/extensions/internal-another-ifnode-component-es6-class/index.js new file mode 100644 index 0000000..c378f14 --- /dev/null +++ b/examples/plugins/protected/extensions/internal-another-ifnode-component-es6-class/index.js @@ -0,0 +1,32 @@ +var _defaults = require('lodash/defaults'); +var Util = require('util'); +var PLUGIN_TYPES = require('./../../../../../core/PLUGIN_TYPES'); +var Component = require('./../internal-another-ifnode-component-class/Component'); + +module.exports[PLUGIN_TYPES.COMPONENT] = function() { + /** + * + * @class + * @extends Component + * + * @param {Object} options + */ + function PluginES6ClassAnotherIFNodeComponent(options) { + Component.call(this, _defaults(options, { + alias: 'plugin_es6_class_another_ifnode_component' + })); + } + Util.inherits(PluginES6ClassAnotherIFNodeComponent, Component); + + /** + * Simulates access of static Component's member accessing + * + * @private + * @type {string} + */ + Object.defineProperty(PluginES6ClassAnotherIFNodeComponent, '__IFNODE_ITEM', { + value: PLUGIN_TYPES.COMPONENT + }); + + return PluginES6ClassAnotherIFNodeComponent; +}; diff --git a/package.json b/package.json index a1de11f..d013c67 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,6 @@ "istanbul": "0.4.5", "mocha": "3.5.0", "serve-static": "1.12.4", - "setprototypeof": "1.0.3", "should": "11.2.1", "supertest": "2.0.1" }, diff --git a/test/03. components.js b/test/03. components.js index 1bf184a..20feb86 100644 --- a/test/03. components.js +++ b/test/03. components.js @@ -22,6 +22,7 @@ describe('Components', function() { app.component('component-in-folder').should.be.a.String(); app.component('ClassES5').should.be.a.Function(); + app.component('ClassES5DoubleInherits').should.be.a.Function(); app.component('ClassES5Component').should.be.not.a.Function(); }); @@ -103,6 +104,11 @@ describe('Components', function() { Should.equal(ClassES5.getName(), 'ClassES5'); Should.equal((new ClassES5).plain(), 'ClassES5#plain'); + + var ClassES5DoubleInherits = app.component('ClassES5DoubleInherits'); + + Should.equal((new ClassES5DoubleInherits).plain(), 'ClassES5#plain'); + Should.equal((new ClassES5DoubleInherits).plain2(), 'ClassES5DoubleInherits#plain'); }); it('should attach component from class', function() { diff --git a/test/plugins.js b/test/plugins.js index ac0b4c6..5b70a6a 100644 --- a/test/plugins.js +++ b/test/plugins.js @@ -15,6 +15,8 @@ var app = IFNode({ app.register([ 'internal-component', 'internal-component-class', + 'internal-another-ifnode-component-class', + 'internal-another-ifnode-component-es6-class', 'defined-controller-plugin' ]); app.load(); @@ -42,6 +44,19 @@ describe('Plugins', function() { ); }); + it('should has loaded plugin another ifnode class component', function() { + Should.ok( + app.component('PluginClassAnotherIFNodeComponent') === app.plugin_class_another_ifnode_component && + app.PluginClassAnotherIFNodeComponent === app.component('plugin_class_another_ifnode_component') && + app.plugin_class_another_ifnode_component === app.component('PluginClassAnotherIFNodeComponent') + ); + Should.ok( + app.component('PluginES6ClassAnotherIFNodeComponent') === app.plugin_es6_class_another_ifnode_component && + app.PluginES6ClassAnotherIFNodeComponent === app.component('plugin_es6_class_another_ifnode_component') && + app.plugin_es6_class_another_ifnode_component === app.component('PluginES6ClassAnotherIFNodeComponent') + ); + }); + it('should load and compile components before application ones', function() { Should.equal( app.component('ApplicationComponent').get_plugin_component(), From 6c5fe735c22e1c8c60a5fe08440b9795d8d4601b Mon Sep 17 00:00:00 2001 From: Ilya Frolov Date: Fri, 22 Dec 2017 23:12:11 +0300 Subject: [PATCH 10/26] v1.8.0 release notes: Changes: - Added special marker for Component class for checking ones from different IFNode modules --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d013c67..a4f5024 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ifnode", - "version": "1.7.4", + "version": "1.8.0", "description": "Node.js MVC Framework", "main": "index.js", "scripts": { From 464db19694147f7cee15809c5a74d32237e076fe Mon Sep 17 00:00:00 2001 From: Illia Fralou Date: Wed, 4 Apr 2018 14:10:22 +0300 Subject: [PATCH 11/26] feature: added possibility for catching errors from async handlers and pass them to "next" callback --- core/Controller.js | 31 +++++++++++++--- .../controllers-with-promised-handlers.js | 7 ++++ .../controllers_with_promised_handlers/~.js | 34 ++++++++++++++++++ package.json | 1 + test/05. controllers.js | 36 +++++++++++++++++++ 5 files changed, 105 insertions(+), 4 deletions(-) create mode 100644 examples/controllers/config/controllers-with-promised-handlers.js create mode 100644 examples/controllers/protected/controllers_with_promised_handlers/~.js diff --git a/core/Controller.js b/core/Controller.js index 3c739e3..dbfb42d 100644 --- a/core/Controller.js +++ b/core/Controller.js @@ -401,8 +401,17 @@ Controller.prototype._generate_url = function(method) { this.router[method](url, function(request, response, next_route) { eachSeries(callbacks, function(callback, next_callback, interrupt) { + /** + * + * @param {*|Error} error + */ + function unexpected_error_handler(error) { + interrupt(); + next_route(error); + } + try { - callback( + var result = callback( request, response, /** @@ -419,9 +428,23 @@ Controller.prototype._generate_url = function(method) { }, next_route ); - } catch(err) { - interrupt(); - next_route(err); + + if(result) { + var Class = result.constructor; + + /** + * Rough detection of Promise's instance + * + * @type {boolean} + */ + var is_promise = !!(Class.all && Class.race && result.then && result.catch); + + if(is_promise) { + result.catch(unexpected_error_handler); + } + } + } catch(error) { + unexpected_error_handler(error); } }, next_route); }); diff --git a/examples/controllers/config/controllers-with-promised-handlers.js b/examples/controllers/config/controllers-with-promised-handlers.js new file mode 100644 index 0000000..311c4ba --- /dev/null +++ b/examples/controllers/config/controllers-with-promised-handlers.js @@ -0,0 +1,7 @@ +module.exports = { + application: { + folders: { + controllers: 'protected/controllers_with_promised_handlers/' + } + } +}; diff --git a/examples/controllers/protected/controllers_with_promised_handlers/~.js b/examples/controllers/protected/controllers_with_promised_handlers/~.js new file mode 100644 index 0000000..6697348 --- /dev/null +++ b/examples/controllers/protected/controllers_with_promised_handlers/~.js @@ -0,0 +1,34 @@ +var Promise = require('es6-promise'); +var app = require('../../../../')('controllers-with-promised-handlers'); +var controller = app.Controller({ + name: 'with_promised_handlers', + root: '/with-promised-handlers' +}); + +controller.get('/resolved-with-returning-non-promise', function(request, response) { + Promise.resolve('resolved').then(function(data) { + response.ok(data); + }); + + return 'uninfluenced-retured-data'; +}); + +controller.get('/resolved-with-returning-promise', function(request, response) { + return Promise.resolve('resolved').then(function(data) { + response.ok(data); + }); +}); + +controller.get('/rejected-caught-by-handler', function(request, response) { + return Promise.reject('rejected').catch(function(data) { + response.error(data); + }); +}); + +controller.get('/rejected-caught-by-ifnode', function() { + return Promise.reject('rejected'); +}); + +controller.error(function(error, request, response) { + response.error('default-error-handler:' + error) +}); diff --git a/package.json b/package.json index a4f5024..781dcb0 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "devDependencies": { "body-parser": "1.17.2", "coveralls": "2.13.1", + "es6-promise": "4.2.4", "eslint": "4.5.0", "eslint-plugin-require-jsdoc": "1.0.4", "istanbul": "0.4.5", diff --git a/test/05. controllers.js b/test/05. controllers.js index f947025..c06f776 100644 --- a/test/05. controllers.js +++ b/test/05. controllers.js @@ -346,4 +346,40 @@ describe('Controllers', function() { .expect(500, done); }) }); + + describe('Controller with promised handlers', function() { + /** + * + * @type {Application} + */ + var app = IFNode({ + project_folder: Path.resolve(__dirname, '../examples/controllers'), + alias: 'controllers-with-promised-handlers', + environment: 'controllers-with-promised-handlers' + }).load(); + + it('should return "resolved" with 200 status (non-promise is returned from handler)', function(done) { + SuperTest(app.listener) + .get('/with-promised-handlers/resolved-with-returning-non-promise') + .expect(200, 'resolved', done); + }); + + it('should return "resolved" with 200 status (promise is returned from handler)', function(done) { + SuperTest(app.listener) + .get('/with-promised-handlers/resolved-with-returning-promise') + .expect(200, 'resolved', done); + }); + + it('should return "rejected" with 500 status', function(done) { + SuperTest(app.listener) + .get('/with-promised-handlers/rejected-caught-by-handler') + .expect(500, 'rejected', done); + }); + + it('should return "default-error:rejected" with 500 status by common controller\'s handler', function(done) { + SuperTest(app.listener) + .get('/with-promised-handlers/rejected-caught-by-ifnode') + .expect(500, 'default-error-handler:rejected', done); + }); + }); }); From 9fc5bb8cc394e9366a1a38b0c29354536fc40893 Mon Sep 17 00:00:00 2001 From: Illia Fralou Date: Wed, 4 Apr 2018 14:36:08 +0300 Subject: [PATCH 12/26] v1.9.0 release notes: Changes: - Feature: added possibility for catching errors from async handlers and pass them to "next" callback --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 781dcb0..84aa7c9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ifnode", - "version": "1.8.0", + "version": "1.9.0", "description": "Node.js MVC Framework", "main": "index.js", "scripts": { From 1ab4856f21c5bfe35238c764c5c826b8592a3e2c Mon Sep 17 00:00:00 2001 From: ilfroloff Date: Fri, 11 Jan 2019 16:47:18 +0300 Subject: [PATCH 13/26] general: added license file --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3d90780 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 ifnode + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From 26164b34e34d5083c208751bc88f32fa9cc44ff1 Mon Sep 17 00:00:00 2001 From: Illia Fralou Date: Thu, 15 Aug 2019 13:05:22 +0300 Subject: [PATCH 14/26] general: app.register throws with original module error --- core/Application.js | 16 ++++++++------ core/application/Extension.js | 14 +++++------- .../requireWithSkippingOfMissedModuleError.js | 22 +++++++++++++++++++ core/helper/tryCatch.js | 15 ------------- .../protected/extensions/with-syntax-error.js | 1 + test/01. application.js | 17 ++++++++++++-- 6 files changed, 53 insertions(+), 32 deletions(-) create mode 100644 core/helper/requireWithSkippingOfMissedModuleError.js delete mode 100644 core/helper/tryCatch.js create mode 100644 examples/extensions/protected/extensions/with-syntax-error.js diff --git a/core/Application.js b/core/Application.js index 6e422b3..15a3589 100644 --- a/core/Application.js +++ b/core/Application.js @@ -9,7 +9,7 @@ var Diread = require('diread'); var toArray = require('./helper/toArray'); var deepFreeze = require('./helper/deepFreeze'); var pathWithoutExtension = require('./helper/pathWithoutExtension'); -var tryCatch = require('./helper/tryCatch'); +var requireWithSkippingOfMissedModuleError = require('./helper/requireWithSkippingOfMissedModuleError'); var debug = require('debug')('ifnode:application'); // eslint-disable-line var Log = require('./Log'); @@ -465,9 +465,7 @@ Application.prototype._initialize_controllers = function _initialize_controllers Application.prototype._require_module = function(module_name) { var Module; - Module = tryCatch(function() { - return require(module_name); - }); + Module = requireWithSkippingOfMissedModuleError(module_name); if(Module) { return Module; @@ -475,9 +473,13 @@ Application.prototype._require_module = function(module_name) { var self = this; - Module = tryCatch(function() { - return self.extension(module_name); - }); + try { + Module = this.extension(module_name); + } catch (error) { + if (!/Cannot\sfind\sextension/.test(error.message)) { + throw error; + } + } if(Module) { return Module; diff --git a/core/application/Extension.js b/core/application/Extension.js index 6405322..e2ceb06 100644 --- a/core/application/Extension.js +++ b/core/application/Extension.js @@ -1,6 +1,7 @@ 'use strict'; var Path = require('path'); +var requireWithSkippingOfMissedModuleError = require('./../helper/requireWithSkippingOfMissedModuleError'); var Log = require('./../Log'); /** @@ -20,16 +21,13 @@ function Extension(start_load_point) { */ Extension.prototype.require = function(id) { var extension_path = Path.resolve(this._start_load_point, id); + var extension = requireWithSkippingOfMissedModuleError(extension_path); - try { - return require(extension_path); - } catch(error) { - if(error.message.indexOf(extension_path) === -1) { - throw error; - } else { - Log.error('extensions', 'Cannot find extension by [' + id + '].'); - } + if(!extension) { + Log.error('extensions', 'Cannot find extension by [' + id + '].'); } + + return extension; }; module.exports = Extension; diff --git a/core/helper/requireWithSkippingOfMissedModuleError.js b/core/helper/requireWithSkippingOfMissedModuleError.js new file mode 100644 index 0000000..32be692 --- /dev/null +++ b/core/helper/requireWithSkippingOfMissedModuleError.js @@ -0,0 +1,22 @@ +/** + * + * @param {function} path + * @returns {*} + * @throws {Error} + */ +function requireWithSkippingOfMissedModuleError(path) { + try { + return require(path); + } catch (error) { + if ( + error instanceof Error && + error.code === 'MODULE_NOT_FOUND' + ) { + return undefined; + } else { + throw error; + } + } +} + +module.exports = requireWithSkippingOfMissedModuleError; diff --git a/core/helper/tryCatch.js b/core/helper/tryCatch.js deleted file mode 100644 index 242bc5d..0000000 --- a/core/helper/tryCatch.js +++ /dev/null @@ -1,15 +0,0 @@ -/** - * - * @param {function} to_invoke - * @param {*} [default_result] - * @returns {*} - */ -function tryCatch(to_invoke, default_result) { - try { - return to_invoke(); - } catch (e) { - return default_result; - } -} - -module.exports = tryCatch; diff --git a/examples/extensions/protected/extensions/with-syntax-error.js b/examples/extensions/protected/extensions/with-syntax-error.js new file mode 100644 index 0000000..06b7587 --- /dev/null +++ b/examples/extensions/protected/extensions/with-syntax-error.js @@ -0,0 +1 @@ +var 123some_syntax_error; diff --git a/test/01. application.js b/test/01. application.js index e1eb1c7..86521cf 100644 --- a/test/01. application.js +++ b/test/01. application.js @@ -1,5 +1,6 @@ 'use strict'; +var assert = require('assert'); var Path = require('path'); var Should = require('should'); var IFNode = require('../'); @@ -95,9 +96,21 @@ describe('Application', function() { it('shouldn\'t load non-exists plugin', function() { var app = IFNode(); - (function() { + try { app.register('non-exists-plugin'); - }).should.throw(); + } catch (error) { + assert.ok(/Cannot\sfind\snode\smodule\sor\sextension/.test(error.message)); + } + }); + + it('should throw original error', function() { + var app = require('../examples/extensions/app'); + + try { + app.register('with-syntax-error'); + } catch (error) { + assert.ok(error instanceof SyntaxError); + } }); it('load by extension name', function(done) { From 3a154363cdd247aca9300ef15c9702dad44ef101 Mon Sep 17 00:00:00 2001 From: Illia Fralou Date: Thu, 15 Aug 2019 13:25:39 +0300 Subject: [PATCH 15/26] v1.9.1 release notes: Bugs: - app.register doesn't throw original module error --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 84aa7c9..0cd4bb4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ifnode", - "version": "1.9.0", + "version": "1.9.1", "description": "Node.js MVC Framework", "main": "index.js", "scripts": { From 70b0b102758cef9bfab7c93c563c8644d2244b88 Mon Sep 17 00:00:00 2001 From: Illia Fralou Date: Mon, 7 Oct 2019 18:17:34 +0300 Subject: [PATCH 16/26] general: added "configuration" option to Application --- core/Application.T.js | 10 +++-- core/Application.js | 19 +++++---- core/ConfigurationBuilder.js | 6 +-- core/helper/cloneDeepPrimitives.js | 23 ++++++++++ examples/config/config/local.js | 12 ++++++ test/application-configuration.js | 67 +++++++++++++++++++++++------- 6 files changed, 108 insertions(+), 29 deletions(-) create mode 100644 core/helper/cloneDeepPrimitives.js diff --git a/core/Application.T.js b/core/Application.T.js index 4153123..b5544a9 100644 --- a/core/Application.T.js +++ b/core/Application.T.js @@ -1,8 +1,10 @@ /** * @typedef {Object} ApplicationOptions * - * @property {string} [app_config.alias] - * @property {string} [app_config.project_folder] - * @property {string} [app_config.projectFolder] - * @property {string} [app_config.environment] + * @property {string} [alias] + * @property {string} [project_folder] + * @property {string} [projectFolder] + * @property {string} [env] + * @property {string} [environment] + * @property {object|string} [configuration] */ diff --git a/core/Application.js b/core/Application.js index 15a3589..fdb0bf6 100644 --- a/core/Application.js +++ b/core/Application.js @@ -60,7 +60,7 @@ function Application(options) { this.project_folder = this.projectFolder = this._project_folder; this.backend_folder = this.backendFolder = this._backend_folder; - this.config = this._initialize_config(options.env || options.environment); + this.config = this._initialize_config(options); deepFreeze(this.config); this.listener = this._initialize_listener(); @@ -270,21 +270,26 @@ Application.prototype.down = Util.deprecate(function(callback) { /** * Initialize application instance configuration * - * @param {string} environment * @private + * @param {ApplicationOptions} options */ -Application.prototype._initialize_config = function(environment) { - var config_path; +Application.prototype._initialize_config = function(options) { + var environment = options.env || options.environment; + var configuration = options.configuration; + var custom_configuration = null; if(environment) { - config_path = Path.resolve(this._project_folder, 'config/', environment); + custom_configuration = require(Path.resolve(this._project_folder, 'config/', environment)); + } else if (configuration) { + custom_configuration = typeof configuration === 'string' ? + require(Path.resolve(this._project_folder, configuration)) : + configuration; } return ConfigurationBuilder({ - environment: environment, project_folder: this._project_folder, backend_folder: this._backend_folder, - config_path: config_path + custom_configuration: custom_configuration }); }; diff --git a/core/ConfigurationBuilder.js b/core/ConfigurationBuilder.js index 8a35617..b92bbc8 100644 --- a/core/ConfigurationBuilder.js +++ b/core/ConfigurationBuilder.js @@ -6,6 +6,7 @@ var _includes = require('lodash/includes'); // var debug = require('debug')('ifnode:config'); var path = require('path'); +var _cloneDeepPrimitives = require('./helper/cloneDeepPrimitives'); /** * @@ -286,13 +287,12 @@ function initialize_default_config(options) { function ConfigurationBuilder(options) { var default_config = initialize_default_config(options); - if(!options.config_path) { + if(!options.custom_configuration) { initialize_additional_site_config(default_config); return default_config; } - var config = require(options.config_path); - // var config = Object.create(ConfigPrototype); + var config = _cloneDeepPrimitives(options.custom_configuration); initialize_properties_config(config, default_config, options.project_folder); initialize_site_config(config, default_config, options.project_folder); diff --git a/core/helper/cloneDeepPrimitives.js b/core/helper/cloneDeepPrimitives.js new file mode 100644 index 0000000..43dabbf --- /dev/null +++ b/core/helper/cloneDeepPrimitives.js @@ -0,0 +1,23 @@ +var _isPlainObject = require('lodash/isPlainObject'); + +/** + * + * @param {object} object + * @returns {object} + */ +function cloneDeepPrimitives(object) { + var cloned = {}; + var keys = Object.keys(object); + + keys.forEach(function(key) { + var value = object[key]; + + cloned[key] = _isPlainObject(value) ? + cloneDeepPrimitives(value) : + value; + }); + + return cloned; +} + +module.exports = cloneDeepPrimitives; diff --git a/examples/config/config/local.js b/examples/config/config/local.js index 155991a..3fb6b3f 100644 --- a/examples/config/config/local.js +++ b/examples/config/config/local.js @@ -1,3 +1,10 @@ +/** + * + * @class + */ +function SomeClass() { +} + module.exports = { site: { local: { @@ -23,5 +30,10 @@ module.exports = { 'testable-middleware': function() { } } + }, + + some: { + function: SomeClass, + instance: new SomeClass } }; diff --git a/test/application-configuration.js b/test/application-configuration.js index 31bca5e..df49637 100644 --- a/test/application-configuration.js +++ b/test/application-configuration.js @@ -11,28 +11,65 @@ var app = application_builder({ environment: 'local' }); -describe('Application configuration', function() { - it('should be valid configuration', function() { - var config = app.config; - var site_config = config.site; +/** + * + * @param {Application} app + */ +function validate_local_application(app) { + var config = app.config; + var site_config = config.site; + + config.should.be.an.Object(); + + config.env.should.be.equal('local'); + config.environment.should.be.equal('local'); + + site_config.local.origin.should.be.equal('https://localhost:8080'); + site_config.local.url('/some/path').should.be.equal('https://localhost:8080/some/path'); - config.should.be.an.Object(); + site_config.global.ssl.key.should.be.equal(Path.resolve(app.project_folder, 'path/to/key')); - config.env.should.be.equal('local'); - config.environment.should.be.equal('local'); + site_config.global.origin.should.be.equal('https://ifnode.com:3000'); + site_config.global.url('some/path').should.be.equal('https://ifnode.com:3000/some/path'); - site_config.local.origin.should.be.equal('https://localhost:8080'); - site_config.local.url('/some/path').should.be.equal('https://localhost:8080/some/path'); + config.application.express.should.be.an.Object(); + config.application.folders.should.be.an.Object(); - site_config.global.ssl.key.should.be.equal(Path.resolve(app.project_folder, 'path/to/key')); + config.db.virtual.schema.should.be.equal('virtual'); - site_config.global.origin.should.be.equal('https://ifnode.com:3000'); - site_config.global.url('some/path').should.be.equal('https://ifnode.com:3000/some/path'); + config.some.function.should.be.Function(); + config.some.instance.should.be.instanceOf(config.some.function); +} - config.application.express.should.be.an.Object(); - config.application.folders.should.be.an.Object(); +describe('Application configuration', function() { + it('should be valid configuration', function() { + validate_local_application(app); + }); - config.db.virtual.schema.should.be.equal('virtual'); + describe('"configuration" option', function() { + it("should be valid configuration by relative path", function() { + var app = application_builder({ + project_folder: Path.resolve(__dirname, '../examples/config'), + configuration: 'config/local' + }); + validate_local_application(app); + }); + it("should be valid configuration by full path", function() { + var project_path = Path.resolve(__dirname, '../examples/config'); + var app = application_builder({ + project_folder: project_path, + configuration: Path.resolve(project_path, './config/local') + }); + validate_local_application(app); + }); + it("should be valid configuration by object", function() { + var project_path = Path.resolve(__dirname, '../examples/config'); + var app = application_builder({ + project_folder: project_path, + configuration: require(Path.resolve(project_path, './config/local')) + }); + validate_local_application(app); + }); }); it("should be non-editable configuration", function() { From b95798cac95bdc87831910a975720d88caca6f74 Mon Sep 17 00:00:00 2001 From: Illia Fralou Date: Tue, 8 Oct 2019 12:36:39 +0300 Subject: [PATCH 17/26] v1.10.0 release notes: Changes: - General: added "configuration" option to Application --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0cd4bb4..a5c3f12 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ifnode", - "version": "1.9.1", + "version": "1.10.0", "description": "Node.js MVC Framework", "main": "index.js", "scripts": { From 82f6f007566c2c595a16fd7f44f635c9ec580f8a Mon Sep 17 00:00:00 2001 From: Illia Fralou Date: Thu, 19 Nov 2020 18:55:49 +0300 Subject: [PATCH 18/26] general: updated modules with critical issues --- package.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index a5c3f12..e03dce5 100644 --- a/package.json +++ b/package.json @@ -19,21 +19,21 @@ "author": "Ilya Frolov ", "license": "MIT", "dependencies": { - "debug": "3.1.0", + "debug": "3.2.7", "diread": "0.2.0", - "express": "4.16.1", - "lodash": "4.17.4", - "uuid": "3.1.0" + "express": "4.17.1", + "lodash": "4.17.20", + "uuid": "3.4.0" }, "devDependencies": { - "body-parser": "1.17.2", + "body-parser": "1.19.0", "coveralls": "2.13.1", "es6-promise": "4.2.4", "eslint": "4.5.0", "eslint-plugin-require-jsdoc": "1.0.4", "istanbul": "0.4.5", "mocha": "3.5.0", - "serve-static": "1.12.4", + "serve-static": "1.14.1", "should": "11.2.1", "supertest": "2.0.1" }, From afd370c36e849d68018738eb886213455778d09a Mon Sep 17 00:00:00 2001 From: Illia Fralou Date: Thu, 19 Nov 2020 19:48:34 +0300 Subject: [PATCH 19/26] v1.10.1 release notes: Changes: - General: updated modules with critical issues --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e03dce5..ae1f86b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ifnode", - "version": "1.10.0", + "version": "1.10.1", "description": "Node.js MVC Framework", "main": "index.js", "scripts": { From 403ea14cfd4dc23f0392c32889664e7187c2fb80 Mon Sep 17 00:00:00 2001 From: snyk-bot Date: Fri, 26 Feb 2021 22:56:51 +0000 Subject: [PATCH 20/26] fix: package.json to reduce vulnerabilities The following vulnerabilities are fixed with an upgrade: - https://snyk.io/vuln/SNYK-JS-LODASH-1018905 - https://snyk.io/vuln/SNYK-JS-LODASH-1040724 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ae1f86b..0c97b50 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "debug": "3.2.7", "diread": "0.2.0", "express": "4.17.1", - "lodash": "4.17.20", + "lodash": "4.17.21", "uuid": "3.4.0" }, "devDependencies": { From f1bcbc42afecb575fc06dee5563777879cd817e4 Mon Sep 17 00:00:00 2001 From: Illia Fralou Date: Wed, 3 Mar 2021 16:49:51 +0300 Subject: [PATCH 21/26] [feat] added functionality to exclude controllers by RegExp --- core/Application.js | 33 ++++++++++++++++--- core/application/ControllersBuilder.js | 11 ++++++- .../controllers/config/controllers-partial.js | 7 ++++ .../controllers_partial/api/v1/skip.js | 1 + .../controllers_partial/api/v1/user.js | 10 ++++++ test/05. controllers.js | 26 +++++++++++++++ 6 files changed, 82 insertions(+), 6 deletions(-) create mode 100644 examples/controllers/config/controllers-partial.js create mode 100644 examples/controllers/protected/controllers_partial/api/v1/skip.js create mode 100644 examples/controllers/protected/controllers_partial/api/v1/user.js diff --git a/core/Application.js b/core/Application.js index fdb0bf6..4d5696a 100644 --- a/core/Application.js +++ b/core/Application.js @@ -1,5 +1,6 @@ 'use strict'; +var _defaults = require('lodash/defaults'); var Path = require('path'); var Util = require('util'); var Express = require('express'); @@ -124,20 +125,41 @@ Application.prototype.register = function(module) { return this; }; +/** + * @typedef {Object} ModuleLoadOptions + * + * @property {RegExp} exclude + */ + +/** + * @typedef {Object} ApplicationLoadOptions + * + * @property {boolean|ModuleLoadOptions} [controllers] + */ + /** * Loads all maintenance parts of application * + * @param {ApplicationLoadOptions} [options] * @returns {Application} */ -Application.prototype.load = function() { +Application.prototype.load = function(options) { + options = _defaults(options, { + controllers: true + }); + this.models = this._initialize_models(); Object.freeze(this.models); this._initialize_components(); // Object.freeze(this.components); - this.controllers = this._initialize_controllers(); - Object.freeze(this.controllers); + if(options.controllers) { + this.controllers = options.controllers === true? + this._initialize_controllers() : + this._initialize_controllers(options.controllers); + Object.freeze(this.controllers); + } this._is_loaded = true; @@ -441,9 +463,10 @@ Application.prototype._initialize_components = function _initialize_components() /** * * @private + * @param {ModuleLoadOptions} [options] */ -Application.prototype._initialize_controllers = function _initialize_controllers() { - var controllers_builder = this._controllers_builder = new ControllersBuilder(); +Application.prototype._initialize_controllers = function _initialize_controllers(options) { + var controllers_builder = this._controllers_builder = new ControllersBuilder(options); var modules = this._modules; for(var i = 0; i < modules.length; ++i) { diff --git a/core/application/ControllersBuilder.js b/core/application/ControllersBuilder.js index b84a36c..d86b271 100644 --- a/core/application/ControllersBuilder.js +++ b/core/application/ControllersBuilder.js @@ -26,10 +26,14 @@ function is_directory(root_path, rest_path) { /** * * @class ControllersBuilder + * @param {ModuleLoadOptions} [options] */ -function ControllersBuilder() { +function ControllersBuilder(options) { this._controllers = {}; this._autoformed_config = null; + this._options = _defaults(options, { + exclude: null + }); } ControllersBuilder.FIRST_LOADED_FILE = '!'; @@ -139,6 +143,7 @@ ControllersBuilder.prototype._read_controllers = function _read_controllers(main var Class = this.constructor; var FIRST_LOADED_FILE = Class.FIRST_LOADED_FILE; var LAST_LOADED_FILE = Class.LAST_LOADED_FILE; + var self = this; /** * @@ -187,6 +192,10 @@ ControllersBuilder.prototype._read_controllers = function _read_controllers(main * @param {function} finder */ function read_file(root_path, full_file_path, finder) { + if(self._options.exclude && self._options.exclude.test(full_file_path)) { + return; + } + finder(full_file_path, full_file_path.replace(root_path, '')); } diff --git a/examples/controllers/config/controllers-partial.js b/examples/controllers/config/controllers-partial.js new file mode 100644 index 0000000..3b7c83c --- /dev/null +++ b/examples/controllers/config/controllers-partial.js @@ -0,0 +1,7 @@ +module.exports = { + application: { + folders: { + controllers: 'protected/controllers_partial/' + } + } +}; diff --git a/examples/controllers/protected/controllers_partial/api/v1/skip.js b/examples/controllers/protected/controllers_partial/api/v1/skip.js new file mode 100644 index 0000000..3cec42a --- /dev/null +++ b/examples/controllers/protected/controllers_partial/api/v1/skip.js @@ -0,0 +1 @@ +throw new Error("Shouldn't be loaded"); diff --git a/examples/controllers/protected/controllers_partial/api/v1/user.js b/examples/controllers/protected/controllers_partial/api/v1/user.js new file mode 100644 index 0000000..8a1f8cf --- /dev/null +++ b/examples/controllers/protected/controllers_partial/api/v1/user.js @@ -0,0 +1,10 @@ +/** + * + * @type {Application} + */ +var app = require('./../../../../../../')('controllers-partial-loading'); +var router = app.Controller({ + root: '/api/v1/user' +}); + +router.end(); diff --git a/test/05. controllers.js b/test/05. controllers.js index c06f776..4988811 100644 --- a/test/05. controllers.js +++ b/test/05. controllers.js @@ -43,6 +43,32 @@ describe('Controllers', function() { Should.exist(app.controllers.from_custom_folder); }); + + it('should disable controllers loading', function() { + var app = IFNode({ + project_folder: Path.resolve(__dirname, '../examples/controllers'), + alias: 'controllers-not-loaded', + environment: 'controllers-partial' + }).load({ + controllers: false + }); + + app.controllers.should.be.empty(); + }); + + it('should load controllers partially', function() { + var app = IFNode({ + project_folder: Path.resolve(__dirname, '../examples/controllers'), + alias: 'controllers-partial-loading', + environment: 'controllers-partial' + }).load({ + controllers: { + exclude: /api\/v1\/skip/ + } + }); + + Should.not.exist(app.controllers['api/v1/skip']); + }); }); describe('app.Controller(options?: Object)', function() { From 8f49b428297f9500f3d89fb34e0155003713d063 Mon Sep 17 00:00:00 2001 From: Illia Fralou Date: Wed, 3 Mar 2021 17:24:25 +0300 Subject: [PATCH 22/26] [feat] exclude examples/ and test/ folders for npm --- .npmignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.npmignore b/.npmignore index f81eb0f..048c6dd 100644 --- a/.npmignore +++ b/.npmignore @@ -4,6 +4,8 @@ **/*.DS_store coverage/ +examples/ +test/ ideas manual.txt From b18c6e310e739aeef99394a3f31e3784a8e1a0f4 Mon Sep 17 00:00:00 2001 From: Illia Fralou Date: Wed, 3 Mar 2021 17:36:55 +0300 Subject: [PATCH 23/26] [feat] added functionality to include controllers by RegExp --- core/Application.js | 3 ++- core/application/ControllersBuilder.js | 6 +++++- test/05. controllers.js | 16 +++++++++++++++- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/core/Application.js b/core/Application.js index 4d5696a..39827db 100644 --- a/core/Application.js +++ b/core/Application.js @@ -128,7 +128,8 @@ Application.prototype.register = function(module) { /** * @typedef {Object} ModuleLoadOptions * - * @property {RegExp} exclude + * @property {RegExp} [include] + * @property {RegExp} [exclude] */ /** diff --git a/core/application/ControllersBuilder.js b/core/application/ControllersBuilder.js index d86b271..298b3f0 100644 --- a/core/application/ControllersBuilder.js +++ b/core/application/ControllersBuilder.js @@ -32,6 +32,7 @@ function ControllersBuilder(options) { this._controllers = {}; this._autoformed_config = null; this._options = _defaults(options, { + include: null, exclude: null }); } @@ -192,7 +193,10 @@ ControllersBuilder.prototype._read_controllers = function _read_controllers(main * @param {function} finder */ function read_file(root_path, full_file_path, finder) { - if(self._options.exclude && self._options.exclude.test(full_file_path)) { + if( + self._options.include && !self._options.include.test(full_file_path) || + self._options.exclude && self._options.exclude.test(full_file_path) + ) { return; } diff --git a/test/05. controllers.js b/test/05. controllers.js index 4988811..2b55662 100644 --- a/test/05. controllers.js +++ b/test/05. controllers.js @@ -56,7 +56,7 @@ describe('Controllers', function() { app.controllers.should.be.empty(); }); - it('should load controllers partially', function() { + it('should load controllers partially (exclude)', function() { var app = IFNode({ project_folder: Path.resolve(__dirname, '../examples/controllers'), alias: 'controllers-partial-loading', @@ -69,6 +69,20 @@ describe('Controllers', function() { Should.not.exist(app.controllers['api/v1/skip']); }); + + it('should load controllers partially (include)', function() { + var app = IFNode({ + project_folder: Path.resolve(__dirname, '../examples/controllers'), + alias: 'controllers-partial-loading', + environment: 'controllers-partial' + }).load({ + controllers: { + include: /api\/v1\/user/ + } + }); + + Should.not.exist(app.controllers['api/v1/skip']); + }); }); describe('app.Controller(options?: Object)', function() { From d80bcb1d8dd0bf66edb104715794bc958cf854e6 Mon Sep 17 00:00:00 2001 From: Illia Fralou Date: Wed, 3 Mar 2021 17:48:52 +0300 Subject: [PATCH 24/26] v1.11.0 release notes: Changes: - [feat] added functionality to include/exclude controllers by RegExp --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ae1f86b..bd7a425 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ifnode", - "version": "1.10.1", + "version": "1.11.0", "description": "Node.js MVC Framework", "main": "index.js", "scripts": { From fcd7ad56a4fbcc1f66dee7cac8bf52fe2c17f632 Mon Sep 17 00:00:00 2001 From: Illia Fralou Date: Tue, 7 Dec 2021 17:02:35 +0300 Subject: [PATCH 25/26] general: added app.inject to inject ifnode instances --- core/Application.js | 32 +++++++++++++++++++++++++++- test/01. application.js | 47 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 1 deletion(-) diff --git a/core/Application.js b/core/Application.js index 39827db..19b5bf7 100644 --- a/core/Application.js +++ b/core/Application.js @@ -196,10 +196,40 @@ Application.prototype.ext = Util.deprecate( 'Deprecated from 2.0.0 version. Needs to use app.extension() method' ); +/** + * + * @template T + * @param {string|Function} instance + * @returns {T} + */ +Application.prototype.inject = function(instance) { + if (typeof instance === 'string') { + return this.component(instance); + } else { + var name = instance.name; + + if(name in this.components) { + return this.components[name]; + } + + var components_configs = this.config.components; + var component = new instance({ + name: name, + config: (components_configs && components_configs[name]) || {} + }, this); + + var components_builder = this._components_builder; + components_builder.save_component(component, name); + components_builder.components_compiled[name] = true; + + return component; + } +} + /** * * @param {string} id - * @returns {Object} + * @returns {Component} */ Application.prototype.component = function(id) { if(id in this.components) { diff --git a/test/01. application.js b/test/01. application.js index 86521cf..3bbbf14 100644 --- a/test/01. application.js +++ b/test/01. application.js @@ -3,6 +3,7 @@ var assert = require('assert'); var Path = require('path'); var Should = require('should'); +var Component = require("../core/Component"); var IFNode = require('../'); describe('Application', function() { @@ -183,6 +184,52 @@ describe('Application', function() { }); }); + describe('app.inject(instance)', function() { + it('should inject component by id', function() { + var app = IFNode({ + project_folder: Path.resolve(__dirname, '../examples/components'), + }).load(); + + app.inject('first').should.be.an.Object(); + }); + + it('should inject component by class', function(done) { + function Simple(options) { + Component.call(this, options); + } + + function WithConfig(options, injected_app) { + Component.call(this, options); + Should.deepEqual(this.config, { + passed: 'options' + }); + Should.equal(injected_app, app); + } + + WithConfig.prototype.finish_test = function() { + done(); + } + + var app = IFNode({ + configuration: { + components: { + WithConfig: { + passed: 'options' + } + } + } + }).load(); + + var component = app.inject(Simple); + + Should.equal(component.name, 'Simple'); + Should.equal(app.inject(Simple), component); + + component = app.inject(WithConfig) + component.finish_test(); + }) + }) + describe('app.load()', function() { var app = IFNode({ alias: 'app-load-test' From 5290801807aebe6a4fcedeee65c81c1bd9fb342a Mon Sep 17 00:00:00 2001 From: Illia Fralou Date: Tue, 7 Dec 2021 17:17:10 +0300 Subject: [PATCH 26/26] v1.12.0 release notes: Changes: - [feat] general: added app.inject to inject ifnode instances --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7ccdbe8..303ba6d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ifnode", - "version": "1.11.0", + "version": "1.12.0", "description": "Node.js MVC Framework", "main": "index.js", "scripts": {