@ti-engine/core 1.0.14 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  /*
2
- * SPDX-FileCopyrightText: © 2021 Boris Kostadinov <kostadinov.boris@gmail.com>
2
+ * SPDX-FileCopyrightText: © 2021-2023 Boris Kostadinov <kostadinov.boris@gmail.com>
3
3
  * SPDX-License-Identifier: ICU
4
4
  */
5
5
 
@@ -7,6 +7,8 @@ const _ = require( "lodash" );
7
7
  const tools = require( "#tools" );
8
8
  const config = require( "#config" );
9
9
  const logger = require( "#logger" );
10
+ const exceptions = require( "#exceptions" );
11
+ const cache = require( "#cache" );
10
12
 
11
13
  /**
12
14
  * @typedef {Object} TiTraceEntry
@@ -14,13 +16,18 @@ const logger = require( "#logger" );
14
16
  * @property {string} dispatchEvent
15
17
  * @property {string} fromAddress
16
18
  * @property {string} messageID
17
- * @property {string} messageSnapshot
19
+ * @property {Object} messageSnapshot
18
20
  * @property {string} messageState
19
21
  * @property {string} messageType
20
22
  * @property {string} toAddress
21
23
  * @property {string} traceID
24
+ * @property {number} traceTimestamp
22
25
  */
23
26
 
27
+ const traceRoot = {
28
+ trace: []
29
+ };
30
+
24
31
  /**
25
32
  * Enum for listing message types.
26
33
  *
@@ -96,22 +103,28 @@ let formatLogEntry = ( traceEntry ) => {
96
103
  };
97
104
 
98
105
  /**
99
- * Used to obscure sensitive data in the message snapshot and convert it to string.
106
+ * Used to obscure sensitive data in the message, remove the payload, and return a snapshot.
100
107
  *
101
108
  * @method
102
109
  * @param {Message} message
103
- * @returns {string}
110
+ * @returns {Message}
104
111
  * @private
105
112
  */
106
113
  let obscureSensitiveData = ( message ) => {
107
- let messageSnapshot = tools.stringifyJSON( message );
108
- return _.replace( messageSnapshot, /("\w*?pin\w*?"|"\w*?pass\w*?"|"\w*?otp\w*?"):"(.*?)"/gmi, "\"SENSITIVE_PROPERTY\":\"OBSCURED_BY_SYSTEM\"" );
114
+ /** @type Message */
115
+ let messageSnapshot = tools.parseJSON( _.replace( tools.stringifyJSON( message ), /("\w*?pin\w*?"|"\w*?pass\w*?"|"\w*?otp\w*?"):"(.*?)"/gmi, "\"SENSITIVE_PROPERTY\":\"OBSCURED_BY_SYSTEM\"" ) );
116
+ delete messageSnapshot.payload;
117
+ return messageSnapshot;
109
118
  };
110
119
 
111
120
  /**
112
121
  * Used to create a trace entry for the provided {@link Message} and parameters.
113
122
  * <br/>
114
- * NOTE: With the exception of failed message delivery, trace events are logged with severity level DEBUG.
123
+ * NOTE: By default all trace events are stored in the memory cache for further processing and analysis. The
124
+ * location is configured in the MESSAGE_EXCHANGE_TRACE_REPOSITORY setting.
125
+ * <br/>
126
+ * NOTE: Trace events are logged with severity level NOTICE or ERROR for failed dispatches. They still might be
127
+ * filtered out if the minimum log level setting is set too high.
115
128
  *
116
129
  * @method
117
130
  * @param {Message} message The message to trace.
@@ -124,6 +137,10 @@ module.exports.recordTraceEntry = ( message, messageType, dispatchEvent, message
124
137
  // depending on whether the message comes as request or response, the from and to addresses will be opposite:
125
138
  let source = message.source.route + "." + message.source.instanceID;
126
139
  let destination = message.destination.route + ( ( message.destination.instanceID != null ) ? "." + message.destination.instanceID : "" );
140
+ let messageSnapshot = obscureSensitiveData( message );
141
+ delete messageSnapshot.chainID;
142
+ delete messageSnapshot.messageID;
143
+ let currentDate = new Date();
127
144
 
128
145
  /** @type TiTraceEntry */
