Backup
This commit is contained in:
parent
338a488ec7
commit
39d7fe720e
@ -90,9 +90,10 @@ export class NopeConnectivityManager implements INopeConnectivityManager {
|
||||
|
||||
public readonly ready: INopeObservable<boolean>;
|
||||
public readonly dispatchers: IMapBasedMergeData<
|
||||
string,
|
||||
string,
|
||||
INopeStatusInfo
|
||||
string, // Dispatcher ID
|
||||
INopeStatusInfo, // Orginal Message
|
||||
string, // Dispatcher ID
|
||||
string // Dispatcher ID
|
||||
>;
|
||||
|
||||
/**
|
||||
|
@ -114,15 +114,22 @@ export class NopeInstanceManager implements INopeInstanceManager {
|
||||
* @type {IMapBasedMergeData<string>}
|
||||
* @memberof NopeInstanceManager
|
||||
*/
|
||||
public readonly constructors: IMapBasedMergeData<string, string, string[]>;
|
||||
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<string, string[], string, string>;
|
||||
|
||||
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) {
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -119,17 +119,20 @@ export class NopeRpcManager<T extends IFunctionOptions = IFunctionOptions>
|
||||
* 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<string>}
|
||||
* @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<T extends IFunctionOptions = IFunctionOptions>
|
||||
|
||||
this.services = new MapBasedMergeData(
|
||||
this._mappingOfDispatchersAndServices,
|
||||
"services"
|
||||
"services/+",
|
||||
"services/+/id"
|
||||
);
|
||||
|
||||
this.onCancelTask = new NopeEventEmitter();
|
||||
@ -453,7 +457,9 @@ export class NopeRpcManager<T extends IFunctionOptions = IFunctionOptions>
|
||||
// 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<T extends IFunctionOptions = IFunctionOptions>
|
||||
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)) {
|
||||
|
@ -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));
|
||||
|
@ -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),
|
||||
|
@ -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();
|
||||
|
@ -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<K, V>} map
|
||||
* @return {*} {Set<V>}
|
||||
* @template D Return Type
|
||||
* @template K The Key of the Map
|
||||
* @template V The Value of the Map
|
||||
* @param {Map<K, V>} 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<D>}
|
||||
*/
|
||||
export function extractUniqueValues<D>(map: Map<any, any>, path = ""): Set<D> {
|
||||
export function extractUniqueValues<D, K = any, V = any>(
|
||||
map: Map<K, V>,
|
||||
path = "",
|
||||
pathKey: string = null
|
||||
): Set<D> {
|
||||
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<D, K>(map: Map<K, any>, path = ""): Array<D> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform the values.
|
||||
*
|
||||
*
|
||||
* @author M.Karkowski
|
||||
* @export
|
||||
* @template D
|
||||
* @template K
|
||||
* @param {Map<K, any>} map
|
||||
* @param {string} [path=""]
|
||||
* @return {*} {Map<K, D>}
|
||||
* @template ExtractedValue
|
||||
* @template ExtractedKey
|
||||
* @template OriginalKey
|
||||
* @param {Map<OriginalKey, any>} map
|
||||
* @param {string} [pathExtractedValue=""]
|
||||
* @param {string} [pathExtractedKey=null] Additional Path of a Key.
|
||||
* @return {*} {Map<ExtractedKey, ExtractedData>}
|
||||
*/
|
||||
export function transformValues<D, K = any>(
|
||||
map: Map<K, any>,
|
||||
path = ""
|
||||
): Map<K, D> {
|
||||
const m = new Map<K, D>();
|
||||
export function tranformMap<
|
||||
ExtractedKey = string,
|
||||
ExtractedValue = any,
|
||||
OriginalKey = string
|
||||
>(
|
||||
map: Map<OriginalKey, any>,
|
||||
pathExtractedValue: string,
|
||||
pathExtractedKey: string,
|
||||
equals: (a: ExtractedValue, b: ExtractedValue) => boolean = deepEqual
|
||||
) {
|
||||
const keyMapping = new Map<OriginalKey, Set<ExtractedKey>>();
|
||||
const reverseKeyMapping = new Map<ExtractedKey, Set<OriginalKey>>();
|
||||
const conflicts = new Map<ExtractedKey, Set<ExtractedValue>>();
|
||||
const extractedMap = new Map<ExtractedKey, ExtractedValue>();
|
||||
const orgKeyToExtractedValue = new Map<OriginalKey, Set<ExtractedValue>>();
|
||||
const amountOf = new Map<ExtractedKey, number>();
|
||||
|
||||
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<K, V>} map
|
||||
* @param {Map<any,any>} map
|
||||
* @param {string} [path=""]
|
||||
* @param {string} [pathKey=null]
|
||||
* @return {*} {Map<V, Set<K>>}
|
||||
*/
|
||||
export function reverse<K, V>(map: Map<K, V>): Map<V, Set<K>> {
|
||||
export function reverse<K, V>(
|
||||
map: Map<any, any>,
|
||||
path: string = "",
|
||||
pathKey: string = null
|
||||
): Map<V, Set<K>> {
|
||||
const m = new Map<V, Set<K>>();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<string, { key: string; data: string }>();
|
||||
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<string, { key: string; data: string }[]>();
|
||||
// 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<string, string>();
|
||||
const d = new MapBasedMergeData(m);
|
||||
|
@ -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<T, D = any> implements IMergeData<T, D> {
|
||||
/**
|
||||
@ -92,22 +89,48 @@ export class MergeData<T, D = any> implements IMergeData<T, D> {
|
||||
}
|
||||
}
|
||||
|
||||
export class MapBasedMergeData<T, K, V>
|
||||
extends MergeData<T, Map<K, V>>
|
||||
implements IMergeData<T, Map<K, V>>
|
||||
export class MapBasedMergeData<
|
||||
OriginalKey,
|
||||
OriginalValue,
|
||||
ExtractedKey = OriginalKey,
|
||||
ExtractedValue = OriginalValue
|
||||
>
|
||||
extends MergeData<ExtractedValue, Map<OriginalKey, OriginalValue>>
|
||||
implements
|
||||
IMapBasedMergeData<
|
||||
OriginalKey,
|
||||
OriginalValue,
|
||||
ExtractedKey,
|
||||
ExtractedValue
|
||||
>
|
||||
{
|
||||
public amountOf: Map<T, number>;
|
||||
public simplified: Map<K, T>;
|
||||
public reverseSimplified: Map<T, Set<K>>;
|
||||
public amountOf: Map<ExtractedKey, number>;
|
||||
public simplified: Map<ExtractedKey, ExtractedValue>;
|
||||
public keyMapping: Map<OriginalKey, Set<ExtractedKey>>;
|
||||
public keyMappingReverse: Map<ExtractedKey, Set<OriginalKey>>;
|
||||
public conflicts: Map<ExtractedKey, Set<ExtractedValue>>;
|
||||
public orgKeyToExtractedValue: Map<OriginalKey, Set<ExtractedValue>>;
|
||||
public extractedKey: ExtractedKey[];
|
||||
public extractedValue: ExtractedValue[];
|
||||
|
||||
constructor(originalData: Map<K, V>, protected _path = "") {
|
||||
constructor(
|
||||
originalData: Map<OriginalKey, OriginalValue>,
|
||||
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<ExtractedKey, number>();
|
||||
this.simplified = new Map<ExtractedKey, ExtractedValue>();
|
||||
this.keyMapping = new Map<OriginalKey, Set<ExtractedKey>>();
|
||||
this.keyMappingReverse = new Map<ExtractedKey, Set<OriginalKey>>();
|
||||
this.conflicts = new Map<ExtractedKey, Set<ExtractedValue>>();
|
||||
this.orgKeyToExtractedValue = new Map<OriginalKey, Set<ExtractedValue>>();
|
||||
|
||||
this.extractedKey = [];
|
||||
this.extractedValue = [];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -117,15 +140,27 @@ export class MapBasedMergeData<T, K, V>
|
||||
* @param {*} [data=this.originalData]
|
||||
* @memberof MergeData
|
||||
*/
|
||||
public update(data: Map<K, V> = null): void {
|
||||
public update(data: Map<OriginalKey, OriginalValue> = 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<ExtractedKey, ExtractedValue, OriginalKey>(
|
||||
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);
|
||||
}
|
||||
|
@ -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) {}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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<T = any>(
|
||||
_data: any,
|
||||
@ -57,6 +64,118 @@ export function rgetattr<T = any>(
|
||||
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<T>(
|
||||
data: any,
|
||||
query: string
|
||||
): {
|
||||
path: string;
|
||||
data: T;
|
||||
}[] {
|
||||
if (!containsWildcards(query)) {
|
||||
const _sentinel = {
|
||||
id: Date.now(),
|
||||
};
|
||||
const extractedData = rgetattr<T>(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<T>(
|
||||
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
|
||||
|
178
lib/helpers/path.spec.ts
Normal file
178
lib/helpers/path.spec.ts
Normal file
@ -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 })`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
@ -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;
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ import {
|
||||
INopeEventEmitter,
|
||||
INopeObserver,
|
||||
INopeTopic,
|
||||
IPubSubEmitterOptions,
|
||||
IPubSubOptions,
|
||||
IPubSubSystem,
|
||||
ITopicSetContentOptions,
|
||||
@ -69,10 +70,20 @@ export class PubSubSystemBase<
|
||||
readonly onIncrementalDataChange: INopeEventEmitter<IIncrementalChange>;
|
||||
|
||||
// See interface description
|
||||
readonly subscriptions: IMapBasedMergeData<string>;
|
||||
readonly subscriptions: IMapBasedMergeData<
|
||||
O,
|
||||
IPubSubEmitterOptions<AD>,
|
||||
O,
|
||||
string
|
||||
>;
|
||||
|
||||
// See interface description
|
||||
readonly publishers: IMapBasedMergeData<string>;
|
||||
readonly publishers: IMapBasedMergeData<
|
||||
O,
|
||||
IPubSubEmitterOptions<AD>,
|
||||
O,
|
||||
string
|
||||
>;
|
||||
|
||||
protected _comparePatternAndPath: TcomparePatternAndPathFunc;
|
||||
|
||||
|
@ -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 = {
|
||||
|
@ -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<string, string, INopeStatusInfo>;
|
||||
dispatchers: IMapBasedMergeData<
|
||||
string, // Dispatcher ID
|
||||
INopeStatusInfo, // Orginal Message
|
||||
string, // Dispatcher ID
|
||||
string // Dispatcher ID
|
||||
>;
|
||||
|
||||
/**
|
||||
* Options of the StatusManager.
|
||||
|
@ -40,18 +40,55 @@ export interface IMergeData<T = any, K = any> {
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
export interface IMapBasedMergeData<T = any, K = any, V = any>
|
||||
extends IMergeData<T, Map<K, V>> {
|
||||
export interface IMapBasedMergeData<
|
||||
OriginalKey,
|
||||
OriginalValue,
|
||||
ExtractedKey = OriginalKey,
|
||||
ExtractedValue = OriginalValue
|
||||
> extends IMergeData<ExtractedValue, Map<OriginalKey, OriginalValue>> {
|
||||
/**
|
||||
* Adds a dinfition of the Amounts, of the elements.
|
||||
*/
|
||||
amountOf: Map<T, number>;
|
||||
amountOf: Map<ExtractedKey, number>;
|
||||
|
||||
/**
|
||||
* Simplifed Data Access.
|
||||
*/
|
||||
simplified: Map<K, T>;
|
||||
simplified: Map<ExtractedKey, ExtractedValue>;
|
||||
|
||||
/**
|
||||
* A Reverse Mapping
|
||||
* Contains the Mapping of the original Key to the Extracted Key.
|
||||
* key = OriginalKey;
|
||||
* value = Set<ExtractedKey>;
|
||||
*/
|
||||
reverseSimplified: Map<T, Set<K>>;
|
||||
keyMapping: Map<OriginalKey, Set<ExtractedKey>>;
|
||||
|
||||
/**
|
||||
* Contains the Mapping of the `extracted Key` to the `original Key`.
|
||||
* key = ExtractedKey;
|
||||
* value = OriginalKey;
|
||||
*/
|
||||
keyMappingReverse: Map<ExtractedKey, Set<OriginalKey>>;
|
||||
|
||||
/**
|
||||
* Contains conflicts.
|
||||
* key = ExtractedKey;
|
||||
* value = All determined different Values.
|
||||
*/
|
||||
conflicts: Map<ExtractedKey, Set<ExtractedValue>>;
|
||||
|
||||
/**
|
||||
* Maps the Original Key to the Extracted value;
|
||||
*/
|
||||
orgKeyToExtractedValue: Map<OriginalKey, Set<ExtractedValue>>;
|
||||
|
||||
/**
|
||||
* Contains the extracted Keys as Array.
|
||||
*/
|
||||
extractedKey: ExtractedKey[];
|
||||
|
||||
/**
|
||||
* Contains the extracted Values.
|
||||
*/
|
||||
extractedValue: ExtractedValue[];
|
||||
}
|
||||
|
@ -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
|
||||
>;
|
||||
|
||||
/**
|
||||
|
@ -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<T = any> extends Partial<ICallOptions> {
|
||||
*/
|
||||
ui?: {
|
||||
serviceConfiguration?: TRenderConfigureServicePage<T>;
|
||||
getPorts?: TGetPorts<T>;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -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<unknown, AD>;
|
||||
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<string>}
|
||||
* @memberof IPubSubSystem
|
||||
*/
|
||||
readonly subscriptions: IMapBasedMergeData<string>;
|
||||
readonly subscriptions: IMapBasedMergeData<
|
||||
O,
|
||||
IPubSubEmitterOptions<AD>,
|
||||
O,
|
||||
string
|
||||
>;
|
||||
|
||||
/**
|
||||
* List containing all publishers.
|
||||
@ -218,7 +235,12 @@ export interface IPubSubSystem<
|
||||
* @type {IMapBasedMergeData<string>}
|
||||
* @memberof IPubSubSystem
|
||||
*/
|
||||
readonly publishers: IMapBasedMergeData<string>;
|
||||
readonly publishers: IMapBasedMergeData<
|
||||
O,
|
||||
IPubSubEmitterOptions<AD>,
|
||||
O,
|
||||
string
|
||||
>;
|
||||
|
||||
/**
|
||||
* Disposes the Pub-Sub-System.
|
||||
|
@ -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<string>}
|
||||
* @type {IMapBasedMergeData<T>}
|
||||
* @memberof INopeRpcManager
|
||||
*/
|
||||
services: IMapBasedMergeData<string, string, IAvailableServicesMsg>;
|
||||
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
|
||||
|
@ -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<T = any> = (data?: T) => TServiceGetPortsReturn;
|
||||
|
||||
export interface IServiceEditPage<T = any> extends TRenderFunctionResult {
|
||||
/**
|
||||
* Function, which must return the current service-data.
|
||||
@ -32,19 +50,7 @@ export interface IServiceEditPage<T = any> 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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user