@ti-engine/core 1.0.14 → 1.1.0
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.
- package/CHANGELOG.md +27 -0
- package/bin/settings.json +3 -1
- package/bin/start-instance.js +1 -1
- package/components/auditing.js +1 -1
- package/components/connection-observer.js +1 -1
- package/components/exchange/default/default-message-exchange.js +1 -1
- package/components/exchange/default/default-message-receiver.js +1 -1
- package/components/exchange/default/default-message-sender.js +1 -1
- package/components/exchange/message-dispatcher.js +1 -1
- package/components/exchange/message-exchange.js +1 -1
- package/components/exchange/message-handler.js +1 -1
- package/components/exchange/message-memory-cache.js +1 -1
- package/components/exchange/message-observer.js +1 -1
- package/components/exchange/message-receiver.js +1 -1
- package/components/exchange/message-sender.js +1 -1
- package/components/exchange/message-tracer.js +45 -10
- package/components/service-caller.js +1 -1
- package/components/service-consumer.js +1 -1
- package/components/service-executor.js +1 -1
- package/components/service-instance.js +1 -1
- package/components/service-provider.js +1 -1
- package/integrations/gcloud-integration.js +1 -1
- package/integrations/redis-integration.js +97 -3
- package/package.json +2 -2
- package/utils/cache.js +126 -2
- package/utils/config.js +7 -2
- package/utils/exceptions.js +2 -1
- package/utils/logger.js +1 -1
- package/utils/tools.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,18 +1,44 @@
|
|
|
1
1
|
# ti-engine changelog
|
|
2
2
|
|
|
3
|
+
## Version 1.1.0
|
|
4
|
+
|
|
5
|
+
* feat(redis integration): implement better process for fetching and storing the remote Redis server settings and enabled features
|
|
6
|
+
* feat(redis integration): add getter method `serverVersion` that returns the Redis server version
|
|
7
|
+
* feat(redis integration): add support for RedisJSON functionality
|
|
8
|
+
* feat(redis integration): add getter method `isJSONSupported` to verify if this module is enabled/supported in Redis server
|
|
9
|
+
* feat(redis integration): add separate enum for override modes `TiRedisOverrideMode`
|
|
10
|
+
* feat(redis integration): add support for executing any single command in Redis that is otherwise unsupported by the underlying framework. Use method `callCommand` for that in case you need to access something more exotic or currently unimplemented
|
|
11
|
+
* feat(cache): add support for working with JSON values. Currently supporting operations `set`, `get`, and `append array` with more coming in future versions
|
|
12
|
+
* feat(cache): add support for setting manual expiration of values
|
|
13
|
+
* feat(message tracer): change log level of trace entries from `DEBUG` to `NOTICE` for all events
|
|
14
|
+
* feat(message tracer): add functionality for storing the trace entries in the `cache` in JSON format. This is the default behavior and will always work. Traces can later be retrieved from the `cache` for further processing by specialized application
|
|
15
|
+
* feat(message tracer): add new property `traceTimestamp` to the trace entries as it may differ from the actual message timestamp
|
|
16
|
+
* feat(config): add new setting `messageExchange.traceExpirationTime` that controls the preservation time for all trace entries. Default is one hour and will be refreshed every time a new trace is generated. Setting this to `0` will disable
|
|
17
|
+
expiration altogether
|
|
18
|
+
* feat(config): add new setting `messageExchange.traceRepository` that specifies the name of the key in `cache` that will store the trace entries. The default one should be sufficient for most cases
|
|
19
|
+
* feat(config): change default of setting `messageExchange.traceLogEnabled` to `false`. With this change trace entries will not be written to the standard log output
|
|
20
|
+
* feat(exceptions): add new exception code `1006` as `E_GEN_FEATURE_UNSUPPORTED` to indicate functionality that is unsupported by the system or integrated application
|
|
21
|
+
* refactor(config)!: correct the name of the ENV variable controlling `memoryCache.user` to `TI_MEMORY_CACHE_USER` from previous `MEMORY_CACHE_USER`
|
|
22
|
+
* build(npm): update npm development dependencies to their latest versions
|
|
23
|
+
* docs: update some JSDoc types and other entries to reflect the current state of the code
|
|
24
|
+
|
|
3
25
|
## Version 1.0.14
|
|
26
|
+
|
|
4
27
|
* feat(message exchange): improve the behavior of `onConnectionRecovered` and `onConnectionDisrupted` events in modules `cache`, `message-handler` and `service-executor` to prevent cross-activation or deactivation
|
|
5
28
|
* feat(redis integration): improve Redis integration for greater stability, support of Redis Cloud, and support for Redis 7
|
|
6
29
|
* feat(config): enable setting `auditing.logDetails` by default for all logs
|
|
7
30
|
* docs: update general documentation
|
|
8
31
|
|
|
9
32
|
## Version 1.0.13
|
|
33
|
+
|
|
10
34
|
* build(npm): update npm dependencies to their latest versions
|
|
11
35
|
|
|
12
36
|
## Version 1.0.12
|
|
37
|
+
|
|
13
38
|
* build(npm): update npm dependencies to their latest versions
|
|
14
39
|
|
|
15
40
|
## Version 1.0.11
|
|
41
|
+
|
|
16
42
|
* feat(message exchange): implement message tampering and insertion protection
|
|
17
43
|
* feat(config): provide option to turn on/off the message tampering and insertion protection
|
|
18
44
|
* feat(config): provide option to turn on/off message tracing
|
|
@@ -21,4 +47,5 @@
|
|
|
21
47
|
* docs: add a change log
|
|
22
48
|
|
|
23
49
|
## Version 1.0.0
|
|
50
|
+
|
|
24
51
|
* feat: create initial version of the framework
|
package/bin/settings.json
CHANGED
|
@@ -23,7 +23,9 @@
|
|
|
23
23
|
"messageStore": "ti:messages:store",
|
|
24
24
|
"securityHashEnabled": true,
|
|
25
25
|
"securityHashKey": "23e7bdc7-a793-41f9-856e-6760332f0c73",
|
|
26
|
-
"
|
|
26
|
+
"traceExpirationTime": 3600,
|
|
27
|
+
"traceLogEnabled": false,
|
|
28
|
+
"traceRepository": "ti:messages:trace"
|
|
27
29
|
},
|
|
28
30
|
"serviceConfig": {
|
|
29
31
|
"executionTimeout": 180000,
|
package/bin/start-instance.js
CHANGED
package/components/auditing.js
CHANGED
|
@@ -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 {
|
|
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
|
|
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 {
|
|
110
|
+
* @returns {Message}
|
|
104
111
|
* @private
|
|
105
112
|
*/
|
|
106
113
|
let obscureSensitiveData = ( message ) => {
|
|
107
|
-
|
|
108
|
-
|
|
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:
|
|
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:
|
|
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.
|
|
161
|
+
createLogEntry( traceEntry, ( dispatchEvent === dispatchEventEnum.FAILED ) ? logger.logSeverity.ERROR : logger.logSeverity.NOTICE );
|
|
143
162
|
}
|
|
144
163
|
|
|
145
|
-
//
|
|
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
|
|
|
@@ -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
|
-
|
|
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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ti-engine/core",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
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.",
|
|
5
5
|
"author": "Boris Kostadinov <kostadinov.boris@gmail.com>",
|
|
6
6
|
"license": "ISC",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
},
|
|
51
51
|
"optionalDependencies": {
|
|
52
52
|
"@google-cloud/error-reporting": "^3.0.5",
|
|
53
|
-
"zeromq": "^6.0.0-beta.
|
|
53
|
+
"zeromq": "^6.0.0-beta.19"
|
|
54
54
|
},
|
|
55
55
|
"repository": {
|
|
56
56
|
"type": "git",
|
package/utils/cache.js
CHANGED
|
@@ -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
|
|
|
@@ -278,6 +278,32 @@ class CommonMemoryCache extends ConnectionObserver {
|
|
|
278
278
|
} );
|
|
279
279
|
}
|
|
280
280
|
|
|
281
|
+
/**
|
|
282
|
+
* Used to set expiration in seconds to an existing key.
|
|
283
|
+
* <br/>
|
|
284
|
+
* NOTE: For performance optimization reasons, only use this only if the Redis command does not itself support the 'EX' argument.
|
|
285
|
+
*
|
|
286
|
+
* @method
|
|
287
|
+
* @param {string} key
|
|
288
|
+
* @param {number} seconds
|
|
289
|
+
* @returns {Promise<number>} This will resolve with the seconds as provided initially by the caller.
|
|
290
|
+
* @public
|
|
291
|
+
*/
|
|
292
|
+
expireValue( key, seconds ) {
|
|
293
|
+
return new Promise( ( resolve, reject ) => {
|
|
294
|
+
if ( this.#isOperational === true ) {
|
|
295
|
+
let commandExpire = [ redis.cacheCommands.EXPIRE, key, seconds ];
|
|
296
|
+
this.#redisClient.executeCommands( [ commandExpire ] ).then( () => {
|
|
297
|
+
resolve( seconds );
|
|
298
|
+
} ).catch( ( error ) => {
|
|
299
|
+
reject( error );
|
|
300
|
+
} );
|
|
301
|
+
} else {
|
|
302
|
+
reject( exceptions.raise( exceptions.exceptionCode.E_GEN_SYSTEM_CACHE_UNAVAILABLE ) );
|
|
303
|
+
}
|
|
304
|
+
} );
|
|
305
|
+
}
|
|
306
|
+
|
|
281
307
|
/**
|
|
282
308
|
* Used to add the specified values to a list.
|
|
283
309
|
*
|
|
@@ -313,7 +339,7 @@ class CommonMemoryCache extends ConnectionObserver {
|
|
|
313
339
|
*
|
|
314
340
|
* @method
|
|
315
341
|
* @param {string} key
|
|
316
|
-
* @param {string} value
|
|
342
|
+
* @param {string|Object} value
|
|
317
343
|
* @returns {Promise}
|
|
318
344
|
* @public
|
|
319
345
|
*/
|
|
@@ -516,6 +542,104 @@ class CommonMemoryCache extends ConnectionObserver {
|
|
|
516
542
|
}
|
|
517
543
|
} );
|
|
518
544
|
}
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* Used to store a JSON variable.
|
|
548
|
+
* <br/>
|
|
549
|
+
* NOTE: Requires ReJSON module installed on server to work.
|
|
550
|
+
*
|
|
551
|
+
* @method
|
|
552
|
+
* @param {string} key
|
|
553
|
+
* @param {Object} value
|
|
554
|
+
* @param {string} [path='&']
|
|
555
|
+
* @param {number} [overrideMode=0] By default this allows full override for existing keys.
|
|
556
|
+
* Option 1 will set the key only if it doesn't already exist. Option 2 will set it only if it already exists.
|
|
557
|
+
* @returns {Promise}
|
|
558
|
+
* @public
|
|
559
|
+
*/
|
|
560
|
+
setJSON( key, value, path = "$", overrideMode = 0 ) {
|
|
561
|
+
return new Promise( ( resolve, reject ) => {
|
|
562
|
+
if ( this.#isOperational === true ) {
|
|
563
|
+
if ( this.#redisClient.isJSONSupported ) {
|
|
564
|
+
let commandArguments = [ redis.cacheCommands.JSON_SET, key, path, tools.stringifyJSON( value ) ];
|
|
565
|
+
if ( overrideMode !== 0 ) {
|
|
566
|
+
commandArguments.push( overrideMode === 1 ? redis.cacheOverrideMode.NX : redis.cacheOverrideMode.XX );
|
|
567
|
+
}
|
|
568
|
+
this.#redisClient.callCommand( commandArguments ).then( () => {
|
|
569
|
+
resolve();
|
|
570
|
+
} ).catch( ( error ) => {
|
|
571
|
+
reject( error );
|
|
572
|
+
} );
|
|
573
|
+
} else {
|
|
574
|
+
reject( exceptions.raise( exceptions.exceptionCode.E_GEN_FEATURE_UNSUPPORTED ), { details: "No RedisJSON module installed on server." } );
|
|
575
|
+
}
|
|
576
|
+
} else {
|
|
577
|
+
reject( exceptions.raise( exceptions.exceptionCode.E_GEN_SYSTEM_CACHE_UNAVAILABLE ) );
|
|
578
|
+
}
|
|
579
|
+
} );
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* Used to fetch a JSON variable.
|
|
584
|
+
* <br/>
|
|
585
|
+
* NOTE: Requires ReJSON module installed on server to work.
|
|
586
|
+
*
|
|
587
|
+
* @method
|
|
588
|
+
* @param {string} key
|
|
589
|
+
* @param {string} path
|
|
590
|
+
* @returns {Promise<Object>}
|
|
591
|
+
* @public
|
|
592
|
+
*/
|
|
593
|
+
getJSON( key, path = "$" ) {
|
|
594
|
+
return new Promise( ( resolve, reject ) => {
|
|
595
|
+
if ( this.#isOperational === true ) {
|
|
596
|
+
if ( this.#redisClient.isJSONSupported ) {
|
|
597
|
+
let commandArguments = [ redis.cacheCommands.JSON_GET, key, path ];
|
|
598
|
+
this.#redisClient.callCommand( commandArguments ).then( ( result ) => {
|
|
599
|
+
resolve( tools.parseJSON( result ) );
|
|
600
|
+
} ).catch( ( error ) => {
|
|
601
|
+
reject( error );
|
|
602
|
+
} );
|
|
603
|
+
} else {
|
|
604
|
+
reject( exceptions.raise( exceptions.exceptionCode.E_GEN_FEATURE_UNSUPPORTED ), { details: "No RedisJSON module installed on server." } );
|
|
605
|
+
}
|
|
606
|
+
} else {
|
|
607
|
+
reject( exceptions.raise( exceptions.exceptionCode.E_GEN_SYSTEM_CACHE_UNAVAILABLE ) );
|
|
608
|
+
}
|
|
609
|
+
} );
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/**
|
|
613
|
+
* Used to add an item to a JSON array. That array needs to exist already.
|
|
614
|
+
* <br/>
|
|
615
|
+
* NOTE: Requires ReJSON module installed on server to work.
|
|
616
|
+
*
|
|
617
|
+
* @method
|
|
618
|
+
* @param {string} key
|
|
619
|
+
* @param {Object} value
|
|
620
|
+
* @param {string} path
|
|
621
|
+
* @returns {Promise}
|
|
622
|
+
* @public
|
|
623
|
+
*/
|
|
624
|
+
arrayAppendJSON( key, value, path = "$" ) {
|
|
625
|
+
return new Promise( ( resolve, reject ) => {
|
|
626
|
+
if ( this.#isOperational === true ) {
|
|
627
|
+
if ( this.#redisClient.isJSONSupported ) {
|
|
628
|
+
let commandArguments = [ redis.cacheCommands.JSON_ARRAY_APPEND, key, path, tools.stringifyJSON( value ) ];
|
|
629
|
+
this.#redisClient.callCommand( commandArguments ).then( () => {
|
|
630
|
+
resolve();
|
|
631
|
+
} ).catch( ( error ) => {
|
|
632
|
+
reject( error );
|
|
633
|
+
} );
|
|
634
|
+
} else {
|
|
635
|
+
reject( exceptions.raise( exceptions.exceptionCode.E_GEN_FEATURE_UNSUPPORTED ), { details: "No RedisJSON module installed on server." } );
|
|
636
|
+
}
|
|
637
|
+
} else {
|
|
638
|
+
reject( exceptions.raise( exceptions.exceptionCode.E_GEN_SYSTEM_CACHE_UNAVAILABLE ) );
|
|
639
|
+
}
|
|
640
|
+
} );
|
|
641
|
+
}
|
|
642
|
+
|
|
519
643
|
}
|
|
520
644
|
|
|
521
645
|
const instance = new CommonMemoryCache();
|
package/utils/config.js
CHANGED
|
@@ -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
|
|
|
@@ -27,6 +27,7 @@ const tools = require( "#tools" );
|
|
|
27
27
|
* @property {EnvironmentVariable} env.TI_MEMORY_CACHE_REDIS_DB
|
|
28
28
|
* @property {EnvironmentVariable} env.TI_MEMORY_CACHE_REDIS_HOST
|
|
29
29
|
* @property {EnvironmentVariable} env.TI_MEMORY_CACHE_REDIS_PORT
|
|
30
|
+
* @property {EnvironmentVariable} env.TI_MEMORY_CACHE_USER
|
|
30
31
|
* @property {EnvironmentVariable} env.TI_MESSAGE_EXCHANGE_SECURITY_HASH_ENABLED
|
|
31
32
|
* @property {EnvironmentVariable} env.TI_MESSAGE_EXCHANGE_SECURITY_HASH_KEY
|
|
32
33
|
* @property {EnvironmentVariable} env.TI_MESSAGE_EXCHANGE_TRACE_LOG_ENABLED
|
|
@@ -76,7 +77,9 @@ const tools = require( "#tools" );
|
|
|
76
77
|
* @property {string} messageStore
|
|
77
78
|
* @property {boolean} securityHashEnabled
|
|
78
79
|
* @property {string} securityHashKey
|
|
80
|
+
* @property {number} traceExpirationTime
|
|
79
81
|
* @property {boolean} traceLogEnabled
|
|
82
|
+
* @property {string} traceRepository
|
|
80
83
|
*/
|
|
81
84
|
|
|
82
85
|
/**
|
|
@@ -110,7 +113,9 @@ let settingsEnum = tools.enum( {
|
|
|
110
113
|
MESSAGE_EXCHANGE_MESSAGE_STORE: [ "messageExchange.messageStore", "messageStore", "" ],
|
|
111
114
|
MESSAGE_EXCHANGE_SECURITY_HASH_ENABLED: [ "messageExchange.securityHashEnabled", "securityHashEnabled", "" ],
|
|
112
115
|
MESSAGE_EXCHANGE_SECURITY_HASH_KEY: [ "messageExchange.securityHashKey", "securityHashKey", "" ],
|
|
116
|
+
MESSAGE_EXCHANGE_TRACE_EXPIRATION_TIME: [ "messageExchange.traceExpirationTime", "traceExpirationTime", "" ],
|
|
113
117
|
MESSAGE_EXCHANGE_TRACE_LOG_ENABLED: [ "messageExchange.traceLogEnabled", "traceLogEnabled", "" ],
|
|
118
|
+
MESSAGE_EXCHANGE_TRACE_REPOSITORY: [ "messageExchange.traceRepository", "traceRepository", "" ],
|
|
114
119
|
SERVICE_EXECUTION_TIMEOUT: [ "serviceConfig.executionTimeout", "executionTimeout", "" ],
|
|
115
120
|
SERVICE_HEALTH_CHECK_ADDRESS: [ "serviceConfig.healthCheckAddress", "healthCheckAddress", "" ],
|
|
116
121
|
SERVICE_HEALTH_CHECK_INTERVAL: [ "serviceConfig.healthCheckInterval", "healthCheckInterval", "" ],
|
|
@@ -138,7 +143,7 @@ if ( settings.memoryCache ) {
|
|
|
138
143
|
settings.memoryCache.redisDB = ( process.env.TI_MEMORY_CACHE_REDIS_DB !== undefined ) ? process.env.TI_MEMORY_CACHE_REDIS_DB : settings.memoryCache.redisDB;
|
|
139
144
|
settings.memoryCache.redisHost = ( process.env.TI_MEMORY_CACHE_REDIS_HOST !== undefined ) ? process.env.TI_MEMORY_CACHE_REDIS_HOST : settings.memoryCache.redisHost;
|
|
140
145
|
settings.memoryCache.redisPort = ( process.env.TI_MEMORY_CACHE_REDIS_PORT !== undefined ) ? process.env.TI_MEMORY_CACHE_REDIS_PORT : settings.memoryCache.redisPort;
|
|
141
|
-
settings.memoryCache.user = ( process.env.
|
|
146
|
+
settings.memoryCache.user = ( process.env.TI_MEMORY_CACHE_USER !== undefined ) ? process.env.TI_MEMORY_CACHE_USER : settings.memoryCache.user;
|
|
142
147
|
}
|
|
143
148
|
if ( settings.messageExchange ) {
|
|
144
149
|
settings.messageExchange.securityHashEnabled = ( process.env.TI_MESSAGE_EXCHANGE_SECURITY_HASH_ENABLED !== undefined ) ? tools.toBool( process.env.TI_MESSAGE_EXCHANGE_SECURITY_HASH_ENABLED ) : settings.messageExchange.securityHashEnabled;
|
package/utils/exceptions.js
CHANGED
|
@@ -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
|
|
|
@@ -21,6 +21,7 @@ let exceptionCodeEnum = tools.enum( {
|
|
|
21
21
|
E_GEN_INVALID_SERVICE_DOMAIN_NAME: [ 1003, "invalid service domain name", "Invalid or no service domain name provided at microservice startup." ],
|
|
22
22
|
E_GEN_SYSTEM_CACHE_UNAVAILABLE: [ 1004, "system cache unavailable", "The system cache required for proper engine operation is unavailable." ],
|
|
23
23
|
E_GEN_BAD_SERVICE_HANDLER: [ 1005, "bad service handler", "The provided service handler is not a proper function." ],
|
|
24
|
+
E_GEN_FEATURE_UNSUPPORTED: [ 1006, "feature unsupported", "The requested feature is not supported by current configuration or version." ],
|
|
24
25
|
/** Security & Administration related exceptions - codes under 2xxx */
|
|
25
26
|
E_SEC_INVALID_AUTH_TOKEN: [ 2000, "invalid auth token", "Invalid authorization token provided." ],
|
|
26
27
|
E_SEC_INVALID_EXPIRED_SESSION: [ 2001, "invalid or expired session", "Invalid or expired session encountered." ],
|
package/utils/logger.js
CHANGED
package/utils/tools.js
CHANGED