@ti-engine/core 1.1.5 → 1.1.8

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,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-2023 Boris Kostadinov <kostadinov.boris@gmail.com>
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/>.
@@ -82,103 +82,149 @@ let messageStateEnum = tools.enum( {
82
82
  module.exports.messageState = messageStateEnum;
83
83
 
84
84
  /**
85
- * Used to create a log entry from the trace entry.
85
+ * Used for recording message trace entries.
86
86
  *
87
- * @method
88
- * @param {TiTraceEntry} traceEntry The trace entry to log.
89
- * @param {TiLogSeverity} severity The log entry severity.
90
- * @private
87
+ * @class MessageTracer
88
+ * @singleton
89
+ * @public
91
90
  */
92
- let createLogEntry = ( traceEntry, severity ) => {
93
- logger.log( formatLogEntry( traceEntry ), severity, traceEntry );
94
- };
91
+ class MessageTracer {
95
92
 
96
- /**
97
- * Used to format trace entry into log-suitable string.
98
- *
99
- * @method
100
- * @param {TiTraceEntry} traceEntry
101
- * @return {string} Prepared trace info.
102
- * @private
103
- */
104
- let formatLogEntry = ( traceEntry ) => {
105
- return `Message(${ traceEntry.chainID || traceEntry.messageID }) Trace: '${ traceEntry.messageType } ${ traceEntry.dispatchEvent } ${ traceEntry.messageState }' From: '${ traceEntry.fromAddress }' To: '${ traceEntry.toAddress }'`;
106
- };
93
+ static #instance = null;
107
94
 
108
- /**
109
- * Used to obscure sensitive data in the message, remove the payload, and return a snapshot.
110
- *
111
- * @method
112
- * @param {Message} message
113
- * @returns {Message}
114
- * @private
115
- */
116
- let obscureSensitiveData = ( message ) => {
117
- /** @type Message */
118
- let messageSnapshot = tools.parseJSON( _.replace( tools.stringifyJSON( message ), /("\w*?pin\w*?"|"\w*?pass\w*?"|"\w*?otp\w*?"):"(.*?)"/gmi, "\"SENSITIVE_PROPERTY\":\"OBSCURED_BY_SYSTEM\"" ) );
119
- delete messageSnapshot.payload;
120
- return messageSnapshot;
121
- };
122
-
123
- /**
124
- * Used to create a trace entry for the provided {@link Message} and parameters.
125
- * <br/>
126
- * NOTE: By default all trace events are stored in the memory cache for further processing and analysis. The
127
- * location is configured in the MESSAGE_EXCHANGE_TRACE_REPOSITORY setting.
128
- * <br/>
129
- * NOTE: Trace events are logged with severity level NOTICE or ERROR for failed dispatches. They still might be
130
- * filtered out if the minimum log level setting is set too high.
131
- *
132
- * @method
133
- * @param {Message} message The message to trace.
134
- * @param {TiMessageType} messageType The type of the message.
135
- * @param {TiDispatchEvent} dispatchEvent The event in the dispatch system that triggered the trace entry.
136
- * @param {TiMessageState} messageState The state of the processing of the message.
137
- * @public
138
- */
139
- module.exports.recordTraceEntry = ( message, messageType, dispatchEvent, messageState ) => {
140
- // depending on whether the message comes as request or response, the from and to addresses will be opposite:
141
- let source = message.source.route + "." + message.source.instanceID;
142
- let destination = message.destination.route + ( ( message.destination.instanceID != null ) ? "." + message.destination.instanceID : "" );
143
- let messageSnapshot = obscureSensitiveData( message );
144
- delete messageSnapshot.chainID;
145
- delete messageSnapshot.messageID;
146
- let currentDate = new Date();
147
-
148
- /** @type TiTraceEntry */
149
- let traceEntry = {
150
- chainID: message.chainID,
151
- dispatchEvent: tools.getEnumName( dispatchEventEnum, dispatchEvent ),
152
- fromAddress: ( messageType === messageTypeEnum.MESSAGE_REQUEST ) ? source : destination,
153
- messageID: message.messageID,
154
- messageSnapshot: messageSnapshot,
155
- messageState: tools.getEnumName( messageStateEnum, messageState ),
156
- messageType: tools.getEnumName( messageTypeEnum, messageType ),
157
- toAddress: ( messageType === messageTypeEnum.MESSAGE_REQUEST ) ? destination : source,
158
- traceTimestamp: currentDate.getTime(),
159
- traceID: tools.getUUID()
160
- };
161
-
162
- // only write the trace in the general log if this is enabled:
163
- if ( config.getSetting( config.setting.MESSAGE_EXCHANGE_TRACE_LOG_ENABLED ) === true ) {
164
- createLogEntry( traceEntry, ( dispatchEvent === dispatchEventEnum.FAILED ) ? logger.logSeverity.ERROR : logger.logSeverity.NOTICE );
95
+ /**
96
+ * @constructor
97
+ * @return {MessageTracer}
98
+ */
99
+ constructor() {
100
+ if ( !MessageTracer.#instance ) {
101
+ MessageTracer.#instance = this;
102
+ }
103
+ return MessageTracer.#instance;
165
104
  }
166
105
 
167
- // add the trace entry to the repository in the memory cache:
168
- cache.setJSON( config.getSetting( config.setting.MESSAGE_EXCHANGE_TRACE_REPOSITORY ), traceRoot, "$", 1 ).then( () => {
169
- return cache.arrayAppendJSON( config.getSetting( config.setting.MESSAGE_EXCHANGE_TRACE_REPOSITORY ), traceEntry, "$.trace" );
170
- } ).then( () => {
171
- // this will refresh the expiration time for the trace repository on each new record:
172
- let expiration = config.getSetting( config.setting.MESSAGE_EXCHANGE_TRACE_EXPIRATION_TIME );
173
- return ( expiration > 0 ) ? cache.expireValue( config.getSetting( config.setting.MESSAGE_EXCHANGE_TRACE_REPOSITORY ), expiration ) : expiration;
174
- } ).catch( ( error ) => {
175
- if ( error.code === exceptions.exceptionCode.E_GEN_FEATURE_UNSUPPORTED ) {
176
- cache.addToSet( config.getSetting( config.setting.MESSAGE_EXCHANGE_TRACE_REPOSITORY ), traceEntry ).catch( ( error ) => {
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 );
106
+ /* Public interface */
107
+
108
+ /**
109
+ * Used to initialize the message tracer.
110
+ *
111
+ * @method
112
+ * @returns {Promise}
113
+ * @public
114
+ */
115
+ initialize() {
116
+ return new Promise( ( resolve, reject ) => {
117
+ cache.instance.setJSON( config.getSetting( config.setting.MESSAGE_EXCHANGE_TRACE_REPOSITORY ), traceRoot, "$", 1 ).then( () => {
118
+ resolve();
119
+ } ).catch( ( error ) => {
120
+ reject( exceptions.raise( error ) );
178
121
  } );
179
- } else {
180
- 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 );
122
+ } );
123
+ }
124
+
125
+ /**
126
+ * Used to create a trace entry for the provided {@link Message} and parameters.
127
+ * <br/>
128
+ * NOTE: By default all trace events are stored in the memory cache for further processing and analysis. The
129
+ * location is configured in the MESSAGE_EXCHANGE_TRACE_REPOSITORY setting.
130
+ * <br/>
131
+ * NOTE: Trace events are logged with severity level NOTICE or ERROR for failed dispatches. They still might be
132
+ * filtered out if the minimum log level setting is set too high.
133
+ *
134
+ * @method
135
+ * @param {Message} message The message to trace.
136
+ * @param {TiMessageType} messageType The type of the message.
137
+ * @param {TiDispatchEvent} dispatchEvent The event in the dispatch system that triggered the trace entry.
138
+ * @param {TiMessageState} messageState The state of the message processing.
139
+ * @public
140
+ */
141
+ recordTraceEntry( message, messageType, dispatchEvent, messageState ) {
142
+ // Depending on whether the message comes as request or response, the from and to addresses will be opposite:
143
+ let source = message.source.route + "." + message.source.instanceID;
144
+ let destination = message.destination.route + ( ( message.destination.instanceID != null ) ? "." + message.destination.instanceID : "" );
145
+ let messageSnapshot = MessageTracer.#obscureSensitiveData( message );
146
+ delete messageSnapshot.chainID;
147
+ delete messageSnapshot.messageID;
148
+ let currentDate = new Date();
149
+
150
+ /** @type TiTraceEntry */
151
+ let traceEntry = {
152
+ chainID: message.chainID,
153
+ dispatchEvent: tools.getEnumName( dispatchEventEnum, dispatchEvent ),
154
+ fromAddress: ( messageType === messageTypeEnum.MESSAGE_REQUEST ) ? source : destination,
155
+ messageID: message.messageID,
156
+ messageSnapshot: messageSnapshot,
157
+ messageState: tools.getEnumName( messageStateEnum, messageState ),
158
+ messageType: tools.getEnumName( messageTypeEnum, messageType ),
159
+ toAddress: ( messageType === messageTypeEnum.MESSAGE_REQUEST ) ? destination : source,
160
+ traceTimestamp: currentDate.getTime(),
161
+ traceID: tools.getUUID()
162
+ };
163
+
164
+ // Only write the trace in the general log if this is enabled:
165
+ if ( config.getSetting( config.setting.MESSAGE_EXCHANGE_TRACE_LOG_ENABLED ) === true ) {
166
+ MessageTracer.#createLogEntry( traceEntry, ( dispatchEvent === dispatchEventEnum.FAILED ) ? logger.logSeverity.ERROR : logger.logSeverity.NOTICE );
181
167
  }
182
- } );
183
168
 
184
- };
169
+ // Add the trace entry to the repository in the memory cache:
170
+ cache.instance.arrayAppendJSON( config.getSetting( config.setting.MESSAGE_EXCHANGE_TRACE_REPOSITORY ), traceEntry, "$.trace" ).then( () => {
171
+ // This will refresh the expiration time for the trace repository on each new record:
172
+ let expiration = config.getSetting( config.setting.MESSAGE_EXCHANGE_TRACE_EXPIRATION_TIME );
173
+ return ( expiration > 0 ) ? cache.instance.expireValue( config.getSetting( config.setting.MESSAGE_EXCHANGE_TRACE_REPOSITORY ), expiration ) : expiration;
174
+ } ).catch( ( error ) => {
175
+ // If JSON is unsupported in Redis server, then try to store the trace entry in a Set:
176
+ if ( error.code === exceptions.exceptionCode.E_GEN_FEATURE_UNSUPPORTED ) {
177
+ cache.instance.addToSet( config.getSetting( config.setting.MESSAGE_EXCHANGE_TRACE_REPOSITORY ), traceEntry ).catch( ( error ) => {
178
+ 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 );
179
+ } );
180
+ } else {
181
+ 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 );
182
+ }
183
+ } );
184
+ }
185
+
186
+ /* Private interface */
187
+
188
+ /**
189
+ * Used to create a log entry from the trace entry.
190
+ *
191
+ * @method
192
+ * @param {TiTraceEntry} traceEntry The trace entry to log.
193
+ * @param {TiLogSeverity} severity The log entry severity.
194
+ * @private
195
+ */
196
+ static #createLogEntry( traceEntry, severity ) {
197
+ logger.log( MessageTracer.#formatLogEntry( traceEntry ), severity, traceEntry );
198
+ }
199
+
200
+ /**
201
+ * Used to format trace entry into log-suitable string.
202
+ *
203
+ * @method
204
+ * @param {TiTraceEntry} traceEntry
205
+ * @return {string} Prepared trace info.
206
+ * @private
207
+ */
208
+ static #formatLogEntry( traceEntry ) {
209
+ return `Message(${ traceEntry.chainID || traceEntry.messageID }) Trace: '${ traceEntry.messageType } ${ traceEntry.dispatchEvent } ${ traceEntry.messageState }' From: '${ traceEntry.fromAddress }' To: '${ traceEntry.toAddress }'`;
210
+ }
211
+
212
+ /**
213
+ * Used to obscure sensitive data in the message, remove the payload, and return a snapshot.
214
+ *
215
+ * @method
216
+ * @param {Message} message
217
+ * @returns {Message}
218
+ * @private
219
+ */
220
+ static #obscureSensitiveData( message ) {
221
+ /** @type Message */
222
+ let messageSnapshot = tools.parseJSON( _.replace( tools.stringifyJSON( message ), /("\w*?pin\w*?"|"\w*?pass\w*?"|"\w*?otp\w*?"):"(.*?)"/gmi, "\"SENSITIVE_PROPERTY\":\"OBSCURED_BY_SYSTEM\"" ) );
223
+ delete messageSnapshot.payload;
224
+ return messageSnapshot;
225
+ }
226
+
227
+ }
228
+
229
+ const instance = new MessageTracer();
230
+ module.exports.instance = Object.freeze( instance );
@@ -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-2023 Boris Kostadinov <kostadinov.boris@gmail.com>
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/>.
@@ -95,7 +95,7 @@ class ServiceCaller extends MessageObserver {
95
95
  this.#findServiceInRegistry( serviceAddress ).then( () => {
96
96
  return this.#prepareServiceCall( serviceAddress, serviceParams, serviceExecContext );
97
97
  } ).then( ( serviceCall ) => {
98
- return messageDispatcher.sendRequest( serviceCall );
98
+ return messageDispatcher.instance.sendRequest( serviceCall );
99
99
  } ).then( ( messageID ) => {
100
100
  this.#addTaskHandler( messageID, ( serviceCall ) => {
101
101
  this.#completeServiceCall( serviceCall ).then( ( serviceCall ) => {
@@ -187,7 +187,7 @@ class ServiceCaller extends MessageObserver {
187
187
  #findServiceInRegistry( serviceAddress ) {
188
188
  return new Promise( ( resolve, reject ) => {
189
189
  let serviceCatalog = config.getSetting( config.setting.SERVICE_REGISTRY_ADDRESS ) + serviceAddress.serviceDomainName;
190
- cache.isSetMember( serviceCatalog, serviceAddress.serviceAlias ).then( ( result ) => {
190
+ cache.instance.isSetMember( serviceCatalog, serviceAddress.serviceAlias ).then( ( result ) => {
191
191
  if ( result === true ) {
192
192
  resolve();
193
193
  } else {
@@ -210,7 +210,7 @@ class ServiceCaller extends MessageObserver {
210
210
  * @private
211
211
  */
212
212
  #prepareServiceCall( serviceAddress, serviceParams, serviceExecContext ) {
213
- return new Promise( ( resolve, reject ) => {
213
+ return new Promise( ( resolve ) => {
214
214
  const ServiceInstance = require( "#service-instance" );
215
215
 
216
216
  // assemble the new service call:
@@ -258,7 +258,7 @@ class ServiceCaller extends MessageObserver {
258
258
  * @private
259
259
  */
260
260
  #completeServiceCall( serviceCall ) {
261
- return new Promise( ( resolve, reject ) => {
261
+ return new Promise( ( resolve ) => {
262
262
  serviceCall.finishedOn = Date.now();
263
263
  serviceCall.executionTime = serviceCall.finishedOn - serviceCall.createdOn;
264
264
  serviceCall.isCompleted = true;
@@ -306,4 +306,4 @@ class ServiceCaller extends MessageObserver {
306
306
 
307
307
  }
308
308
 
309
- module.exports = ServiceCaller;
309
+ module.exports = ServiceCaller;
@@ -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-2023 Boris Kostadinov <kostadinov.boris@gmail.com>
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/>.
@@ -65,7 +65,7 @@ class ServiceConsumer extends ServiceInstance {
65
65
  this.#serviceCaller = new ServiceCaller();
66
66
 
67
67
  super.onStart().then( () => {
68
- messageDispatcher.addMessageObserverResponsesIn( this.#serviceCaller );
68
+ messageDispatcher.instance.addMessageObserverResponsesIn( this.#serviceCaller );
69
69
  resolve();
70
70
  } ).catch( ( error ) => {
71
71
  reject( exceptions.raise( error ) );
@@ -128,4 +128,4 @@ class ServiceConsumer extends ServiceInstance {
128
128
 
129
129
  }
130
130
 
131
- module.exports = ServiceConsumer;
131
+ module.exports = ServiceConsumer;
@@ -58,6 +58,8 @@ class ServiceExecutor extends MessageObserver {
58
58
  #serviceInterface = {};
59
59
  /** @type VerifyAccessMethod */
60
60
  #verifyAccess;
61
+ #registrationTasks = {};
62
+ #registrationRetryInterval = 500;
61
63
 
62
64
  /**
63
65
  * @constructor
@@ -65,11 +67,12 @@ class ServiceExecutor extends MessageObserver {
65
67
  constructor() {
66
68
  super();
67
69
 
70
+ // Setup a default empty verify access method:
68
71
  this.#verifyAccess = () => {
69
72
  return Promise.resolve();
70
73
  };
71
74
 
72
- cache.addConnectionObserver( this );
75
+ cache.instance.addConnectionObserver( this );
73
76
  }
74
77
 
75
78
  /* Public interface */
@@ -81,7 +84,9 @@ class ServiceExecutor extends MessageObserver {
81
84
  * @returns {ServiceInterface}
82
85
  * @public
83
86
  */
84
- get serviceInterface() { return this.#serviceInterface; }
87
+ get serviceInterface() {
88
+ return this.#serviceInterface;
89
+ }
85
90
 
86
91
  /**
87
92
  *
@@ -94,7 +99,7 @@ class ServiceExecutor extends MessageObserver {
94
99
  */
95
100
  onMessage( identifier, message ) {
96
101
  this.#processServiceCall( message ).then( ( serviceCall ) => {
97
- return messageDispatcher.sendResponse( serviceCall );
102
+ return messageDispatcher.instance.sendResponse( serviceCall );
98
103
  } ).catch( ( error ) => {
99
104
  logger.log( `Failed to send service call response after processing! Service call ID was: '${ message.messageID }'`, logger.logSeverity.ERROR, error );
100
105
  } );
@@ -123,17 +128,15 @@ class ServiceExecutor extends MessageObserver {
123
128
  onConnectionRecovered( identifier ) {
124
129
  super.onConnectionRecovered( identifier );
125
130
 
126
- if ( identifier !== cache.connectionIdentifier ) {
127
- if ( cache.isOperational ) {
128
- let serviceCatalog = config.getSetting( config.setting.SERVICE_REGISTRY_ADDRESS ) + ServiceInstance.serviceDomainName;
129
- _.forOwn( this.#serviceInterface, ( versions, serviceAlias ) => {
130
- cache.addToSet( serviceCatalog, serviceAlias ).catch( ( error ) => {
131
- logger.log( `Record for service '${ serviceAlias }' could not be added to the service registry.`, logger.logSeverity.ERROR, error );
132
- } );
133
- } );
134
- } else {
135
- //TODO: Retry service registration after 0.5 seconds.
136
- }
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
+ } );
137
140
  }
138
141
  }
139
142
 
@@ -200,6 +203,25 @@ class ServiceExecutor extends MessageObserver {
200
203
  };
201
204
  }
202
205
 
206
+ /**
207
+ * Used to register a service in the service registry.
208
+ *
209
+ * @method
210
+ * @param {string} serviceCatalog
211
+ * @param {string} serviceAlias
212
+ * @private
213
+ */
214
+ #registerServiceToCatalog( serviceCatalog, serviceAlias ) {
215
+ if ( cache.instance.isOperational ) {
216
+ cache.instance.addToSet( serviceCatalog, serviceAlias ).then( () => {
217
+ clearTimeout( this.#registrationTasks[ serviceAlias ] );
218
+ delete this.#registrationTasks[ serviceAlias ];
219
+ } ).catch( ( error ) => {
220
+ logger.log( `Record for service '${ serviceAlias }' could not be added to the service registry. Will retry in '${ this.#registrationRetryInterval }' milliseconds.`, logger.logSeverity.ERROR, error );
221
+ } );
222
+ }
223
+ }
224
+
203
225
  /**
204
226
  * Used to process the actual service call.
205
227
  *
@@ -209,7 +231,7 @@ class ServiceExecutor extends MessageObserver {
209
231
  * @private
210
232
  */
211
233
  #processServiceCall( serviceCall ) {
212
- return new Promise( ( resolve, reject ) => {
234
+ return new Promise( ( resolve ) => {
213
235
  this.#verifyAccess( serviceCall.authToken, serviceCall.serviceAddress ).then( () => {
214
236
  return this.#identifyService( serviceCall.serviceAddress );
215
237
  } ).then( ( serviceHandler ) => {
@@ -256,4 +278,4 @@ class ServiceExecutor extends MessageObserver {
256
278
 
257
279
  }
258
280
 
259
- module.exports = ServiceExecutor;
281
+ module.exports = ServiceExecutor;
@@ -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-2023 Boris Kostadinov <kostadinov.boris@gmail.com>
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/>.
@@ -24,8 +24,6 @@ const messageDispatcher = require( "#message-dispatcher" );
24
24
  * Abstract class used to define a Service Instance behavior.
25
25
  * <br/>
26
26
  * NOTE: Inherit this to create a module that can be started as a microservice instance.
27
- * <br/>
28
- * NOTE: This class does not
29
27
  *
30
28
  * @class ServiceInstance
31
29
  * @abstract
@@ -46,12 +44,22 @@ class ServiceInstance {
46
44
  * @param {Object} [serviceConfig={}] The JSON configuration for this service.
47
45
  */
48
46
  constructor( serviceDomainName, serviceConfig = {} ) {
49
- // make sure this abstract class cannot be instantiated:
47
+ // Ensure this abstract class cannot be instantiated:
50
48
  if ( new.target === ServiceInstance ) {
51
49
  throw exceptions.raise( exceptions.exceptionCode.E_GEN_ABSTRACT_CLASS_INIT, { name: this.constructor.name } );
52
50
  }
53
51
 
54
- ServiceInstance.#instanceID = process.env.TI_INSTANCE_ID || tools.getUUID();
52
+ // Guard against multiple instances in a single process (not supported):
53
+ if ( ServiceInstance.#instanceID && ServiceInstance.#serviceDomainName ) {
54
+ throw exceptions.raise( exceptions.exceptionCode.E_GEN_FEATURE_UNSUPPORTED, {
55
+ details: "Multiple ServiceInstance initializations per process are not supported."
56
+ } );
57
+ }
58
+
59
+ // Ensure uniform 'ti-' prefix even if env is missing or custom starter script is used:
60
+ const envID = process.env.TI_INSTANCE_ID;
61
+ ServiceInstance.#instanceID = ( envID && String( envID ).startsWith( "ti-" ) ) ? envID : ( "ti-" + ( envID || tools.getUUID() ) );
62
+
55
63
  ServiceInstance.#serviceDomainName = serviceDomainName;
56
64
  this.#serviceConfig = ( _.isObjectLike( serviceConfig ) ) ? serviceConfig : { services: [] };
57
65
  }
@@ -65,7 +73,9 @@ class ServiceInstance {
65
73
  * @returns {string}
66
74
  * @public
67
75
  */
68
- static get instanceID() { return ServiceInstance.#instanceID; }
76
+ static get instanceID() {
77
+ return ServiceInstance.#instanceID;
78
+ }
69
79
 
70
80
  /**
71
81
  * Property returning the current service domain name.
@@ -74,7 +84,9 @@ class ServiceInstance {
74
84
  * @returns {string}
75
85
  * @public
76
86
  */
77
- static get serviceDomainName() { return ServiceInstance.#serviceDomainName; }
87
+ static get serviceDomainName() {
88
+ return ServiceInstance.#serviceDomainName;
89
+ }
78
90
 
79
91
  /**
80
92
  * Property to indicate that this and every child class is a {@link ServiceInstance}.
@@ -83,7 +95,9 @@ class ServiceInstance {
83
95
  * @returns {boolean}
84
96
  * @public
85
97
  */
86
- get isServiceInstance() { return true; }
98
+ get isServiceInstance() {
99
+ return true;
100
+ }
87
101
 
88
102
  /**
89
103
  * Property returning the service configuration JSON.
@@ -92,7 +106,9 @@ class ServiceInstance {
92
106
  * @returns {ServiceConfiguration}
93
107
  * @public
94
108
  */
95
- get serviceConfig() { return this.#serviceConfig; }
109
+ get serviceConfig() {
110
+ return this.#serviceConfig;
111
+ }
96
112
 
97
113
  /**
98
114
  * Initializes the instance.
@@ -140,8 +156,7 @@ class ServiceInstance {
140
156
 
141
157
  let configureInbound = ( this instanceof ServiceProvider );
142
158
  let configureOutbound = ( this instanceof ServiceConsumer );
143
-
144
- messageDispatcher.initialize( new DefaultMessageExchange( ServiceInstance.instanceID, ServiceInstance.serviceDomainName ), configureInbound, configureOutbound ).then( () => {
159
+ messageDispatcher.instance.initialize( new DefaultMessageExchange( ServiceInstance.instanceID, ServiceInstance.serviceDomainName ), configureInbound, configureOutbound ).then( () => {
145
160
  resolve();
146
161
  } ).catch( ( error ) => {
147
162
  reject( exceptions.raise( error ) );
@@ -160,6 +175,8 @@ class ServiceInstance {
160
175
  return new Promise( ( resolve, reject ) => {
161
176
  this.#preStop().then( () => {
162
177
  return this.onStop();
178
+ } ).then( () => {
179
+ return cache.instance.shutDown();
163
180
  } ).then( () => {
164
181
  return this.#postStop();
165
182
  } ).then( () => {
@@ -175,7 +192,7 @@ class ServiceInstance {
175
192
  * <br/>
176
193
  * NOTE: This method will be invoked automatically.
177
194
  * <br/>
178
- * NOTE: If you need to add more onStop logic you can override this method but make sure to call it in the
195
+ * NOTE: If you need to add more onStop logic, you can override this method but make sure to call it in the
179
196
  * overriding method using: super.onStop()
180
197
  *
181
198
  * @method
@@ -185,7 +202,7 @@ class ServiceInstance {
185
202
  */
186
203
  onStop() {
187
204
  return new Promise( ( resolve, reject ) => {
188
- messageDispatcher.shutDown().then( () => {
205
+ messageDispatcher.instance.shutDown().then( () => {
189
206
  resolve();
190
207
  } ).catch( ( error ) => {
191
208
  reject( exceptions.raise( error ) );
@@ -205,9 +222,9 @@ class ServiceInstance {
205
222
  * @public
206
223
  */
207
224
  reportHealthy() {
208
- if ( cache.isOperational ) {
225
+ if ( cache.instance.isOperational ) {
209
226
  let timestamp = new Date();
210
- cache.setValue( this.#serviceHealthCheck, timestamp.toISOString(), config.getSetting( config.setting.SERVICE_HEALTH_CHECK_TIMEOUT ) ).catch( ( error ) => {
227
+ cache.instance.setValue( this.#serviceHealthCheck, timestamp.toISOString(), config.getSetting( config.setting.SERVICE_HEALTH_CHECK_TIMEOUT ) ).catch( ( error ) => {
211
228
  logger.log( `Error while trying to report for health check from '${ ServiceInstance.instanceID }'!`, logger.logSeverity.WARNING, error );
212
229
  } );
213
230
  }
@@ -225,8 +242,8 @@ class ServiceInstance {
225
242
  * @private
226
243
  */
227
244
  #preStart() {
228
- return new Promise( ( resolve, reject ) => {
229
- this.#serviceHealthCheck = config.getSetting( config.setting.SERVICE_HEALTH_CHECK_ADDRESS ) + process.env.TI_INSTANCE_NAME + ":" + ServiceInstance.instanceID;
245
+ return new Promise( ( resolve ) => {
246
+ this.#serviceHealthCheck = config.getSetting( config.setting.SERVICE_HEALTH_CHECK_ADDRESS ) + ServiceInstance.serviceDomainName + ":" + ServiceInstance.instanceID;
230
247
  resolve();
231
248
  } );
232
249
  }
@@ -241,8 +258,8 @@ class ServiceInstance {
241
258
  * @private
242
259
  */
243
260
  #postStart() {
244
- return new Promise( ( resolve, reject ) => {
245
- // schedule regular health check:
261
+ return new Promise( ( resolve ) => {
262
+ // Schedule regular health check:
246
263
  this.#reportHealthyJob = schedule.scheduleJob( config.getSetting( config.setting.SERVICE_HEALTH_CHECK_INTERVAL ), () => {
247
264
  this.reportHealthy();
248
265
  } );
@@ -266,11 +283,10 @@ class ServiceInstance {
266
283
  * @private
267
284
  */
268
285
  #preStop() {
269
- return new Promise( ( resolve, reject ) => {
286
+ return new Promise( ( resolve ) => {
270
287
  if ( this.#reportHealthyJob ) {
271
288
  this.#reportHealthyJob.cancel();
272
289
  }
273
-
274
290
  resolve();
275
291
  } );
276
292
  }
@@ -285,13 +301,12 @@ class ServiceInstance {
285
301
  * @private
286
302
  */
287
303
  #postStop() {
288
- return new Promise( ( resolve, reject ) => {
304
+ return new Promise( ( resolve ) => {
289
305
  logger.log( `Instance '${ ServiceInstance.instanceID }' shut down successfully.`, logger.logSeverity.NOTICE );
290
-
291
306
  resolve();
292
307
  } );
293
308
  }
294
309
 
295
310
  }
296
311
 
297
- module.exports = ServiceInstance;
312
+ module.exports = ServiceInstance;
@@ -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-2023 Boris Kostadinov <kostadinov.boris@gmail.com>
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/>.
@@ -74,7 +74,7 @@ class ServiceProvider extends ServiceConsumer {
74
74
  let serviceDefinitions = this.serviceConfig.services;
75
75
  return this.registerServices( serviceDefinitions );
76
76
  } ).then( () => {
77
- messageDispatcher.addMessageObserverRequestsIn( this.#serviceExecutor );
77
+ messageDispatcher.instance.addMessageObserverRequestsIn( this.#serviceExecutor );
78
78
  resolve();
79
79
  } ).catch( ( error ) => {
80
80
  reject( exceptions.raise( error ) );
@@ -203,10 +203,10 @@ class ServiceProvider extends ServiceConsumer {
203
203
  // If this happens, a corresponding log entry will be created but the loading process will continue. Therefore, the following
204
204
  // promise will always resolve (unless a programming error occurs in it of course).
205
205
  let registrationPromise = ( serviceDefinition, defaultServiceHandler ) => {
206
- return new Promise( ( resolve, reject ) => {
206
+ return new Promise( ( resolve ) => {
207
207
  this.registerService( serviceDefinition, defaultServiceHandler ).then( () => {
208
208
  resolve( true );
209
- } ).catch( ( error ) => {
209
+ } ).catch( () => {
210
210
  resolve( false );
211
211
  } );
212
212
  } );
@@ -244,4 +244,4 @@ class ServiceProvider extends ServiceConsumer {
244
244
 
245
245
  }
246
246
 
247
- module.exports = ServiceProvider;
247
+ module.exports = ServiceProvider;