diff --git a/lib/cli/generateDefaultPackageConfig.ts b/lib/cli/generateDefaultPackageConfig.ts index 1ff9e60..6da3d63 100644 --- a/lib/cli/generateDefaultPackageConfig.ts +++ b/lib/cli/generateDefaultPackageConfig.ts @@ -2,7 +2,7 @@ * @author Martin Karkowski * @email m.karkowski@zema.de * @create date 2020-11-11 13:27:58 - * @modify date 2020-11-11 15:41:24 + * @modify date 2020-11-12 13:49:17 * @desc [description] */ @@ -39,7 +39,7 @@ const main = async function () { ['-f', '--file'], { help: 'File containing containing the package definitions.', - defaultValue: './config/packages.json', + defaultValue: './config/settings.json', type: 'string', dest: 'file' } diff --git a/lib/cli/generateOpenApi.ts b/lib/cli/generateOpenApi.ts index 1faeec4..a3adb40 100644 --- a/lib/cli/generateOpenApi.ts +++ b/lib/cli/generateOpenApi.ts @@ -2,7 +2,7 @@ * @author Martin Karkowski * @email m.karkowski@zema.de * @create date 2020-11-09 13:27:58 - * @modify date 2020-11-10 16:24:52 + * @modify date 2020-11-12 11:31:20 * @desc [description] */ @@ -109,7 +109,7 @@ const main = async function () { } try { - await writeFile(join('dist', 'src', 'apidoc.json'), JSON.stringify(data.generalInformationModel, undefined, 4)); + await writeFile(join('dist', 'lib', 'open-api', 'apidoc.json'), JSON.stringify(data.generalInformationModel, undefined, 4)); } catch(e){ opts.logger.error('Failed to generate ' + join('dist', 'src', 'apidoc.json')); console.error(e); diff --git a/lib/cli/runNopeBackend.ts b/lib/cli/runNopeBackend.ts index 72e5692..1f9352b 100644 --- a/lib/cli/runNopeBackend.ts +++ b/lib/cli/runNopeBackend.ts @@ -2,28 +2,29 @@ * @author Martin Karkowski * @email m.karkowski@zema.de * @create date 2020-11-11 13:27:58 - * @modify date 2020-11-11 16:59:09 + * @modify date 2020-11-12 14:10:27 * @desc [description] */ import { ArgumentParser } from 'argparse'; import { readFile } from 'fs/promises'; import "reflect-metadata"; -import { promisify } from 'util'; import { AmqpLayer } from '../communication/amqpLayer'; import { EventLayer } from '../communication/eventLayer'; import { IoSocketClient } from '../communication/IoSocketClient'; import { IoSocketServer } from '../communication/IoSocketServer'; import { getLinkedDispatcher } from '../dispatcher/getLinkedDispatcher'; import { getPackageLoader } from '../loader/getPackageLoader'; -import { loadPackages } from '../loader/loadPackages'; +import { loadFunctions, loadPackages } from '../loader/loadPackages'; import { getNopeLogger } from '../logger/getLogger'; +import { LoggerLevels } from '../logger/nopeLogger'; +import { setGlobalLoggerLevel } from '../logger/setGlobalLoggerLevel'; import { INopeDispatcher } from '../types/nope/nopeDispatcher.interface'; // Define the Main Function. // This function is used as cli tool. -const main = async function () { +export async function runNopeBackend() { const parser = new ArgumentParser({ version: '1.0.0', @@ -59,13 +60,11 @@ const main = async function () { opts = {} as any } - - parser.addArgument( ['-f', '--file'], { help: 'File containing containing the package definitions.', - defaultValue: './config/packages.json', + defaultValue: './config/settings.json', type: 'string', dest: 'file' } @@ -82,7 +81,7 @@ const main = async function () { } ); parser.addArgument( - ['-params', '--params'], + ['-p', '--params'], { help: 'Paramas for the Channel, to connect to. The Following Defaults are used: \n' + JSON.stringify(layerDefaults, undefined, 4), defaultValue: 'not-provided', @@ -90,8 +89,34 @@ const main = async function () { dest: 'params' } ); + parser.addArgument( + ['-s', '--skipLoadingConfig'], + { + help: 'Flag to prevent loading the elements defined in the settings.json.', + action: 'append', nargs: '?', + dest: 'skipLoadingConfig' + } + ); + parser.addArgument( + ['-l', '--log'], + { + help: 'Specify the Logger Level. Defaults to "info". Valid values are: ' + LoggerLevels.join(', '), + defaultValue: 'info', + type: 'string', + dest: 'log' + } + ); const args = parser.parseArgs(); + + if (LoggerLevels.includes(args.log)){ + setGlobalLoggerLevel(args.level); + } + + + + args.skipLoadingConfig = Array.isArray(args.skipLoadingConfig); + // Define a Logger const logger = getNopeLogger('starter'); @@ -112,11 +137,26 @@ const main = async function () { } } + // If required load all Packages. + if (!args.skipLoadingConfig){ + // Try to load the Modules. + try { + logger.info('loading Packages') + await loadFunctions(args.file) + } catch (e) { + logger.error('Unable to load the Packages defined in ' + args.file + ' See Output for detailled information'); + console.error(e) + return; + } + } + + // Try to create an dispatcher: let dispatcher: INopeDispatcher; try { dispatcher = getLinkedDispatcher({ - communicator: new validLayers[args.channel](... opts.params) + communicator: new validLayers[args.channel](... opts.params), + logger: getNopeLogger('dispatcher', 'silly') }) } catch (e) { logger.error('Unable to create the Dispatcher. Please check the Provided Parameters.'); @@ -126,14 +166,25 @@ const main = async function () { await dispatcher.ready.waitFor(value => value); - // Try to load the Modules. - try { - await loadPackages(getPackageLoader(dispatcher), args.file) - } catch (e) { - logger.error('Unable to load the Packages defined in ' + args.file + ' See Output for detailled information'); - console.error(e) - return; - } + // If required load all Packages. + if (!args.skipLoadingConfig){ + // Try to load the Modules. + try { + logger.info('loading Packages') + await loadPackages(getPackageLoader(dispatcher), args.file) + } catch (e) { + logger.error('Unable to load the Packages defined in ' + args.file + ' See Output for detailled information'); + console.error(e) + return; + } + } + + + // Return the Dispatcher. + return dispatcher; } -main().catch(e => console.error(e)); \ No newline at end of file +// If requested As Main => Perform the Operation. +if (require.main === module) { + runNopeBackend().catch(console.error); +} \ No newline at end of file diff --git a/lib/cli/runOpenApiServer.ts b/lib/cli/runOpenApiServer.ts new file mode 100644 index 0000000..ff17ba8 --- /dev/null +++ b/lib/cli/runOpenApiServer.ts @@ -0,0 +1,25 @@ +/** + * @author Martin Karkowski + * @email m.karkowski@zema.de + * @create date 2020-11-12 11:22:27 + * @modify date 2020-11-12 11:22:36 + * @desc [description] + */ + +import "reflect-metadata"; +import { getNopeLogger } from '../logger/getLogger'; +import { startOpenApiBackend } from '../open-api/startOpenApiBackend'; +import { runNopeBackend } from './runNopeBackend'; + +// Define the Main Function. +// This function is used as cli tool. +export async function runOpenApiServer() { + const dispatcher = await runNopeBackend(); + + const result = await startOpenApiBackend(dispatcher, { port: 3001, logger: getNopeLogger('open-api-server', 'debug') }); +} + +// If requested As Main => Perform the Operation. +if (require.main === module) { + runOpenApiServer().catch(console.error); +} \ No newline at end of file diff --git a/lib/communication/IoSocketClient.ts b/lib/communication/IoSocketClient.ts index f151719..d824837 100644 --- a/lib/communication/IoSocketClient.ts +++ b/lib/communication/IoSocketClient.ts @@ -2,22 +2,24 @@ * @author Martin Karkowski * @email m.karkowski@zema.de * @create date 2020-11-04 17:36:04 - * @modify date 2020-11-06 08:46:24 + * @modify date 2020-11-12 13:23:48 * @desc [description] */ +import { EventEmitter } from 'events'; import { connect, Socket } from 'socket.io-client'; -import { getCentralNopeLogger, getNopeLogger } from "../logger/getLogger"; +import { Logger } from "winston"; +import { getNopeLogger } from "../logger/getLogger"; import { NopeObservable } from '../observables/nopeObservable'; import { IAvailableInstanceGeneratorsMsg, IAvailableServicesMsg, IAvailableTopicsMsg, ICommunicationInterface, IExternalEventMsg, IRequestTaskMsg, IResponseTaskMsg, ITaskCancelationMsg } from "../types/nope/nopeCommunication.interface"; import { INopeObservable } from '../types/nope/nopeObservable.interface'; -import { Logger } from "winston"; export class IoSocketClient implements ICommunicationInterface { connected: INopeObservable; protected _emitter: typeof Socket; + protected _internalEmitter: EventEmitter protected _logger: Logger; constructor(public uri: string) { @@ -34,7 +36,8 @@ export class IoSocketClient implements ICommunicationInterface { this._logger.info('connecting to: ' + uri); this._emitter = connect(uri); - + this._internalEmitter = new EventEmitter(); + this._emitter.on('connect', (...args) => { // Element is connected _this._logger.info('connected'); @@ -48,10 +51,12 @@ export class IoSocketClient implements ICommunicationInterface { } async onTaskCancelation(cb: (msg: ITaskCancelationMsg) => void) { + this._internalEmitter.on('cancel', cb); this._emitter.on('cancel', cb); } async emitTaskCancelation(msg: ITaskCancelationMsg) { + this._internalEmitter.emit('cancel', msg); this._emitter.emit('cancel', msg); } @@ -64,70 +69,87 @@ export class IoSocketClient implements ICommunicationInterface { } async emitNewInstanceGeneratorsAvailable(generators: IAvailableInstanceGeneratorsMsg) { + this._internalEmitter.emit('generators', generators); this._emitter.emit('generators', generators); } async onNewInstanceGeneratorsAvailable(cb: (generators: IAvailableInstanceGeneratorsMsg) => void) { - this._emitter.on('generators', cb) + this._internalEmitter.on('generators', cb); + this._emitter.on('generators', cb); } async emitRpcRequest(name: string, request: IRequestTaskMsg) { + this._internalEmitter.emit(name, request); this._emitter.emit(name, request); } async emitRpcResult(name: string, result: IResponseTaskMsg) { + this._internalEmitter.emit(name, result); this._emitter.emit(name, result); } async onRpcResult(name: string, cb: (result: IResponseTaskMsg) => void) { + this._internalEmitter.on(name, cb); this._emitter.on(name, cb); } async offRpcResponse(name: string, cb: (result: IResponseTaskMsg) => void) { + this._internalEmitter.off(name, cb); this._emitter.off(name, cb); } async onRpcRequest(name: string, cb: (data: IRequestTaskMsg) => void) { + this._internalEmitter.on(name, cb); this._emitter.on(name, cb); } async offRpcRequest(name: string, cb: (data: IRequestTaskMsg) => void) { + this._internalEmitter.off(name, cb); this._emitter.off(name, cb); } async emitNewServicesAvailable(services: IAvailableServicesMsg) { + this._internalEmitter.emit('services', services) this._emitter.emit('services', services) } async onNewServicesAvailable(cb: (services: IAvailableServicesMsg) => void) { + this._internalEmitter.on('services', cb); this._emitter.on('services', cb); } async onBonjour(cb: (dispatcher: string) => void) { + this._internalEmitter.on('bonjour', cb); this._emitter.on('bonjour', cb); } async emitBonjour(dispatcher: string) { + this._internalEmitter.emit('bonjour', dispatcher); this._emitter.emit('bonjour', dispatcher); } async emitNewTopicsAvailable(topics: IAvailableTopicsMsg) { + this._internalEmitter.emit('topics', topics); this._emitter.emit('topics', topics) } async onNewTopicsAvailable(cb: (topics: IAvailableTopicsMsg) => void) { - this._emitter.on('topics', cb) + this._emitter.on('topics', cb); + this._emitter.on('topics', cb); } async onEvent(event: string, cb: (data: IExternalEventMsg) => void) { this._emitter.on('event_' + event, cb); + this._emitter.on('event_' + event, cb); } async emitEvent(event: string, data: IExternalEventMsg) { - this._emitter.emit('event_' + event, data) + this._internalEmitter.emit('event_' + event, data); + this._emitter.emit('event_' + event, data); } async offEvent(event: string, cb: (data: IExternalEventMsg) => void) { + this._internalEmitter.off('event_' + event, cb); this._emitter.off('event_' + event, cb); } } \ No newline at end of file diff --git a/lib/communication/IoSocketServer.ts b/lib/communication/IoSocketServer.ts index 0b744ec..f8c72e0 100644 --- a/lib/communication/IoSocketServer.ts +++ b/lib/communication/IoSocketServer.ts @@ -2,10 +2,11 @@ * @author Martin Karkowski * @email m.karkowski@zema.de * @create date 2020-11-06 08:52:42 - * @modify date 2020-11-11 16:29:07 + * @modify date 2020-11-12 13:26:43 * @desc [description] */ +import { EventEmitter } from "events"; import { Server } from "http"; import * as io from 'socket.io'; import { Logger } from "winston"; @@ -24,6 +25,7 @@ export class IoSocketSeverEventEmitter { protected _sockets: Set; protected _funcs: Map void>; protected _logger: Logger; + protected _internalEmitter: EventEmitter; /** * Creates an instance of IoSocketServer. @@ -38,6 +40,8 @@ export class IoSocketSeverEventEmitter { this._socket = (io as any)(); } + this._internalEmitter = new EventEmitter(); + this.connected = new NopeObservable(); this.connected.setContent(false); @@ -99,6 +103,7 @@ export class IoSocketSeverEventEmitter { // Store the Function this._funcs.set(event, cb); + this._internalEmitter.on(event, cb); } emit(event: string, data: any): void { @@ -106,6 +111,7 @@ export class IoSocketSeverEventEmitter { this._logger.debug('sending data on: ' + event); } this._socket.emit(event, data); + this._internalEmitter.emit(event,data); } } diff --git a/lib/decorators/dispatcherDecorators.ts b/lib/decorators/dispatcherDecorators.ts index 78a01cd..7a168a7 100644 --- a/lib/decorators/dispatcherDecorators.ts +++ b/lib/decorators/dispatcherDecorators.ts @@ -2,7 +2,7 @@ * @author Martin Karkowski * @email m.karkowski@zema.de * @create date 2020-11-06 08:52:55 - * @modify date 2020-11-06 09:21:34 + * @modify date 2020-11-12 13:55:43 * @desc [description] */ @@ -34,12 +34,13 @@ export type callable = { * @param options The Options. */ export function exportFunctionToDispatcher(func: T, options: IExportFunctionToDispatcherParameters) { - - container.instance.set(options.id, { - callback: async (...args) => await ((func as any)(...args)), - options, - uri: options.id || (func as any).name - }); - + // Only add the element if it doesnt exists. + if (!container.instance.has(options.id)){ + container.instance.set(options.id, { + callback: async (...args) => await ((func as any)(...args)), + options, + uri: options.id || (func as any).name + }); + } return func; } diff --git a/lib/dispatcher/getLinkedDispatcher.ts b/lib/dispatcher/getLinkedDispatcher.ts index 245736a..31b75e0 100644 --- a/lib/dispatcher/getLinkedDispatcher.ts +++ b/lib/dispatcher/getLinkedDispatcher.ts @@ -2,7 +2,7 @@ * @author Martin Karkowski * @email m.karkowski@zema.de * @create date 2020-08-25 23:27:28 - * @modify date 2020-11-11 16:38:47 + * @modify date 2020-11-12 14:11:17 * @desc [description] */ @@ -16,18 +16,21 @@ import { getDispatcher } from "./getDispatcher"; * @param options */ export function getLinkedDispatcher(options: INopeRpcDispatcherOptions) { - const container = getSingleton('nopeBackendDispatcher.container', () => { - return new Map Promise, - options: IExportFunctionToDispatcherParameters - }>() - }); + // Create the Dispatcher Instance. const dispatcher = getDispatcher(options); + // Define a Container, which contains all functions. + const container = getSingleton('nopeBackendDispatcher.container', () => { + return new Map Promise, + options: IExportFunctionToDispatcherParameters + }>() + }); + // If the Dispatcher has been connected, register all functions. - dispatcher.ready.waitFor(value => value).then(() => { + dispatcher.ready.waitFor(value => value === true).then(() => { if (dispatcher.ready.getContent()){ // Iterate over the Functions for (const [uri, settings] of container.instance.entries()) { @@ -36,7 +39,7 @@ export function getLinkedDispatcher(options: INopeRpcDispatcherOptions) { }); } } else { - console.log('Failed') + // Failed to Setup the Container. } }); diff --git a/lib/dispatcher/nopeDispatcher.ts b/lib/dispatcher/nopeDispatcher.ts index 9614c40..e1e55ea 100644 --- a/lib/dispatcher/nopeDispatcher.ts +++ b/lib/dispatcher/nopeDispatcher.ts @@ -2,7 +2,7 @@ * @author Martin Karkowski * @email m.karkowski@zema.de * @create date 2020-10-12 18:52:00 - * @modify date 2020-11-09 09:43:40 + * @modify date 2020-11-12 14:27:58 * @desc [description] */ @@ -141,8 +141,8 @@ export class nopeDispatcher implements INopeDispatcher { provideInstanceGeneratorForExternalDispatchers(identifier: string, cb: IGenerateRemoteInstanceForOtherDispatcherCallback) { const _this = this; - if (this._logger?.isDebugEnabled()) { - this._logger.debug('Adding instance generator for "' + identifier + '" to external Generators. Other Elements can now create instances of this type.'); + if (this._logger?.isSillyEnabled()) { + this._logger.silly('Adding instance generator for "' + identifier + '" to external Generators. Other Elements can now create instances of this type.'); } const _cb = this.registerFunction(async (data: IInstanceCreationMsg) => { @@ -219,8 +219,8 @@ export class nopeDispatcher implements INopeDispatcher { unprovideInstanceGeneratorForExternalDispatchers(identifier: string) { if (this._externalGenerators.has(identifier)) { - if (this._logger?.isDebugEnabled()) { - this._logger.debug('Removing instance generator for "' + identifier + '" from external Generators. Other Elements cant create instances of this type anymore.'); + if (this._logger?.isSillyEnabled()) { + this._logger.silly('Removing instance generator for "' + identifier + '" from external Generators. Other Elements cant create instances of this type anymore.'); } this.unregisterFunction(this._externalGenerators.get(identifier)); @@ -230,8 +230,8 @@ export class nopeDispatcher implements INopeDispatcher { registerInternalInstanceGenerator(identifier: string, cb: IGenerateRemoteInstanceCallback) { - if (this._logger?.isDebugEnabled()) { - this._logger.debug('Adding instance generator for "' + identifier + '" as internal Generator. This Generator wont be used externally.'); + if (this._logger?.isSillyEnabled()) { + this._logger.silly('Adding instance generator for "' + identifier + '" as internal Generator. This Generator wont be used externally.'); } this._internalGenerators.set(identifier, cb); @@ -239,8 +239,8 @@ export class nopeDispatcher implements INopeDispatcher { unregisterInternalInstanceGenerator(identifier: string) { - if (this._logger?.isDebugEnabled()) { - this._logger.debug('Rmoving instance generator for "' + identifier + '" from internal Generator. The sytem cant create elements of this type any more.'); + if (this._logger?.isSillyEnabled()) { + this._logger.silly('Rmoving instance generator for "' + identifier + '" from internal Generator. The sytem cant create elements of this type any more.'); } this._internalGenerators.delete(identifier); @@ -267,13 +267,13 @@ export class nopeDispatcher implements INopeDispatcher { throw Error('Please Provide at least a "type" and "identifier" in the paremeters'); } - if (this._logger?.isDebugEnabled()) { - this._logger.debug('Requesting an Instance of type: "' + _defDescription.type + '" with the identifier: "' + _defDescription.identifier + '"'); + if (this._logger?.isSillyEnabled()) { + this._logger.silly('Requesting an Instance of type: "' + _defDescription.type + '" with the identifier: "' + _defDescription.identifier + '"'); } if (this._instances.has(_description.identifier)) { - if (this._logger?.isDebugEnabled()) { - this._logger.debug('Already created instance with the identifiert: "' + _defDescription.identifier + '" => returning this instance'); + if (this._logger?.isSillyEnabled()) { + this._logger.silly('Already created instance with the identifiert: "' + _defDescription.identifier + '" => returning this instance'); } // Add the Dispatcher to the Element: @@ -296,16 +296,16 @@ export class nopeDispatcher implements INopeDispatcher { if (this._internalGenerators.has(_type)) { - if (this._logger?.isDebugEnabled()) { - this._logger.debug('No instance with the identifiert: "' + _defDescription.identifier + '" found, but an internal generator is available. Using the internal one for creating the instance and requesting the "real" instance externally'); + if (this._logger?.isSillyEnabled()) { + this._logger.silly('No instance with the identifiert: "' + _defDescription.identifier + '" found, but an internal generator is available. Using the internal one for creating the instance and requesting the "real" instance externally'); } const result = await this.performCall('generateInstance_' + _description.type, [_description], { paramsHasNoCallback: true, }); - if (this._logger?.isDebugEnabled()) { - this._logger.debug('Received a description for the instance'); + if (this._logger?.isSillyEnabled()) { + this._logger.silly('Received a description for the instance'); } // Create the Instance @@ -470,12 +470,16 @@ export class nopeDispatcher implements INopeDispatcher { if (this._logger?.isErrorEnabled()) { // If there is a Logger: this._logger.error('Dispatcher "' + this.id + '" failed with request: "' + data.taskId + '"'); - this._logger.error(error); + console.error(error); + this._logger.error(error); } // An Error occourd => Forward the Error. const result: IResponseTaskMsg = { - error, + error: { + error, + msg: error.toString() + }, taskId: data.taskId, type: 'response' } @@ -508,6 +512,12 @@ export class nopeDispatcher implements INopeDispatcher { // Based on the Result of the Remote => if (task && data.error) { + if (this._logger?.isErrorEnabled()){ + this._logger.error('Failed with task ' + data.taskId); + this._logger.error('Reason: ' + data.error.msg); + console.log(data.error.error); + } + task.reject(data.error); // Clearout the Timer @@ -592,6 +602,10 @@ export class nopeDispatcher implements INopeDispatcher { if (data.dispatcher !== _this.id) { _this._mappingOfRemoteDispatchersAndServices.set(data.dispatcher, data); _this._updateExternalServices(); + if (this._logger?.isSillyEnabled()) { + // If there is a Logger: + this._logger.silly('received new services'); + } } } catch (e) { } @@ -603,6 +617,10 @@ export class nopeDispatcher implements INopeDispatcher { if (data.dispatcher !== _this.id) { _this._mappingOfRemoteDispatchersAndTopics.set(data.dispatcher, data); _this._updateExternalEvents(); + if (this._logger?.isSillyEnabled()) { + // If there is a Logger: + this._logger.silly('received new events'); + } } } catch (e) { } @@ -612,6 +630,10 @@ export class nopeDispatcher implements INopeDispatcher { try { _this._mappingOfRemoteDispatchersAndGenerators.set(data.dispatcher, data); _this._updateExternalGenerators(); + if (this._logger?.isSillyEnabled()) { + // If there is a Logger: + this._logger.silly('received new generators'); + } } catch (e) { } }) @@ -638,6 +660,11 @@ export class nopeDispatcher implements INopeDispatcher { _this._updateExternalServices(); _this._updateExternalGenerators(); _this._updateExternalEvents(); + + if (this._logger?.isDebugEnabled()) { + // If there is a Logger: + this._logger.debug('a dispatcher went offline'); + } }); this._communicator.onTaskCancelation((event) => { @@ -765,7 +792,7 @@ export class nopeDispatcher implements INopeDispatcher { for (const dispatcherInfo of this._mappingOfRemoteDispatchersAndServices.values()) { dispatcherInfo.services.map(service => _this._externalProvidedServices.add(service)); } - + // Create a Comparing loop. // The Loop checks if the element doesnt exists in the known services // before the update. @@ -914,9 +941,9 @@ export class nopeDispatcher implements INopeDispatcher { // Register Functions. this._communicator.onRpcRequest(_req, cb); - if (this._logger?.isDebugEnabled()) { + if (this._logger?.isSillyEnabled()) { // If there is a Logger: - this._logger.debug('Dispatcher "' + this.id + '" listening on: "' + _req + '"'); + this._logger.silly('Dispatcher "' + this.id + '" listening on: "' + _req + '"'); } } } @@ -1014,9 +1041,9 @@ export class nopeDispatcher implements INopeDispatcher { // Publish the Available Services. this._sendAvailableServices(); - if (this._logger?.isDebugEnabled()) { + if (this._logger?.isSillyEnabled()) { // If there is a Logger: - this._logger.debug('Dispatcher "' + this.id + '" registered: "' + _id + '"'); + this._logger.silly('Dispatcher "' + this.id + '" registered: "' + _id + '"'); } } @@ -1042,9 +1069,9 @@ export class nopeDispatcher implements INopeDispatcher { // Publish the Available Services. this._sendAvailableServices(); - if (this._logger?.isDebugEnabled()) { + if (this._logger?.isSillyEnabled()) { // If there is a Logger: - this._logger.debug('Dispatcher "' + this.id + '" unregistered: "' + _id + '"'); + this._logger.silly('Dispatcher "' + this.id + '" unregistered: "' + _id + '"'); } } @@ -1061,9 +1088,9 @@ export class nopeDispatcher implements INopeDispatcher { // Publish the Available Services. this._sendAvailableProperties(); - if (this._logger?.isDebugEnabled()) { + if (this._logger?.isSillyEnabled()) { // If there is a Logger: - this._logger.debug('Dispatcher "' + this.id + '" unregistered: "' + _id + '"'); + this._logger.silly('Dispatcher "' + this.id + '" unregistered: "' + _id + '"'); } } @@ -1490,13 +1517,13 @@ export class nopeDispatcher implements INopeDispatcher { } - if (_this._logger?.isDebugEnabled()) { - _this._logger.debug('Clearing Callbacks from ' + _taskId); + if (_this._logger?.isSillyEnabled()) { + _this._logger.silly('Clearing Callbacks from ' + _taskId); } } - if (_this._logger?.isDebugEnabled()) { - _this._logger.debug('Dispatcher "' + this.id + '" requesting externally Function "' + serviceName + '" with task: "' + _taskId + '"'); + if (_this._logger?.isSillyEnabled()) { + _this._logger.silly('Dispatcher "' + this.id + '" requesting externally Function "' + serviceName + '" with task: "' + _taskId + '"'); } // Define a Callback-Function, which will expect the Task. @@ -1592,15 +1619,15 @@ export class nopeDispatcher implements INopeDispatcher { if (_this._subscriptionMode === 'individual') { await _this._communicator.emitRpcRequest(_this._getServiceName(taskRequest.functionId, 'request'), taskRequest); - if (_this._logger && _this._logger.isDebugEnabled()) { - _this._logger.debug('Dispatcher "' + this.id + '" putting task "' + _taskId + '" on: "' + _this._getServiceName(taskRequest.functionId, 'request') + '"'); + if (_this._logger && _this._logger.isSillyEnabled()) { + _this._logger.silly('Dispatcher "' + this.id + '" putting task "' + _taskId + '" on: "' + _this._getServiceName(taskRequest.functionId, 'request') + '"'); } } else { await _this._communicator.emitRpcRequest('request', taskRequest); - if (_this._logger && _this._logger.isDebugEnabled()) { - _this._logger.debug('Dispatcher "' + this.id + '" putting task "' + _taskId + '" on: "request"'); + if (_this._logger && _this._logger.isSillyEnabled()) { + _this._logger.silly('Dispatcher "' + this.id + '" putting task "' + _taskId + '" on: "request"'); } } diff --git a/lib/loader/loadPackages.ts b/lib/loader/loadPackages.ts index b4b1f3c..e4c49b4 100644 --- a/lib/loader/loadPackages.ts +++ b/lib/loader/loadPackages.ts @@ -2,7 +2,7 @@ * @author Martin Karkowski * @email m.karkowski@zema.de * @create date 2020-11-11 14:19:10 - * @modify date 2020-11-11 16:12:18 + * @modify date 2020-11-12 13:53:17 * @desc [description] */ @@ -15,7 +15,7 @@ import { IInstanceCreationMsg } from "../types/nope/nopeCommunication.interface" import { IPackageDescription } from "../types/nope/nopePackage.interface"; import { INopePackageLoader } from "../types/nope/nopePackageLoader.interface"; -export interface IConfigFile { +export interface IPackageConfig { nameOfPackage: string; defaultInstances: { options: Partial; @@ -31,6 +31,14 @@ export interface IConfigFile { path: string; } +export interface IConfigFile { + functions: { + path: string, + functions: [] + }[], + packages: IPackageConfig[] +} + /** * List the available Packages * @@ -65,6 +73,33 @@ export async function listPackages(dir: string = './modules') { return ret; } +export async function listFunctions(dir: string = './modules') { + + // Define the Return Array. + const ret = new Array<{ + content: any, + path: string, + }>(); + + // Scan for the Package-Files + // And iterate over them. + for (const fileName of await listFiles(dir, '.functions.js')) { + // Now Try to load a Package, to test whether is is an assembly. + try { + ret.push({ + content: (await import(resolve(fileName))).DESCRIPTION, + path: fileName + }); + } catch (e) { + getNopeLogger('helper-list-functions').error('Failed Loading the functions in file: ' + fileName); + getNopeLogger('helper-list-functions').error(e); + } + + } + + return ret; +} + /** * Helper Function to write a default configuration. * @@ -77,8 +112,7 @@ export async function writeDefaultConfig( filename: string = join(resolve(process.cwd()), 'config', 'assembly.json') ) { // Determine all Packages - const packages = await listPackages(dir); - const valuesToStore: IConfigFile[] = packages.map(item => { + const packages: IPackageConfig[] = (await listPackages(dir)).map(item => { return { nameOfPackage: item.package.nameOfPackage, defaultInstances: item.package.defaultInstances, @@ -87,7 +121,18 @@ export async function writeDefaultConfig( } }); - await createFile(filename, JSON.stringify(valuesToStore, undefined, 4)); + const functions = (await listFunctions(dir)).map(item => { + return { + path: item.path, + functions: Object.getOwnPropertyNames(item.content || {}) + } + }); + + + await createFile(filename, JSON.stringify({ + functions, + packages + }, undefined, 4)); } /** @@ -98,7 +143,10 @@ export async function writeDefaultConfig( * @param {string} filename */ export async function loadPackages(loader: INopePackageLoader,filename: string = join(resolve(process.cwd()), 'config', 'assembly.json')) { - let data: IConfigFile[] = []; + let data: IConfigFile = { + functions: [], + packages: [] + }; try { /** Load the File and Parse it. */ @@ -126,7 +174,7 @@ export async function loadPackages(loader: INopePackageLoader,filename: string = // Scan for the Package-Files // And iterate over them. - for (const item of data) { + for (const item of data.packages) { // Now Try to load a Package, to test whether is is an assembly. try { const loadedPackage = (await import(resolve(item.path))).DESCRIPTION as IPackageDescription; @@ -149,4 +197,49 @@ export async function loadPackages(loader: INopePackageLoader,filename: string = // Generate the instances. await loader.generateInstances() +} + +export async function loadFunctions(filename: string = join(resolve(process.cwd()), 'config', 'assembly.json')){ + let data: IConfigFile = { + functions: [], + packages: [] + }; + + try { + /** Load the File and Parse it. */ + data = JSON.parse( + await readFile(filename,{encoding: 'utf8'}) + ); + } catch (e) { + + // Generate the Default File + await writeDefaultConfig(filename); + + // Show an Hint + getNopeLogger('helper-load-packages').warn('No configuration was present. Created a new config file in ' + filename); + + // Readin the newly created Data. + data = JSON.parse(await readFile( + filename, { + encoding:'utf8' + }) + ); + } + + // Define the Return Array. + const functionPackages = new Array(); + + // Scan for the Package-Files + // And iterate over them. + for (const item of data.functions) { + // Now Try to load a Package, to test whether is is an assembly. + try { + const loadedFunction = (await import(resolve(item.path))).DESCRIPTION as IPackageDescription; + functionPackages.push(loadedFunction); + } catch (e) { + getNopeLogger('helper-load-packages').error('Failed Loading function-file at ' + item.path); + } + } + + return functionPackages; } \ No newline at end of file diff --git a/lib/loader/nopePackageLoader.ts b/lib/loader/nopePackageLoader.ts index 83b3d9e..411a9cb 100644 --- a/lib/loader/nopePackageLoader.ts +++ b/lib/loader/nopePackageLoader.ts @@ -2,7 +2,7 @@ * @author Martin Karkowski * @email m.karkowski@zema.de * @create date 2018-07-01 09:10:35 - * @modify date 2020-11-07 01:15:42 + * @modify date 2020-11-12 13:17:43 * @desc [description] */ @@ -444,6 +444,8 @@ export class NopePackageLoader implements INopePackageLoader { 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; diff --git a/lib/logger/nopeLogger.ts b/lib/logger/nopeLogger.ts index 66fb7e8..6c757d6 100644 --- a/lib/logger/nopeLogger.ts +++ b/lib/logger/nopeLogger.ts @@ -5,6 +5,7 @@ import { SPLITCHAR } from "../helpers/objectMethods"; * Contains the Valid Types of the Loger. */ export type LoggerLevel = 'error' | 'warn' | 'info' | 'http' | 'verbose' | 'debug' | 'silly'; +export const LoggerLevels = ['error' , 'warn' , 'info' , 'http' , 'verbose' , 'debug' , 'silly'] const order: {[K in LoggerLevel]: number} = { error: 0, diff --git a/src/customServer.js b/lib/open-api/customServer.js similarity index 100% rename from src/customServer.js rename to lib/open-api/customServer.js diff --git a/src/getBackendAccessors.ts b/lib/open-api/getBackendAccessors.ts similarity index 100% rename from src/getBackendAccessors.ts rename to lib/open-api/getBackendAccessors.ts diff --git a/src/runMiddleware.ts b/lib/open-api/runMiddleware.ts similarity index 94% rename from src/runMiddleware.ts rename to lib/open-api/runMiddleware.ts index 1df750f..93f5208 100644 --- a/src/runMiddleware.ts +++ b/lib/open-api/runMiddleware.ts @@ -32,16 +32,16 @@ export function runMiddleware(app) { new_req[i] = options[i]; } } else { - new_req = createReq(path, options); + new_req = _createReq(path, options); } - new_res = createRes(callback); + new_res = _createRes(callback); app(new_req, new_res); }; /* end - APP.runMiddleware*/ }; -function createReq(path, options) { +function _createReq(path, options) { if (!options) options = {}; var req = _.extend( { @@ -58,7 +58,7 @@ function createReq(path, options) { // req.connection=_req.connection return req; } -function createRes(callback) { +function _createRes(callback) { var res: any = { _removedHeader: {}, }; diff --git a/src/runOpenApiServer.ts b/lib/open-api/runOpenApiServer.ts similarity index 100% rename from src/runOpenApiServer.ts rename to lib/open-api/runOpenApiServer.ts diff --git a/src/startBackend.ts b/lib/open-api/startOpenApiBackend.ts similarity index 87% rename from src/startBackend.ts rename to lib/open-api/startOpenApiBackend.ts index ef93846..33c74a8 100644 --- a/src/startBackend.ts +++ b/lib/open-api/startOpenApiBackend.ts @@ -1,3 +1,11 @@ +/** + * @author Martin Karkowski + * @email m.karkowski@zema.de + * @create date 2020-11-12 11:21:41 + * @modify date 2020-11-12 11:21:43 + * @desc [description] + */ + import * as bodyParser from "body-parser"; import * as cors from 'cors'; import * as express from "express"; @@ -7,8 +15,7 @@ import { assignIn } from 'lodash'; import { join } from "path"; import "reflect-metadata"; import { Logger } from 'winston'; -import { IoSocketServer } from "../lib/communication/IoSocketServer"; -import { getLinkedDispatcher } from "../lib/dispatcher/getLinkedDispatcher"; +import { INopeDispatcher } from "../types/nope/nopeDispatcher.interface"; import { getBackendAccesors } from './getBackendAccessors'; /** @@ -35,9 +42,12 @@ function _genApiDoc(title: string, version: string, basePath: string, definition /** * Function to start a Open-API-Server + * @param _dispatcher The Dispatcher, which should be used. * @param options Options for the Server */ -export async function startBackend(options: { +export async function startOpenApiBackend( +_dispatcher: INopeDispatcher, +options: { port?: number, backendPort?: number, basePath?: string, @@ -77,11 +87,7 @@ export async function startBackend(options: { routesGlob: '**/*.{ts,js}', routesIndexFileRegExp: /(?:index)?\.[tj]s$/, dependencies: { - _dispatcher: getLinkedDispatcher( - { - communicator: new IoSocketServer(opts.backendPort, 'generic', 'generic') - } - ) + _dispatcher }, }); diff --git a/lib/parsers/open-api/OpenApiParser.ts b/lib/parsers/open-api/OpenApiParser.ts index 6a90cfd..8a228b8 100644 --- a/lib/parsers/open-api/OpenApiParser.ts +++ b/lib/parsers/open-api/OpenApiParser.ts @@ -76,6 +76,9 @@ export async function parseModuleToOpenAPI(description: IParsableDescription, op // 1. Specify the Mode (No Params = GET, else POST) (method as any).mode = method.schema.inputs.length > 0 ? 'POST' : 'GET'; + (method as any).required = method.schema.inputs.filter(param => !param.optional).map(param => param.name); + (method as any).hasInput = method.schema.inputs.length > 0; + // Now adapt the Schema of the Method. // Iterate over the Inputs, add a Description if not provided // And determine whehter the Parameters are required or not. @@ -83,8 +86,6 @@ export async function parseModuleToOpenAPI(description: IParsableDescription, op // Provide a Description. (If not provided) param.description = param.description || 'Not provided'; - // Open API uses "required" instead of optional => invert - param.optional = !param.optional; if (options.sharedDefinitions){ param.schema.definitions = undefined; @@ -111,6 +112,8 @@ export async function parseModuleToOpenAPI(description: IParsableDescription, op (method as any).resultDescription = method.schema.outputs.description || 'Not Provided'; // And add a Schema for the Return type. (method as any).parsedOutput = JSON.stringify(method.schema.outputs); + (method as any).parsedInput = JSON.stringify(_unifySchema(method.schema.inputs, options)); + (method as any).hasInput = method.schema.inputs.length > 0; (method as any).tag = description.name; // Determine the Filename. @@ -134,7 +137,7 @@ export async function parseModuleToOpenAPI(description: IParsableDescription, op for (const imp of imports){ const relativDir = relative(fileDir, imp.dir); (method as any)[imp.name] = replaceAll(join(relativDir, imp.fileName), '\\', '/'); - } + } // Write down the Schema: await createFile( @@ -192,8 +195,6 @@ export async function parseFunctionToOpenAPI(description: IFunctionOptions, opti // Provide a Description. (If not provided) param.description = param.description || 'Not provided'; - // Open API uses "required" instead of optional => invert - param.optional = !param.optional; if (options.sharedDefinitions){ param.schema.definitions = undefined; @@ -221,10 +222,14 @@ export async function parseFunctionToOpenAPI(description: IFunctionOptions, opti // And add a Schema for the Return type. (_description as any).parsedOutput = JSON.stringify(_description.schema.outputs); (_description as any).tag = 'generic-services'; + (_description as any).parsedInput = JSON.stringify(_unifySchema(_description.schema.inputs, options)); + (_description as any).hasInput = _description.schema.inputs.length > 0; + (_description as any).required = _description.schema.inputs.filter(param => !param.optional).map(param => param.name); + (_description as any).name = _description.id; // Determine the Filename. const fileDir = join(options.outputDir, 'generic-services'); - const fileName = join(fileDir, _description.id+'.ts'); + const fileName = join(fileDir, _description.id + '.ts'); // Determine the Import Pathes. const imports = [ diff --git a/lib/parsers/open-api/templates/function.handlebars b/lib/parsers/open-api/templates/function.handlebars index cb639b1..3808c9a 100644 --- a/lib/parsers/open-api/templates/function.handlebars +++ b/lib/parsers/open-api/templates/function.handlebars @@ -34,16 +34,15 @@ export default function (_dispatcher: INopeDispatcher) { {{#if methodDescription}}summary: '{{methodDescription}}',{{/if}} {{#if operationId}}operationId: '{{operationId}}',{{/if}} parameters: [ - {{#each schema.inputs}} + {{#if hasInput}} { name: '{{name}}', in: "body", description: '{{description}}', - required: {{optional}}, - schema: {{{parsedSchema}}} - - }{{#unless @last}},{{/unless}} - {{/each}} + required: true, + schema: {{{parsedInput}}} + } + {{/if}} ], responses: { 200: { diff --git a/lib/parsers/open-api/templates/function.js.handlebars b/lib/parsers/open-api/templates/function.js.handlebars index cb639b1..4fd28b8 100644 --- a/lib/parsers/open-api/templates/function.js.handlebars +++ b/lib/parsers/open-api/templates/function.js.handlebars @@ -43,7 +43,17 @@ export default function (_dispatcher: INopeDispatcher) { schema: {{{parsedSchema}}} }{{#unless @last}},{{/unless}} - {{/each}} + {{/each}} + + schema: { + type: 'object', + properties: { + {{#each schema.inputs}} + {{name}}: {{{parsedSchema}}}{{#unless @last}},{{/unless}} + {{/each}} + }, + required: [{{#each required}}element{{#unless @last}},{{/unless}}{{/each}}] + } ], responses: { 200: { diff --git a/lib/types/nope/nopeCommunication.interface.ts b/lib/types/nope/nopeCommunication.interface.ts index 359c7e3..64b1a14 100644 --- a/lib/types/nope/nopeCommunication.interface.ts +++ b/lib/types/nope/nopeCommunication.interface.ts @@ -2,7 +2,7 @@ * @author Martin Karkowski * @email m.karkowski@zema.de * @create date 2020-10-12 18:31:54 - * @modify date 2020-11-06 23:17:07 + * @modify date 2020-11-12 14:26:21 * @desc [description] */ @@ -439,7 +439,10 @@ export type IResponseTaskMsg = { * * @type {*} */ - error?: any + error?: { + error: any, + msg: string + } } export type IAvailableServicesMsg = { diff --git a/modules/xetics-lean-connector/src/xetics.package.ts b/modules/xetics-lean-connector/src/xetics.package.ts index 9f7c980..7a32ae1 100644 --- a/modules/xetics-lean-connector/src/xetics.package.ts +++ b/modules/xetics-lean-connector/src/xetics.package.ts @@ -2,11 +2,12 @@ * @author Martin Karkowski * @email m.karkowski@zema.de * @create date 2020-11-10 16:53:43 - * @modify date 2020-11-11 16:57:30 + * @modify date 2020-11-12 14:29:57 * @desc [description] */ import { IPackageDescription } from "../../../lib/types/nope/nopePackage.interface"; +import { startTask } from "./xetics.functions"; import { XeticsInterfaceClient } from "./xetics.module"; const TYPES = { @@ -22,7 +23,7 @@ export const DESCRIPTION: IPackageDescription = { identifier: "xetics-einlegen", params: [ 'Einlegen', - 0 + 1 ], type: XeticsInterfaceClient.prototype.constructor.name.toString() }, @@ -33,7 +34,7 @@ export const DESCRIPTION: IPackageDescription = { identifier: "xetics-schrauben", params: [ 'Verschrauben', - 0 + 1 ], type: XeticsInterfaceClient.prototype.constructor.name.toString() }, @@ -43,7 +44,7 @@ export const DESCRIPTION: IPackageDescription = { identifier: "xetics-qualitaetskontrolle", params: [ 'Qualitaetskontrolle', - 0 + 1 ], type: XeticsInterfaceClient.prototype.constructor.name.toString() }, @@ -53,7 +54,7 @@ export const DESCRIPTION: IPackageDescription = { identifier: "xetics-verpacken", params: [ 'Verpacken', - 0 + 1 ], type: XeticsInterfaceClient.prototype.constructor.name.toString() }, @@ -64,7 +65,7 @@ export const DESCRIPTION: IPackageDescription = { providedClasses: [ { description: { - name: XeticsInterfaceClient.constructor.name, + name: XeticsInterfaceClient.prototype.constructor.name.toString(), selector: TYPES.xeticsClient, type: XeticsInterfaceClient }, @@ -73,7 +74,14 @@ export const DESCRIPTION: IPackageDescription = { } } ], - providedFunctions: [], + providedFunctions: [ + { + function: startTask, + options: { + id: 'startTask' + } + } + ], requiredPackages: [], types: TYPES } diff --git a/nopeconfig.json b/nopeconfig.json index 9885de5..253ca64 100644 --- a/nopeconfig.json +++ b/nopeconfig.json @@ -12,5 +12,5 @@ }, "tempDir": "./temp/", "configDir": "./config", - "modules": "./modules" + "modules": "./dist/modules" } \ No newline at end of file