UNPKG

nope-js-node

Version:

NoPE Runtime for Nodejs. For Browser-Support please use nope-browser

641 lines (640 loc) 29.1 kB
"use strict"; /** * @author Martin Karkowski * @email m.karkowski@zema.de * @desc [description] */ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.NopePackageLoader = void 0; const inversify_1 = require("inversify"); const lodash_1 = require("lodash"); require("reflect-metadata"); const decorators_1 = require("../decorators"); const arrayMethods_1 = require("../helpers/arrayMethods"); const async_1 = require("../helpers/async"); const runtimeMethods_1 = require("../helpers/runtimeMethods"); const getLogger_1 = require("../logger/getLogger"); const identifiers_1 = require("../symbols/identifiers"); /** * Helper Class to Build an inversify Container. * * @export * @class NopePackageLoader * @implements {INopePackageLoader} */ let NopePackageLoader = class NopePackageLoader { get activationHandlers() { return Array.from(this._actionHandlers.values()); } /** * Adds an Activation Handler. (Those are called, after an Object has been created) * * @param {(context: interfaces.Context, element: any) => any} func The Corresponding Method which will be called. * @memberof NopePackageLoader */ async addActivationHandler(func) { if (!Array.isArray(func)) { func = [func]; } for (const _func of func) { const _name = _func.name; if (!this._actionHandlers.has(_name)) this._actionHandlers.set(_name, _func); else { this._logger.warn("Trying to Add Activation Handler twice!"); } } } get dispatcher() { // Define a Lazy - Getter for an Modul: if (this._dispatcher === null) { this._dispatcher = this.container.get(identifiers_1.DISPATCHER_INSTANCE); } return this._dispatcher; } /** * Adds the Container to the given Container. * * @param {Container} container the Container, that should be merged * @memberof NopePackageLoader */ addContainers(container) { this.container = inversify_1.Container.merge(this.container, container); } /** * Function which will perform all Activation Handlers. * * @protected * @param {interfaces.Context} _context * @param {*} _element * @returns * @memberof NopePackageLoader */ _onActivation(_context, _element) { // Perform the Handlers on the Object for (const _handler of this._actionHandlers.values()) { _element = _handler(_context, _element); } return _element; } _compareElements(_a, _b) { if (_a.options && _a.options.addition && _b.options && _b.options.addition) { return (_b.options.addition.name === _a.options.addition.name && (0, arrayMethods_1.arraysEqual)(_b.options.addition.args, _a.options.addition.args)); } return true; } /** * Internal Method to Test, whether an Element exists or not. * * @protected * @param {IClassDescriptor} _item * @return {*} {boolean} * @memberof NopePackageLoader */ _hasElement(_item) { for (const _element of this.availableElements) { try { if (Array.isArray(_element.selector)) { if ((0, arrayMethods_1.arraysEqual)(_element.selector, Array.isArray(_item.selector) ? _item.selector : [_item.selector]) && this._compareElements(_element, _item)) { return true; } } else if (!Array.isArray(_item.selector) && _element.selector === _item.selector && this._compareElements(_element, _item)) { return true; } else if (_item.factorySelector && _item.factorySelector === _element.factorySelector && this._compareElements(_element, _item)) { return true; } } catch (e) { this._logger.error(e, "Error During Check", _item.factorySelector, _element.factorySelector); } } return false; } /** * Method to add an Element to the Build * * @param {IClassDescriptor[]} _elements Definition containing the Elements that should be added * @memberof NopePackageLoader */ async addDescription(_elements, _instance = null) { if (_instance === null) { for (const _element of _elements) { if (!this._hasElement(_element)) { this.availableElements.push(_element); this._addElement(_element); } else { this._logger.warn("Using the Same Selector / Factory of", _element); if (runtimeMethods_1.RUNNINGINNODE) { this.availableElements.push(_element); this._addElement(_element); } } } } else { for (const _element of _elements) { if (!this._hasElement(_element)) { this.availableElements.push(_element); this._linkExternalBuilder(_element, _instance); } else { this._logger.warn("Using the Same Selector / Factory of", _element); } } } } /** * Internal Helper Function to Merge an external Builder for creating Tasks. * * @protected * @param {IClassDescriptor} _element * @param {NopePackageLoader} _instance * @memberof NopePackageLoader */ _linkExternalBuilder(_element, _instance) { // Bind Factory if (!Array.isArray(_element.selector)) { _element.selector = [_element.selector]; } if (_element.factorySelector && !Array.isArray(_element.factorySelector)) { _element.factorySelector = [_element.factorySelector]; } // Add al instances. for (const _selector of _element.selector) { this.container.bind(_selector).toDynamicValue(() => { return _instance.container.get(_selector); }); } for (const _selector of _element.factorySelector) { this.container.bind(_selector).toDynamicValue(() => { return _instance.container.get(_selector); }); } } /** * Internal Funcitont to add an Description. * * @protected * @param {IClassDescriptor} _element * @memberof NopePackageLoader */ _addElement(_element) { if (_element) { const _this = this; const _dict = { whenTargetTagged: "getTagged", whenTargetNamed: "getNamed", }; // Define Option if required // And use the line below to update them and // put them into the correct format. if (!_element.options) { _element.options = {}; } if (_element.selector) { if (!Array.isArray(_element.selector)) { _element.selector = [_element.selector]; } } else { _element.selector = []; } if (_element.factorySelector && !Array.isArray(_element.factorySelector)) { _element.factorySelector = [_element.factorySelector]; } // Select the Method const _method = _element.options.toConstant ? "toConstantValue" : "to"; // Check if a specific Scope is given if (_element.options.scope != undefined) { // A specified Scope for Inversify is given if (_element.options.addition) { // Add all instances. for (const [_index, _selector] of _element.selector.entries()) { // Firstly bind to the First Selector if (_index === 0) { this._logger.debug("adding selector", _selector.toString()); this.container .bind(_selector)[_method](_element.type)[_element.options.scope]()[_element.options.addition.name](..._element.options.addition.args) .onActivation((_context, _element) => { return _this._onActivation(_context, _element); }); } else { this._logger.debug("adding selector", _selector.toString()); // Afterwards redirect to the Se this.container.bind(_selector).toDynamicValue((context) => { // Create the First Element and return it. return context.container[_dict[_element.options.addition.name]](_element.selector[0], ..._element.options.addition.args); }); } } } else { // Add all instances. for (const [_index, _selector] of _element.selector.entries()) { // Firstly bind to the First Selector if (_index === 0) { this._logger.debug("adding selector", _selector.toString()); this.container .bind(_selector)[_method](_element.type)[_element.options.scope]() .onActivation((_context, _element) => { return _this._onActivation(_context, _element); }); } else { this._logger.debug("adding selector", _selector.toString()); // Afterwards redirect to the Get the Element with the First Tag. this.container.bind(_selector).toDynamicValue((context) => { // Create the First Element and return it. return context.container.get(_element.selector[0]); }); } } } } else { // No Scope is given if (_element.options.addition) { // Add all instances. for (const [_index, _selector] of _element.selector.entries()) { // Firstly bind to the First Selector if (_index === 0) { this._logger.debug("adding selector", _selector.toString()); this.container .bind(_selector)[_method](_element.type)[_element.options.addition.name](..._element.options.addition.args) .onActivation((_context, _element) => { return _this._onActivation(_context, _element); }); } else { this._logger.debug("adding selector", _selector.toString()); // Afterwards redirect to the Se this.container.bind(_selector).toDynamicValue((context) => { // Create the First Element and return it. return context.container[_dict[_element.options.addition.name]](_element.selector[0], ..._element.options.addition.args); }); } } } else { // Add all instances. for (const [_index, _selector] of _element.selector.entries()) { // Firstly bind to the First Selector if (_index === 0) { this._logger.debug("adding selector", _selector.toString()); this.container .bind(_selector)[_method](_element.type) .onActivation((_context, _element) => { return _this._onActivation(_context, _element); }); } else { this._logger.debug("adding selector", _selector.toString()); // Afterwards redirect to the Get the Element with the First Tag. this.container.bind(_selector).toDynamicValue((context) => { // Create the First Element and return it. return context.container.get(_element.selector[0]); }); } } } } // Add the Factory if neccessary if (_element.factorySelector) { // Generate a Dict for converting the Instanciation Values to the Factory Methods. if (_element.options.factoryCallback) { if (_element.options.addition) { if (Array.isArray(_element.options.addition)) { // TODO } else { const firstArg = _element.options.addition.args[0]; const secondArg = _element.options.addition.args[1]; for (const _selector of _element.factorySelector) { this.container .bind(_selector) .toFactory(_element.options.factoryCallback)[_element.options.addition.name](firstArg, secondArg); } } } else { for (const _selector of _element.factorySelector) { console.log(_selector, _element); this.container .bind(_selector) .toFactory(_element.options.factoryCallback); } } } else { if (_element.options.addition) { if (Array.isArray(_element.options.addition)) { // TODO } else { const firstArg = _element.options.addition.args[0]; const secondArg = _element.options.addition.args[1]; for (const _selector of _element.factorySelector) { this.container .bind(_selector) .toFactory((context) => { return () => { // Call get function on the Container return context.container[_dict[_element.options.addition.name]]( // Return the First Element _element.selector[0], ..._element.options.addition.args); }; })[_element.options.addition.name](firstArg, secondArg); } } } else { for (const _selector of _element.factorySelector) { this.container .bind(_selector) .toFactory((context) => { return () => { return context.container.get(_element.selector[0]); }; }); } } } } } } async reset() { // Generate the container this.container = new inversify_1.Container(); const _globalConfig = {}; this.container.bind("global.config").toConstantValue(_globalConfig); this.availableElements = new Array(); } constructor() { /** * Array containing multipl Activation Handlers * * @protected * @memberof NopePackageLoader */ this._actionHandlers = new Map(); this._logger = (0, getLogger_1.getNopeLogger)("package-loader", "debug"); this._dispatcher = null; this.packages = {}; this._instances = new Map(); this._disposeDefaultInstance = []; this.reset(); } /** * Loader Function. This function will register all provided functions, * create the desired instances. Additionally it will add all descriptors. * * @param {IPackageDescription<any>} element * @memberof NopePackageLoader */ async addPackage(element) { var _c; if (this.packages[element.nameOfPackage] !== undefined) { throw Error('Already loaded a Package with the name "' + element.nameOfPackage + '" !'); } this._logger.info("loading package " + element.nameOfPackage); // Store the Package this.packages[element.nameOfPackage] = element; // Firstly add all Descriptors: await this.addDescription(element.providedClasses.map((item) => { return item.description; })); // Load the Activation Handlers: await this.addActivationHandler(element.activationHandlers); const _this = this; // Based on the provided settings register a generator Function for the Instances: for (const cl of element.providedClasses) { // Based on the Defintion extract the corresponding selector: let selector = null; let factory = false; if (cl.description.factorySelector) { // Firstly try to use a Factory Selector => new instances are generated instead of only // one singleton Object. selector = Array.isArray(cl.description.factorySelector) ? cl.description.factorySelector[0] : cl.description.factorySelector; factory = true; } else if (cl.description.selector) { // Otherwise select the Selector of the Singleton. selector = Array.isArray(cl.description.selector) ? cl.description.selector[0] : cl.description.selector; } if (selector && ((_c = cl.settings) === null || _c === void 0 ? void 0 : _c.allowInstanceGeneration)) { this._logger.info("Adding an instance generator for " + cl.description.name); // Register an Instance Generator, only if allowed await this.dispatcher.instanceManager.registerConstructor(cl.description.name, async (_, identifier) => { const currentAmount = _this._instances.get(selector) || 0; if (cl.settings.allowInstanceGeneration && currentAmount < (cl.settings.maxAmountOfInstance || Infinity)) { // Define the Instance: const instance = factory ? _this.container.get(selector)() : _this.container.get(selector); instance.identifier = identifier; // Update the Used Instance _this._instances.set(selector, currentAmount + 1); // Return the instance. return instance; } throw Error("Not allowed to create instances"); }); } } // Iterate over the provided Functions: for (const func of element.providedServices) { await this.dispatcher.rpcManager.registerService(func.service, func.options); } } /** * Function to initialize all the instances. * * @param {boolean} [testRequirements=true] * @memberof NopePackageLoader */ async generateInstances(testRequirements = true) { var _c; const _this = this; if (this._logger) { this._logger.info("Package Loader generates the instances."); } if (testRequirements) { const availablePackages = Object.getOwnPropertyNames(this.packages); // First extract all required Packages const reuqiredPackages = Array.from(new Set((0, lodash_1.flatten)(availablePackages.map((name) => { return _this.packages[name].requiredPackages; })))); // Now Check if every Package is present. reuqiredPackages.map((_package) => { if (!availablePackages.includes(_package)) { throw Error("Packages are not known"); } }); } for (const name in this.packages) { const definitions = this.packages[name].defaultInstances; for (const definition of definitions) { if ((_c = this._logger) === null || _c === void 0 ? void 0 : _c.enabledFor) { this._logger.debug('Requesting Generating Instance "' + definition.options.identifier + '" of type "' + definition.options.type + '"'); } const instance = await this.dispatcher.instanceManager.createInstance(definition.options); if (this._logger) { this._logger.info('Sucessfully Generated the Instance "' + definition.options.identifier + '" of type "' + definition.options.type + '"'); } // Store the Function, that the instance will be disposed on leaving. this._disposeDefaultInstance.push(() => { return instance.dispose(); }); if (definition.options.identifier in this.packages[name].autostart) { // There are autostart Tasks in the Package for the considered Instance, // which has been recently defined. try { const autostart = this.packages[name].autostart[definition.options.identifier]; for (const task of autostart) { if (task.delay) { await (0, async_1.sleep)(task.delay); } await instance[task.service](...task.params); } } catch (e) { this._logger.error("Failed with autostart tasks for " + instance.identifier); this._logger.error(e); } } else { this._logger.info("No autostart for " + instance.identifier); } } } if (this._logger) { this._logger.info("generated all defined Instances"); } } /** * Helper to provide all linked services. */ async provideLinkedServices() { // Define a Container, which contains all functions. const container = (0, decorators_1.getCentralDecoratedContainer)(); // If the Dispatcher has been connected, register all functions. await this.dispatcher.ready.waitFor(); // Iterate over the Functions for (const [uri, settings] of container.services.entries()) { if (!this.dispatcher.rpcManager.isProviding(uri)) { await this.dispatcher.rpcManager.registerService(settings.callback, { ...settings.options, id: uri, }); } } } /** * Function to load all decorated elements with the decorators `exportAsNopeService` * * @param options */ async addDecoratedElements(options = { consider: ["services"], }) { const _this = this; // Get the container containing all registered Services and Classes. const CONTAINER = (0, decorators_1.getCentralDecoratedContainer)(); // Helper to list the Promises. const promises = new Array(); if (!Array.isArray(options.consider)) { options.consider = []; } if (typeof options.addServiceCallback === "function") { options.consider.push("services"); } if (typeof options.addClassCallback === "function") { options.consider.push("classes"); } if (options.consider.includes("services")) { for (const serviceDefintion of CONTAINER.services.values()) { if (typeof options.addServiceCallback === "function") { let resolve = null; // Create a Promise, that will be completed, if // the function is been added const promise = new Promise((_resolve) => (resolve = _resolve)); promises.push(promise); // Now we call the function to decide whether the // service should be added or not. options .addServiceCallback(serviceDefintion.options) .then((decision) => { if (decision) { _this.dispatcher.rpcManager .registerService(serviceDefintion.callback, serviceDefintion.options) .catch((e) => { if (_this._logger) { _this._logger.error(`Failed to register service '${serviceDefintion.uri}'`); _this._logger.error(e); } }); } resolve(null); }); } else { // Just add the Service. await this.dispatcher.rpcManager.registerService(serviceDefintion.callback, serviceDefintion.options); } } } // for (const classDefinition of CONTAINER.classes.values()){ // await this.dispatcher.rpcManager.registerService(classDefinition.,classDefinition.options) // } // Wait for all Promises to be added. await Promise.all(promises); } async dispose() { // Start all dispose Values const promises = this._disposeDefaultInstance.map((cb) => { return cb(); }); // Wait for all to finish! await Promise.all(promises); this._disposeDefaultInstance = []; } }; NopePackageLoader = __decorate([ (0, inversify_1.injectable)() ], NopePackageLoader); exports.NopePackageLoader = NopePackageLoader;