updating to version 1.5.0. See Changelog
This commit is contained in:
parent
169f24cc81
commit
9e358be84a
22
CHANGELOG.md
22
CHANGELOG.md
@ -319,7 +319,7 @@ Inital commit, which is working with the browser
|
|||||||
- Dispatcher Health is only checked if required.
|
- Dispatcher Health is only checked if required.
|
||||||
- `lib\dispatcher\InstanceManager\InstanceManager.ts`:
|
- `lib\dispatcher\InstanceManager\InstanceManager.ts`:
|
||||||
- Made: `getServiceName` public
|
- Made: `getServiceName` public
|
||||||
- `lib/dispatcher/RpcManager/NopeRpcManager.ts`:
|
- `lib\dispatcher\RpcManager\NopeRpcManager.ts`:
|
||||||
- The following functions are now async:
|
- The following functions are now async:
|
||||||
- `_sendAvailableServices` -> it is awaited in some functions now (hasnt before)
|
- `_sendAvailableServices` -> it is awaited in some functions now (hasnt before)
|
||||||
- `unregisterService` -> now returns a boolean for sucess
|
- `unregisterService` -> now returns a boolean for sucess
|
||||||
@ -331,27 +331,35 @@ Inital commit, which is working with the browser
|
|||||||
- `lib\dispatcher\ConnectivityManager\ConnectivityManager.ts`:
|
- `lib\dispatcher\ConnectivityManager\ConnectivityManager.ts`:
|
||||||
- Fixing Master Assignment.
|
- Fixing Master Assignment.
|
||||||
- Only sending one Status on init.
|
- Only sending one Status on init.
|
||||||
- `lib/dispatcher/InstanceManager/InstanceManager.ts`:
|
- `lib\dispatcher\InstanceManager\InstanceManager.ts`:
|
||||||
- Fixing pathes of `constructors` variable. Now `amountOf` etc is working
|
- Fixing pathes of `constructors` variable. Now `amountOf` etc is working
|
||||||
- Fixing pathes of `constructorExists`. Now working with Type-Name.
|
- Fixing pathes of `constructorExists`. Now working with Type-Name.
|
||||||
- Only sending one Status on init.
|
- Only sending one Status on init.
|
||||||
- `lib/helpers/mapMethods.ts`:
|
- `lib\helpers\mapMethods.ts`:
|
||||||
- Fixing `tranformMap` in the case of only a `pathExtractedValue` or `pathExtractedKey` is given.
|
- Fixing `tranformMap` in the case of only a `pathExtractedValue` or `pathExtractedKey` is given.
|
||||||
- `lib/helpers/objectMethods.ts`:
|
- `lib\helpers\objectMethods.ts`:
|
||||||
- fixing `rgetattr` -> Now correctly returns "null" in all cases.
|
- fixing `rgetattr` -> Now correctly returns "null" in all cases.
|
||||||
- `lib\demo`:
|
- `lib\demo`:
|
||||||
- Fixing imports of demo instances.
|
- Fixing imports of demo instances.
|
||||||
- Modified:
|
- Modified:
|
||||||
- `lib\types`:
|
- `lib\types`:
|
||||||
- renamed `IFunctionOptions` to `IServiceOptions`
|
- renamed `IFunctionOptions` to `IServiceOptions`
|
||||||
- `lib/types/nope/nopeModule.interface.ts`:
|
- `lib\types\nope\nopeModule.interface.ts`:
|
||||||
- `listMethods` now returns a different array, where the attribute is named `method` instead of `func` -> Adaptions affect `BaseModule` and `GenericModule`
|
- `listMethods` now returns a different array, where the attribute is named `method` instead of `func` -> Adaptions affect `BaseModule` and `GenericModule`
|
||||||
- Added:
|
- Added:
|
||||||
- Added Tests for the Properties of NopeRpcManager, NopeConnectivityManager
|
- Added Tests for the Properties of NopeRpcManager, NopeConnectivityManager
|
||||||
- `lib/helpers`:
|
- `lib\helpers`:
|
||||||
- `PriorityList`: -> List, which sorts the items based on a given priority.
|
- `PriorityList`: -> List, which sorts the items based on a given priority.
|
||||||
- `LimitedList`: -> Ring-Like list. If the max amount of items is reached, the oldest one will be removed
|
- `LimitedList`: -> Ring-Like list. If the max amount of items is reached, the oldest one will be removed
|
||||||
- `ParallelPriorityTaskQueue` -> A Task-Queue (Parallel and if desired with priority)
|
- `ParallelPriorityTaskQueue` -> A Task-Queue (Parallel and if desired with priority)
|
||||||
- `generateHash` -> A function to generate a hash
|
- `generateHash` -> A function to generate a hash
|
||||||
|
|
||||||
|
# 1.5.0
|
||||||
|
- Modified:
|
||||||
|
- `lib\logger`:
|
||||||
|
- Adding colors to log.
|
||||||
|
- `lib\eventEmitter`:
|
||||||
|
- Adding the possibility of a timeout in `waitFor`
|
||||||
|
- Added:
|
||||||
|
- `lib\plugins`:
|
||||||
|
- Added a full fetch plugin-system for javascript. That allows the user to customize different aspects of the lib. For an plugin see: `lib\plugins\ackMessages.ts` as an example.
|
@ -1 +1 @@
|
|||||||
1.4.6
|
1.5.0
|
@ -358,6 +358,7 @@ export class NopeEventEmitter<
|
|||||||
|
|
||||||
let resolved = false;
|
let resolved = false;
|
||||||
let subscription: INopeObserver = null;
|
let subscription: INopeObserver = null;
|
||||||
|
let timeout = null;
|
||||||
|
|
||||||
return new Promise<G>((resolve, reject) => {
|
return new Promise<G>((resolve, reject) => {
|
||||||
const finish = (error: any, test: boolean, data: G) => {
|
const finish = (error: any, test: boolean, data: G) => {
|
||||||
@ -366,6 +367,10 @@ export class NopeEventEmitter<
|
|||||||
reject(error);
|
reject(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (timeout) {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
}
|
||||||
|
|
||||||
// Unsubscribe the Subscription.
|
// Unsubscribe the Subscription.
|
||||||
if (test && subscription) {
|
if (test && subscription) {
|
||||||
subscription.unsubscribe();
|
subscription.unsubscribe();
|
||||||
@ -402,6 +407,12 @@ export class NopeEventEmitter<
|
|||||||
subscription = _this.subscribe((data, opts) => {
|
subscription = _this.subscribe((data, opts) => {
|
||||||
checkData(data, opts);
|
checkData(data, opts);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (options?.timeout > 0) {
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
finish(Error("Timeout.!"), false, null);
|
||||||
|
}, options.timeout);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
reject(e);
|
reject(e);
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ import * as promise from "./promise/index";
|
|||||||
import * as pubSub from "./pubSub";
|
import * as pubSub from "./pubSub";
|
||||||
import * as types from "./types/index";
|
import * as types from "./types/index";
|
||||||
import * as ui from "./ui/index.browser";
|
import * as ui from "./ui/index.browser";
|
||||||
|
import * as plugins from "./plugins/index";
|
||||||
|
|
||||||
export * from "./communication/index.browser";
|
export * from "./communication/index.browser";
|
||||||
export * from "./decorators";
|
export * from "./decorators";
|
||||||
@ -46,4 +47,5 @@ export {
|
|||||||
promise,
|
promise,
|
||||||
pubSub,
|
pubSub,
|
||||||
ui,
|
ui,
|
||||||
|
plugins,
|
||||||
};
|
};
|
||||||
|
@ -9,13 +9,13 @@ export {
|
|||||||
runNopeBackend,
|
runNopeBackend,
|
||||||
} from "./cli/runNopeBackend";
|
} from "./cli/runNopeBackend";
|
||||||
export * from "./communication/index.nodejs";
|
export * from "./communication/index.nodejs";
|
||||||
export * as communcation from "./communication/index.nodejs";
|
export * as communication from "./communication/index.nodejs";
|
||||||
export * from "./decorators";
|
export * from "./decorators";
|
||||||
export * as decorators from "./decorators";
|
export * as decorators from "./decorators";
|
||||||
export * as dispatcher from "./dispatcher";
|
export * as dispatcher from "./dispatcher";
|
||||||
export * from "./dispatcher/index";
|
export * from "./dispatcher/index";
|
||||||
export * from "./eventEmitter";
|
export * from "./eventEmitter";
|
||||||
export * as eventEmitters from "./eventEmitter";
|
export * as eventEmitter from "./eventEmitter";
|
||||||
export * from "./helpers/index.nodejs";
|
export * from "./helpers/index.nodejs";
|
||||||
export * as helpers from "./helpers/index.nodejs";
|
export * as helpers from "./helpers/index.nodejs";
|
||||||
export * from "./loader/index.nodejs";
|
export * from "./loader/index.nodejs";
|
||||||
@ -26,6 +26,7 @@ export * from "./module/index";
|
|||||||
export * as modules from "./module/index";
|
export * as modules from "./module/index";
|
||||||
export * as observables from "./observables";
|
export * as observables from "./observables";
|
||||||
export * from "./observables/index";
|
export * from "./observables/index";
|
||||||
|
export * as plugins from "./plugins";
|
||||||
export * as promises from "./promise";
|
export * as promises from "./promise";
|
||||||
export * from "./promise/index";
|
export * from "./promise/index";
|
||||||
export * from "./pubSub";
|
export * from "./pubSub";
|
||||||
|
@ -68,9 +68,10 @@ export const formatMsgForConsole = RUNNINGINNODE
|
|||||||
"-",
|
"-",
|
||||||
colors.BgWhite + colors.FgBlack,
|
colors.BgWhite + colors.FgBlack,
|
||||||
context.name,
|
context.name,
|
||||||
colors.Reset,
|
colorMatching[context.level.name],
|
||||||
":",
|
":",
|
||||||
...message,
|
...message,
|
||||||
|
colors.Reset,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
: function (message, context) {
|
: function (message, context) {
|
||||||
@ -101,8 +102,10 @@ Logger.useDefaults({
|
|||||||
colors.BgWhite + colors.FgBlack,
|
colors.BgWhite + colors.FgBlack,
|
||||||
context.name,
|
context.name,
|
||||||
colors.Reset,
|
colors.Reset,
|
||||||
|
colorMatching[context.level.name],
|
||||||
":"
|
":"
|
||||||
);
|
);
|
||||||
|
messages.push(colors.Reset);
|
||||||
}
|
}
|
||||||
: function (messages, context) {
|
: function (messages, context) {
|
||||||
messages.unshift(
|
messages.unshift(
|
||||||
|
37
lib/plugins/ackMessages.spec.ts
Normal file
37
lib/plugins/ackMessages.spec.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { describe, it } from "mocha";
|
||||||
|
|
||||||
|
describe("Plugins", function () {
|
||||||
|
// Describe the required Test:
|
||||||
|
describe("AckMessage", function () {
|
||||||
|
it("by-name", async function () {
|
||||||
|
delete require.cache[require.resolve("../index.nodejs")];
|
||||||
|
const nope = require("../index.nodejs");
|
||||||
|
nope.plugins.installPlugins(nope as any, "ackMessages", false);
|
||||||
|
|
||||||
|
const loader = await nope.runNopeBackend({
|
||||||
|
skipLoadingConfig: true,
|
||||||
|
log: "error",
|
||||||
|
});
|
||||||
|
|
||||||
|
await loader.dispatcher.ready.waitFor();
|
||||||
|
const err = Error("This should not be raised!");
|
||||||
|
try {
|
||||||
|
await loader.dispatcher.communicator.emit(
|
||||||
|
"hello",
|
||||||
|
{ data: "test" },
|
||||||
|
"wont be there",
|
||||||
|
1000
|
||||||
|
);
|
||||||
|
throw err;
|
||||||
|
} catch (e) {
|
||||||
|
if (e === err) {
|
||||||
|
delete require.cache[require.resolve("../index.nodejs")];
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await loader.dispatcher.dispose();
|
||||||
|
|
||||||
|
delete require.cache[require.resolve("../index.nodejs")];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
226
lib/plugins/ackMessages.ts
Normal file
226
lib/plugins/ackMessages.ts
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
import { plugin } from "./plugin";
|
||||||
|
import { Bridge as OrgBridge } from "../communication/index.browser";
|
||||||
|
import { NopeConnectivityManager as OrgConnectivityManager } from "../dispatcher/ConnectivityManager";
|
||||||
|
import { ValidLoggerDefinition } from "../logger/getLogger";
|
||||||
|
import { INopeEventEmitter, NopeEventEmitter } from "../eventEmitter";
|
||||||
|
import {
|
||||||
|
EventnameToEventType,
|
||||||
|
IEventAdditionalData,
|
||||||
|
INopeINopeConnectivityOptions,
|
||||||
|
INopeObservable,
|
||||||
|
toConstructor,
|
||||||
|
} from "../types";
|
||||||
|
import { generateId, difference } from "../helpers/index.browser";
|
||||||
|
|
||||||
|
export const extend = plugin(
|
||||||
|
[
|
||||||
|
"communication.Bridge",
|
||||||
|
"dispatcher.connectivityManager.NopeConnectivityManager",
|
||||||
|
],
|
||||||
|
(
|
||||||
|
clBridge: toConstructor<OrgBridge>,
|
||||||
|
clConnectivityManager: toConstructor<OrgConnectivityManager>
|
||||||
|
) => {
|
||||||
|
interface AckMessage {
|
||||||
|
messageId: string;
|
||||||
|
dispatcherId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Bridge extends clBridge {
|
||||||
|
/**
|
||||||
|
* Helper to forward received messages.
|
||||||
|
*
|
||||||
|
* @protected
|
||||||
|
* @type {INopeEventEmitter<AckMessage>}
|
||||||
|
* @memberof Bridge
|
||||||
|
*/
|
||||||
|
protected _onMessageReceived: INopeEventEmitter<AckMessage>;
|
||||||
|
/**
|
||||||
|
* Map storing the messages, where we expect an Acknowledgement.
|
||||||
|
*
|
||||||
|
* @protected
|
||||||
|
* @memberof Bridge
|
||||||
|
*/
|
||||||
|
protected _openMessages: Map<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
received: Set<string>;
|
||||||
|
target: Set<string>;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
|
||||||
|
public onTransportError: INopeEventEmitter;
|
||||||
|
public defaultTargets: Array<string>;
|
||||||
|
public ackReplyId: string;
|
||||||
|
|
||||||
|
constructor(id?: string, logger?: ValidLoggerDefinition) {
|
||||||
|
super(id, logger);
|
||||||
|
|
||||||
|
this._onMessageReceived = new NopeEventEmitter();
|
||||||
|
this._openMessages = new Map();
|
||||||
|
this.defaultTargets = [];
|
||||||
|
this.ackReplyId = null;
|
||||||
|
this.onTransportError = new NopeEventEmitter();
|
||||||
|
|
||||||
|
this.onTransportError.subscribe((err) => {
|
||||||
|
if (this._logger) {
|
||||||
|
this._logger.error("Failed to receive an acknowledge message!");
|
||||||
|
this._logger.error(err);
|
||||||
|
} else {
|
||||||
|
console.error("Failed to receive an acknowledge message!");
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.on("ackMessage" as any, (msg) =>
|
||||||
|
this._onMessageReceived.emit(msg)
|
||||||
|
).catch((err) => {
|
||||||
|
if (this._logger) {
|
||||||
|
this._logger.error("Failed to subscribe to 'ackMessage'");
|
||||||
|
this._logger.error(err);
|
||||||
|
} else {
|
||||||
|
console.error("Failed to subscribe to 'ackMessage'");
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async emit<T extends keyof EventnameToEventType>(
|
||||||
|
eventname: T,
|
||||||
|
data: EventnameToEventType[T],
|
||||||
|
target: string | Array<string> = null,
|
||||||
|
timeout: number = 0
|
||||||
|
): Promise<void> {
|
||||||
|
if ((eventname as any) !== "ackMessage" && this.ackReplyId) {
|
||||||
|
// Firstly we try to define the Target.
|
||||||
|
let targetToUse = new Set();
|
||||||
|
|
||||||
|
if (target === null) {
|
||||||
|
if (this.defaultTargets) {
|
||||||
|
targetToUse = new Set(this.defaultTargets);
|
||||||
|
} else if (data.target) {
|
||||||
|
targetToUse.add(data.target);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (typeof target === "string") {
|
||||||
|
targetToUse.add(target);
|
||||||
|
} else if (Array.isArray(target)) {
|
||||||
|
target.map((item) => targetToUse.add(item));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetToUse.size) {
|
||||||
|
const messageId = generateId();
|
||||||
|
data.messageId = messageId;
|
||||||
|
|
||||||
|
// We will define a Promise, which will wait for the ackknowledge ment.
|
||||||
|
const promise = this._onMessageReceived.waitFor(
|
||||||
|
(msg) => {
|
||||||
|
// If the Message is still open we try to
|
||||||
|
// close it.
|
||||||
|
if (this._openMessages.has(msg.messageId)) {
|
||||||
|
const target = this._openMessages.get(msg.messageId).target;
|
||||||
|
const received = this._openMessages.get(
|
||||||
|
msg.messageId
|
||||||
|
).received;
|
||||||
|
received.add(msg.dispatcherId);
|
||||||
|
|
||||||
|
// Therefore we determine the difference between
|
||||||
|
// the targets and
|
||||||
|
if (difference(target, received).size === 0) {
|
||||||
|
this._openMessages.delete(msg.messageId);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timeout,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Now lets call emit
|
||||||
|
const res = await super.emit(eventname, data);
|
||||||
|
|
||||||
|
// And now we will await the
|
||||||
|
// Wait - For result.
|
||||||
|
await promise;
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return await super.emit(eventname, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async on<T extends keyof EventnameToEventType>(
|
||||||
|
eventname: T,
|
||||||
|
cb: (data: EventnameToEventType[T]) => void
|
||||||
|
): Promise<void> {
|
||||||
|
if ((eventname as string) === "ackMessage") {
|
||||||
|
return await super.on(eventname, cb);
|
||||||
|
} else {
|
||||||
|
return await super.on(eventname, (msg) => {
|
||||||
|
cb(msg);
|
||||||
|
|
||||||
|
if (msg.messageId && this.ackReplyId) {
|
||||||
|
this.emit("ackMessage" as any, {
|
||||||
|
messageId: msg.messageId,
|
||||||
|
dispatcherId: this.ackReplyId,
|
||||||
|
}).catch((err) => {
|
||||||
|
if (this._logger) {
|
||||||
|
this._logger.error("Failed to emit an acknowledge message!");
|
||||||
|
this._logger.error(err);
|
||||||
|
} else {
|
||||||
|
console.error("Failed to emit an acknowledge message!");
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NopeConnectivityManager extends clConnectivityManager {
|
||||||
|
public forceAckMessage: boolean;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
options: INopeINopeConnectivityOptions,
|
||||||
|
_generateObservable: <T>() => INopeObservable<
|
||||||
|
T,
|
||||||
|
T,
|
||||||
|
T,
|
||||||
|
IEventAdditionalData
|
||||||
|
>,
|
||||||
|
id?: string
|
||||||
|
) {
|
||||||
|
super(options, _generateObservable, id);
|
||||||
|
|
||||||
|
(this._communicator as Bridge).ackReplyId = this.id;
|
||||||
|
this.forceAckMessage = true;
|
||||||
|
|
||||||
|
this.dispatchers.data.subscribe((dispatchers) => {
|
||||||
|
if (this.forceAckMessage) {
|
||||||
|
(this._communicator as Bridge).defaultTargets = dispatchers;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
adapted: Bridge,
|
||||||
|
name: "Bridge",
|
||||||
|
path: "communication.Bridge",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
adapted: NopeConnectivityManager,
|
||||||
|
name: "NopeConnectivityManager",
|
||||||
|
path: "dispatcher.connectivityManager.NopeConnectivityManager",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
},
|
||||||
|
"ackMessages"
|
||||||
|
);
|
34
lib/plugins/hello.ts
Normal file
34
lib/plugins/hello.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { plugin } from "./plugin";
|
||||||
|
import { NopeConnectivityManager as OrgConnectivityManager } from "../dispatcher/ConnectivityManager";
|
||||||
|
import { toConstructor } from "../types";
|
||||||
|
|
||||||
|
export const extend = plugin(
|
||||||
|
[
|
||||||
|
"dispatcher.connectivityManager.NopeConnectivityManager",
|
||||||
|
"helpers.ids.generateId",
|
||||||
|
],
|
||||||
|
(clConnectivityManager: toConstructor<OrgConnectivityManager>, orgGenId) => {
|
||||||
|
class NopeConnectivityManager extends clConnectivityManager {
|
||||||
|
public hello(name: string) {
|
||||||
|
return `Hello ${name}!`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
adapted: NopeConnectivityManager,
|
||||||
|
name: "NopeConnectivityManager",
|
||||||
|
path: "dispatcher.connectivityManager.NopeConnectivityManager",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
adapted: (...args) => {
|
||||||
|
const id = orgGenId(...args);
|
||||||
|
return id;
|
||||||
|
},
|
||||||
|
name: "generateId",
|
||||||
|
path: "helpers.ids.generateId",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
},
|
||||||
|
"hello"
|
||||||
|
);
|
10
lib/plugins/index.ts
Normal file
10
lib/plugins/index.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { plugin, installPlugins, allPlugins } from "./plugin";
|
||||||
|
export { plugin, installPlugins, allPlugins };
|
||||||
|
|
||||||
|
import { extend as ackMessages } from "./ackMessages";
|
||||||
|
import { extend as hello } from "./hello";
|
||||||
|
|
||||||
|
export const availablePlugins = {
|
||||||
|
ackMessages,
|
||||||
|
hello,
|
||||||
|
};
|
67
lib/plugins/plugin.spec.ts
Normal file
67
lib/plugins/plugin.spec.ts
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import { assert } from "chai";
|
||||||
|
import { describe, it } from "mocha";
|
||||||
|
import { availablePlugins } from "./index";
|
||||||
|
import { allPlugins, installPlugins, plugin } from "./plugin";
|
||||||
|
|
||||||
|
describe("PluginSystem", function () {
|
||||||
|
// Describe the required Test:
|
||||||
|
|
||||||
|
it("List Plugins", function () {
|
||||||
|
assert(allPlugins().length == 2, "There Should be to Plugins");
|
||||||
|
});
|
||||||
|
describe("load single Plugins", function () {
|
||||||
|
it("by-name", function () {
|
||||||
|
delete require.cache[require.resolve("../index.nodejs")];
|
||||||
|
const nope = require("../index.nodejs");
|
||||||
|
installPlugins(nope, "hello", false);
|
||||||
|
delete require.cache[require.resolve("../index.nodejs")];
|
||||||
|
});
|
||||||
|
it("by-path", function () {
|
||||||
|
delete require.cache[require.resolve("../index.nodejs")];
|
||||||
|
const nope = require("../index.nodejs");
|
||||||
|
installPlugins(nope, "plugins.availablePlugins.hello", false);
|
||||||
|
delete require.cache[require.resolve("../index.nodejs")];
|
||||||
|
});
|
||||||
|
it("by-plugin", function () {
|
||||||
|
delete require.cache[require.resolve("../index.nodejs")];
|
||||||
|
const nope = require("../index.nodejs");
|
||||||
|
installPlugins(nope, availablePlugins.hello, false);
|
||||||
|
delete require.cache[require.resolve("../index.nodejs")];
|
||||||
|
});
|
||||||
|
it("dynamic-plugin", function () {
|
||||||
|
delete require.cache[require.resolve("../index.nodejs")];
|
||||||
|
const nope = require("../index.nodejs");
|
||||||
|
const extend = plugin("generateId", (org) => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
adapted: (...args) => {
|
||||||
|
return org(...args);
|
||||||
|
},
|
||||||
|
name: "generateId",
|
||||||
|
path: "generateId",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
|
installPlugins(nope, extend, false);
|
||||||
|
delete require.cache[require.resolve("../index.nodejs")];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe("load single Plugins", function () {
|
||||||
|
it("single-list-item", function () {
|
||||||
|
delete require.cache[require.resolve("../index.nodejs")];
|
||||||
|
const nope = require("../index.nodejs");
|
||||||
|
installPlugins(nope, ["hello"], false);
|
||||||
|
delete require.cache[require.resolve("../index.nodejs")];
|
||||||
|
});
|
||||||
|
it("by-path", function () {
|
||||||
|
delete require.cache[require.resolve("../index.nodejs")];
|
||||||
|
const nope = require("../index.nodejs");
|
||||||
|
installPlugins(
|
||||||
|
nope,
|
||||||
|
["hello", "plugins.availablePlugins.hello", availablePlugins.hello],
|
||||||
|
false
|
||||||
|
);
|
||||||
|
delete require.cache[require.resolve("../index.nodejs")];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
380
lib/plugins/plugin.ts
Normal file
380
lib/plugins/plugin.ts
Normal file
@ -0,0 +1,380 @@
|
|||||||
|
import { rgetattr, rsetattr, getType } from "../helpers/objectMethods";
|
||||||
|
import { union } from "../helpers/setMethods";
|
||||||
|
import { getNopeLogger } from "../logger/index.browser";
|
||||||
|
import { getSingleton } from "../helpers/singletonMethod";
|
||||||
|
|
||||||
|
let COUNTER = 0;
|
||||||
|
const SPLITCHAR = ".";
|
||||||
|
const PLUGIN_STORE = getSingleton("nope.plugins", () => {
|
||||||
|
return new Map<string, Plugin>();
|
||||||
|
});
|
||||||
|
|
||||||
|
const ABORT_INSPECTION_TESTERS = [
|
||||||
|
(item) => {
|
||||||
|
const type = typeof item;
|
||||||
|
|
||||||
|
const not = [
|
||||||
|
"string",
|
||||||
|
"number",
|
||||||
|
"bigint",
|
||||||
|
"boolean",
|
||||||
|
"symbol",
|
||||||
|
"undefined",
|
||||||
|
"function",
|
||||||
|
];
|
||||||
|
|
||||||
|
return not.includes(type);
|
||||||
|
},
|
||||||
|
(item) => Array.isArray(item),
|
||||||
|
];
|
||||||
|
|
||||||
|
function shouldAbort(item) {
|
||||||
|
for (const test of ABORT_INSPECTION_TESTERS) {
|
||||||
|
if (test(item)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function recursiveForEachModule(
|
||||||
|
obj: any,
|
||||||
|
prefix: string = "",
|
||||||
|
map: Map<string, any> = null,
|
||||||
|
splitchar: string = SPLITCHAR,
|
||||||
|
maxDepth = Infinity,
|
||||||
|
level = 0
|
||||||
|
): any {
|
||||||
|
if (map === null) {
|
||||||
|
map = new Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
map.set(prefix, obj);
|
||||||
|
|
||||||
|
if (level > maxDepth) {
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldAbort(obj)) {
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create an Array with the Keys.
|
||||||
|
const keys = Object.getOwnPropertyNames(obj);
|
||||||
|
|
||||||
|
// If there are Keys => It is a List or a Default Object
|
||||||
|
if (keys.length > 0) {
|
||||||
|
for (const _key of keys) {
|
||||||
|
// Define the variable, containing the path
|
||||||
|
const path = prefix === "" ? _key : prefix + splitchar + _key;
|
||||||
|
map = recursiveForEachModule(
|
||||||
|
obj[_key],
|
||||||
|
path,
|
||||||
|
map,
|
||||||
|
splitchar,
|
||||||
|
maxDepth,
|
||||||
|
level + 1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flattens an Object to a Map.
|
||||||
|
*
|
||||||
|
* For Instance:
|
||||||
|
*
|
||||||
|
* data = {a : { b : { c : 1, d: "hallo"}}}
|
||||||
|
*
|
||||||
|
* // Normal Call
|
||||||
|
* res = flatteObject(data)
|
||||||
|
* => res = {"a.b.c":1,"a.b.d":"hallo"}
|
||||||
|
*
|
||||||
|
* // With a Selected prefix 'additional.name'
|
||||||
|
* res = flatteObject(data,{prefix:'additional.name'})
|
||||||
|
* => res = {"additional.name.a.b.c":1,"additional.name.a.b.d":"hallo"}
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @param {*} lib The Data that should be converted
|
||||||
|
* @param {string} [prefix=''] An additional prefix.
|
||||||
|
* @returns {Map<string, any>} The flatten Object
|
||||||
|
*/
|
||||||
|
function flattenLibrary(
|
||||||
|
lib: any,
|
||||||
|
options: {
|
||||||
|
prefix?: string;
|
||||||
|
splitchar?: string;
|
||||||
|
maxDepth?: number;
|
||||||
|
} = {}
|
||||||
|
): Map<string, any> {
|
||||||
|
const optionsToUse = Object.assign(
|
||||||
|
{
|
||||||
|
prefix: "",
|
||||||
|
splitchar: SPLITCHAR,
|
||||||
|
maxDepth: Infinity,
|
||||||
|
},
|
||||||
|
options
|
||||||
|
);
|
||||||
|
|
||||||
|
return recursiveForEachModule(
|
||||||
|
lib,
|
||||||
|
optionsToUse.prefix,
|
||||||
|
new Map(),
|
||||||
|
options.splitchar,
|
||||||
|
options.maxDepth,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function listOccourence(
|
||||||
|
lib,
|
||||||
|
options: {
|
||||||
|
splitchar?: string;
|
||||||
|
maxDepth?: number;
|
||||||
|
} = {}
|
||||||
|
) {
|
||||||
|
const optionsToUse = Object.assign(
|
||||||
|
{
|
||||||
|
splitchar: SPLITCHAR,
|
||||||
|
maxDepth: Infinity,
|
||||||
|
},
|
||||||
|
options
|
||||||
|
);
|
||||||
|
|
||||||
|
const flattend = flattenLibrary(lib, optionsToUse);
|
||||||
|
|
||||||
|
const occourence = new Map<string, Set<string>>();
|
||||||
|
|
||||||
|
for (const key of flattend.keys()) {
|
||||||
|
const split = key.split(optionsToUse.splitchar);
|
||||||
|
const last = split[split.length - 1];
|
||||||
|
|
||||||
|
if (!occourence.has(last)) {
|
||||||
|
occourence.set(last, new Set());
|
||||||
|
}
|
||||||
|
|
||||||
|
occourence.get(last).add(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
flattend,
|
||||||
|
occourence,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to install an addon.
|
||||||
|
* @param library
|
||||||
|
* @param item
|
||||||
|
* @param replacer
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function implementChanges(library, item: string | any, replacer) {
|
||||||
|
const { occourence, flattend } = listOccourence(library);
|
||||||
|
const failed = new Array<{ error: any; destination: string }>();
|
||||||
|
if (occourence.has(item)) {
|
||||||
|
for (const destination of occourence.get(item)) {
|
||||||
|
try {
|
||||||
|
rsetattr(library, destination, replacer, SPLITCHAR);
|
||||||
|
} catch (error) {
|
||||||
|
failed.push({
|
||||||
|
error,
|
||||||
|
destination,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return library;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Plugin = ExtendFunction & {
|
||||||
|
install: (lib: string | NodeModule) => Set<string>;
|
||||||
|
base: string[];
|
||||||
|
pluginName: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function isPlugin(plug: Plugin): plug is Plugin {
|
||||||
|
if (typeof plug !== "function") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ((plug as Plugin).install === undefined) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ((plug as Plugin).pluginName === undefined) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ExtendFunction = (
|
||||||
|
...args
|
||||||
|
) => Array<{ path: string; name: string; adapted: any }>;
|
||||||
|
|
||||||
|
export function plugin(
|
||||||
|
base: string | string[],
|
||||||
|
extend: ExtendFunction,
|
||||||
|
name = ""
|
||||||
|
): Plugin {
|
||||||
|
if (!Array.isArray(base)) {
|
||||||
|
base = [base];
|
||||||
|
}
|
||||||
|
if (name === "") {
|
||||||
|
try {
|
||||||
|
name = `anonymousPlugin${COUNTER++}@${arguments.callee.name}`;
|
||||||
|
} catch (e) {
|
||||||
|
name = `anonymousPlugin${COUNTER++}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(extend as Plugin).base = base;
|
||||||
|
(extend as Plugin).pluginName = name;
|
||||||
|
(extend as Plugin).install = (lib: string | NodeModule) => {
|
||||||
|
if (typeof lib == "string") {
|
||||||
|
lib = require(lib);
|
||||||
|
}
|
||||||
|
|
||||||
|
const itemsToUpdate = (base as string[]).map((item) =>
|
||||||
|
rgetattr(lib, item, false, ".")
|
||||||
|
);
|
||||||
|
|
||||||
|
if (itemsToUpdate.includes(false)) {
|
||||||
|
throw Error(
|
||||||
|
"Faild to grap some of the given base elements. Please check parameter 'base'"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let modified = new Set<string>();
|
||||||
|
|
||||||
|
// Now apply the addon:
|
||||||
|
const adaptions = extend(...itemsToUpdate);
|
||||||
|
|
||||||
|
if (!Array.isArray(adaptions)) {
|
||||||
|
throw Error("Return-Type of the Plugin doesnt match.");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const { path, name, adapted } of adaptions) {
|
||||||
|
lib = implementChanges(lib, path, adapted);
|
||||||
|
modified = union(modified, checkRequireCache(name, adapted));
|
||||||
|
}
|
||||||
|
|
||||||
|
return modified;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Store our Plugin as store.
|
||||||
|
PLUGIN_STORE.instance.set(name, extend as Plugin);
|
||||||
|
|
||||||
|
return extend as Plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to install Plugins.
|
||||||
|
* @param lib The Library to modify.
|
||||||
|
* @param plugins The Plugins install. This can be the registered names, pathes in the library or the plugin itself.
|
||||||
|
* @param log Flag to control the log information.
|
||||||
|
*/
|
||||||
|
export function installPlugins(
|
||||||
|
lib: string | NodeModule,
|
||||||
|
plugins: string | Plugin | Array<Plugin | string>,
|
||||||
|
log: boolean = true
|
||||||
|
) {
|
||||||
|
let modified = new Set<string>();
|
||||||
|
|
||||||
|
if (!Array.isArray(plugins)) {
|
||||||
|
plugins = [plugins];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof lib == "string") {
|
||||||
|
lib = require(lib);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pluginsToUse = new Array<Plugin>();
|
||||||
|
|
||||||
|
// In this loop we ensure that we load the correct plugin.
|
||||||
|
for (const plug of plugins) {
|
||||||
|
if (typeof plug === "string") {
|
||||||
|
// The Plugin is provided as String.
|
||||||
|
// 1. Check if the name is present:
|
||||||
|
|
||||||
|
if (PLUGIN_STORE.instance.has(plug)) {
|
||||||
|
pluginsToUse.push(PLUGIN_STORE.instance.get(plug));
|
||||||
|
} else if (isPlugin(rgetattr(lib, plug, false, "."))) {
|
||||||
|
pluginsToUse.push(rgetattr(lib, plug, false, "."));
|
||||||
|
} else {
|
||||||
|
const p = require(plug as string).extend;
|
||||||
|
if (isPlugin(p)) {
|
||||||
|
pluginsToUse.push(PLUGIN_STORE.instance.get(plug));
|
||||||
|
} else {
|
||||||
|
throw Error(
|
||||||
|
"Cannot find plugin '" +
|
||||||
|
plug +
|
||||||
|
"'. If this is a file, make shure the plugin is exported as 'extend'"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (isPlugin(plug)) {
|
||||||
|
pluginsToUse.push(plug);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let used_plugins_str =
|
||||||
|
"Plugins used!\n\n" +
|
||||||
|
"-".repeat(50) +
|
||||||
|
"\nPLUGIN INSTALLTION REPORT:\n" +
|
||||||
|
"-".repeat(50) +
|
||||||
|
"\n\nInstalled the following plugins:";
|
||||||
|
let used_bases_str = "\n\nThe following source have been modified:";
|
||||||
|
let used_bases = new Set<string>();
|
||||||
|
|
||||||
|
for (const plug of pluginsToUse) {
|
||||||
|
// Store the Plugin
|
||||||
|
used_plugins_str += "\n\t- " + plug.pluginName;
|
||||||
|
// Store the modified elements:
|
||||||
|
plug.base.map((item) => used_bases.add(item));
|
||||||
|
// Update the modified sources
|
||||||
|
modified = union(modified, plug.install(lib));
|
||||||
|
}
|
||||||
|
|
||||||
|
Array.from(used_bases).map((item) => (used_bases_str += "\n\t- " + item));
|
||||||
|
|
||||||
|
const end_str = "\n\nWatchout this may change the default behavior!\n\n";
|
||||||
|
|
||||||
|
const to_print = used_plugins_str + used_bases_str + end_str;
|
||||||
|
|
||||||
|
if (log) {
|
||||||
|
const logger = getNopeLogger("plugin-system", "debug");
|
||||||
|
logger.warn(to_print);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to list all Plugins
|
||||||
|
* @returns List of recognized Plugins
|
||||||
|
*/
|
||||||
|
export function allPlugins() {
|
||||||
|
return Array.from(PLUGIN_STORE.instance.keys());
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkRequireCache(name: string, adapted: any) {
|
||||||
|
const modified = new Set<string>();
|
||||||
|
for (const absFileName in require.cache) {
|
||||||
|
const mod = require.cache[absFileName];
|
||||||
|
|
||||||
|
if (mod.loaded) {
|
||||||
|
const exportedItems = Object.getOwnPropertyNames(mod.exports);
|
||||||
|
if (exportedItems.includes(name)) {
|
||||||
|
try {
|
||||||
|
mod.exports[name] = adapted;
|
||||||
|
modified.add(absFileName);
|
||||||
|
} catch (e) {
|
||||||
|
// We are not allowed to reassign
|
||||||
|
// exported members only.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return modified;
|
||||||
|
}
|
@ -10,3 +10,7 @@ export * from "./IJSONSchema";
|
|||||||
export * from "./nope/index";
|
export * from "./nope/index";
|
||||||
export * from "./ui/index";
|
export * from "./ui/index";
|
||||||
export { nope, ui };
|
export { nope, ui };
|
||||||
|
|
||||||
|
export type toConstructor<T> = {
|
||||||
|
new (...args): T;
|
||||||
|
};
|
||||||
|
@ -16,11 +16,21 @@ export type IWaitForCallback<
|
|||||||
export interface INopeWaitForEventOptions {
|
export interface INopeWaitForEventOptions {
|
||||||
subscriptionMode?: "immediate" | "sync";
|
subscriptionMode?: "immediate" | "sync";
|
||||||
triggerMode?: Array<"sub" | "super" | "direct">;
|
triggerMode?: Array<"sub" | "super" | "direct">;
|
||||||
|
/**
|
||||||
|
* Timeout in *ms* after the waifFor fails.
|
||||||
|
*/
|
||||||
|
timeout?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface INopeObserver extends Subscription {
|
export interface INopeObserver extends Subscription {
|
||||||
options: INopeSubscriptionOptions;
|
options: INopeSubscriptionOptions;
|
||||||
|
/**
|
||||||
|
* Pauses the Subscription
|
||||||
|
*/
|
||||||
pause(): void;
|
pause(): void;
|
||||||
|
/**
|
||||||
|
* Unpauses the Subscription
|
||||||
|
*/
|
||||||
unpause(): void;
|
unpause(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,12 +17,17 @@ import { INopeEventEmitter } from ".";
|
|||||||
import {
|
import {
|
||||||
IEventAdditionalData,
|
IEventAdditionalData,
|
||||||
IEventCallback,
|
IEventCallback,
|
||||||
|
INopeWaitForEventOptions,
|
||||||
} from "./nopeEventEmitter.interface";
|
} from "./nopeEventEmitter.interface";
|
||||||
|
|
||||||
export interface INopeWaitForObservableChangeOptions {
|
export interface INopeWaitForObservableChangeOptions
|
||||||
|
extends INopeWaitForEventOptions {
|
||||||
|
/**
|
||||||
|
* Directly test the current value.
|
||||||
|
* Otherwise waits for an updated
|
||||||
|
* value.
|
||||||
|
*/
|
||||||
testCurrent?: boolean;
|
testCurrent?: boolean;
|
||||||
subscriptionMode?: "immediate" | "sync";
|
|
||||||
triggerMode?: Array<"sub" | "super" | "direct">;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface INopePartialObserver<
|
export interface INopePartialObserver<
|
||||||
|
Loading…
Reference in New Issue
Block a user