@ti-engine/core 1.0.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/components/auditing.js +163 -0
- package/components/connection-observer.js +53 -0
- package/components/exchange/default/default-message-exchange.js +133 -0
- package/components/exchange/default/default-message-receiver.js +89 -0
- package/components/exchange/default/default-message-sender.js +90 -0
- package/components/exchange/message-dispatcher.js +162 -0
- package/components/exchange/message-exchange.js +418 -0
- package/components/exchange/message-handler.js +173 -0
- package/components/exchange/message-memory-cache.js +150 -0
- package/components/exchange/message-observer.js +76 -0
- package/components/exchange/message-receiver.js +113 -0
- package/components/exchange/message-sender.js +133 -0
- package/components/exchange/message-tracer.js +146 -0
- package/components/service-caller.js +306 -0
- package/components/service-consumer.js +112 -0
- package/components/service-executor.js +231 -0
- package/components/service-instance.js +280 -0
- package/components/service-provider.js +221 -0
- package/integrations/gcloud-integration.js +61 -0
- package/integrations/redis-integration.js +261 -0
- package/package.json +54 -0
- package/settings.json +33 -0
- package/utils/cache.js +507 -0
- package/utils/config.js +146 -0
- package/utils/exceptions.js +241 -0
- package/utils/logger.js +69 -0
- package/utils/tools.js +537 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* SPDX-FileCopyrightText: © 2021 Boris Kostadinov <kostadinov.boris@gmail.com>
|
|
3
|
+
* SPDX-License-Identifier: ICU
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const ConnectionObserver = require( "#connection-observer" );
|
|
7
|
+
const ioredis = require( "ioredis" );
|
|
8
|
+
const tools = require( "#tools" );
|
|
9
|
+
const logger = require( "#logger" );
|
|
10
|
+
const exceptions = require( "#exceptions" );
|
|
11
|
+
const _ = require( "lodash" );
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Enum for listing all used Redis cache commands.
|
|
15
|
+
*
|
|
16
|
+
* @readonly
|
|
17
|
+
* @enum {string}
|
|
18
|
+
*/
|
|
19
|
+
let cacheCommandsEnum = tools.enum( {
|
|
20
|
+
ADD_TO_SET: [ "sadd", "add to set", "https://redis.io/commands/sadd" ],
|
|
21
|
+
DELETE_VALUE: [ "del", "delete value", "https://redis.io/commands/del" ],
|
|
22
|
+
GET_ALL_FROM_SET: [ "smembers", "get all set members", "https://redis.io/commands/smembers" ],
|
|
23
|
+
GET_VALUE: [ "get", "get value", "https://redis.io/commands/get" ],
|
|
24
|
+
HASH_GET: [ "hget", "hash get", "https://redis.io/commands/hget" ],
|
|
25
|
+
HASH_GET_ALL: [ "hgetall", "hash get all", "https://redis.io/commands/hgetall" ],
|
|
26
|
+
HASH_REMOVE: [ "hdel", "hash remove", "https://redis.io/commands/hdel" ],
|
|
27
|
+
HASH_SET: [ "hset", "", "https://redis.io/commands/hset" ],
|
|
28
|
+
HASH_SET_MANY: [ "hmset", "", "https://redis.io/commands/hmset" ],
|
|
29
|
+
IS_SET_MEMBER: [ "sismember", "", "https://redis.io/commands/sismember" ],
|
|
30
|
+
KEYS: [ "keys", "", "https://redis.io/commands/keys" ],
|
|
31
|
+
LIST_PUSH: [ "lpush", "list push", "https://redis.io/commands/lpush" ],
|
|
32
|
+
LIST_POP_TAIL_BLOCKING: [ "brpop", "list pop tail blocking", "https://redis.io/commands/brpop" ],
|
|
33
|
+
LIST_POP_TAIL_PUSH_HEAD_BLOCKING: [ "brpoplpush", "list pop tail push head blocking", "https://redis.io/commands/brpoplpush" ],
|
|
34
|
+
LIST_REMOVE: [ "lrem", "list remove", "https://redis.io/commands/lrem" ],
|
|
35
|
+
SET_VALUE: [ "set", "set value", "https://redis.io/commands/set" ],
|
|
36
|
+
UNION_OF_SETS: [ "sunion", "union of sets", "https://redis.io/commands/sunion" ]
|
|
37
|
+
} );
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @typedef {string} TiRedisCommand
|
|
41
|
+
*/
|
|
42
|
+
module.exports.cacheCommands = cacheCommandsEnum;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Used to create a Redis Cache client.
|
|
46
|
+
*
|
|
47
|
+
* @class RedisClient
|
|
48
|
+
* @public
|
|
49
|
+
*/
|
|
50
|
+
class RedisClient {
|
|
51
|
+
|
|
52
|
+
#clientIdentifier = "default";
|
|
53
|
+
#retryMaxInterval = 1000;
|
|
54
|
+
#retryMaxAttempts = undefined;
|
|
55
|
+
#redisClient = undefined;
|
|
56
|
+
#connectionObservers = [];
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @constructor
|
|
60
|
+
* @param {string} identifier
|
|
61
|
+
* @param {string} host
|
|
62
|
+
* @param {number} port
|
|
63
|
+
* @param {string} authKey
|
|
64
|
+
* @param {number} defaultDB
|
|
65
|
+
* @param {boolean} autoRetryUnfulfilled
|
|
66
|
+
* @param {number} maxRetries
|
|
67
|
+
*/
|
|
68
|
+
constructor( identifier, host, port, authKey, defaultDB, autoRetryUnfulfilled, maxRetries ) {
|
|
69
|
+
this.#clientIdentifier = identifier || this.#clientIdentifier;
|
|
70
|
+
|
|
71
|
+
let retryStrategy = ( attempt ) => {
|
|
72
|
+
let result = Math.min( attempt * 50, this.#retryMaxInterval );
|
|
73
|
+
|
|
74
|
+
if ( this.#retryMaxAttempts != null && attempt > this.#retryMaxAttempts ) {
|
|
75
|
+
logger.log( "In Redis retry strategy: reached max attempts for command retry. Aborting...", logger.logSeverity.WARNING, { attempts: attempt } );
|
|
76
|
+
result = exceptions.raise( exceptions.exceptionCode.E_COM_RETRY_ATTEMPTS_EXCEEDED );
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return result;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
let reconnectOnError = ( error ) => {
|
|
83
|
+
logger.log( `In Redis reconnect on error strategy: ${ error.message }`, logger.logSeverity.ERROR, error );
|
|
84
|
+
return !!error.message.includes( "READONLY" );
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
let options = {
|
|
88
|
+
port: port,
|
|
89
|
+
host: host,
|
|
90
|
+
password: authKey,
|
|
91
|
+
db: defaultDB,
|
|
92
|
+
autoResendUnfulfilledCommands: autoRetryUnfulfilled,
|
|
93
|
+
maxRetriesPerRequest: maxRetries,
|
|
94
|
+
retryStrategy: retryStrategy,
|
|
95
|
+
reconnectOnError: reconnectOnError
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/** @type Redis */
|
|
99
|
+
this.#redisClient = new ioredis( options );
|
|
100
|
+
|
|
101
|
+
this.#redisClient.on( "ready", () => {
|
|
102
|
+
logger.log( `Connection to Redis server ${ host }:${ port } (re)established by client '${ this.identifier }' and is ready to be used.`, logger.logSeverity.INFO, {
|
|
103
|
+
redis_version: this.#redisClient.serverInfo.redis_version,
|
|
104
|
+
redis_mode: this.#redisClient.serverInfo.redis_mode,
|
|
105
|
+
os: this.#redisClient.serverInfo.os,
|
|
106
|
+
uptime_in_days: this.#redisClient.serverInfo.uptime_in_days,
|
|
107
|
+
connected_clients: this.#redisClient.serverInfo.connected_clients,
|
|
108
|
+
role: this.#redisClient.serverInfo.role,
|
|
109
|
+
connected_slaves: this.#redisClient.serverInfo.connected_slaves
|
|
110
|
+
} );
|
|
111
|
+
|
|
112
|
+
// notify all connection observers about this event:
|
|
113
|
+
_.forEach( this.#connectionObservers, ( connectionObservers ) => {
|
|
114
|
+
connectionObservers.onConnectionRecovered( this.#clientIdentifier );
|
|
115
|
+
} );
|
|
116
|
+
} );
|
|
117
|
+
this.#redisClient.on( "error", ( error ) => {
|
|
118
|
+
logger.log( `Error received in Redis client '${ this.identifier }'.`, logger.logSeverity.ERROR, error );
|
|
119
|
+
|
|
120
|
+
// notify all connection observers about this event:
|
|
121
|
+
_.forEach( this.#connectionObservers, ( connectionObservers ) => {
|
|
122
|
+
connectionObservers.onConnectionDisrupted( this.#clientIdentifier );
|
|
123
|
+
} );
|
|
124
|
+
} );
|
|
125
|
+
this.#redisClient.on( "reconnecting", ( info ) => {
|
|
126
|
+
if ( info.attempt > 1 ) {
|
|
127
|
+
logger.log( `Client '${ this.identifier }' reconnecting to Redis server after ${ info.delay } ms. This is attempt ${ info.attempt }.`, logger.logSeverity.DEBUG );
|
|
128
|
+
}
|
|
129
|
+
} );
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/* Public interface */
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Used to return the Redis client identifier.
|
|
136
|
+
*
|
|
137
|
+
* @property
|
|
138
|
+
* @return {string}
|
|
139
|
+
* @public
|
|
140
|
+
*/
|
|
141
|
+
get identifier() {
|
|
142
|
+
return this.#clientIdentifier;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Used to register a new {@link ConnectionObserver} for events related to the Redis connection state.
|
|
147
|
+
*
|
|
148
|
+
* @method
|
|
149
|
+
* @param {ConnectionObserver} connectionObserver The {@link ConnectionObserver} that will be notified of any changes.
|
|
150
|
+
* @public
|
|
151
|
+
*/
|
|
152
|
+
addConnectionObserver( connectionObserver ) {
|
|
153
|
+
if ( connectionObserver instanceof ConnectionObserver ) {
|
|
154
|
+
this.#connectionObservers.push( connectionObserver );
|
|
155
|
+
} else {
|
|
156
|
+
logger.log( `Attempting to add '${ connectionObserver.constructor.name }' as connection observer but it's not a child-class of 'ConnectionObserver'!`, logger.logSeverity.WARNING );
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Used to execute multiple commands within a Redis transaction.
|
|
162
|
+
*
|
|
163
|
+
* @method
|
|
164
|
+
* @param {Array[]} commands
|
|
165
|
+
* @return {Promise<*>}
|
|
166
|
+
* @public
|
|
167
|
+
*/
|
|
168
|
+
executeCommands( commands ) {
|
|
169
|
+
return new Promise( ( resolve, reject ) => {
|
|
170
|
+
this.#redisClient.multi( commands ).exec().then( ( results ) => {
|
|
171
|
+
resolve( results );
|
|
172
|
+
} ).catch( ( error ) => {
|
|
173
|
+
reject( exceptions.raise( error ) );
|
|
174
|
+
} );
|
|
175
|
+
} );
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Used to send a new blocking command to Redis.
|
|
180
|
+
* WARNING: This will reserve the client connection until a result is received.
|
|
181
|
+
*
|
|
182
|
+
* @method
|
|
183
|
+
* @param {string} command
|
|
184
|
+
* @param {Array} commandArguments
|
|
185
|
+
* @return {Promise<*>}
|
|
186
|
+
* @public
|
|
187
|
+
*/
|
|
188
|
+
blockingCommand( command, commandArguments ) {
|
|
189
|
+
return new Promise( ( resolve, reject ) => {
|
|
190
|
+
this.#redisClient[ command ].apply( this.#redisClient, commandArguments ).then( ( results ) => {
|
|
191
|
+
resolve( results );
|
|
192
|
+
} ).catch( ( error ) => {
|
|
193
|
+
reject( exceptions.raise( error ) );
|
|
194
|
+
} );
|
|
195
|
+
} );
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Used to publish a message to the specified channel.
|
|
200
|
+
*
|
|
201
|
+
* @method
|
|
202
|
+
* @param {string} channel
|
|
203
|
+
* @param {(Object|string)} message
|
|
204
|
+
* @return {Promise<number>}
|
|
205
|
+
* @public
|
|
206
|
+
*/
|
|
207
|
+
publishCommand( channel, message ) {
|
|
208
|
+
return new Promise( ( resolve, reject ) => {
|
|
209
|
+
this.#redisClient.publish( channel, tools.stringifyJSON( message ) ).then( ( receivedBy ) => {
|
|
210
|
+
resolve( receivedBy );
|
|
211
|
+
} ).catch( ( error ) => {
|
|
212
|
+
reject( exceptions.raise( error ) );
|
|
213
|
+
} );
|
|
214
|
+
} );
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Used to subscribe to the specified channel for messages.
|
|
219
|
+
*
|
|
220
|
+
* @method
|
|
221
|
+
* @param {string} channel
|
|
222
|
+
* @param {function( Object )} messageHandler Will execute this handler every time a new message is received.
|
|
223
|
+
* @return {Promise}
|
|
224
|
+
* @public
|
|
225
|
+
*/
|
|
226
|
+
subscribeCommand( channel, messageHandler ) {
|
|
227
|
+
return new Promise( ( resolve, reject ) => {
|
|
228
|
+
this.#redisClient.on( "subscribe", ( channel, count ) => {
|
|
229
|
+
logger.log( "Subscription to message channel '" + channel + "' successful.", logger.logSeverity.DEBUG );
|
|
230
|
+
resolve();
|
|
231
|
+
} );
|
|
232
|
+
this.#redisClient.on( "message", ( channel, message ) => {
|
|
233
|
+
if ( typeof ( messageHandler ) === "function" ) {
|
|
234
|
+
messageHandler( tools.parseJSON( message ) );
|
|
235
|
+
}
|
|
236
|
+
} );
|
|
237
|
+
this.#redisClient.subscribe( channel ).catch( ( error ) => {
|
|
238
|
+
reject( exceptions.raise( error ) );
|
|
239
|
+
} );
|
|
240
|
+
} );
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Create and return a new Redis client.
|
|
247
|
+
*
|
|
248
|
+
* @method
|
|
249
|
+
* @param {string} identifier
|
|
250
|
+
* @param {string} host
|
|
251
|
+
* @param {number} [port=6379]
|
|
252
|
+
* @param {string} [authKey=undefined]
|
|
253
|
+
* @param {number} [defaultDB=0]
|
|
254
|
+
* @param {boolean} [autoRetryUnfulfilled=true]
|
|
255
|
+
* @param {number} [maxRetries=20]
|
|
256
|
+
* @return {RedisClient}
|
|
257
|
+
* @public
|
|
258
|
+
*/
|
|
259
|
+
module.exports.createRedisClient = ( identifier, host, port = 6379, authKey = undefined, defaultDB = 0, autoRetryUnfulfilled = true, maxRetries = 20 ) => {
|
|
260
|
+
return Object.freeze( new RedisClient( identifier, host, port, authKey, defaultDB, autoRetryUnfulfilled, maxRetries ) );
|
|
261
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ti-engine/core",
|
|
3
|
+
"version": "1.0.0",
|
|
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
|
+
"author": "Boris Kostadinov <kostadinov.boris@gmail.com>",
|
|
6
|
+
"license": "ISC",
|
|
7
|
+
"exports": {
|
|
8
|
+
"./service-consumer": "./components/service-consumer.js",
|
|
9
|
+
"./service-instance": "./components/service-instance.js",
|
|
10
|
+
"./service-provider": "./components/service-provider.js",
|
|
11
|
+
"./exceptions": "./utils/exceptions.js",
|
|
12
|
+
"./tools": "./utils/tools.js"
|
|
13
|
+
},
|
|
14
|
+
"imports": {
|
|
15
|
+
"#auditing": "./components/auditing.js",
|
|
16
|
+
"#cache": "./utils/cache.js",
|
|
17
|
+
"#config": "./utils/config.js",
|
|
18
|
+
"#connection-observer": "./components/connection-observer.js",
|
|
19
|
+
"#default-message-exchange": "./components/exchange/default/default-message-exchange.js",
|
|
20
|
+
"#default-message-receiver": "./components/exchange/default/default-message-receiver.js",
|
|
21
|
+
"#default-message-sender": "./components/exchange/default/default-message-sender.js",
|
|
22
|
+
"#exceptions": "./utils/exceptions.js",
|
|
23
|
+
"#gcloud-integration": "./integrations/gcloud-integration.js",
|
|
24
|
+
"#logger": "./utils/logger.js",
|
|
25
|
+
"#message-dispatcher": "./components/exchange/message-dispatcher.js",
|
|
26
|
+
"#message-exchange": "./components/exchange/message-exchange.js",
|
|
27
|
+
"#message-handler": "./components/exchange/message-handler.js",
|
|
28
|
+
"#message-memory-cache": "./components/exchange/message-memory-cache.js",
|
|
29
|
+
"#message-observer": "./components/exchange/message-observer.js",
|
|
30
|
+
"#message-receiver": "./components/exchange/message-receiver.js",
|
|
31
|
+
"#message-sender": "./components/exchange/message-sender.js",
|
|
32
|
+
"#message-tracer": "./components/exchange/message-tracer.js",
|
|
33
|
+
"#redis-integration": "./integrations/redis-integration.js",
|
|
34
|
+
"#service-caller": "./components/service-caller.js",
|
|
35
|
+
"#service-consumer": "./components/service-consumer.js",
|
|
36
|
+
"#service-executor": "./components/service-executor.js",
|
|
37
|
+
"#service-instance": "./components/service-instance.js",
|
|
38
|
+
"#service-provider": "./components/service-provider.js",
|
|
39
|
+
"#settings": "./settings.json",
|
|
40
|
+
"#tools": "./utils/tools.js"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"fs-extra": "^10.0.0",
|
|
44
|
+
"lodash": "^4.17.21",
|
|
45
|
+
"node-schedule": "^2.0.0",
|
|
46
|
+
"ioredis": "^4.27.7"
|
|
47
|
+
},
|
|
48
|
+
"optionalDependencies": {
|
|
49
|
+
"@google-cloud/error-reporting": "^2.0.2"
|
|
50
|
+
},
|
|
51
|
+
"engines": {
|
|
52
|
+
"node": ">=14.17.0"
|
|
53
|
+
}
|
|
54
|
+
}
|
package/settings.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"auditing": {
|
|
3
|
+
"logConsoleEnabled": true,
|
|
4
|
+
"logDetails": false,
|
|
5
|
+
"logMinLevel": 0,
|
|
6
|
+
"logUsesJSON": false
|
|
7
|
+
},
|
|
8
|
+
"gcloudIntegration": {
|
|
9
|
+
"apiKey": "",
|
|
10
|
+
"loggingEnabled": false,
|
|
11
|
+
"logName": "ti-engine",
|
|
12
|
+
"projectID": ""
|
|
13
|
+
},
|
|
14
|
+
"memoryCache": {
|
|
15
|
+
"authKey": null,
|
|
16
|
+
"redisDB": 0,
|
|
17
|
+
"redisHost": "127.0.0.1",
|
|
18
|
+
"redisPort": 6379
|
|
19
|
+
},
|
|
20
|
+
"messageExchange": {
|
|
21
|
+
"messageQueuePrefix": "ti:messages:",
|
|
22
|
+
"messageStore": "ti:messages:store",
|
|
23
|
+
"traceLogEnabled": true
|
|
24
|
+
},
|
|
25
|
+
"serviceConfig": {
|
|
26
|
+
"executionTimeout": 180000,
|
|
27
|
+
"healthCheckAddress": "ti:services:registry:health:",
|
|
28
|
+
"healthCheckInterval": "*/1 * * * * *",
|
|
29
|
+
"healthCheckTimeout": 3,
|
|
30
|
+
"serviceRegistryAddress": "ti:services:registry:catalog:"
|
|
31
|
+
},
|
|
32
|
+
"operationMode": "production"
|
|
33
|
+
}
|