129
146
  let traceEntry = {
@@ -131,16 +148,34 @@ module.exports.recordTraceEntry = ( message, messageType, dispatchEvent, message
131
148
  dispatchEvent: tools.getEnumName( dispatchEventEnum, dispatchEvent ),
132
149
  fromAddress: ( messageType === messageTypeEnum.MESSAGE_REQUEST ) ? source : destination,
133
150
  messageID: message.messageID,
134
- messageSnapshot: obscureSensitiveData( message ),
151
+ messageSnapshot: messageSnapshot,
135
152
  messageState: tools.getEnumName( messageStateEnum, messageState ),
136
153
  messageType: tools.getEnumName( messageTypeEnum, messageType ),
137
154
  toAddress: ( messageType === messageTypeEnum.MESSAGE_REQUEST ) ? destination : source,
155
+ traceTimestamp: currentDate.getTime(),
138
156
  traceID: tools.getUUID()
139
157
  };
140
158
 
159
+ // only write the trace in the general log if this is enabled:
141
160
  if ( config.getSetting( config.setting.MESSAGE_EXCHANGE_TRACE_LOG_ENABLED ) === true ) {
142
- createLogEntry( traceEntry, ( dispatchEvent === dispatchEventEnum.FAILED ) ? logger.logSeverity.ERROR : logger.logSeverity.DEBUG );
161
+ createLogEntry( traceEntry, ( dispatchEvent === dispatchEventEnum.FAILED ) ? logger.logSeverity.ERROR : logger.logSeverity.NOTICE );
143
162
  }
144
163
 
145
- // TODO Feature: Functionality that can dispatch the trace entry to a configurable database and/or monitoring system.
164
+ // add the trace entry to the repository in the memory cache:
165
+ cache.setJSON( config.getSetting( config.setting.MESSAGE_EXCHANGE_TRACE_REPOSITORY ), traceRoot, "$", 1 ).then( () => {
166
+ return cache.arrayAppendJSON( config.getSetting( config.setting.MESSAGE_EXCHANGE_TRACE_REPOSITORY ), traceEntry, "$.trace" );
167
+ } ).then( () => {
168
+ // this will refresh the expiration time for the trace repository on each new record:
169
+ let expiration = config.getSetting( config.setting.MESSAGE_EXCHANGE_TRACE_EXPIRATION_TIME );
170
+ return ( expiration > 0 ) ? cache.expireValue( config.getSetting( config.setting.MESSAGE_EXCHANGE_TRACE_REPOSITORY ), expiration ) : expiration;
171
+ } ).catch( ( error ) => {
172
+ if ( error.code === exceptions.exceptionCode.E_GEN_FEATURE_UNSUPPORTED ) {
173
+ cache.addToSet( config.getSetting( config.setting.MESSAGE_EXCHANGE_TRACE_REPOSITORY ), traceEntry ).catch( ( error ) => {
174
+ logger.log( `Failed to add message trace entry to the trace repository. While this will not prevent the application from running, it might still be a sign of a more serious problem!`, logger.logSeverity.WARNING, error );
175
+ } );
176
+ } else {
177
+ logger.log( `Failed to add message trace entry to the trace repository. While this will not prevent the application from running, it might still be a sign of a more serious problem!`, logger.logSeverity.WARNING, error );
178
+ }
179
+ } );
180
+
146
181
  };
@@ -1,5 +1,5 @@
1
1
  /*
2
- * SPDX-FileCopyrightText: © 2021 Boris Kostadinov <kostadinov.boris@gmail.com>
2
+ * SPDX-FileCopyrightText: © 2021-2023 Boris Kostadinov <kostadinov.boris@gmail.com>
3
3
  * SPDX-License-Identifier: ICU
4
4
  */
5
5
 
@@ -20,7 +20,7 @@ const messageDispatcher = require( "#message-dispatcher" );
20
20
 
21
21
  /**
22
22
  * @typedef {Object} ServiceExecContext
23
- * @property {string} authToken A valid authentication token that initialized the service call.
23
+ * @property {string|undefined} authToken A valid authentication token that initialized the service call (if applicable).
24
24
  * @property {ServiceCallPredecessor|undefined} previousServiceCall The previous service call in the execution chain (if such exists).
25
25
  */
