@ti-engine/core 1.1.9 → 1.2.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 +144 -129
- package/README.md +2 -2
- package/bin/localization/labels.json +64 -64
- package/bin/settings.json +1 -1
- package/components/exchange/default/default-message-receiver.js +11 -4
- package/components/exchange/default/default-message-sender.js +8 -4
- package/components/exchange/message-handler.js +3 -3
- package/components/exchange/message-memory-cache.js +18 -8
- package/components/exchange/message-receiver.js +43 -13
- package/components/exchange/message-tracer.js +8 -3
- package/components/service-executor.js +26 -28
- package/components/service-instance.js +2 -2
- package/components/service-provider.js +9 -6
- package/integrations/redis-integration.js +160 -102
- package/package.json +65 -63
- package/utils/cache.js +3 -3
- package/utils/config.js +2 -2
- package/utils/exceptions.js +1 -0
- package/utils/localization.js +32 -29
- package/testRedis.js +0 -22
|
@@ -20,6 +20,7 @@ const redis = require( "#redis-integration" );
|
|
|
20
20
|
class MessageMemoryCache {
|
|
21
21
|
|
|
22
22
|
#redisClient = null;
|
|
23
|
+
#isShuttingDown = false;
|
|
23
24
|
|
|
24
25
|
/**
|
|
25
26
|
* @constructor
|
|
@@ -60,6 +61,7 @@ class MessageMemoryCache {
|
|
|
60
61
|
* @public
|
|
61
62
|
*/
|
|
62
63
|
shutDown( timeoutMs ) {
|
|
64
|
+
this.#isShuttingDown = true;
|
|
63
65
|
return this.#redisClient.shutDown( timeoutMs );
|
|
64
66
|
}
|
|
65
67
|
|
|
@@ -85,13 +87,17 @@ class MessageMemoryCache {
|
|
|
85
87
|
*/
|
|
86
88
|
sendMessage( message, queue ) {
|
|
87
89
|
return new Promise( ( resolve, reject ) => {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
90
|
+
if ( this.#isShuttingDown === true ) {
|
|
91
|
+
reject( exceptions.raise( exceptions.exceptionCode.E_COM_MESSAGE_SENDER_UNAVAILABLE ) );
|
|
92
|
+
} else {
|
|
93
|
+
let command = [ redis.cacheCommands.LIST_PUSH, queue, tools.stringifyJSON( message ) ];
|
|
94
|
+
this.#redisClient.executeCommands( [ command ] ).then( ( results ) => {
|
|
95
|
+
results = results[ 0 ];
|
|
96
|
+
resolve( ( results && results.length > 1 ) ? results[ 1 ] : undefined );
|
|
97
|
+
} ).catch( ( error ) => {
|
|
98
|
+
reject( exceptions.raise( error ) );
|
|
99
|
+
} );
|
|
100
|
+
}
|
|
95
101
|
} );
|
|
96
102
|
}
|
|
97
103
|
|
|
@@ -134,7 +140,11 @@ class MessageMemoryCache {
|
|
|
134
140
|
results = ( results && results.length > 1 ) ? results[ 1 ] : undefined;
|
|
135
141
|
resolve( tools.parseJSON( results ) );
|
|
136
142
|
} ).catch( ( error ) => {
|
|
137
|
-
|
|
143
|
+
if ( this.#isShuttingDown === true ) {
|
|
144
|
+
reject( exceptions.raise( exceptions.exceptionCode.E_COM_MESSAGE_RECEIVER_UNAVAILABLE ) );
|
|
145
|
+
} else {
|
|
146
|
+
reject( exceptions.raise( error ) );
|
|
147
|
+
}
|
|
138
148
|
} );
|
|
139
149
|
} );
|
|
140
150
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* 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
|
-
* Copyright © 2021-
|
|
3
|
+
* Copyright © 2021-2025 Boris Kostadinov <kostadinov.boris@gmail.com>
|
|
4
4
|
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
|
5
5
|
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
6
6
|
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
@@ -22,6 +22,7 @@ const config = require( "#config" );
|
|
|
22
22
|
class MessageReceiver extends MessageHandler {
|
|
23
23
|
|
|
24
24
|
#receiveQueue;
|
|
25
|
+
#isReceiving = false;
|
|
25
26
|
|
|
26
27
|
/**
|
|
27
28
|
* @constructor
|
|
@@ -48,7 +49,31 @@ class MessageReceiver extends MessageHandler {
|
|
|
48
49
|
* @returns {string}
|
|
49
50
|
* @public
|
|
50
51
|
*/
|
|
51
|
-
get receiveQueue() {
|
|
52
|
+
get receiveQueue() {
|
|
53
|
+
return this.#receiveQueue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Indicates whether the receiver is currently receiving messages.
|
|
58
|
+
*
|
|
59
|
+
* @property
|
|
60
|
+
* @returns {boolean}
|
|
61
|
+
* @public
|
|
62
|
+
*/
|
|
63
|
+
get isReceiving() {
|
|
64
|
+
return this.#isReceiving;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Used to set the value of the {@link MessageReceiver#isReceiving} property.
|
|
69
|
+
*
|
|
70
|
+
* @property
|
|
71
|
+
* @param {boolean} value
|
|
72
|
+
* @public
|
|
73
|
+
*/
|
|
74
|
+
set isReceiving( value ) {
|
|
75
|
+
this.#isReceiving = value;
|
|
76
|
+
}
|
|
52
77
|
|
|
53
78
|
/**
|
|
54
79
|
* Used to initialize and enable the communication capabilities of the handler.
|
|
@@ -81,21 +106,26 @@ class MessageReceiver extends MessageHandler {
|
|
|
81
106
|
/**
|
|
82
107
|
* Used to receive messages.
|
|
83
108
|
* <br/>
|
|
84
|
-
* NOTE: This method will start a recursion of subsequent
|
|
109
|
+
* NOTE: This method will start a recursion of subsequent receive calls that will continue even if an individual message fetch fails.
|
|
85
110
|
*
|
|
86
111
|
* @method
|
|
112
|
+
* @recursion
|
|
87
113
|
* @public
|
|
88
114
|
*/
|
|
89
115
|
receive() {
|
|
90
|
-
this.
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
116
|
+
if ( this.isReceiving === true ) {
|
|
117
|
+
this.onReceive().then( ( message ) => {
|
|
118
|
+
return this.#postReceive( message );
|
|
119
|
+
} ).then( ( message ) => {
|
|
120
|
+
this.onMessage( message );
|
|
121
|
+
} ).catch( ( error ) => {
|
|
122
|
+
if ( error.code !== exceptions.exceptionCode.E_COM_MESSAGE_RECEIVER_UNAVAILABLE ) {
|
|
123
|
+
logger.log( `Error while trying to receive the next pending message from memory cache in receiver '${ this.connectionIdentifier }'! Resuming operation...`, logger.logSeverity.ERROR, error );
|
|
124
|
+
}
|
|
125
|
+
} ).finally( () => {
|
|
126
|
+
this.receive();
|
|
127
|
+
} );
|
|
128
|
+
}
|
|
99
129
|
}
|
|
100
130
|
|
|
101
131
|
/**
|
|
@@ -147,4 +177,4 @@ class MessageReceiver extends MessageHandler {
|
|
|
147
177
|
|
|
148
178
|
}
|
|
149
179
|
|
|
150
|
-
module.exports = MessageReceiver;
|
|
180
|
+
module.exports = MessageReceiver;
|
|
@@ -117,7 +117,12 @@ class MessageTracer {
|
|
|
117
117
|
cache.instance.setJSON( config.getSetting( config.setting.MESSAGE_EXCHANGE_TRACE_REPOSITORY ), traceRoot, "$", 1 ).then( () => {
|
|
118
118
|
resolve();
|
|
119
119
|
} ).catch( ( error ) => {
|
|
120
|
-
|
|
120
|
+
// If JSON is unsupported in Redis server, the trace will use a Set later, so we can resolve the promise:
|
|
121
|
+
if ( error.code === exceptions.exceptionCode.E_GEN_FEATURE_UNSUPPORTED ) {
|
|
122
|
+
resolve();
|
|
123
|
+
} else {
|
|
124
|
+
reject( exceptions.raise( error ) );
|
|
125
|
+
}
|
|
121
126
|
} );
|
|
122
127
|
} );
|
|
123
128
|
}
|
|
@@ -125,7 +130,7 @@ class MessageTracer {
|
|
|
125
130
|
/**
|
|
126
131
|
* Used to create a trace entry for the provided {@link Message} and parameters.
|
|
127
132
|
* <br/>
|
|
128
|
-
* NOTE: By default all trace events are stored in the memory cache for further processing and analysis. The
|
|
133
|
+
* NOTE: By default, all trace events are stored in the memory cache for further processing and analysis. The
|
|
129
134
|
* location is configured in the MESSAGE_EXCHANGE_TRACE_REPOSITORY setting.
|
|
130
135
|
* <br/>
|
|
131
136
|
* NOTE: Trace events are logged with severity level NOTICE or ERROR for failed dispatches. They still might be
|
|
@@ -139,7 +144,7 @@ class MessageTracer {
|
|
|
139
144
|
* @public
|
|
140
145
|
*/
|
|
141
146
|
recordTraceEntry( message, messageType, dispatchEvent, messageState ) {
|
|
142
|
-
// Depending on whether the message comes as request or response, the from and to addresses will be opposite:
|
|
147
|
+
// Depending on whether the message comes as a request or response, the from and to addresses will be opposite:
|
|
143
148
|
let source = message.source.route + "." + message.source.instanceID;
|
|
144
149
|
let destination = message.destination.route + ( ( message.destination.instanceID != null ) ? "." + message.destination.instanceID : "" );
|
|
145
150
|
let messageSnapshot = MessageTracer.#obscureSensitiveData( message );
|
|
@@ -58,8 +58,6 @@ class ServiceExecutor extends MessageObserver {
|
|
|
58
58
|
#serviceInterface = {};
|
|
59
59
|
/** @type VerifyAccessMethod */
|
|
60
60
|
#verifyAccess;
|
|
61
|
-
#registrationTasks = {};
|
|
62
|
-
#registrationRetryInterval = 500;
|
|
63
61
|
|
|
64
62
|
/**
|
|
65
63
|
* @constructor
|
|
@@ -67,7 +65,7 @@ class ServiceExecutor extends MessageObserver {
|
|
|
67
65
|
constructor() {
|
|
68
66
|
super();
|
|
69
67
|
|
|
70
|
-
//
|
|
68
|
+
// Set up a default empty verify access method:
|
|
71
69
|
this.#verifyAccess = () => {
|
|
72
70
|
return Promise.resolve();
|
|
73
71
|
};
|
|
@@ -127,17 +125,6 @@ class ServiceExecutor extends MessageObserver {
|
|
|
127
125
|
*/
|
|
128
126
|
onConnectionRecovered( identifier ) {
|
|
129
127
|
super.onConnectionRecovered( identifier );
|
|
130
|
-
|
|
131
|
-
if ( identifier !== cache.instance.connectionIdentifier ) {
|
|
132
|
-
let serviceCatalog = config.getSetting( config.setting.SERVICE_REGISTRY_ADDRESS ) + ServiceInstance.serviceDomainName;
|
|
133
|
-
_.forOwn( this.#serviceInterface, ( versions, serviceAlias ) => {
|
|
134
|
-
if ( !this.#registrationTasks[ serviceAlias ] ) {
|
|
135
|
-
this.#registrationTasks[ serviceAlias ] = setInterval( () => {
|
|
136
|
-
this.#registerServiceToCatalog( serviceCatalog, serviceAlias );
|
|
137
|
-
}, this.#registrationRetryInterval );
|
|
138
|
-
}
|
|
139
|
-
} );
|
|
140
|
-
}
|
|
141
128
|
}
|
|
142
129
|
|
|
143
130
|
/**
|
|
@@ -164,16 +151,26 @@ class ServiceExecutor extends MessageObserver {
|
|
|
164
151
|
* @param {ServiceHandlerMethod} serviceHandler
|
|
165
152
|
* @param {ServiceDefinition} serviceDefinition
|
|
166
153
|
* @param {ServiceInstance} serviceInstance This will be used as context to bind all business services.
|
|
154
|
+
* @returns {Promise}
|
|
167
155
|
* @public
|
|
168
156
|
*/
|
|
169
157
|
addServiceHandler( serviceHandler, serviceDefinition, serviceInstance ) {
|
|
170
|
-
|
|
171
|
-
this.#
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
158
|
+
return new Promise( ( resolve, reject ) => {
|
|
159
|
+
this.#registerServiceToCatalog( serviceDefinition.serviceAlias ).then( () => {
|
|
160
|
+
// Bind the service handler to the service alias and version:
|
|
161
|
+
if ( !this.#serviceInterface[ serviceDefinition.serviceAlias ] ) {
|
|
162
|
+
this.#serviceInterface[ serviceDefinition.serviceAlias ] = {};
|
|
163
|
+
}
|
|
164
|
+
if ( this.#serviceInterface[ serviceDefinition.serviceAlias ][ serviceDefinition.serviceVersion ] ) {
|
|
165
|
+
logger.log( `Service handler for '${ serviceDefinition.serviceAlias }' version '${ serviceDefinition.serviceVersion }' already existed and will be overridden.`, logger.logSeverity.WARNING );
|
|
166
|
+
}
|
|
167
|
+
this.#serviceInterface[ serviceDefinition.serviceAlias ][ serviceDefinition.serviceVersion ] = serviceHandler.bind( serviceInstance, _.cloneDeep( serviceDefinition ) );
|
|
168
|
+
|
|
169
|
+
resolve();
|
|
170
|
+
} ).catch( ( error ) => {
|
|
171
|
+
reject( exceptions.raise( error ) );
|
|
172
|
+
} );
|
|
173
|
+
} );
|
|
177
174
|
}
|
|
178
175
|
|
|
179
176
|
/* Private interface */
|
|
@@ -207,19 +204,20 @@ class ServiceExecutor extends MessageObserver {
|
|
|
207
204
|
* Used to register a service in the service registry.
|
|
208
205
|
*
|
|
209
206
|
* @method
|
|
210
|
-
* @param {string} serviceCatalog
|
|
211
207
|
* @param {string} serviceAlias
|
|
208
|
+
* @returns {Promise}
|
|
212
209
|
* @private
|
|
213
210
|
*/
|
|
214
|
-
#registerServiceToCatalog(
|
|
215
|
-
|
|
211
|
+
#registerServiceToCatalog( serviceAlias ) {
|
|
212
|
+
return new Promise( ( resolve, reject ) => {
|
|
213
|
+
let serviceCatalog = config.getSetting( config.setting.SERVICE_REGISTRY_ADDRESS ) + ServiceInstance.serviceDomainName;
|
|
216
214
|
cache.instance.addToSet( serviceCatalog, serviceAlias ).then( () => {
|
|
217
|
-
|
|
218
|
-
delete this.#registrationTasks[ serviceAlias ];
|
|
215
|
+
resolve();
|
|
219
216
|
} ).catch( ( error ) => {
|
|
220
|
-
logger.log( `Record for service '${ serviceAlias }' could not be added to the service registry.
|
|
217
|
+
logger.log( `Record for service '${ serviceAlias }' could not be added to the service registry. Service will not be visible to Service Consumers!`, logger.logSeverity.ERROR, error );
|
|
218
|
+
reject( exceptions.raise( error ) );
|
|
221
219
|
} );
|
|
222
|
-
}
|
|
220
|
+
} );
|
|
223
221
|
}
|
|
224
222
|
|
|
225
223
|
/**
|
|
@@ -56,7 +56,7 @@ class ServiceInstance {
|
|
|
56
56
|
} );
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
// Ensure uniform 'ti-' prefix even if env is missing or custom starter script is used:
|
|
59
|
+
// Ensure a uniform 'ti-' prefix even if env is missing or a custom starter script is used:
|
|
60
60
|
const envID = process.env.TI_INSTANCE_ID;
|
|
61
61
|
ServiceInstance.#instanceID = ( envID && String( envID ).startsWith( "ti-" ) ) ? envID : ( "ti-" + ( envID || tools.getUUID() ) );
|
|
62
62
|
|
|
@@ -217,7 +217,7 @@ class ServiceInstance {
|
|
|
217
217
|
* Used to report health status of the service instance for external monitoring.
|
|
218
218
|
* This is a scheduled job that will be executed at SERVICE_HEALTH_CHECK_INTERVAL time.
|
|
219
219
|
* <br/>
|
|
220
|
-
* NOTE: By default this method will update a Redis key with an expiration timer. You can override this
|
|
220
|
+
* NOTE: By default, this method will update a Redis key with an expiration timer. You can override this
|
|
221
221
|
* functionality with something custom like calling an HTTP endpoint.
|
|
222
222
|
*
|
|
223
223
|
* @method
|
|
@@ -140,10 +140,10 @@ class ServiceProvider extends ServiceConsumer {
|
|
|
140
140
|
/**
|
|
141
141
|
* Used to register a single service to the service provider's API. One service can have multiple versions accessible at the same time.
|
|
142
142
|
* <br/>
|
|
143
|
-
* NOTE: This will actually bind the serviceDefinition as first parameter of the service handler function. When creating default service handlers,
|
|
143
|
+
* NOTE: This will actually bind the serviceDefinition as the first parameter of the service handler function. When creating default service handlers,
|
|
144
144
|
* keep in mind that your first param must always be the 'serviceDefinition' and the second one will be the general 'serviceParams' object.
|
|
145
145
|
* <br/>
|
|
146
|
-
* 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
|
|
146
|
+
* NOTE: Additionally, if you intend to call another service inside the service handler, then you have to use a normal function for the handler and not
|
|
147
147
|
* 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.
|
|
148
148
|
*
|
|
149
149
|
* @method
|
|
@@ -171,12 +171,15 @@ class ServiceProvider extends ServiceConsumer {
|
|
|
171
171
|
}
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
-
// if we have a valid service handler proceed with the registration:
|
|
174
|
+
// if we have a valid service handler, proceed with the registration:
|
|
175
175
|
if ( typeof ( serviceHandler ) === "function" ) {
|
|
176
176
|
// make sure we have a version and parent service provider specified:
|
|
177
177
|
serviceDefinition.serviceVersion = serviceDefinition.serviceVersion || 1;
|
|
178
|
-
this.#serviceExecutor.addServiceHandler( serviceHandler, serviceDefinition, this )
|
|
179
|
-
|
|
178
|
+
this.#serviceExecutor.addServiceHandler( serviceHandler, serviceDefinition, this ).then( () => {
|
|
179
|
+
resolve();
|
|
180
|
+
} ).catch( ( error ) => {
|
|
181
|
+
reject( exceptions.raise( error ) );
|
|
182
|
+
} );
|
|
180
183
|
} else {
|
|
181
184
|
reject( exceptions.raise( exceptions.exceptionCode.E_GEN_BAD_SERVICE_HANDLER ) );
|
|
182
185
|
}
|
|
@@ -201,7 +204,7 @@ class ServiceProvider extends ServiceConsumer {
|
|
|
201
204
|
_.forEach( serviceDefinitions, ( serviceDefinition ) => {
|
|
202
205
|
// NOTE: we are not going to interrupt the service interface loading if one of the services fails to load or is not found!
|
|
203
206
|
// If this happens, a corresponding log entry will be created but the loading process will continue. Therefore, the following
|
|
204
|
-
// promise will always resolve (unless a programming error occurs in it of course).
|
|
207
|
+
// promise will always resolve (unless a programming error occurs in it, of course).
|
|
205
208
|
let registrationPromise = ( serviceDefinition, defaultServiceHandler ) => {
|
|
206
209
|
return new Promise( ( resolve ) => {
|
|
207
210
|
this.registerService( serviceDefinition, defaultServiceHandler ).then( () => {
|