UNPKG

nope-js-node

Version:

NoPE Runtime for Nodejs. For Browser-Support please use nope-browser

436 lines (434 loc) 15.7 kB
/** * @author Martin Karkowski * @email m.karkowski@zema.de */ import { INopeDescriptor } from "./nopeDescriptor.interface"; import { IEventAdditionalData, IEventCallback, INopeEventEmitter, INopeObserver } from "./nopeEventEmitter.interface"; import { IMapBasedMergeData } from "./nopeHelpers.interface"; import { IEventOptions } from "./nopeModule.interface"; import { INopeObservable } from "./nopeObservable.interface"; export interface ITopicSetContentOptions extends IEventAdditionalData { topicOfSubscription: string; topicOfContent: string; topicOfChange: string; } /** * The Topic Type. */ export type INopeTopic<T = any, S = T, G = T> = INopeEventEmitter<T, S, G, ITopicSetContentOptions>; export type INopeTopicWithDirectAccess<T = any, S = T, G = T> = INopeObservable<T, S, G, ITopicSetContentOptions>; export interface IPubSubSystemConstructor { new (_generateObservable: <T>() => INopeEventEmitter<T>): IPubSubSystem; } /** * Options to define the behavior of a {@link IPubSubSystem} * * The **Default** settings are. * * ```typescript * { * mqttPatternBasedSubscriptions: true, * forwardChildData: true, * forwardParentData: true, * matchTopicsWithoutWildcards: true, * } * ``` */ export interface IPubSubOptions { /** * If set to true, subscriptions with patterns will be handled * like on mqtt. Otherwise, we will distribute the content more * or lesss like on pattern less topic. ('#' and '+' in a topic * will cause an exception) * * @author M.Karkowski * @type {boolean} * @memberof TPubSubOptions */ mqttPatternBasedSubscriptions?: boolean; /** * Flag to enable propagation of child data. * Defaults to true. This will forward data changes, if * a subscriber is listening to `'a/b'` and data on `'a/b/c'` is * changed. The subscriber will receive an object like { ... , c: ... } * * If set to false, these changes arent forwarded. * * @author M.Karkowski * @type {boolean} * @memberof TPubSubOptions */ forwardChildData?: boolean; /** * Flag to enable the propagation of parent * changed data. Defaults to true. This will forward data changes, if * a subscriber is listening to `'a/b/c'` and data on `'a/b'` is * changed. The subscriber will get the property `c` of the published object * on `'a/b'` (if it exists, otherwise it will receive null) * * If set to false, these changes arent forwarded. * * * @author M.Karkowski * @type {boolean} * @memberof TPubSubOptions */ forwardParentData?: boolean; /** * If enabled, the topics match without wildcards. (must be enabled for * the defined behavior in `forwardChildData` and `forwardParentData`) * * @author M.Karkowski * @type {boolean} * @memberof TPubSubOptions */ matchTopicsWithoutWildcards?: boolean; } /** * Element, showing the changes, of data. This contains the path and the updated data. */ export interface IIncrementalChange extends IEventAdditionalData { path: string; data: unknown; } /*** * Only used interanlly. */ export interface IPubSubEmitterOptions<AD extends ITopicSetContentOptions & { pubSubUpdate?: boolean; } = ITopicSetContentOptions> { /** * The options for a event. */ options: IEventOptions; /** * The Topic, to subscribe to. If set to `false` no update will be received via the system. */ subTopic: string | false; /** * The Topic to publish the content to. If set to `false` no update will be emitted. */ pubTopic: string | false; /** * For internal use only. A Callback to call if there is an update. */ callback?: IEventCallback<unknown, AD>; /** * For internal use only. */ observer?: INopeObserver; } /** * The default Publish and Subscribe System. * * The System contains of `publishers` and `subscribers` which are linked using `topics` (based on `strings`). * - To add new `publishers` or `subscribers` use the function: `register` and provide the required options (see {@link IPubSubSystem.register}) * - To add new `subscriber` you can use the function : `registerSubscription` which will receive a topic and a `callback` (see {@link IPubSubSystem.registerSubscription}) * - After adding `publishers` or `subscribers` you can change the behavior using `updateOptions` (see {@link IPubSubSystem.updateOptions}) * - To remove `publishers` or `subscribers` use `unregister` (see {@link IPubSubSystem.unregister}) * - to `emit` data use `emit` (see {@link IPubSubSystem.emit}) * - internally, if a subscriber / publisher is added, its options are changed or its removed, the pub sub system updates an matching structure. In the case you want to perform this manually execute {@link IPubSubSystem.updateMatching} * - to check which `publishers` and `subscribers` are present, checkout the corresponding properties. * - You can subscribe to incremental changes using the eventEmitter {@link IPubSubSystem.onIncrementalDataChange} * - If the pub-sub-system isnt needed any more `dispose` it! * * The publisher might be `observabes` or `eventEmitters`. * * The `IPubSubSystem` is implemented by the `PubSubSystemBase`-**Class** * * The Behavior may differ based on the settings. Your are not able to change these options, after the instance has been created. (see {@link IPubSubOptions} for details) * The **Default** of these options are settings are. * * ```typescript * { * mqttPatternBasedSubscriptions: true, * forwardChildData: true, * forwardParentData: true, * matchTopicsWithoutWildcards: true, * } * ``` * * * # Example 1 * * ```typescript * // Describe the required Test: * let pubSubSystem = new PubSubSystemBase({ * generateEmitterType: function () { * return new NopeEventEmitter() as INopeEventEmitter; * }, * }); * * * // Create a Publisher for the system: * let publisher: INopeEventEmitter = new NopeEventEmitter(); * * pubSubSystem.register(publisher, { * mode: "publish", * schema: {}, * topic: "this/is/a/test", * }); * * // Create a Subscriber * let subscriber: INopeEventEmitter = new NopeEventEmitter(); * * subscriber = pubSubSystem.register(subscriber, { * mode: "subscribe", * schema: {}, * topic: "this/#", * }); * * subscriber.subscribe(console.log); * publisher.emit("Hello World!"); // Logs the following => "Hello World!", {...} * ``` * * * @author M.Karkowski * @export * @class PubSubSystem */ export interface IPubSubSystem<AD extends ITopicSetContentOptions = ITopicSetContentOptions, I extends INopeEventEmitter<unknown, unknown, unknown, AD> = INopeEventEmitter<unknown, unknown, unknown, AD>, O extends INopeTopic = INopeTopic> { /** * Options which describe the Behavior * * @author M.Karkowski * @type {IPubSubOptions} * @memberof IPubSubSystem */ readonly options: IPubSubOptions; /** * List all known Emitters in the System. */ readonly emitters: { publishers: { name: string; schema: INopeDescriptor; }[]; subscribers: { name: string; schema: INopeDescriptor; }[]; }; /** * Function to register an Observable. Please define the Options, to decide * whether the data of the observable should be published or subscribed. * * @author M.Karkowski * @param {I} emitter The Emitter to consider * @param {IEventOptions} options * @return {*} {O} * @memberof IPubSubSystem */ register(emitter: I, options: IEventOptions): O; /** * Function to update the options and there by the topics of an observable. * * @author M.Karkowski * @param {I} emitter The Emitter to consider * @param {IEventOptions} options The modified options * @memberof IPubSubSystem */ updateOptions(emitter: I, options: IEventOptions): void; /** * Removes an observable of the Pub-Sub-System. * * @author M.Karkowski * @param {I} observable * @return {*} {boolean} * @memberof IPubSubSystem */ unregister(emitter: I): boolean; /** * A Helper, that allows the user to subscribe to changes. Therfore he must transmit * * @author M.Karkowski * @template T Expected Type of the content * @param {string} path The * @param {IEventCallback<T>} subscription * @return {*} {INopeObserver} * @memberof IPubSubSystem */ registerSubscription<T = unknown>(path: string, subscription: IEventCallback<T, AD>): INopeObserver; /** * Emits an Events and all subscribes, where the pattern matches * will be informed * * @author M.Karkowski * @param {string} path The Topic. * @param {*} data The Data of the Event. * @param {AD} options * @memberof IPubSubSystem */ emit(path: string, data: any, options?: Partial<AD>): void; /** * Helper to manually Trigger an update of the Matching. This will update subscribers and publishers and link them. Normally this is not necessary. * * This will build an internal linking (based on the settings) between publishers and subscribers. * * @author M.Karkowski * @memberof IPubSubSystem */ updateMatching(): void; /** * An observable which holds the incremental data change. * this will be triggered, if the an emitter (publisher) changes its data. * Contains only the last emitted data and the topic * * ```typescript * // Describe the required Test: * let pubSubSystem = new PubSubSystemBase({ * generateEmitterType: function () { * return new NopeEventEmitter() as INopeEventEmitter; * }, * }); * * * // Create a Publisher for the system: * let publisher: INopeEventEmitter = new NopeEventEmitter(); * * pubSubSystem.register(publisher, { * mode: "publish", * schema: {}, * topic: "this/is/a/test", * }); * * * pubSubSystem.onIncrementalDataChange.subscribe(console.log); * publisher.emit("Hello World!"); // Logs the following => {path: "this/is/a/test", data: "Hello World!"} * ``` * * @author M.Karkowski * @type {INopeObservable<{ * path: string, * data: unknown * }>} * @memberof IPubSubSystem */ readonly onIncrementalDataChange: INopeEventEmitter<IIncrementalChange>; /** * List, containing all subscribers. * * @author M.Karkowski * @type {IMapBasedMergeData<string>} * @memberof IPubSubSystem */ readonly subscriptions: IMapBasedMergeData<O, IPubSubEmitterOptions<AD>, O, string>; /** * List containing all publishers. * * @author M.Karkowski * @type {IMapBasedMergeData<string>} * @memberof IPubSubSystem */ readonly publishers: IMapBasedMergeData<O, IPubSubEmitterOptions<AD>, O, string>; /** * Disposes the Pub-Sub-System. * * @author M.Karkowski * @memberof IPubSubSystem */ dispose(): void; /** * Lists all publishers and subscribers of the system. */ toDescription(): { publishers: { name: string; schema: INopeDescriptor; }[]; subscribers: { name: string; schema: INopeDescriptor; }[]; }; } /** * A data-based Publish and Subscribe system. * Extends {@link IPubSubSystem} by providing the the methods and properties: * - `pushData` to push data into the system. * - `pullData` to pull data from the system. Will allways return the current data or the default value if no data is present at the given path. * - `patternbasedPullData` to pull data with a given pattern. See the example for details. * - `patternBasedPush` to push data with a given pattern into the system. * * @author M.Karkowski * @export * @class PubSubSystem */ export interface IDataPubSubSystem<AD extends ITopicSetContentOptions = ITopicSetContentOptions, I extends INopeObservable<unknown, unknown, unknown, AD> = INopeObservable<unknown, unknown, unknown, AD>, O extends INopeTopicWithDirectAccess = INopeTopicWithDirectAccess> extends IPubSubSystem<AD, I, O> { /** * A Getter to return a COPY of the item. Outside of the system, * you'll never receive the original object. * * @author M.Karkowski * @readonly * @type {unknown} * @memberof IPubSubSystem */ data: unknown; /** * Function, to push data. Every subscriber will be informed, if pushing the data on the * given path will affect the subscribers. * * @author M.Karkowski * @template T Type which is pushed * @param {string} path The Path, on which the data should be changed * @param {T} content The content to store in the given path. * @param {Partial<IEventAdditionalData>} options The Options, that will be forwarded to subscribers. * @memberof IPubSubSystem */ pushData<T = unknown>(path: string, content: T, options?: Partial<IEventAdditionalData>): void; /** * Pull some Data of System. You will allways receive a just a copy. This method prevents you * to use a pattern like path. If you want to use patterns please use the "patternbasedPullData" * * @author M.Karkowski * @template T Expected Type of the return. Defaults to unkown * @template D Default Value. * @param {string} path * @param {D} _default If no data is found => return the default data. * @return {T} The Expected Type * @memberof IPubSubSystem */ pullData<T = unknown, D = null>(path: string, _default: D): T; /** * A Pattern based Pull. You can provide a mqtt based pattern and receive an array which contains * all the data which matches the topic. * * @author M.Karkowski * @template T * @template D * @param {string} pattern The pattern to pull the data from * @param {D} _default a default value, o * @return {*} {{ path: string, data: T }[]} * @memberof IPubSubSystem */ patternbasedPullData<T = unknown, D = null>(pattern: string, _default: D): { path: string; data: T; }[]; /** * Pushes data to the elements, where the pattern matches. * * @author M.Karkowski * @template T * @param {string} pattern The pattern, which should be used to forward the data. For valid patterns see {@link pattern} * @param {T} content The content to store in the given path. * @param {Partial<IEventAdditionalData>} options * @memberof IDataPubSubSystem */ patternBasedPush<T = unknown>(pattern: string, content: T, options: Partial<IEventAdditionalData>): void; /** * Lists all publishers and subscribers of the system. */ toDescription(): { publishers: { name: string; schema: INopeDescriptor; }[]; subscribers: { name: string; schema: INopeDescriptor; }[]; data: any; }; }