26
26
 
@@ -36,7 +36,7 @@ const messageDispatcher = require( "#message-dispatcher" );
36
36
  * @property {string} authToken A valid authentication token that initialized the service call.
37
37
  * @property {number} createdOn A unix timestamp taken at creation time of the service call.
38
38
  * @property {number} executionTime The total execution time of this service call in milliseconds.
39
- * @property {Object|undefined} exception If there was exception during the service call processing, it will be set here. Otherwise it will be 'undefined'.
39
+ * @property {Object|undefined} exception If there was exception during the service call processing, it will be set here. Otherwise, it will be 'undefined'.
40
40
  * @property {number|undefined} finishedOn A unix timestamp taken at finish time of the service call.
41
41
  * @property {boolean} isCompleted Flag to indicate if this service call has been completed.
42
42
  * @property {boolean|undefined} isSuccessful A flag indicating if this service call can be considered successful or not. Will be 'undefined' until the service call is processed.
@@ -45,9 +45,9 @@ const messageDispatcher = require( "#message-dispatcher" );
45
45
 
46
46
  /**
47
47
  * @typedef {Object} ServiceCallResult
48
- * @property {Object|undefined} exception If there was exception during the service call processing, it will be set here. Otherwise it will be 'undefined'.
48
+ * @property {Object|undefined} exception If there was exception during the service call processing, it will be set here. Otherwise, it will be 'undefined'.
49
49
  * @property {boolean} isSuccessful A flag indicating if this service call can be considered successful or not.
50
- * @property {Object|string|undefined} payload The payload containing the results from the service call processing. If string, it is ID of the payload in the memory cache instead.
50
+ * @property {Object|string|undefined} payload The payload containing the results from the service call processing. If string it is ID of the payload in the memory cache instead.
51
51
  */
52
52
 
53
53
  /**
@@ -78,7 +78,7 @@ class ServiceCaller extends MessageObserver {
78
78
  /**
79
79
  * Used to call a service in the service ecosystem asynchronously.
80
80
  * <br/>
81
- * NOTE: This method will timeout after specific preconfigured time, in which case it will resolve with {@link E_COM_SERVICE_EXEC_TIMEOUT} error.
81
+ * NOTE: This method will time out after specific preconfigured time, in which case it will resolve with {@link E_COM_SERVICE_EXEC_TIMEOUT} error.
82
82
  *
83
83
  * @method
84
84
  * @param {ServiceAddress} serviceAddress The service address has to define a valid service domain name, service alias, and optionally a service version.
@@ -1,5 +1,5 @@
1
1
  /*
2
- * SPDX-FileCopyrightText: © 2021 Boris Kostadinov <kostadinov.boris@gmail.com>
2
+ * SPDX-FileCopyrightText: © 2021-2023 Boris Kostadinov <kostadinov.boris@gmail.com>
3
3
  * SPDX-License-Identifier: ICU
4
4
  */
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * SPDX-FileCopyrightText: © 2021 Boris Kostadinov <kostadinov.boris@gmail.com>
2
+ * SPDX-FileCopyrightText: © 2021-2023 Boris Kostadinov <kostadinov.boris@gmail.com>
3
3
  * SPDX-License-Identifier: ICU
4
4
  */
5
5
 
@@ -19,6 +19,14 @@ const messageDispatcher = require( "#message-dispatcher" );
19
19
  * @property {number} [serviceVersion] Service version.
20
20
  */
21
21
 
22
+ /**
23
+ * @typedef {Object.<string, ServiceInterfaceVersion>} ServiceInterface
24
+ */
25
+
26
+ /**
27
+ * @typedef {Object.<number, ServiceHandlerMethod>} ServiceInterfaceVersion
28
+ */
29
+
22
30
  /**
23
31
  * @callback VerifyAccessMethod
24
32
  * @param {string} authToken
@@ -43,6 +51,7 @@ const messageDispatcher = require( "#message-dispatcher" );
43
51
  */
