diff --git a/lib/dispatcher/ConnectivityManager/ConnectivityManager.ts b/lib/dispatcher/ConnectivityManager/ConnectivityManager.ts index efa1aa6..ac95bac 100644 --- a/lib/dispatcher/ConnectivityManager/ConnectivityManager.ts +++ b/lib/dispatcher/ConnectivityManager/ConnectivityManager.ts @@ -90,9 +90,10 @@ export class NopeConnectivityManager implements INopeConnectivityManager { public readonly ready: INopeObservable; public readonly dispatchers: IMapBasedMergeData< - string, - string, - INopeStatusInfo + string, // Dispatcher ID + INopeStatusInfo, // Orginal Message + string, // Dispatcher ID + string // Dispatcher ID >; /** diff --git a/lib/dispatcher/InstanceManager/InstanceManager.ts b/lib/dispatcher/InstanceManager/InstanceManager.ts index 5d27dd3..d186c8d 100644 --- a/lib/dispatcher/InstanceManager/InstanceManager.ts +++ b/lib/dispatcher/InstanceManager/InstanceManager.ts @@ -114,15 +114,22 @@ export class NopeInstanceManager implements INopeInstanceManager { * @type {IMapBasedMergeData} * @memberof NopeInstanceManager */ - public readonly constructors: IMapBasedMergeData; + public readonly constructors: IMapBasedMergeData< + string, // Dispatcher ID + string[], + string, // Dispatcher ID + string // Available Instances + >; /** * Element showing the available instances. * Its more or less a map, that maps the * instances with their dispatchers. * - * T = INopeModuleDescription. - * K = dispatcher - ids + * originalKey = DispatcherID (string); + * originalValue = Available Instance Messages (IAvailableInstancesMsg); + * extractedKey = The name of the Instance (string); + * extractedValue = instance-description (INopeModuleDescription); * * @author M.Karkowski * @type {IMapBasedMergeData< @@ -133,9 +140,10 @@ export class NopeInstanceManager implements INopeInstanceManager { * @memberof NopeInstanceManager */ public readonly instances: IMapBasedMergeData< - INopeModuleDescription, - string, - IAvailableInstancesMsg + string, // Dispatcher ID + IAvailableInstancesMsg, // Available Instance Messages + string, // The name of the Instance? + INopeModuleDescription // The instance-description >; /** @@ -195,11 +203,13 @@ export class NopeInstanceManager implements INopeInstanceManager { this._mappingOfRemoteDispatchersAndGenerators = new Map(); this.constructors = new MapBasedMergeData( this._mappingOfRemoteDispatchersAndGenerators - ); + ) as MapBasedMergeData; this._mappingOfRemoteDispatchersAndInstances = new Map(); this.instances = new MapBasedMergeData( - this._mappingOfRemoteDispatchersAndInstances + this._mappingOfRemoteDispatchersAndInstances, + "instances/+", + "instances/+/identifier" ); this.internalInstances = new NopeObservable(); @@ -214,11 +224,13 @@ export class NopeInstanceManager implements INopeInstanceManager { services, ] of _this._rpcManager.services.originalData.entries()) { // Filter the Generators based on the existing services - const generators = services.services.filter((svc) => - svc.startsWith( - `nope${SPLITCHAR}core${SPLITCHAR}constructor${SPLITCHAR}` + const generators = services.services + .filter((svc) => + svc?.id.startsWith( + `nope${SPLITCHAR}core${SPLITCHAR}constructor${SPLITCHAR}` + ) ) - ); + .map((item) => item.id); // If the Dispatcher has a generator we will add it. if (generators.length) { diff --git a/lib/dispatcher/RpcManager/NopeRpcManager.spec.ts b/lib/dispatcher/RpcManager/NopeRpcManager.spec.ts index 24fd715..154f06d 100644 --- a/lib/dispatcher/RpcManager/NopeRpcManager.spec.ts +++ b/lib/dispatcher/RpcManager/NopeRpcManager.spec.ts @@ -63,7 +63,7 @@ describe("NopeRpcManager", function () { await sleep(10); // Get the Services - const services = manager.services.data.getContent(); + const services = manager.services.extractedKey; expect(services).to.include("helloWorld"); }); @@ -213,7 +213,7 @@ describe("NopeRpcManager", function () { await sleep(10); // Get the Services - const services = caller.services.data.getContent(); + const services = caller.services.extractedKey; expect(services).to.include("helloWorld"); }); @@ -228,7 +228,7 @@ describe("NopeRpcManager", function () { await sleep(10); // Get the Services - const services = caller.services.data.getContent(); + const services = caller.services.extractedKey; expect(services).to.include((r as any).id); }); diff --git a/lib/dispatcher/RpcManager/NopeRpcManager.ts b/lib/dispatcher/RpcManager/NopeRpcManager.ts index 342f205..f4f5693 100644 --- a/lib/dispatcher/RpcManager/NopeRpcManager.ts +++ b/lib/dispatcher/RpcManager/NopeRpcManager.ts @@ -119,17 +119,20 @@ export class NopeRpcManager * Its more or less a map, that maps the * services with their dispatchers. * - * T = services name. - * K = dispatcher - ids + * OriginalKey = Dispatcher ID (string); + * OriginalValue = Original Message (IAvailableServicesMsg); + * ExtractedKey = Function ID (string); + * ExtractedValue = FunctionOptions (T); * * @author M.Karkowski * @type {IMapBasedMergeData} * @memberof INopeRpcManager */ public readonly services: IMapBasedMergeData< - string, - string, - IAvailableServicesMsg + string, // Dispatcher ID + IAvailableServicesMsg, // Original Message + string, // Function ID + T // Function Options >; /** @@ -242,7 +245,8 @@ export class NopeRpcManager this.services = new MapBasedMergeData( this._mappingOfDispatchersAndServices, - "services" + "services/+", + "services/+/id" ); this.onCancelTask = new NopeEventEmitter(); @@ -453,7 +457,9 @@ export class NopeRpcManager // Define the Message const message: IAvailableServicesMsg = { dispatcher: this._id, - services: Array.from(this._registeredServices.keys()), + services: Array.from(this._registeredServices.values()).map( + (item) => item.options + ), }; if (this._logger?.enabledFor(DEBUG)) { @@ -783,6 +789,9 @@ export class NopeRpcManager let _id = options.id || generateId(); _id = options.addNopeServiceIdPrefix ? this.adaptServiceId(_id) : _id; + // Make shure we assign our id + options.id = _id; + let _func = func; if (!this.__warned && !isAsyncFunction(func)) { diff --git a/lib/dispatcher/RpcManager/selectors.ts b/lib/dispatcher/RpcManager/selectors.ts index a172be3..ceb9866 100644 --- a/lib/dispatcher/RpcManager/selectors.ts +++ b/lib/dispatcher/RpcManager/selectors.ts @@ -31,7 +31,7 @@ export function generateSelector( case "master": return async (opts) => { const masterId = core.connectivityManager.master.id; - const data = core.rpcManager.services.reverseSimplified; + const data = core.rpcManager.services.keyMappingReverse; if (data.has(opts.serviceName)) { const arr = Array.from(data.get(opts.serviceName)); @@ -45,7 +45,7 @@ export function generateSelector( }; case "first": return async (opts) => { - const data = core.rpcManager.services.reverseSimplified; + const data = core.rpcManager.services.keyMappingReverse; if (data.has(opts.serviceName)) { const arr = Array.from(data.get(opts.serviceName)); @@ -75,7 +75,7 @@ export function generateSelector( // 1. Get the current Host name of our dispatcher const host = core.connectivityManager.info.host.name; return async (opts) => { - const data = core.rpcManager.services.reverseSimplified; + const data = core.rpcManager.services.keyMappingReverse; if (data.has(opts.serviceName)) { const items = Array.from(data.get(opts.serviceName)); diff --git a/lib/dispatcher/baseServices/connectivy.ts b/lib/dispatcher/baseServices/connectivy.ts index 45774d7..e57ab76 100644 --- a/lib/dispatcher/baseServices/connectivy.ts +++ b/lib/dispatcher/baseServices/connectivy.ts @@ -89,7 +89,7 @@ export async function generatePingAccessors(dispatcher: INopeDispatcher) { // Function to Ping all Services const pingAll = async () => { const dispatchers = Array.from( - dispatcher.connectivityManager.dispatchers.reverseSimplified.get( + dispatcher.connectivityManager.dispatchers.keyMappingReverse.get( serviceName ) ); @@ -233,12 +233,12 @@ export async function generateDefineMaster(dispatcher: INopeDispatcher) { const service = `nope/baseService/defineAsMaster`; // Get the Matching Dispatchers. - const dispatchers = - dispatcher.connectivityManager.dispatchers.reverseSimplified.get( + const relevantDispatchers = + dispatcher.rpcManager.services.keyMappingReverse.get( `nope/baseService/defineAsMaster` ); - dispatchers.delete(masterId); - const targets = Array.from(dispatchers); + relevantDispatchers.delete(masterId); + const targets = Array.from(relevantDispatchers); await dispatcher.rpcManager.performCall( targets.map((_) => service), diff --git a/lib/dispatcher/nopeDispatcher.ts b/lib/dispatcher/nopeDispatcher.ts index a9b5d4b..8fcc6bf 100644 --- a/lib/dispatcher/nopeDispatcher.ts +++ b/lib/dispatcher/nopeDispatcher.ts @@ -73,7 +73,10 @@ export class NopeDispatcher extends NopeCore implements INopeDispatcher { .map((item) => item.identifier); break; case "services": - items = this.rpcManager.services.data.getContent(); + items = Array.from( + // Extract the Ids of the Services + this.rpcManager.services.simplified.keys() + ); break; case "properties": items = this.dataDistributor.publishers.data.getContent(); diff --git a/lib/helpers/mapMethods.ts b/lib/helpers/mapMethods.ts index abee9d0..2028e6a 100644 --- a/lib/helpers/mapMethods.ts +++ b/lib/helpers/mapMethods.ts @@ -4,7 +4,7 @@ * @desc [description] */ -import { rgetattr } from "./objectMethods"; +import { convertData, deepEqual, rgetattr } from "./objectMethods"; const __sentinal = { unique: "value", @@ -15,12 +15,42 @@ const __sentinal = { * * @author M.Karkowski * @export - * @template K - * @template V - * @param {Map} map - * @return {*} {Set} + * @template D Return Type + * @template K The Key of the Map + * @template V The Value of the Map + * @param {Map} map The Map + * @param {string} [path=""] The Path of the Data to extract. + * @param {string} [pathKey=null] The Path of the unique key. If set to `null` -> The Item is selected directly. + * @return {*} {Set} */ -export function extractUniqueValues(map: Map, path = ""): Set { +export function extractUniqueValues( + map: Map, + path = "", + pathKey: string = null +): Set { + if (pathKey === null) { + pathKey = path; + } + + if (path !== pathKey) { + const items = extractValues(map, path) as D[]; + const itemKeys = new Set(); + + const ret: D[] = []; + + for (const item of items) { + const key = rgetattr(item, pathKey); + + if (!itemKeys.has(key)) { + itemKeys.add(key); + + ret.push(item); + } + + return new Set(ret); + } + } + return new Set(extractValues(map, path)); } @@ -52,62 +82,165 @@ export function extractValues(map: Map, path = ""): Array { } /** + * Transform the values. * * * @author M.Karkowski * @export - * @template D - * @template K - * @param {Map} map - * @param {string} [path=""] - * @return {*} {Map} + * @template ExtractedValue + * @template ExtractedKey + * @template OriginalKey + * @param {Map} map + * @param {string} [pathExtractedValue=""] + * @param {string} [pathExtractedKey=null] Additional Path of a Key. + * @return {*} {Map} */ -export function transformValues( - map: Map, - path = "" -): Map { - const m = new Map(); +export function tranformMap< + ExtractedKey = string, + ExtractedValue = any, + OriginalKey = string +>( + map: Map, + pathExtractedValue: string, + pathExtractedKey: string, + equals: (a: ExtractedValue, b: ExtractedValue) => boolean = deepEqual +) { + const keyMapping = new Map>(); + const reverseKeyMapping = new Map>(); + const conflicts = new Map>(); + const extractedMap = new Map(); + const orgKeyToExtractedValue = new Map>(); + const amountOf = new Map(); + const props: { + query: string; + key: string; + }[] = []; + + if (pathExtractedKey) { + props.push({ + key: "key", + query: pathExtractedKey, + }); + } + + if (pathExtractedValue) { + props.push({ + key: "value", + query: pathExtractedValue, + }); + } + + // Iterate over the Entries of the Map. + // then we will extract the data stored in the Value. for (const [k, v] of map.entries()) { - if (path) { - const data: D | typeof __sentinal = rgetattr(v, path, __sentinal); - if (data !== __sentinal) { - m.set(k, data as D); + const extracted = convertData<{ key: ExtractedKey; value: ExtractedValue }>( + v, + props + ); + + // Store the Key. + keyMapping.set(k, new Set()); + orgKeyToExtractedValue.set(k, new Set()); + + for (const item of extracted) { + if (extractedMap.has(item.key)) { + // If the extracted new key has already been defined, + // we have to determine whether the stored item matches + // the allready provided definition. + if (!equals(extractedMap.get(item.key), item.value)) { + // Conflict detected + if (!conflicts.has(item.key)) { + conflicts.set(item.key, new Set()); + } + + // Store the conflict. + conflicts.get(item.key).add(item.value); + conflicts.get(item.key).add(extractedMap.get(item.key)); + } else { + // Store the determined amount. + amountOf.set(item.key, (amountOf.get(item.key) || 0) + 1); + } + } else { + // Store the item. + extractedMap.set(item.key, item.value); + + // Store the determined amount. + amountOf.set(item.key, (amountOf.get(item.key) || 0) + 1); } - } else { - m.set(k, v as any as D); + + // If the reverse haven't been set ==> create it. + if (!reverseKeyMapping.has(item.key)) { + reverseKeyMapping.set(item.key, new Set()); + } + + // Store the mapping of new-key --> org-key. + reverseKeyMapping.get(item.key).add(k); + // Store the mapping of org-key --> new-key. + keyMapping.get(k).add(item.key); + orgKeyToExtractedValue.get(k).add(item.value); } } - return m; + return { + extractedMap, + keyMapping, + conflicts, + keyMappingReverse: reverseKeyMapping, + orgKeyToExtractedValue, + amountOf, + }; } /** * Reverses the given map. * + * If the path is provided, the Data is extracted based on the given path. + * If the `pathKey`, a different Key is used. + * * @author M.Karkowski * @export * @template K * @template V - * @param {Map} map + * @param {Map} map + * @param {string} [path=""] + * @param {string} [pathKey=null] * @return {*} {Map>} */ -export function reverse(map: Map): Map> { +export function reverse( + map: Map, + path: string = "", + pathKey: string = null +): Map> { const m = new Map>(); + if (pathKey === null) { + pathKey = path; + } + for (const [k, v] of map.entries()) { - if (Array.isArray(v)) { - for (const _v of v) { + let keyToUse = k; + if (pathKey) { + keyToUse = rgetattr(v, pathKey, __sentinal); + } + + let valueToUse = v; + if (path) { + valueToUse = rgetattr(v, path, __sentinal); + } + + if (Array.isArray(valueToUse)) { + for (const _v of valueToUse) { if (!m.has(_v)) { m.set(_v, new Set()); } - m.get(_v).add(k); + m.get(_v).add(keyToUse); } } else { - if (!m.has(v)) { - m.set(v, new Set()); + if (!m.has(valueToUse)) { + m.set(valueToUse, new Set()); } - m.get(v).add(k); + m.get(valueToUse).add(keyToUse); } } diff --git a/lib/helpers/mergedData.spec.ts b/lib/helpers/mergedData.spec.ts index cd399b3..06d5432 100644 --- a/lib/helpers/mergedData.spec.ts +++ b/lib/helpers/mergedData.spec.ts @@ -92,7 +92,7 @@ describe("MapBasedMergeData", function () { d.update(); - expect([...d.reverseSimplified.keys()]).contains("b"); + expect([...d.keyMappingReverse.keys()]).contains("b"); done(); }); @@ -105,7 +105,7 @@ describe("MapBasedMergeData", function () { d.update(); - expect([...d.reverseSimplified.keys()]).contains("b"); + expect([...d.keyMappingReverse.keys()]).contains("b"); done(); }); @@ -150,6 +150,87 @@ describe("MapBasedMergeData", function () { }); }); + it("data subscription - nested data", function (done) { + const m = new Map(); + const d_1 = new MapBasedMergeData< + string, + { key: string; data: string }, + string, + { key: string; data: string } + >(m, "", "key"); + m.set("a", { key: "keyA", data: "dataA" }); + m.set("b", { key: "keyB", data: "dataB" }); + + d_1.update(); + + assert.isTrue( + d_1.amountOf.size === 2, + "The Element contains 3 different items." + ); + + expect([...d_1.keyMapping.keys()]).to.contain("a"); + expect([...d_1.keyMappingReverse.keys()]).to.contain("keyA"); + expect([...d_1.simplified.keys()]).to.contain("keyA"); + expect([...d_1.keyMappingReverse.values()]).to.contain("keyA"); + + const d_2 = new MapBasedMergeData< + string, + { key: string; data: string }, + string, + string + >(m, "data", "key"); + d_2.update(); + + expect([...d_1.keyMapping.keys()]).to.contain("a"); + expect([...d_1.keyMappingReverse.keys()]).to.contain("keyA"); + expect([...d_1.simplified.keys()]).to.contain("keyA"); + expect([...d_1.keyMappingReverse.keys()]).to.contain("dataB"); + + done(); + }); + + // it("data subscription - nested data array", function (done) { + // const m = new Map(); + // const d_1 = new MapBasedMergeData< + // string, + // { key: string; data: string }[], + // string, + // { key: string; data: string } + // >(m, "", "key"); + // m.set("a", [ + // { key: "keyA", data: "dataA" }, + // { key: "keyB", data: "dataB" }, + // ]); + // m.set("b", [{ key: "keyC", data: "dataC" }]); + + // d_1.update(); + + // assert.isTrue( + // d_1.amountOf.size === 2, + // "The Element contains 3 different items." + // ); + + // expect([...d_1.keyMapping.keys()]).to.contain("a"); + // expect([...d_1.keyMappingReverse.keys()]).to.contain("keyA"); + // expect([...d_1.simplified.keys()]).to.contain("keyA"); + // expect([...d_1.keyMappingReverse.values()]).to.contain("keyA"); + + // const d_2 = new MapBasedMergeData< + // string, + // { key: string; data: string }, + // string, + // string + // >(m, "data", "key"); + // d_2.update(); + + // expect([...d_1.keyMapping.keys()]).to.contain("a"); + // expect([...d_1.keyMappingReverse.keys()]).to.contain("keyA"); + // expect([...d_1.simplified.keys()]).to.contain("keyA"); + // expect([...d_1.keyMappingReverse.keys()]).to.contain("dataB"); + + // done(); + // }); + it("data subscription. Update called twice", function (done) { const m = new Map(); const d = new MapBasedMergeData(m); diff --git a/lib/helpers/mergedData.ts b/lib/helpers/mergedData.ts index 4e033ed..4c9ec58 100644 --- a/lib/helpers/mergedData.ts +++ b/lib/helpers/mergedData.ts @@ -8,14 +8,11 @@ import { NopeEventEmitter } from "../eventEmitter/nopeEventEmitter"; import { determineDifference } from "../helpers/setMethods"; import { NopeObservable } from "../observables/nopeObservable"; import { INopeEventEmitter, INopeObservable } from "../types/nope"; -import { IMergeData } from "../types/nope/nopeHelpers.interface"; -import { countElements } from "./arrayMethods"; import { - extractUniqueValues, - extractValues, - reverse, - transformValues, -} from "./mapMethods"; + IMapBasedMergeData, + IMergeData, +} from "../types/nope/nopeHelpers.interface"; +import { extractUniqueValues, tranformMap } from "./mapMethods"; export class MergeData implements IMergeData { /** @@ -92,22 +89,48 @@ export class MergeData implements IMergeData { } } -export class MapBasedMergeData - extends MergeData> - implements IMergeData> +export class MapBasedMergeData< + OriginalKey, + OriginalValue, + ExtractedKey = OriginalKey, + ExtractedValue = OriginalValue + > + extends MergeData> + implements + IMapBasedMergeData< + OriginalKey, + OriginalValue, + ExtractedKey, + ExtractedValue + > { - public amountOf: Map; - public simplified: Map; - public reverseSimplified: Map>; + public amountOf: Map; + public simplified: Map; + public keyMapping: Map>; + public keyMappingReverse: Map>; + public conflicts: Map>; + public orgKeyToExtractedValue: Map>; + public extractedKey: ExtractedKey[]; + public extractedValue: ExtractedValue[]; - constructor(originalData: Map, protected _path = "") { + constructor( + originalData: Map, + protected _path: keyof OriginalValue | string = "", + protected _pathKey: keyof OriginalValue | string = null + ) { super(originalData, (m) => { - return extractUniqueValues(m, _path); + return extractUniqueValues(m, _path as string, _pathKey as string); }); - this.amountOf = new Map(); - this.simplified = new Map(); - this.reverseSimplified = new Map(); + this.amountOf = new Map(); + this.simplified = new Map(); + this.keyMapping = new Map>(); + this.keyMappingReverse = new Map>(); + this.conflicts = new Map>(); + this.orgKeyToExtractedValue = new Map>(); + + this.extractedKey = []; + this.extractedValue = []; } /** @@ -117,15 +140,27 @@ export class MapBasedMergeData * @param {*} [data=this.originalData] * @memberof MergeData */ - public update(data: Map = null): void { + public update(data: Map = null): void { if (data !== null) { this.originalData = data; } // Now lets update the amount of the data: - this.amountOf = countElements(extractValues(this.originalData, this._path)); - this.simplified = transformValues(this.originalData, this._path); - this.reverseSimplified = reverse(this.simplified); + const result = tranformMap( + this.originalData, + this._path as string, + this._pathKey as string + ); + + // Now assign the results to our items. + this.simplified = result.extractedMap; + this.amountOf = result.amountOf; + this.keyMapping = result.keyMapping; + this.keyMappingReverse = result.keyMappingReverse; + this.conflicts = result.conflicts; + this.orgKeyToExtractedValue = result.orgKeyToExtractedValue; + this.extractedKey = [...this.simplified.keys()]; + this.extractedValue = [...this.simplified.values()]; super.update(data); } diff --git a/lib/helpers/objectMethods.spec.ts b/lib/helpers/objectMethods.spec.ts index fe8f3dd..765f2c7 100644 --- a/lib/helpers/objectMethods.spec.ts +++ b/lib/helpers/objectMethods.spec.ts @@ -6,7 +6,13 @@ import { assert, expect } from "chai"; import { describe, it } from "mocha"; -import { deepClone, flattenObject, rgetattr } from "./objectMethods"; +import { + convertData, + deepClone, + flattenObject, + rgetattr, + rqueryAttr, +} from "./objectMethods"; describe("objectMethods", function () { // Describe the required Test: @@ -164,4 +170,231 @@ describe("objectMethods", function () { assert.isTrue(result === "test", "we expected 'test' but got: " + result); }); }); + + describe("rgettattrQuery", function () { + it("query empty data", function () { + let data = {}; + let result = rqueryAttr(data, "test/+"); + assert.isTrue( + result.length === 0, + "we expected 0 entries but got: " + result.length.toString() + ); + }); + + it("query data - single", function () { + let data = { deep: { nested: "test" } }; + let result = rqueryAttr(data, "deep/nested"); + assert.isTrue( + result.length === 1, + "we expected 1 entries but got: " + result.length.toString() + ); + assert.isTrue( + result[0].path === "deep/nested", + "we expected the path to be 'deep/nested', but got" + result[0].path + ); + assert.isTrue( + result[0].data === "test", + "we expected the data to be 'test', but got" + result[0].data + ); + }); + + it("query data - singlelevel-wildcard", function () { + let data = { + deep: { nested_01: { nested_02: "test_01" }, nested_03: "test_02" }, + not: { nested: "hello" }, + }; + let result = rqueryAttr(data, "deep/+"); + assert.isTrue( + result.length === 2, + "we expected 2 entries but got: " + result.length.toString() + ); + const pathes = result.map((item) => item.path); + assert.isTrue( + pathes.includes("deep/nested_01") && pathes.includes("deep/nested_03"), + `we expected the "deep/nested_01" and "deep/nested_03" have been found, but got` + + pathes.toString() + ); + }); + + it("query data-array - singlelevel-wildcard", function () { + let data = { + array: [ + { + data: 0, + }, + { + data: 1, + }, + ], + not: { nested: "hello" }, + }; + let result = rqueryAttr(data, "array/+/data"); + assert.isTrue( + result.length === 2, + "we expected 2 entries but got: " + result.length.toString() + ); + const items = result.map((item) => item.data); + assert.isTrue( + items.includes(1) && items.includes(0), + `we expected the data contains "0" and "1", but got` + items.toString() + ); + }); + + it("query data - multilevel-wildcard", function () { + let data = { + deep: { nested_01: { nested_02: "test_01" }, nested_03: "test_02" }, + not: { nested: "hello" }, + }; + + let result = rqueryAttr(data, "deep/#"); + const pathes = result.map((item) => item.path); + assert.isTrue( + result.length === 3, + "we expected 3 entries but got: " + pathes.toString() + ); + assert.isTrue( + pathes.includes("deep/nested_01") && + pathes.includes("deep/nested_01/nested_02") && + pathes.includes("deep/nested_03"), + `we expected the "deep/nested_01" and "deep/nested_01/nested_02" and "deep/nested_03" have been found, but got` + + pathes.toString() + ); + }); + }); + + describe("convertData", function () { + it("query empty data", function () { + let data = {}; + let result = convertData(data, [ + { + key: "result", + query: "a/b", + }, + ]); + assert.isTrue( + result.length === 0, + "we expected 0 entries but got: " + + result.length.toString() + + "\n" + + JSON.stringify(result) + ); + }); + + it("query data - single", function () { + let data = { deep: { nested: "test" } }; + let result = convertData<{ result: string }>(data, [ + { + key: "result", + query: "deep/nested", + }, + ]); + assert.isTrue( + result.length === 1, + "we expected 1 entries but got: " + + result.length.toString() + + "\n" + + JSON.stringify(result) + ); + assert.isTrue( + result[0].result === "test", + "we expected the path to be 'test', but got" + + result[0] + + "\n" + + JSON.stringify(result) + ); + }); + + it("query data - singlelevel-wildcard", function () { + let data = { deep: { nested: "test" } }; + let result = convertData<{ result: string }>(data, [ + { + key: "result", + query: "deep/+", + }, + ]); + assert.isTrue( + result.length === 1, + "we expected 1 entries but got: " + + result.length.toString() + + "\n" + + JSON.stringify(result) + ); + assert.isTrue( + result[0].result === "test", + "we expected the path to be 'test', but got" + + result[0] + + "\n" + + JSON.stringify(result) + ); + }); + + it("query data-array - singlelevel-wildcard", function () { + let data = { + array: [ + { + data1: 0, + data2: "a", + }, + { + data1: 1, + data2: "a", + }, + ], + not: { nested: "hello" }, + }; + let result = convertData<{ a: number; b: string }>(data, [ + { + key: "a", + query: "array/+/data1", + }, + { + key: "b", + query: "array/+/data2", + }, + ]); + assert.isTrue( + result.length === 2, + "we expected 2 entries but got: " + + result.length.toString() + + "\n" + + JSON.stringify(result) + ); + const items = result.map((item) => item.a); + assert.isTrue( + items.includes(1) && items.includes(0), + `we expected the data contains "0" and "1", but got` + + items.toString() + + "\n" + + JSON.stringify(result) + ); + }); + it("query data-array - test exception", function () { + let data = { + array: [ + { + data1: 0, + data2: "a", + }, + { + data1: 1, + data2: "a", + }, + ], + not: { nested: "hello" }, + }; + try { + let result = convertData<{ a: number; b: string }>(data, [ + { + key: "a", + query: "array/+/data1", + }, + { + key: "b", + query: "array/+/data2", + }, + ]); + assert.isTrue(false, "Failed to raise exception"); + } catch (e) {} + }); + }); }); diff --git a/lib/helpers/objectMethods.ts b/lib/helpers/objectMethods.ts index b91374b..685f82d 100644 --- a/lib/helpers/objectMethods.ts +++ b/lib/helpers/objectMethods.ts @@ -6,6 +6,12 @@ export const SPLITCHAR = "/"; import { deepEqual as _deepEqual } from "assert"; +import { getLeastCommonPathSegment } from "./path"; +import { + comparePatternAndPath, + containsWildcards, + MULTI_LEVEL_WILDCARD, +} from "./pathMatchingMethods"; const _sentinel = new Object(); @@ -13,10 +19,11 @@ const _sentinel = new Object(); * Function to recurvely get an Attribute of the Object. * * @export - * @param {*} _data - * @param {string} _path - * @param {*} [_default=_sentinel] - * @returns {*} + * @example data = [{a:1},{a:2}]; rgetattr(data, "0/a") -> 0; rgetattr(data,"hallo", "default") -> "default" + * @param {*} _data Data, where the item should be received + * @param {string} _path The path to extract + * @param {*} [_default=_sentinel] Default Object, if nothing else is provided + * @returns {*} The extracted data. */ export function rgetattr( _data: any, @@ -57,6 +64,118 @@ export function rgetattr( return _obj; } +/** + * Helper to query data from an object. + * @example data = [{a:1},{a:2}]; rqueryAttr(data, "+/a") -> [{path: "0/a", data: 0},{path: "1/a", data: 1}] + * @param data The data + * @param query The query to use. + * @returns Returns an array + */ +export function rqueryAttr( + data: any, + query: string +): { + path: string; + data: T; +}[] { + if (!containsWildcards(query)) { + const _sentinel = { + id: Date.now(), + }; + const extractedData = rgetattr(data, query, _sentinel); + + if (extractedData === (_sentinel as any)) { + return []; + } + + return [{ path: query, data: extractedData }]; + } + + let ret: { + path: string; + data: T; + }[] = []; + + const multiLevel = query.includes(MULTI_LEVEL_WILDCARD); + // Determine the max depth + const maxDepth = multiLevel ? Infinity : query.split(SPLITCHAR).length; + + // get the flatten object + const map = flattenObject(data, { + maxDepth, + onlyPathToSimpleValue: false, + }); + + // Iterate over the items and use our + // path matcher to extract the matching items. + for (const [path, value] of map.entries()) { + const r = comparePatternAndPath(query, path); + + if (r.affectedOnSameLevel || (multiLevel && r.affectedByChild)) { + ret.push({ + path, + data: value, + }); + } + } + + return ret; +} + +/** + * Helper to query data from an object. + * @example data = [{a:1},{a:2}]; rqueryAttr(data, "+/a") -> [{path: "0/a", data: 0},{path: "1/a", data: 1}] + * @param data The data + * @param query The query to use. + * @returns Returns an array + */ +export function convertData( + data: any, + props: { + key: string; + query: string; + }[] +): T[] { + const ret: { + [index: string]: { + path: string; + data: any; + }[]; + } = {}; + + const commonPattern = getLeastCommonPathSegment( + props.map((item) => item.query) + ); + + if (!commonPattern) { + throw Error("No common pattern has been found"); + } + + props.map((prop) => { + ret[prop.key] = rqueryAttr(data, prop.query); + }); + + const helper: { [index: string]: { [index: string]: any } } = {}; + + for (const prop of props) { + // get the item + const items = ret[prop.key]; + + for (const item of items) { + const result = comparePatternAndPath(commonPattern, item.path); + + if (result.pathToExtractData) { + if (helper[result.pathToExtractData] === undefined) { + helper[result.pathToExtractData] = {}; + } + helper[result.pathToExtractData][prop.key] = item.data; + } + } + } + + return Object.getOwnPropertyNames(helper).map((key) => helper[key]) as T[]; +} + /** * Function to Set recursely a Attribute of an Object * @@ -213,11 +332,11 @@ export function isObjectOrArray(value: any): boolean { * data = {a : { b : { c : 1, d: "hallo"}}} * * // Normal Call - * res = flatteObject(data,'') + * res = flatteObject(data) * => res = {"a.b.c":1,"a.b.d":"hallo"} * * // With a Selected prefix 'additional.name' - * res = flatteObject(data,'additional.name') + * res = flatteObject(data,{prefix:'additional.name'}) * => res = {"additional.name.a.b.c":1,"additional.name.a.b.d":"hallo"} * * @export diff --git a/lib/helpers/path.spec.ts b/lib/helpers/path.spec.ts new file mode 100644 index 0000000..781470b --- /dev/null +++ b/lib/helpers/path.spec.ts @@ -0,0 +1,178 @@ +/** + * @author Martin Karkowski + * @email m.karkowski@zema.de + * @desc [description] + */ + +import { assert } from "chai"; +import { describe, it } from "mocha"; +import { getLeastCommonPathSegment } from "./path"; + +describe("path", function () { + // Describe the required Test: + + describe("getLeastCommonPathSegment", function () { + it("equal pathes", function () { + assert.equal( + "a/b", + getLeastCommonPathSegment(["a/b", "a/b"]), + `getLeastCommonPathSegment(["a/b", "a/b"])` + ); + + assert.equal( + "a/b", + getLeastCommonPathSegment(["a/b", "a/b/c"]), + `getLeastCommonPathSegment("a/b", "a/b/c")` + ); + + assert.equal( + "a/b", + getLeastCommonPathSegment(["a/b/c", "a/b", "a/b/d"]), + `getLeastCommonPathSegment("a/b/c", "a/b")` + ); + + assert.equal( + "a", + getLeastCommonPathSegment(["a/b", "a/+"]), + `getLeastCommonPathSegment(["a/b", "a/+"])` + ); + + assert.isFalse( + getLeastCommonPathSegment(["c/a", "a/+"]), + "Pathes should be different" + ); + }); + + it("equal pathes - singlelevel", function () { + assert.equal( + "a/b", + getLeastCommonPathSegment(["a/b", "a/b"], { + considerSingleLevel: true, + }), + `getLeastCommonPathSegment(["a/b", "a/b"], { considerSingleLevel: true })` + ); + + assert.equal( + "a/b", + getLeastCommonPathSegment(["a/b", "a/+"], { + considerSingleLevel: true, + }), + + `getLeastCommonPathSegment(["a/b", "a/+"], { considerSingleLevel: true })` + ); + + assert.equal( + "a/b/c", + getLeastCommonPathSegment(["a/b/c", "a/+/c"], { + considerSingleLevel: true, + }), + + `getLeastCommonPathSegment(["a/b/c", "a/+/c"], { considerSingleLevel: true })` + ); + + assert.equal( + "a/b/c", + getLeastCommonPathSegment(["+/b/c", "a/+/c"], { + considerSingleLevel: true, + }), + + `getLeastCommonPathSegment(["+/b/c", "a/+/c"], { considerSingleLevel: true })` + ); + + assert.equal( + "a/b/c", + getLeastCommonPathSegment(["+/+/+", "a/b/c"], { + considerSingleLevel: true, + }), + `getLeastCommonPathSegment("+/+/+", "a/b/c", { considerSingleLevel: true })` + ); + + assert.equal( + "a/b/c", + getLeastCommonPathSegment(["+/b/+", "a/+/c"], { + considerSingleLevel: true, + }), + `getLeastCommonPathSegment(["+/b/+", "a/+/c"], { considerSingleLevel: true })` + ); + + assert.equal( + "a/+/c", + getLeastCommonPathSegment(["+/+/+", "a/+/c"], { + considerSingleLevel: true, + }), + `getLeastCommonPathSegment(["+/+/+", "a/+/c"], { considerSingleLevel: true })` + ); + }); + + it("equal pathes - multilevel", function () { + assert.equal( + "a/b", + getLeastCommonPathSegment(["a/b", "a/b"], { considerMultiLevel: true }), + `getLeastCommonPathSegment(["a/b", "a/b"], { considerMultiLevel: true }) == false` + ); + + assert.equal( + "a", + getLeastCommonPathSegment(["a/b", "a/+"], { considerMultiLevel: true }), + `getLeastCommonPathSegment(["a/b", "a/+"], { considerMultiLevel: true })` + ); + + assert.equal( + "a", + getLeastCommonPathSegment(["a/b/c", "a/+/c"], { + considerMultiLevel: true, + }), + `getLeastCommonPathSegment(["a/b/c", "a/+/c"], { considerMultiLevel: true })` + ); + + assert.equal( + false as any, + getLeastCommonPathSegment(["+/b/c", "a/+/c"], { + considerMultiLevel: true, + }), + `getLeastCommonPathSegment(["+/b/c", "a/+/c"], { considerMultiLevel: true })` + ); + + assert.equal( + false as any, + getLeastCommonPathSegment(["+/+/+", "a/b/c"], { + considerMultiLevel: true, + }), + `getLeastCommonPathSegment(["+/+/+", "a/b/c"], { considerMultiLevel: true })` + ); + + assert.equal( + false as any, + getLeastCommonPathSegment(["+/b/+", "a/+/c"], { + considerMultiLevel: true, + }), + + `getLeastCommonPathSegment(["+/b/+", "a/+/c"], { considerMultiLevel: true })` + ); + + assert.equal( + "a/b/c", + getLeastCommonPathSegment(["#", "a/b/c"], { + considerMultiLevel: true, + }), + `getLeastCommonPathSegment(["#", "a/b/c"], { considerMultiLevel: true })` + ); + + assert.equal( + "a/b/c", + getLeastCommonPathSegment(["a/#", "a/b/c"], { + considerMultiLevel: true, + }), + `getLeastCommonPathSegment(["a/#", "a/b/c"], { considerMultiLevel: true })` + ); + + assert.equal( + "a/b", + getLeastCommonPathSegment(["a/#", "a/b", "a/b/c"], { + considerMultiLevel: true, + }), + `getLeastCommonPathSegment(["a/#", "a/b"], { considerMultiLevel: true })` + ); + }); + }); +}); diff --git a/lib/helpers/path.ts b/lib/helpers/path.ts index 2130bbd..a902c27 100644 --- a/lib/helpers/path.ts +++ b/lib/helpers/path.ts @@ -4,8 +4,97 @@ * @desc [description] */ +import { SPLITCHAR } from "./objectMethods"; +import { + MULTI_LEVEL_WILDCARD, + SINGLE_LEVEL_WILDCARD, +} from "./pathMatchingMethods"; import { replaceAll } from "./stringMethods"; export function convertPath(path: string): string { - return replaceAll(path, [".", "[", ["]", ""]], "/"); + return replaceAll(path, [".", "[", ["]", ""]], SPLITCHAR); +} + +/** + * + * @param pathes The Segments to compare + * @param opts Additional Options. + * @returns + */ +export function getLeastCommonPathSegment( + pathes: string[], + opts: { + considerSingleLevel?: boolean; + considerMultiLevel?: boolean; + } = {} +): string | false { + let currentPath = pathes.pop(); + + while (pathes.length > 0) { + let next = pathes.pop(); + + currentPath = _getLeastCommonPathSegment(currentPath, next, opts) as string; + + if (!currentPath) { + return currentPath; + } + } + + return currentPath; +} + +function _getLeastCommonPathSegment( + path01: string, + path02: string, + opts: { + considerSingleLevel?: boolean; + considerMultiLevel?: boolean; + } = {} +) { + const p1 = convertPath(path01).split(SPLITCHAR); + const p2 = convertPath(path02).split(SPLITCHAR); + + const ret: string[] = []; + + let idx = 0; + const max = Math.max(p1.length, p2.length); + + while (idx < max) { + if (p1[idx] == p2[idx]) { + // Add the Item. + ret.push(p1[idx]); + } else if (opts.considerSingleLevel) { + if (p1[idx] === SINGLE_LEVEL_WILDCARD) { + // Add the Item. + ret.push(p2[idx]); + } else if (p2[idx] === SINGLE_LEVEL_WILDCARD) { + // Add the Item. + ret.push(p1[idx]); + } else { + break; + } + } else if (opts.considerMultiLevel) { + if (p1[idx] === MULTI_LEVEL_WILDCARD) { + // Add the Item. + ret.push(...p2.slice(idx)); + break; + } else if (p2[idx] === MULTI_LEVEL_WILDCARD) { + // Add the Item. + ret.push(...p1.slice(idx)); + break; + } else { + break; + } + } else { + break; + } + + idx += 1; + } + + if (ret.length) { + return ret.join(SPLITCHAR); + } + + return false; } diff --git a/lib/pubSub/nopePubSubSystem.ts b/lib/pubSub/nopePubSubSystem.ts index efd691b..b9ac5c9 100644 --- a/lib/pubSub/nopePubSubSystem.ts +++ b/lib/pubSub/nopePubSubSystem.ts @@ -26,6 +26,7 @@ import { INopeEventEmitter, INopeObserver, INopeTopic, + IPubSubEmitterOptions, IPubSubOptions, IPubSubSystem, ITopicSetContentOptions, @@ -69,10 +70,20 @@ export class PubSubSystemBase< readonly onIncrementalDataChange: INopeEventEmitter; // See interface description - readonly subscriptions: IMapBasedMergeData; + readonly subscriptions: IMapBasedMergeData< + O, + IPubSubEmitterOptions, + O, + string + >; // See interface description - readonly publishers: IMapBasedMergeData; + readonly publishers: IMapBasedMergeData< + O, + IPubSubEmitterOptions, + O, + string + >; protected _comparePatternAndPath: TcomparePatternAndPathFunc; diff --git a/lib/types/nope/nopeCommunication.interface.ts b/lib/types/nope/nopeCommunication.interface.ts index 73d2f36..2e32418 100644 --- a/lib/types/nope/nopeCommunication.interface.ts +++ b/lib/types/nope/nopeCommunication.interface.ts @@ -4,7 +4,10 @@ */ import { INopeStatusInfo } from "./nopeConnectivityManager.interface"; -import { INopeModuleDescription } from "./nopeModule.interface"; +import { + IFunctionOptions, + INopeModuleDescription, +} from "./nopeModule.interface"; import { INopeObservable } from "./nopeObservable.interface"; import { IIncrementalChange } from "./nopePubSub.interface"; @@ -203,7 +206,7 @@ export interface IAvailableServicesMsg { * * @type {string[]} */ - services: string[]; + services: IFunctionOptions[]; } export type ITaskCancelationMsg = { diff --git a/lib/types/nope/nopeConnectivityManager.interface.ts b/lib/types/nope/nopeConnectivityManager.interface.ts index 86f56ee..5986c97 100644 --- a/lib/types/nope/nopeConnectivityManager.interface.ts +++ b/lib/types/nope/nopeConnectivityManager.interface.ts @@ -239,15 +239,20 @@ export interface INopeConnectivityManager { * get the latest changes. Use the "data" * field, to subscribe for the latest data. * + * OriginalKey = Dispatcher ID; + * OriginalValue = INopeStatusInfo; + * ExtractedKey = Dispatcher ID; + * ExtractedValue = Dispatcher ID; + * * @author M.Karkowski - * @type {IMapBasedMergeData< - * INopeStatusInfo, - * string, - * INopeStatusInfo - * >} * @memberof INopeStatusManager */ - dispatchers: IMapBasedMergeData; + dispatchers: IMapBasedMergeData< + string, // Dispatcher ID + INopeStatusInfo, // Orginal Message + string, // Dispatcher ID + string // Dispatcher ID + >; /** * Options of the StatusManager. diff --git a/lib/types/nope/nopeHelpers.interface.ts b/lib/types/nope/nopeHelpers.interface.ts index 51e7fd9..17c241a 100644 --- a/lib/types/nope/nopeHelpers.interface.ts +++ b/lib/types/nope/nopeHelpers.interface.ts @@ -40,18 +40,55 @@ export interface IMergeData { dispose(): void; } -export interface IMapBasedMergeData - extends IMergeData> { +export interface IMapBasedMergeData< + OriginalKey, + OriginalValue, + ExtractedKey = OriginalKey, + ExtractedValue = OriginalValue +> extends IMergeData> { /** * Adds a dinfition of the Amounts, of the elements. */ - amountOf: Map; + amountOf: Map; + /** * Simplifed Data Access. */ - simplified: Map; + simplified: Map; + /** - * A Reverse Mapping + * Contains the Mapping of the original Key to the Extracted Key. + * key = OriginalKey; + * value = Set; */ - reverseSimplified: Map>; + keyMapping: Map>; + + /** + * Contains the Mapping of the `extracted Key` to the `original Key`. + * key = ExtractedKey; + * value = OriginalKey; + */ + keyMappingReverse: Map>; + + /** + * Contains conflicts. + * key = ExtractedKey; + * value = All determined different Values. + */ + conflicts: Map>; + + /** + * Maps the Original Key to the Extracted value; + */ + orgKeyToExtractedValue: Map>; + + /** + * Contains the extracted Keys as Array. + */ + extractedKey: ExtractedKey[]; + + /** + * Contains the extracted Values. + */ + extractedValue: ExtractedValue[]; } diff --git a/lib/types/nope/nopeInstanceManager.interface.ts b/lib/types/nope/nopeInstanceManager.interface.ts index a8faa93..cc91b7e 100644 --- a/lib/types/nope/nopeInstanceManager.interface.ts +++ b/lib/types/nope/nopeInstanceManager.interface.ts @@ -71,25 +71,28 @@ export interface INopeInstanceManager { */ readonly constructors: IMapBasedMergeData< string, // Dispatcher ID + string[], // Available Generators of the Dispatcher string, // Dispatcher ID - string[] // Available Instances + string // Generators-ID >; /** * Overview of the available instances in the network. * + * OriginalKey = DispatcherID (string); + * OriginalValue = Available Instance Messages (IAvailableInstancesMsg); + * ExtractedKey = The name of the Instance (string); + * ExtractedValue = instance-description (INopeModuleDescription); + * + * * @author M.Karkowski - * @type {IMapBasedMergeData< - * INopeModuleDescription, - * string, - * IAvailableInstancesMsg - * >} * @memberof INopeInstanceManager */ readonly instances: IMapBasedMergeData< - INopeModuleDescription, - string, - IAvailableInstancesMsg + string, // Dispatcher ID + IAvailableInstancesMsg, // Available Instance Messages + string, // The name of the Instance? + INopeModuleDescription // The instance-description >; /** diff --git a/lib/types/nope/nopeModule.interface.ts b/lib/types/nope/nopeModule.interface.ts index 278eed7..51d7b09 100644 --- a/lib/types/nope/nopeModule.interface.ts +++ b/lib/types/nope/nopeModule.interface.ts @@ -4,7 +4,7 @@ * @desc Defintion of a generic Module. */ -import { TRenderConfigureServicePage } from "../ui"; +import { TGetPorts, TRenderConfigureServicePage } from "../ui"; import { ICallOptions } from "./nopeCommunication.interface"; import { INopeDescriptor } from "./nopeDescriptor.interface"; import { INopeObservable } from "./nopeObservable.interface"; @@ -299,6 +299,7 @@ export interface IFunctionOptions extends Partial { */ ui?: { serviceConfiguration?: TRenderConfigureServicePage; + getPorts?: TGetPorts; }; /** diff --git a/lib/types/nope/nopePubSub.interface.ts b/lib/types/nope/nopePubSub.interface.ts index da92166..e8fb988 100644 --- a/lib/types/nope/nopePubSub.interface.ts +++ b/lib/types/nope/nopePubSub.interface.ts @@ -89,6 +89,18 @@ export interface IIncrementalChange extends IEventAdditionalData { data: unknown; } +export interface IPubSubEmitterOptions< + AD extends ITopicSetContentOptions & { + pubSubUpdate?: boolean; + } = ITopicSetContentOptions +> { + options: IEventOptions; + subTopic: string | false; + pubTopic: string | false; + callback?: IEventCallback; + observer?: INopeObserver; +} + /** * The default Publish and Subscribe System. The Behavior may differ based on the settings. * Your are not able to change these options, after the instance has been created. @@ -209,7 +221,12 @@ export interface IPubSubSystem< * @type {IMapBasedMergeData} * @memberof IPubSubSystem */ - readonly subscriptions: IMapBasedMergeData; + readonly subscriptions: IMapBasedMergeData< + O, + IPubSubEmitterOptions, + O, + string + >; /** * List containing all publishers. @@ -218,7 +235,12 @@ export interface IPubSubSystem< * @type {IMapBasedMergeData} * @memberof IPubSubSystem */ - readonly publishers: IMapBasedMergeData; + readonly publishers: IMapBasedMergeData< + O, + IPubSubEmitterOptions, + O, + string + >; /** * Disposes the Pub-Sub-System. diff --git a/lib/types/nope/nopeRpcManager.interface.ts b/lib/types/nope/nopeRpcManager.interface.ts index a70a2af..25063a9 100644 --- a/lib/types/nope/nopeRpcManager.interface.ts +++ b/lib/types/nope/nopeRpcManager.interface.ts @@ -69,7 +69,9 @@ export interface IRequestTaskWithCallback extends IRequestRpcMsg { * @export * @interface INopeRpcManager */ -export interface INopeRpcManager { +export interface INopeRpcManager< + T extends IFunctionOptions = IFunctionOptions +> { /** * Flag, to show, that the System is ready * @@ -109,14 +111,21 @@ export interface INopeRpcManager { /** * Element showing the available services. * - * T = services name. - * K = dispatcher - ids + * OriginalKey = Dispatcher ID (string); + * OriginalValue = Original Message (IAvailableServicesMsg); + * ExtractedKey = Function ID (string); + * ExtractedValue = FunctionOptions (T); * * @author M.Karkowski - * @type {IMapBasedMergeData} + * @type {IMapBasedMergeData} * @memberof INopeRpcManager */ - services: IMapBasedMergeData; + services: IMapBasedMergeData< + string, // Dispatcher ID + IAvailableServicesMsg, // Original Message + string, // Function ID + T // Function Options + >; /** * Function, that must be called if a dispatcher is is gone. This might be the diff --git a/lib/types/ui/editor/IServiceEditPage.ts b/lib/types/ui/editor/IServiceEditPage.ts index efc5db1..489836c 100644 --- a/lib/types/ui/editor/IServiceEditPage.ts +++ b/lib/types/ui/editor/IServiceEditPage.ts @@ -6,6 +6,24 @@ import { TRenderFunctionResult } from "../layout.interface"; import { IPort } from "./INodes"; +/** + * Type to define the Ports of a UI: + */ +export type TServiceGetPortsReturn = { + inputs: { + id: string; + label: String; + type: IPort["type"]; + }[]; + outputs: { + id: string; + label: String; + type: IPort["type"]; + }[]; +}; + +export type TGetPorts = (data?: T) => TServiceGetPortsReturn; + export interface IServiceEditPage extends TRenderFunctionResult { /** * Function, which must return the current service-data. @@ -32,19 +50,7 @@ export interface IServiceEditPage extends TRenderFunctionResult { type: "node" | "edge"; /** - * Function to store the changed Edge data. - * @param options adapted Edge Options. + * Helper to define the Ports. */ - getPorts(): { - inputs: { - id: string; - label: String; - type: IPort["type"]; - }[]; - outputs: { - id: string; - label: String; - type: IPort["type"]; - }[]; - }; + getPorts?: TGetPorts; }