/** * @author Martin Karkowski * @email m.karkowski@zema.de * @create date 2020-10-12 17:54:08 * @modify date 2020-11-06 09:04:39 * @desc [description] */ import { inject, injectable } from 'inversify'; import { replaceAll } from "../helpers/stringMethods"; import { DISPATCHER_INSTANCE } from '../symbols/identifiers'; import { INopeDispatcher } from "../types/nope/nopeDispatcher.interface"; import { IAuthor, IFunctionOptions, INopeModule, INopeModuleDescription, IPropertyOptions, IVersion } from "../types/nope/nopeModule.interface"; import { INopeObservable } from "../types/nope/nopeObservable.interface"; /** * Base Implementation of a Module. * * The Module is used to share information and data. Although it implements the * the Basic behavior to fullfill a given traget. * * @export * @class BaseModule * @implements {INopeModule} */ @injectable() export class NopeBaseModule implements INopeModule { /** * Module Name must be written in lowercase. */ private _type = ''; public get type(): string { return this._type; } public set type(value: string) { const _replacements = [ [' ', ''], // ['.', ''], // ['_', ''], ]; let adapted = value; for (const [search, replace] of _replacements) { adapted = replaceAll(adapted, search, replace); } this._type = adapted.toLowerCase(); } /** * A Description of the Module. This is used to Describe roughly * what the module is capable of * doing. * * @type {string} * @memberof BaseModule */ public description: string; /** * A Description of the Author. Use to Mail etc. * * @type {IAuthor} * @memberof BaseModule */ public author: IAuthor; /** * Description of the provided Version of the Module. * * @type {IVersion} * @memberof BaseModule */ public version: IVersion; protected _registeredFunctions: Map Promise, options: IFunctionOptions }> protected _registeredProperties: Map options: IPropertyOptions }> /** * Public getter for the functions * * @readonly * @memberof BaseModule */ public get functions() { const ret: { [index: string]: IFunctionOptions; } = {}; for (const [name, funcs] of this._registeredFunctions.entries()){ ret[name] = funcs.options; } return ret; } /** * Public get to receive a Description of the Properties * * @readonly * @memberof BaseModule */ public get properties() { const ret: { [index: string]: IPropertyOptions; } = {}; for (const [name, funcs] of this._registeredProperties.entries()){ ret[name] = funcs.options; } return ret; } /** * The Identifier of the Module. * * @type {string} * @memberof BaseModule */ public identifier: string; public _markedElements: Array<{accessor: string, options:IPropertyOptions | IFunctionOptions, type: 'method' | 'prop'}>; /** * Creates an instance of BaseModule. * @memberof BaseModule */ constructor(@inject(DISPATCHER_INSTANCE) protected _dispatcher: INopeDispatcher) { this._type = null; this.description = null; this.author = null; this.version = null; this.identifier = null; this._registeredFunctions = new Map(); this._registeredProperties = new Map(); } /** * Helper Function to register an Observable (a Property.) * * @template T Type of the Property * @template K Additional Pipe Options * @template S Setter Type of the Property * @template G Getter Type of the Property * @param {string} name Name, which should be used to register the element. The Name will ALLWAYS (automatically) be assembled using the modules identifier an then the name. * @param {INopeObservable} observable The Observable representing the Property * @param {IPropertyOptions} options The Options used to define the registration. * @return {*} {Promise} * @memberof NopeBaseModule */ public async registerProperty(name: string, observable: INopeObservable, options: IPropertyOptions): Promise { // Unregister the Function await this.unregisterProperty(name); // Adapt the Topics if (typeof options.topic === 'string'){ options.topic = this.identifier + '.prop.' + name; } else if (typeof options.topic === 'object'){ if (options.topic.subscribe && !options.topic.subscribe.startsWith(this.identifier + '.prop.') ){ options.topic.subscribe = this.identifier + '.prop.' + options.topic.subscribe; } if (options.topic.publish && !options.topic.publish.startsWith(this.identifier + '.prop.')){ options.topic.publish = this.identifier + '.prop.' + options.topic.publish; } } const _observable = await this._dispatcher.registerObservable(observable, options); // Register the new Property. this._registeredProperties.set(name, { observable: _observable, options }); } /** * Function used to register a Method. This Method will be available in the shared network. * * @param {string} name Name of the Method, which is used during registration at the dispatcher * @param {(...args: any[]) => Promise} func The function itself. It must be async. * @param {IFunctionOptions} options The Options, used for registering. * @return {*} {Promise} * @memberof NopeBaseModule */ public async registerMethod(name: string, func: (...args: any[]) => Promise, options: IFunctionOptions): Promise { // Unregister the Function await this.unregisterFunction(name); // Adapt the Method ID options.id = this.identifier + '.method.' + name; const _func = await this._dispatcher.registerFunction(func, options); // Register the new Function. this._registeredFunctions.set(name, { func: _func, options }); } /** * Unregister a Function * * @param {string} name Name of the function used during registering. * @return {*} {Promise} * @memberof NopeBaseModule */ public async unregisterFunction(name: string): Promise { // Test if the Method is already registerd, // If so => unregister it first. if (this._registeredFunctions.has(name)) { this._dispatcher.unregisterFunction(this._registeredFunctions.get(name).func, { preventSendingToRegistery: true }); } } /** * Helper Function to unregister an Observable (a Property.) * * @param {string} name Name of the Property, that has been used to register. * @return {*} {Promise} * @memberof NopeBaseModule */ public async unregisterProperty(name: string): Promise { // Test if the Property is already registerd, // If so => unregister it first. if (this._registeredProperties.has(name)) { this._dispatcher.unregisterObservable(this._registeredProperties.get(name).observable, { preventSendingToRegistery: true }); } } /** * Function to return all available Methods. * * @return {*} {Promise<{ func: (...args: any[]) => Promise; options: IFunctionOptions; }[]>} * @memberof NopeBaseModule */ public async listFunctions(): Promise<{ func: (...args: any[]) => Promise; options: IFunctionOptions; }[]> { return Array.from(this._registeredFunctions.values()); } /** * Function used to list all available Properties. * * @return {*} {Promise, options: IPropertyOptions }>>} * @memberof NopeBaseModule */ public async listProperties(): Promise, options: IPropertyOptions }>> { return Array.from(this._registeredProperties.values()); } /** * An init Function. Used to initialize the Element. * * @return {*} {Promise} * @memberof NopeBaseModule */ public async init(): Promise { // In this base Implementation, check if every requried property is set // correctly. If not => raise an error. if (this.type === null){ throw Error('Please Provide a Name for the Module before initializing') } if (this.description === null){ throw Error('Please Provide a Description for the Module before initializing') } if (this.author === null){ throw Error('Please Provide an Author for the Module before initializing') } if (this.version === null){ throw Error('Please Provide a Version for the Module before initializing') } if (this.identifier === null){ throw Error('Please Provide an Identifier for the Module before initializing') } if (this._markedElements){ const _this = this; for (const entry of this._markedElements){ switch(entry.type){ case 'method': await this.registerMethod(entry.accessor, (...args) => { return _this[entry.accessor](...args) }, entry.options); break; case 'prop': await this.registerProperty(entry.accessor, _this[entry.accessor], entry.options as IPropertyOptions); break; } } } } /** * Function, which is used to unregister the element. * * @memberof NopeBaseModule */ public async dispose(){ // Unregister all Methods and Functions for (const name of this._registeredFunctions.keys()){ await this.unregisterFunction(name); } // Remove all known Functions this._registeredFunctions.clear(); // Unregister all Properties. for (const name of this._registeredProperties.keys()){ await this.unregisterProperty(name); } // Remove all known Properties. this._registeredProperties.clear(); } /** * Helper function to extract an description of the Module. * * @return {INopeModuleDescription} a parsed description * @memberof NopeBaseModule */ public toDescription(): INopeModuleDescription { const ret: INopeModuleDescription = { author: this.author, description: this.description, functions: this.functions, identifier: this.identifier, properties: this.properties, type: this.type, version: this.version } return ret; } }