44
52
  class ServiceExecutor extends MessageObserver {
45
53
 
54
+ /** @type ServiceInterface */
46
55
  #serviceInterface = {};
47
56
  /** @type VerifyAccessMethod */
48
57
  #verifyAccess;
@@ -62,6 +71,15 @@ class ServiceExecutor extends MessageObserver {
62
71
 
63
72
  /* Public interface */
64
73
 
74
+ /**
75
+ * Property returning the current service interface.
76
+ *
77
+ * @property
78
+ * @returns {ServiceInterface}
79
+ * @public
80
+ */
81
+ get serviceInterface() { return this.#serviceInterface; }
82
+
65
83
  /**
66
84
  *
67
85
  *
@@ -117,7 +135,7 @@ class ServiceExecutor extends MessageObserver {
117
135
  }
118
136
 
119
137
  /**
120
- * Used to setup the method for service access verification.
138
+ * Used to set up the method for service access verification.
121
139
  *
122
140
  * @method
123
141
  * @param {VerifyAccessMethod} verifyAccess
@@ -1,5 +1,5 @@
1
1
  /*
2
- * SPDX-FileCopyrightText: © 2021 Boris Kostadinov <kostadinov.boris@gmail.com>
2
+ * SPDX-FileCopyrightText: © 2021-2023 Boris Kostadinov <kostadinov.boris@gmail.com>
3
3
  * SPDX-License-Identifier: ICU
4
4
  */
5
5
 
@@ -12,10 +12,15 @@ const exceptions = require( "#exceptions" );
12
12
  const cache = require( "#cache" );
13
13
  const messageDispatcher = require( "#message-dispatcher" );
14
14
 
15
+ /**
16
+ * @typedef {Object} ServiceConfiguration
17
+ * @property {ServiceDefinition[]} services A list of service definitions to be registered with the {@link ServiceProvider}.
18
+ */
19
+
15
20
  /**
16
21
  * Abstract class used to define a Service Instance behavior.
17
22
  * <br/>
18
- * NOTE: Inherit this to create an a module that can be started as a microservice instance.
23
+ * NOTE: Inherit this to create a module that can be started as a microservice instance.
19
24
  * <br/>
20
25
  * NOTE: This class does not
21
26
  *
@@ -27,6 +32,7 @@ class ServiceInstance {
27
32
 
28
33
  static #instanceID;
29
34
  static #serviceDomainName;
35
+ /** @type ServiceConfiguration */
30
36
  #serviceConfig;
31
37
  #serviceHealthCheck;
32
38
  #reportHealthyJob;
@@ -44,7 +50,7 @@ class ServiceInstance {
44
50
 
45
51
  ServiceInstance.#instanceID = process.env.TI_INSTANCE_ID || tools.getUUID();
46
52
  ServiceInstance.#serviceDomainName = serviceDomainName;
47
- this.#serviceConfig = ( _.isObjectLike( serviceConfig ) ) ? serviceConfig : {};
53
+ this.#serviceConfig = ( _.isObjectLike( serviceConfig ) ) ? serviceConfig : { services: [] };
48
54
  }
49
55
 
50
56
  /* Public interface */
@@ -80,7 +86,7 @@ class ServiceInstance {
80
86
  * Property returning the service configuration JSON.
81
87
  *
82
88
  * @property
83
- * @returns {Object}
89
+ * @returns {ServiceConfiguration}
84
90
  * @public
85
91
  */
86
92
  get serviceConfig() { return this.#serviceConfig; }
@@ -1,5 +1,5 @@
1
1
  /*
2
- * SPDX-FileCopyrightText: © 2021 Boris Kostadinov <kostadinov.boris@gmail.com>
2
+ * SPDX-FileCopyrightText: © 2021-2023 Boris Kostadinov <kostadinov.boris@gmail.com>
3
3
  * SPDX-License-Identifier: ICU
4
4
  */
5
5
 
@@ -13,7 +13,7 @@ const messageDispatcher = require( "#message-dispatcher" );
13
13
  /**
14
14
  * Abstract class used to define a Service Provider behavior.
15
15
  * <br/>
16
- * NOTE: Inherit this to create an a module that can be started as a microservice provider instance.
16
+ * NOTE: Inherit this to create a module that can be started as a microservice provider instance.
17
17
  * <br/>
18
18
  * NOTE: A service provider is a microservice that offers an API of named business services that can be invoked by other
19
19
  * microservices using {@link ServiceCall} objects. The provider will take care of the actual execution of that service and
@@ -28,6 +28,7 @@ const messageDispatcher = require( "#message-dispatcher" );
28
28
  */
29
29
  class ServiceProvider extends ServiceConsumer {
30
30
 
31
+ /** @type ServiceExecutor */
31
32
  #serviceExecutor;
32
33
 
33
34
  /**
@@ -104,7 +105,7 @@ class ServiceProvider extends ServiceConsumer {
104
105
  /**
105
106
  * Used to verify whether the service caller has authorization to access the service.
106
107
  * <br/>
107
- * NOTE: Override this to implement authorization check. By default this method simply returns.
108
+ * NOTE: Override this to implement authorization check. By default, this method simply returns.
108
109
  *
109
110
  * @method
110
111
  * @param {string} authToken
@@ -124,7 +125,7 @@ class ServiceProvider extends ServiceConsumer {
124
125
  * keep in mind that your first param must always be the 'serviceDefinition' and the second one will be the general 'serviceParams' object.
125
126
  * <br/>
126
127
  * NOTE: Additionally, if you intend to call another service inside the service handler, then you have to use normal function for the handler and not
127
- * an arrow function! Arrow functions cannot bind the scope of the parent class to themselves and you won't have access to it and its methods.
128
+ * an arrow function! Arrow functions cannot bind the scope of the parent class to themselves, and you won't have access to it and its methods.
128
129
  *
129
130
  * @method
130
131
  * @param {ServiceDefinition} serviceDefinition Full service definition object.
@@ -180,7 +181,7 @@ class ServiceProvider extends ServiceConsumer {
180
181
  let promises = [];
181
182
  _.forEach( serviceDefinitions, ( serviceDefinition ) => {
182
183
  // NOTE: we are not going to interrupt the service interface loading if one of the services fails to load or is not found!
183
- // If this happens, a corresponding log entry will be created but the loading process will continue. Therefore the following
184
+ // If this happens, a corresponding log entry will be created but the loading process will continue. Therefore, the following
184
185
  // promise will always resolve (unless a programming error occurs in it of course).
185
186
  let registrationPromise = ( serviceDefinition, defaultServiceHandler ) => {
186
187
  return new Promise( ( resolve, reject ) => {
@@ -211,6 +212,17 @@ class ServiceProvider extends ServiceConsumer {
211
212
  } );
212
213
  }
213
214
 
215
+ /**
216
+ * Used to get an ordered list of all currently registered services. This does not include the service versions.
217
+ *
218
+ * @method
219
+ * @returns {string[]}
220
+ * @public
221
+ */
222
+ getRegisteredServices() {
223
+ return _.sortBy( _.keys( this.#serviceExecutor.serviceInterface ) );
224
+ }
225
+
214
226
  }
215
227
 
216
228
  module.exports = ServiceProvider;
@@ -1,5 +1,5 @@
1
1
  /*
2
- * SPDX-FileCopyrightText: © 2021 Boris Kostadinov <kostadinov.boris@gmail.com>
2
+ * SPDX-FileCopyrightText: © 2021-2023 Boris Kostadinov <kostadinov.boris@gmail.com>
3
3
  * SPDX-License-Identifier: ICU
4
4
  */
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * SPDX-FileCopyrightText: © 2021 Boris Kostadinov <kostadinov.boris@gmail.com>
2
+ * SPDX-FileCopyrightText: © 2021-2023 Boris Kostadinov <kostadinov.boris@gmail.com>
3
3
  * SPDX-License-Identifier: ICU
4
4
  */
5
5
 
@@ -19,6 +19,7 @@ const _ = require( "lodash" );
19
19
  let cacheCommandsEnum = tools.enum( {
20
20
  ADD_TO_SET: [ "sadd", "add to set", "https://redis.io/commands/sadd" ],
21
21
  DELETE_VALUE: [ "del", "delete value", "https://redis.io/commands/del" ],
22
+ EXPIRE: [ "expire", "expire", "https://redis.io/commands/expire" ],
22
23
  GET_ALL_FROM_SET: [ "smembers", "get all set members", "https://redis.io/commands/smembers" ],
23
24
  GET_VALUE: [ "get", "get value", "https://redis.io/commands/get" ],
24
25
  HASH_GET: [ "hget", "hash get", "https://redis.io/commands/hget" ],
@@ -27,6 +28,9 @@ let cacheCommandsEnum = tools.enum( {
27
28
  HASH_SET: [ "hset", "", "https://redis.io/commands/hset" ],
28
29
  HASH_SET_MANY: [ "hmset", "", "https://redis.io/commands/hmset" ],
29
30
  IS_SET_MEMBER: [ "sismember", "", "https://redis.io/commands/sismember" ],
31
+ JSON_ARRAY_APPEND: [ "json.arrappend", "", "https://redis.io/commands/json.arrappend" ],
32
+ JSON_GET: [ "json.get", "", "https://redis.io/commands/json.get" ],
33
+ JSON_SET: [ "json.set", "", "https://redis.io/commands/json.set" ],
30
34
  KEYS: [ "keys", "", "https://redis.io/commands/keys" ],
31
35
  LIST_PUSH: [ "lpush", "list push", "https://redis.io/commands/lpush" ],
32
36
  LIST_POP_TAIL_BLOCKING: [ "brpop", "list pop tail blocking", "https://redis.io/commands/brpop" ],
@@ -36,10 +40,26 @@ let cacheCommandsEnum = tools.enum( {
36
40
  UNION_OF_SETS: [ "sunion", "union of sets", "https://redis.io/commands/sunion" ]
37
41
  } );
38
42
 
43
+ /**
44
+ * Enum for listing the Redis key override modes.
45
+ *
46
+ * @readonly
47
+ * @enum {string}
48
+ */
49
+ let cacheOverrideModeEnum = tools.enum( {
50
+ DEFAULT: [ "", "default", "Standard Redis behaviour when setting new key." ],
51
+ NX: [ "nx", "nx", "Sets the key only if it does not already exist." ],
52
+ XX: [ "xx", "xx", "Sets the key only if it already exists." ]
53
+ } );
54
+
39
55
  /**
40
56
  * @typedef {string} TiRedisCommand
41
57
  */
42
58
  module.exports.cacheCommands = cacheCommandsEnum;
59
+ /**
60
+ * @typedef {string} TiRedisOverrideMode
61
+ */
62
+ module.exports.cacheOverrideMode = cacheOverrideModeEnum;
43
63
 
44
64
  /**
45
65
  * Used to create a Redis Cache client.
@@ -53,6 +73,8 @@ class RedisClient {
53
73
  #retryMaxInterval = 1000;
54
74
  #retryMaxAttempts = undefined;
55
75
  #redisClient = undefined;
76
+ #serverInfo = {};
77
+ #serverFeatures = {};
56
78
  #connectionObservers = [];
57
79
 
58
80
  /**
@@ -101,13 +123,40 @@ class RedisClient {
101
123
  this.#redisClient = new Redis( options );
102
124
 
103
125
  this.#redisClient.on( "ready", () => {
104
- let serverInfo = this.#redisClient.serverInfo || {};
105
- logger.log( `Connection to Redis server ${ host }:${ port } (re)established by client '${ this.identifier }' and is ready to be used.`, logger.logSeverity.INFO, serverInfo );
126
+ logger.log( `Connection to Redis server ${ host }:${ port } (re)established by client '${ this.identifier }' and is ready to be used.`, logger.logSeverity.INFO );
106
127
 
107
128
  // notify all connection observers about this event:
108
129
  _.forEach( this.#connectionObservers, ( connectionObservers ) => {
109
130
  connectionObservers.onConnectionRecovered( this.#clientIdentifier );
110
131
  } );
132
+
133
+ // fetch the server information and store it:
134
+ this.#redisClient.info().then( ( result ) => {
135
+ this.#serverInfo = {};
136
+ if ( _.isString( result ) ) {
137
+ let rawData = _.split( result, "\r\n" );
138
+ _.forEach( rawData, ( entry ) => {
139
+ let details = _.split( entry, ":" );
140
+ if ( !_.startsWith( details[ 0 ], "#" ) && details[ 0 ] !== "" && details[ 0 ] ) {
141
+ if ( _.isNaN( _.toNumber( details[ 1 ] ) ) ) {
142
+ this.#serverInfo[ details[ 0 ] ] = details[ 1 ];
143
+ } else {
144
+ this.#serverInfo[ details[ 0 ] ] = _.toNumber( details[ 1 ] );
145
+ }
146
+ }
147
+ } );
148
+ }
149
+ return this.#redisClient.module( "LIST" );
150
+ } ).then( ( result ) => {
151
+ this.#serverFeatures = {};
152
+ if ( _.isArray( result ) ) {
153
+ _.forEach( result, ( entry ) => {
154
+ this.#serverFeatures[ entry[ 1 ] ] = entry[ 3 ];
155
+ } );
156
+ }
157
+ } ).catch( ( error ) => {
158
+ logger.log( `Failed to fetch server information by client '${ this.identifier }'!`, logger.logSeverity.WARNING, error );
159
+ } );
111
160
  } );
112
161
  this.#redisClient.on( "error", ( error ) => {
113
162
  logger.log( `Error received in Redis client '${ this.identifier }'.`, logger.logSeverity.ERROR, error );
@@ -137,6 +186,28 @@ class RedisClient {
137
186
  return this.#clientIdentifier;
138
187
  }
139
188
 
189
+ /**
190
+ * Used to return the Redis server version.
191
+ *
192
+ * @property
193
+ * @return {number}
194
+ * @public
195
+ */
196
+ get serverVersion() {
197
+ return this.#serverInfo[ "redis_version" ];
198
+ }
199
+
200
+ /**
201
+ * Verify if Redis server supports JSON data types.
202
+ *
203
+ * @property
204
+ * @returns {boolean}
205
+ * @public
206
+ */
207
+ get isJSONSupported() {
208
+ return !_.isNil( this.#serverFeatures[ "ReJSON" ] );
209
+ }
210
+
140
211
  /**
141
212
  * Used to register a new {@link ConnectionObserver} for events related to the Redis connection state.
142
213
  *
@@ -172,6 +243,7 @@ class RedisClient {
172
243
 
173
244
  /**
174
245
  * Used to send a new blocking command to Redis.
246
+ * <br/>
175
247
  * WARNING: This will reserve the client connection until a result is received.
176
248
  *
177
249
  * @method
@@ -235,6 +307,28 @@ class RedisClient {
235
307
  } );
236
308
  }
237
309
 
310
+ /**
311
+ * Used to execute any Redis command in an unmanaged way.
312
+ * <br/>
313
+ * WARNING: Use this only if there is no other implemented function in this module and the command
314
+ * you want to execute is not supported by the 'multi' Redis command (implemented in {@link RedisClient.executeCommands}).
315
+ * Make sure to handle the result as it will be returned raw.
316
+ *
317
+ * @method
318
+ * @param {string[]} commandArguments
319
+ * @returns {Promise<Object>}
320
+ * @public
321
+ */
322
+ callCommand( commandArguments ) {
323
+ return new Promise( ( resolve, reject ) => {
324
+ this.#redisClient[ "call" ].apply( this.#redisClient, commandArguments ).then( ( result ) => {
325
+ resolve( result );
326
+ } ).catch( ( error ) => {
327
+ reject( exceptions.raise( error ) );
328
+ } );
329
+ } );
330
+ }
331
+
238
332
  }
239
333
 
240
334
  /**
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ti-engine/core",
3
- "version": "1.0.14",
4
- "description": "The ti-engine is an open source, free to use - both for personal and commercial projects - framework for the creation of microservice-based solutions using node.js.",
3
+ "version": "1.1.1",
4
+ "description": "The ti-engine is an open source, free to useboth for personal and commercial projectsframework for the creation of microservice-based solutions using node.js.",
5
5
  "author": "Boris Kostadinov <kostadinov.boris@gmail.com>",
6
6
  "license": "ISC",
7
7
  "exports": {
@@ -50,7 +50,7 @@
50
50
  },
51
51
  "optionalDependencies": {
52
52
  "@google-cloud/error-reporting": "^3.0.5",
53
- "zeromq": "^6.0.0-beta.17"
53
+ "zeromq": "^6.0.0-beta.19"
54
54
  },
55
55
  "repository": {
56
56
  "type": "git",