nope-js-node
Version:
NoPE Runtime for Nodejs. For Browser-Support please use nope-browser
641 lines (640 loc) • 29.1 kB
JavaScript
"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;