@ti-engine/core 1.1.5 → 1.1.9

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 CHANGED
@@ -1,5 +1,44 @@
1
1
  # ti-engine changelog
2
2
 
3
+ This document will contain the list of changes made to the framework. The format is based on the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) specification.
4
+
5
+ ## Version 1.1.9
6
+ * feat(redis integration)!: change the way the `redis` client is initialized. Instead of happening automatically on class instantiation, it is now initialized on demand using the `initialize` method.
7
+ * feat(cache): change the way the main cache instance is initialized in compliance with the new redis integration
8
+ * feat(message memory cache)!: change the way the message memory cache is initialized in compliance with the new redis integration. The `initialize` method needs to be called explicitly to initialize the cache instance before it can be used.
9
+ * fix(message dispatcher): fix the way the `messageExchange` is initialized in the `MessageDispatcher` class (was not returning a promise)
10
+
11
+ ## Version 1.1.8
12
+ * feat(auditing): change export of the singleton class in an `instance` variable for consistency and clarity
13
+ * feat(message dispatcher): change export of the singleton class in an `instance` variable for consistency and clarity
14
+ * feat(cache): change export of the singleton class in an `instance` variable for consistency and clarity
15
+ * feat(service executor): implement service registration retry policy
16
+
17
+ ## Version 1.1.7
18
+ * feat(start instance): add support for the detection of `SIGBREAK` events and graceful shutdown on Windows
19
+ * feat(service instance)!: prevent the initialization of multiple `ServiceInstance` within the same process
20
+ * feat(message tracer)!: covert to singleton instance and add initialization method
21
+ * feat(message dispatcher): initialize the message tracer on start before message exchange is enabled
22
+ * fix(message tracer): optimize `recordTraceEntry` to not call set JSON command on every request
23
+ * fix(gcloud integration): fix the bool conversion of the `TI_GCLOUD_ENABLED` ENV variable
24
+ * fix(redis integration): update links to official commands documentation
25
+
26
+ ## Version 1.1.6
27
+ * feat(start instance): add fail-fast mode as default behavior on promise unhandled rejections
28
+ * feat(start instance): implement functionality to derive safe default service domain name when none is provided in the configuration
29
+ * feat(config): add support for new ENV variable `TI_FAIL_FAST_ON_UNHANDLED_OFF` that controls the fail-fast mode
30
+ * feat(service instance): ensure instance ID naming standard is followed when creating new instances and no ID is provided in the configuration
31
+ * feat(redis integration): marked command `hmset` as deprecated (according to official documentation)
32
+ * feat(redis integration)!: change redis client default behavior: set to automatically resend all pending commands on connection recovery with no limit on the retry attempts. This behavior cannot be changed. Arguments `autoRetryUnfulfilled` and `maxRetries` have been removed from the constructor
33
+ * feat(redis integration)!: add options to configure redis connection retry policy. This can be controlled with two new arguments in the constructor `retryMaxIntervalMs` and `retryMaxAttempts`.
34
+ * feat(redis integration)!: improve and extend the behavior of pub/sub implementation functionality. The `subscribeCommand` method can now only subscribe once to the same channel. The client instance will keep a map of all channels it has subscribed to. To unsubscribe, use the `unsubscribeCommand` method
35
+ * feat(redis integration): add a `shutDown` method to gracefully close a redis connection. This is now used on instance shut down sequence as well as in the default message sender and receiver implementations
36
+ * fix(start instance): fix the logging of reason for unhandled rejections and multiple resolves
37
+ * fix(service instance): use the static service domain name to initialize the health check instead of the ENV variable
38
+ * fix(redis integration): add detection of ReJSON2 in the verification of JSON support in the redis server
39
+ * build(npm): update npm dependencies to their latest versions
40
+ * docs: add section about ENV variables to the `README.md` file
41
+
3
42
  ## Version 1.1.5
4
43
 
5
44
  * fix(service instance): add overrides of the `reportHealthy` method in `ServiceConsumer` and `ServiceProvider` classes
package/README.md CHANGED
@@ -12,12 +12,12 @@ The framework is created based on a decade of professional experience with the u
12
12
 
13
13
  This is what you gain by using **ti-engine** in your project:
14
14
 
15
- * Simplicity - begin productive work within minutes and get to codding you business logic
16
- * Flexibility - go as complex as you need to in your implementation
17
- * Reliability - message exchange between the services is constantly tracked across the entire ecosystem
18
- * Security - messages are encrypted in transit and cannot be modified by external agents
19
- * Scalability - serve mullions of requests by multiplying stateless service instances (hardware limitations still apply)
20
- * Containerization - go with containers from the very start as the framework is designed to work in such an environment
15
+ * Simplicity: begin productive work within minutes and get to codding you business logic
16
+ * Flexibility: go as complex as you need to in your implementation
17
+ * Reliability: message exchange between the services is constantly tracked across the entire ecosystem
18
+ * Security: messages are encrypted in transit and cannot be modified by external agents
19
+ * Scalability: serve mullions of requests by multiplying stateless service instances (hardware limitations still apply)
20
+ * Containerization: go with containers from the very start as the framework is designed to work in such an environment
21
21
 
22
22
  These are just some benefits **ti-engine** offers. Get to know it better to find out more ways in which it can help you improve productivity.
23
23
 
@@ -25,12 +25,12 @@ These are just some benefits **ti-engine** offers. Get to know it better to find
25
25
 
26
26
  Being a messaging system, the **ti-engine** relies on a message broker for the actual exchange of messages between microservice instances. The default implementation of the framework uses [Redis](https://redis.io/) cache, however, you could create your own implementation using something like [Rabbit MQ](https://www.rabbitmq.com/). See the [Advanced topics](#advanced-topics) section of this documentation for guides on how to do this. For now let's focus on the default setup.
27
27
 
28
- In order to run the basic ti-engine framework you will need a couple of things:
28
+ In order to run the basic ti-engine framework, you will need a couple of things:
29
29
 
30
30
  * A local [node.js installation](https://nodejs.org/en/download/) with a minimum version of **14.17.0**
31
31
  * A local or remote [Redis cache installation](https://redis.io/download) with a minimum version of **5.0.14**
32
32
 
33
- If you are working under Windows 10+ OS and you need to install Redis, take a look at this [guide](https://redis.com/blog/redis-on-windows-10/). You could also use [Redis Cloud](https://app.redislabs.com/) for development purposes as it offers free basic account. You can configure your connection to remote Redis server using the following ENV variables:
33
+ If you are working under Windows 10+ OS and you need to install Redis, take a look at this [guide](https://redis.com/blog/redis-on-windows-10/). You could also use [Redis Cloud](https://app.redislabs.com/) for development purposes as it offers free basic account. You can configure your connection to a remote Redis server using the following ENV variables:
34
34
 
35
35
  * `TI_MEMORY_CACHE_AUTH_KEY` can be used to provide the Redis password if there is any at all.
36
36
  * `TI_MEMORY_CACHE_REDIS_DB` can be used to specify the Redis DB you want to use. Make sure to set the correct number as for example Redis Cloud only uses DB `0`.
@@ -75,7 +75,7 @@ Now let's analyse that output. For the sake of completeness, the `[timestamp]` a
75
75
  At the start of the output log you can see a NOTICE that tells you a couple of important things:
76
76
 
77
77
  * The instance name - in this case `tester-service`. In the terminology of the framework, this is also known as a _service domain name_.
78
- * The _instance identificator_. It is an uuid string with a `ti-` prefix, that is generated by the framework at process start. It can and will be used to trace the messages during their movement through the microservice ecosystem. But more on that later.
78
+ * The _instance identifier_. It is an uuid string with a `ti-` prefix, that is generated by the framework at process start. It can and will be used to trace the messages during their movement through the microservice ecosystem. But more on that later.
79
79
 
80
80
  Following that come a couple of INFO lines that inform you about the microservice interface state. The framework starts with the process of registration of _business services_ within the service domain of the microservice `tester-service` and successfully adds 2 such services. The necessary information for this is read from a JSON config file included in the package. We'll get into more details on what this all means in the section [Creating a microservice](#creating-a-microservice).
81
81
 
@@ -158,7 +158,7 @@ This tier comprises the actual implementation of your application. Its structure
158
158
  * It needs to take care of any type of stateful behavior like user sessions or transactions
159
159
  * It needs to act as the primary interface between users and your application thus handling access management and user interactions
160
160
 
161
- Depending on the type of software you are building, tier 3 can be an API Gateway, a Web application, backend for a Mobile application, or anything like that.
161
+ Depending on the type of software you are building, tier 3 can be an API Gateway, a Web application, backend for a Mobile application or anything like that.
162
162
 
163
163
  ## Creating a microservice
164
164
 
@@ -338,6 +338,30 @@ And with this step we are done. The new microservice is now operational. You can
338
338
 
339
339
  ## Using the framework
340
340
 
341
+ ### Environment variables
342
+
343
+ These are the system-level ENV variables that can be used to configure the framework. Use a .env file or corresponding containerization features to set the variables you need. Not all of these are mandatory or even required for every microservice you create. Consult the documentation for more details.
344
+
345
+ TI_AUDITING_LOG_MIN_LEVEL
346
+ : Type: Optional
347
+ : Specifies the minimum log level that should be sent to the log output stream. The framework will filter out all log entries with level below this value. The default value is `200` (corresponding to INFO).
348
+
349
+ TI_FAIL_FAST_ON_UNHANDLED_OFF
350
+ : Type: Optional
351
+ : Specifies whether the framework fail-fast on unhandled promise behavior should be turned off. Set this to `true` to disable the default behavior. The default value is `false`.
352
+
353
+ TI_INSTANCE_CLASS
354
+ : Type: Required
355
+ : Specifies the relative path to the implementation of the `ServiceInstance` framework class—in this case a `ServiceProvider`. As stated above, the path is relative to the working directory of the `node` process. This variable is mandatory for every microservice you create with the **ti-engine**. If it is not provided the microservice won't be able to start at all, and you will get an exception.
356
+
357
+ TI_INSTANCE_CONFIG
358
+ : Type: Optional
359
+ : Specifies the relative path to the configuration data for the microservice. Technically, you can omit this variable and the microservice will still start successfully with an empty configuration. There are very few cases, however, where this would be applicable.
360
+
361
+ TI_INSTANCE_NAME
362
+ : Type: Required
363
+ : Specifies the _service domain_ name provided for the microservice. It has to be _unique_ in the context of the microservice ecosystem. If not provided, the framework will attempt to extract this information from the name of the implementation file. That is not a recommended approach though as it might cause hard to identify errors later.
364
+
341
365
  ### Framework settings
342
366
 
343
367
  Here you can find all settings used by **ti-engine** together with information on what they do. They are defined inside the `config` module and the full list can be accessed through the public `setting` enum. To get the current value of a setting, you can use the public method `getSetting` from the same module. Some settings can be overridden by providing ENV variables as specified below at node application start up.
@@ -398,7 +422,17 @@ MEMORY_CACHE_REDIS_HOST
398
422
  MEMORY_CACHE_REDIS_PORT
399
423
  : JSON path `memoryCache.redisPort`, type `number`, default `6379`
400
424
  : ENV variable `TI_MEMORY_CACHE_REDIS_PORT`
401
- : This setting holds the Redis server port.
425
+ : This setting holds the Redis server port. By default, Redis uses `6379` however many implementations might use a custom port that needs to be specified in the connection settings.
426
+
427
+ MEMORY_CACHE_RETRY_MAX_ATTEMPTS
428
+ : JSON path `memoryCache.retryMaxAttempts`, type `number`, default `undefined`
429
+ : ENV variable `TI_MEMORY_CACHE_RETRY_MAX_ATTEMPTS`
430
+ : This setting holds the maximum number of attempts to (re)connect to the Redis server. By default there is no limit.
431
+
432
+ MEMORY_CACHE_RETRY_MAX_INTERVAL
433
+ : JSON path `memoryCache.retryMaxInterval`, type `number`, default `1000`
434
+ : ENV variable `TI_MEMORY_CACHE_RETRY_MAX_INTERVAL`
435
+ : This setting holds the maximum interval in milliseconds between retries when the Redis server is not available.
402
436
 
403
437
  MEMORY_CACHE_USER
404
438
  : JSON path `memoryCache.user`, type `string`, default `default`
@@ -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/>.
@@ -8,21 +8,35 @@
8
8
 
9
9
  "use strict";
10
10
 
11
- const _ = require( "lodash" );
12
11
  const path = require( "path" );
13
12
 
14
- // load any ENV variables defined in a .env file - before including any framework files:
13
+ // Load any ENV variables defined in a .env file - before including any framework files:
15
14
  require( "dotenv" ).config( { path: path.join( process.cwd(), ".env" ) } );
16
15
 
17
16
  const tools = require( "#tools" );
18
17
  const logger = require( "#logger" );
19
18
 
20
- // configure the current instance variables before requiring any platform modules and store the necessary ones in memory cache:
19
+ // Configure the current instance variables before requiring any platform modules and store the necessary ones in memory cache:
21
20
  process.env.TI_INSTANCE_ID = "ti-" + tools.getUUID();
22
21
  process.env.TI_INSTANCE_CLASS = process.env.TI_INSTANCE_CLASS || "";
23
- process.env.TI_INSTANCE_NAME = process.env.TI_INSTANCE_NAME || _.last( _.split( process.env.TI_INSTANCE_CLASS, "/" ) );
24
22
 
25
- // configure the process error handlers:
23
+ // Derive a safe default service domain name if not explicitly provided:
24
+ // - Normalize path separators
25
+ // - Strip directory and file extension
26
+ const defaultNameFromClass = ( () => {
27
+ try {
28
+ const normalized = ( process.env.TI_INSTANCE_CLASS || "" ).replace( /\\/g, "/" );
29
+ const base = path.posix.basename( normalized );
30
+ const name = path.parse( base ).name;
31
+ return name || "";
32
+ } catch {
33
+ return "";
34
+ }
35
+ } )();
36
+
37
+ process.env.TI_INSTANCE_NAME = process.env.TI_INSTANCE_NAME || defaultNameFromClass;
38
+
39
+ // Configure the process error handlers:
26
40
 
27
41
  /**
28
42
  * Will be used to gracefully shut down the instance.
@@ -37,35 +51,45 @@ process.env.TI_INSTANCE_NAME = process.env.TI_INSTANCE_NAME || _.last( _.split(
37
51
  let shutDownInstance = ( exitCode ) => {
38
52
  };
39
53
 
40
- // this event will handle the process termination (Ctrl + C):
54
+ // This event will handle the process termination (Ctrl + C):
41
55
  process.on( "SIGINT", () => {
42
56
  logger.log( `SIGINT event detected in main instance process.`, logger.logSeverity.NOTICE );
43
57
  shutDownInstance( 0 );
44
58
  } );
45
59
 
46
- // this event will handle the process termination (CMD close):
60
+ // This event will handle the process termination via terminal hangup (CMD close) - not delivered on all platforms:
47
61
  process.on( "SIGHUP", () => {
48
62
  logger.log( `SIGHUP event detected in main instance process.`, logger.logSeverity.NOTICE );
49
63
  shutDownInstance( 0 );
50
64
  } );
51
65
 
52
- // this event will handle the process termination:
66
+ // This event will handle the process termination:
53
67
  process.on( "SIGTERM", () => {
54
68
  logger.log( `SIGTERM event detected in main instance process.`, logger.logSeverity.NOTICE );
55
69
  shutDownInstance( 0 );
56
70
  } );
57
71
 
58
- process.on( "unhandledRejection", ( reason, promise ) => {
72
+ // Handle Windows console break (Ctrl + Break):
73
+ process.on( "SIGBREAK", () => {
74
+ logger.log( `SIGBREAK event detected in main instance process.`, logger.logSeverity.NOTICE );
75
+ shutDownInstance( 0 );
76
+ } );
77
+
78
+ process.on( "unhandledRejection", ( reason ) => {
79
+ // Check if the fail-fast behavior has been forcefully disabled:
80
+ const failFastDisabled = tools.toBool( process.env.TI_FAIL_FAST_ON_UNHANDLED_OFF || "" );
59
81
  logger.log( `Unhandled promise rejection identified! Make sure this isn't a software bug.`, logger.logSeverity.WARNING, {
60
- reason: reason,
61
- promise: tools.stringifyJSON( promise )
82
+ reason: tools.errorToJSON && reason instanceof Error ? tools.errorToJSON( reason ) : reason
62
83
  } );
84
+ if ( failFastDisabled !== true ) {
85
+ setImmediate( () => process.exit( 1 ) );
86
+ }
63
87
  } );
64
88
 
65
89
  process.on( "multipleResolves", ( type, promise, reason ) => {
66
90
  logger.log( `Multiple promise resolves detected! Make sure this isn't a software bug.`, logger.logSeverity.WARNING, {
67
- reason: reason,
68
- promise: tools.stringifyJSON( promise )
91
+ type,
92
+ reason: tools.errorToJSON && reason instanceof Error ? tools.errorToJSON( reason ) : reason
69
93
  } );
70
94
  } );
71
95
 
@@ -74,7 +98,7 @@ process.on( "uncaughtException", ( error ) => {
74
98
  setImmediate( () => process.exit( 1 ) );
75
99
  } );
76
100
 
77
- // start the instance:
101
+ // Start the instance:
78
102
  try {
79
103
  logger.log( `Starting new instance of type '${ process.env.TI_INSTANCE_NAME }' with instance ID '${ process.env.TI_INSTANCE_ID }'.`, logger.logSeverity.NOTICE );
80
104
 
@@ -110,4 +134,4 @@ try {
110
134
  } catch ( error ) {
111
135
  logger.log( `Error detected in the instance startup script!`, logger.logSeverity.ALERT, error );
112
136
  setImmediate( () => process.exit( 1 ) );
113
- }
137
+ }
@@ -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/>.
@@ -20,7 +20,7 @@ const gcloud = require( "#gcloud-integration" );
20
20
  * @property {string} reporter
21
21
  * @property {string} message The actual log message.
22
22
  * @property {number} timestamp The timestamp of the log entry in UTC time.
23
- * @property {Object} data Additional JSON data to accompany the message.
23
+ * @property {Object} data Additional JSON data to go with the message.
24
24
  */
25
25
 
26
26
  /**
@@ -163,4 +163,4 @@ class Auditing {
163
163
  }
164
164
 
165
165
  const instance = new Auditing();
166
- module.exports = Object.freeze( instance );
166
+ module.exports.instance = Object.freeze( instance );
@@ -59,8 +59,12 @@ class DefaultMessageReceiver extends MessageReceiver {
59
59
  disable() {
60
60
  return new Promise( ( resolve, reject ) => {
61
61
  this.isAvailable = false;
62
- this.#memoryCache = null;
63
- resolve();
62
+ this.#memoryCache.shutDown().then( () => {
63
+ this.#memoryCache = null;
64
+ resolve();
65
+ } ).catch( ( error ) => {
66
+ reject( exceptions.raise( error ) );
67
+ } );
64
68
  } );
65
69
  }
66
70
 
@@ -84,8 +84,12 @@ class DefaultMessageSender extends MessageSender {
84
84
  disable() {
85
85
  return new Promise( ( resolve, reject ) => {
86
86
  this.isAvailable = false;
87
- this.#memoryCache = null;
88
- resolve();
87
+ this.#memoryCache.shutDown().then( () => {
88
+ this.#memoryCache = null;
89
+ resolve();
90
+ } ).catch( ( error ) => {
91
+ reject( exceptions.raise( error ) );
92
+ } );
89
93
  } );
90
94
  }
91
95
  }
@@ -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/>.
@@ -51,7 +51,11 @@ class MessageDispatcher {
51
51
  initialize( messageExchange, configureInbound, configureOutbound ) {
52
52
  return new Promise( ( resolve, reject ) => {
53
53
  this.#messageExchange = messageExchange;
54
- this.#messageExchange.enableMessaging( configureInbound, configureOutbound ).then( () => {
54
+
55
+ // Initialize the message tracer before enabling the message exchange:
56
+ messageTracer.instance.initialize().then( () => {
57
+ return this.#messageExchange.enableMessaging( configureInbound, configureOutbound );
58
+ } ).then( () => {
55
59
  resolve();
56
60
  } ).catch( ( error ) => {
57
61
  reject( exceptions.raise( error ) );
@@ -95,13 +99,13 @@ class MessageDispatcher {
95
99
  logger.log( `Retrying to send message response with chain ID: ${ message.chainID }. This is attempt ${ attempt }...`, logger.logSeverity.NOTICE );
96
100
  } );
97
101
 
98
- messageTracer.recordTraceEntry( message, messageTracer.messageType.MESSAGE_REQUEST, messageTracer.dispatchEvent.SENT, messageTracer.messageState.PENDING );
102
+ messageTracer.instance.recordTraceEntry( message, messageTracer.messageType.MESSAGE_REQUEST, messageTracer.dispatchEvent.SENT, messageTracer.messageState.PENDING );
99
103
 
100
104
  retry.execute( this.#messageExchange, this.#messageExchange.sendMessageRequest, [ message ] ).then( () => {
101
- messageTracer.recordTraceEntry( message, messageTracer.messageType.MESSAGE_REQUEST, messageTracer.dispatchEvent.DELIVERED, messageTracer.messageState.PENDING );
105
+ messageTracer.instance.recordTraceEntry( message, messageTracer.messageType.MESSAGE_REQUEST, messageTracer.dispatchEvent.DELIVERED, messageTracer.messageState.PENDING );
102
106
  resolve( message.messageID );
103
107
  } ).catch( ( error ) => {
104
- messageTracer.recordTraceEntry( message, messageTracer.messageType.MESSAGE_REQUEST, messageTracer.dispatchEvent.FAILED, messageTracer.messageState.PENDING );
108
+ messageTracer.instance.recordTraceEntry( message, messageTracer.messageType.MESSAGE_REQUEST, messageTracer.dispatchEvent.FAILED, messageTracer.messageState.PENDING );
105
109
  reject( exceptions.raise( error ) );
106
110
  } );
107
111
  } );
@@ -125,13 +129,13 @@ class MessageDispatcher {
125
129
  logger.log( `Retrying to send message response with chain ID: ${ message.chainID }. This is attempt ${ attempt }...`, logger.logSeverity.NOTICE );
126
130
  } );
127
131
 
128
- messageTracer.recordTraceEntry( message, messageTracer.messageType.MESSAGE_RESPONSE, messageTracer.dispatchEvent.SENT, messageTracer.messageState.PROCESSED );
132
+ messageTracer.instance.recordTraceEntry( message, messageTracer.messageType.MESSAGE_RESPONSE, messageTracer.dispatchEvent.SENT, messageTracer.messageState.PROCESSED );
129
133
 
130
134
  retry.execute( this.#messageExchange, this.#messageExchange.sendMessageResponse, [ message ] ).then( () => {
131
- messageTracer.recordTraceEntry( message, messageTracer.messageType.MESSAGE_RESPONSE, messageTracer.dispatchEvent.DELIVERED, messageTracer.messageState.PROCESSED );
135
+ messageTracer.instance.recordTraceEntry( message, messageTracer.messageType.MESSAGE_RESPONSE, messageTracer.dispatchEvent.DELIVERED, messageTracer.messageState.PROCESSED );
132
136
  resolve();
133
137
  } ).catch( ( error ) => {
134
- messageTracer.recordTraceEntry( message, messageTracer.messageType.MESSAGE_RESPONSE, messageTracer.dispatchEvent.FAILED, messageTracer.messageState.PROCESSED );
138
+ messageTracer.instance.recordTraceEntry( message, messageTracer.messageType.MESSAGE_RESPONSE, messageTracer.dispatchEvent.FAILED, messageTracer.messageState.PROCESSED );
135
139
  reject( exceptions.raise( error ) );
136
140
  } );
137
141
  } );
@@ -162,4 +166,4 @@ class MessageDispatcher {
162
166
  }
163
167
 
164
168
  const instance = new MessageDispatcher();
165
- module.exports = Object.freeze( instance );
169
+ module.exports.instance = Object.freeze( instance );
@@ -95,7 +95,9 @@ class MessageExchange extends MessageObserver {
95
95
  * @returns {string}
96
96
  * @public
97
97
  */
98
- static get connectionNameRequestsOut() { return this.#connectionNameRequestsOut; }
98
+ static get connectionNameRequestsOut() {
99
+ return this.#connectionNameRequestsOut;
100
+ }
99
101
 
100
102
  /**
101
103
  * Used to set the connection name for the outgoing message requests.
@@ -104,7 +106,9 @@ class MessageExchange extends MessageObserver {
104
106
  * @param {string} value
105
107
  * @public
106
108
  */
107
- static set connectionNameRequestsOut( value ) { this.#connectionNameRequestsOut = value; }
109
+ static set connectionNameRequestsOut( value ) {
110
+ this.#connectionNameRequestsOut = value;
111
+ }
108
112
 
109
113
  /**
110
114
  * Property returning the configured connection name for the incoming message requests.
@@ -113,7 +117,9 @@ class MessageExchange extends MessageObserver {
113
117
  * @returns {string}
114
118
  * @public
115
119
  */
116
- static get connectionNameRequestsIn() { return this.#connectionNameRequestsIn; }
120
+ static get connectionNameRequestsIn() {
121
+ return this.#connectionNameRequestsIn;
122
+ }
117
123
 
118
124
  /**
119
125
  * Used to set the connection name for the incoming message requests.
@@ -122,7 +128,9 @@ class MessageExchange extends MessageObserver {
122
128
  * @param {string} value
123
129
  * @public
124
130
  */
125
- static set connectionNameRequestsIn( value ) { this.#connectionNameRequestsIn = value; }
131
+ static set connectionNameRequestsIn( value ) {
132
+ this.#connectionNameRequestsIn = value;
133
+ }
126
134
 
127
135
  /**
128
136
  * Property returning the configured connection name for the outgoing message responses.
@@ -131,7 +139,9 @@ class MessageExchange extends MessageObserver {
131
139
  * @returns {string}
132
140
  * @public
133
141
  */
134
- static get connectionNameResponsesOut() { return this.#connectionNameResponsesOut; }
142
+ static get connectionNameResponsesOut() {
143
+ return this.#connectionNameResponsesOut;
144
+ }
135
145
 
136
146
  /**
137
147
  * Used to set the connection name for the outgoing message responses.
@@ -140,7 +150,9 @@ class MessageExchange extends MessageObserver {
140
150
  * @param {string} value
141
151
  * @public
142
152
  */
143
- static set connectionNameResponsesOut( value ) { this.#connectionNameResponsesOut = value; }
153
+ static set connectionNameResponsesOut( value ) {
154
+ this.#connectionNameResponsesOut = value;
155
+ }
144
156
 
145
157
  /**
146
158
  * Property returning the configured connection name for the incoming message responses.
@@ -149,7 +161,9 @@ class MessageExchange extends MessageObserver {
149
161
  * @returns {string}
150
162
  * @public
151
163
  */
152
- static get connectionNameResponsesIn() { return this.#connectionNameResponsesIn; }
164
+ static get connectionNameResponsesIn() {
165
+ return this.#connectionNameResponsesIn;
166
+ }
153
167
 
154
168
  /**
155
169
  * Used to set the connection name for the incoming message responses.
@@ -158,7 +172,9 @@ class MessageExchange extends MessageObserver {
158
172
  * @param {string} value
159
173
  * @public
160
174
  */
161
- static set connectionNameResponsesIn( value ) { this.#connectionNameResponsesIn = value; }
175
+ static set connectionNameResponsesIn( value ) {
176
+ this.#connectionNameResponsesIn = value;
177
+ }
162
178
 
163
179
  /**
164
180
  * Property returning the identifier of the pending messages queue.
@@ -167,7 +183,9 @@ class MessageExchange extends MessageObserver {
167
183
  * @returns {string}
168
184
  * @public
169
185
  */
170
- static get pendingQueue() { return "pending:"; }
186
+ static get pendingQueue() {
187
+ return "pending:";
188
+ }
171
189
 
172
190
  /**
173
191
  * Property returning the identifier of the processed messages queue.
@@ -176,7 +194,9 @@ class MessageExchange extends MessageObserver {
176
194
  * @returns {string}
177
195
  * @public
178
196
  */
179
- static get processedQueue() { return "processed:"; }
197
+ static get processedQueue() {
198
+ return "processed:";
199
+ }
180
200
 
181
201
  /**
182
202
  * Property returning the configured service instance ID.
@@ -185,7 +205,9 @@ class MessageExchange extends MessageObserver {
185
205
  * @returns {string}
186
206
  * @public
187
207
  */
188
- get instanceID() { return this.#instanceID; }
208
+ get instanceID() {
209
+ return this.#instanceID;
210
+ }
189
211
 
190
212
  /**
191
213
  * Property returning the configured service domain name.
@@ -194,7 +216,9 @@ class MessageExchange extends MessageObserver {
194
216
  * @returns {string}
195
217
  * @public
196
218
  */
197
- get serviceDomainName() { return this.#serviceDomainName; }
219
+ get serviceDomainName() {
220
+ return this.#serviceDomainName;
221
+ }
198
222
 
199
223
  /**
200
224
  * Returns the currently configured {@link MessageSender} for outbound message requests.
@@ -203,7 +227,9 @@ class MessageExchange extends MessageObserver {
203
227
  * @returns {MessageSender}
204
228
  * @public
205
229
  */
206
- get messageRequestsOut() { return this.#messageRequestsOut; }
230
+ get messageRequestsOut() {
231
+ return this.#messageRequestsOut;
232
+ }
207
233
 
208
234
  /**
209
235
  * Returns the currently configured {@link MessageSender} for outbound message responses.
@@ -212,7 +238,9 @@ class MessageExchange extends MessageObserver {
212
238
  * @returns {MessageSender}
213
239
  * @public
214
240
  */
215
- get messageResponsesOut() { return this.#messageResponsesOut; }
241
+ get messageResponsesOut() {
242
+ return this.#messageResponsesOut;
243
+ }
216
244
 
217
245
  /**
218
246
  * Returns the currently configured {@link MessageReceiver} for inbound message requests.
@@ -221,7 +249,9 @@ class MessageExchange extends MessageObserver {
221
249
  * @returns {MessageReceiver}
222
250
  * @public
223
251
  */
224
- get messageRequestsIn() { return this.#messageRequestsIn; }
252
+ get messageRequestsIn() {
253
+ return this.#messageRequestsIn;
254
+ }
225
255
 
226
256
  /**
227
257
  * Returns the currently configured {@link MessageReceiver} for inbound message responses.
@@ -230,7 +260,9 @@ class MessageExchange extends MessageObserver {
230
260
  * @returns {MessageReceiver}
231
261
  * @public
232
262
  */
233
- get messageResponsesIn() { return this.#messageResponsesIn; }
263
+ get messageResponsesIn() {
264
+ return this.#messageResponsesIn;
265
+ }
234
266
 
235
267
  /**
236
268
  * Returns a flag indicating if the message exchange is configured for outbound communication.
@@ -239,7 +271,9 @@ class MessageExchange extends MessageObserver {
239
271
  * @returns {boolean}
240
272
  * @public
241
273
  */
242
- get configuredOutbound() { return this.#configuredOutbound; }
274
+ get configuredOutbound() {
275
+ return this.#configuredOutbound;
276
+ }
243
277
 
244
278
  /**
245
279
  * Returns a flag indicating if the message exchange is configured for inbound communication.
@@ -248,7 +282,9 @@ class MessageExchange extends MessageObserver {
248
282
  * @returns {boolean}
249
283
  * @public
250
284
  */
251
- get configuredInbound() { return this.#configuredInbound; }
285
+ get configuredInbound() {
286
+ return this.#configuredInbound;
287
+ }
252
288
 
253
289
  /**
254
290
  * Should be used to enable all communication channels for messaging.
@@ -371,9 +407,9 @@ class MessageExchange extends MessageObserver {
371
407
  message.destination.instanceID = this.#instanceID;
372
408
 
373
409
  if ( MessageExchange.connectionNameRequestsIn === identifier ) {
374
- messageTracer.recordTraceEntry( message, messageTracer.messageType.MESSAGE_REQUEST, messageTracer.dispatchEvent.RECEIVED, messageTracer.messageState.PENDING );
410
+ messageTracer.instance.recordTraceEntry( message, messageTracer.messageType.MESSAGE_REQUEST, messageTracer.dispatchEvent.RECEIVED, messageTracer.messageState.PENDING );
375
411
  } else if ( MessageExchange.connectionNameResponsesIn === identifier ) {
376
- messageTracer.recordTraceEntry( message, messageTracer.messageType.MESSAGE_RESPONSE, messageTracer.dispatchEvent.RECEIVED, messageTracer.messageState.PROCESSED );
412
+ messageTracer.instance.recordTraceEntry( message, messageTracer.messageType.MESSAGE_RESPONSE, messageTracer.dispatchEvent.RECEIVED, messageTracer.messageState.PROCESSED );
377
413
  }
378
414
  }
379
415
 
@@ -419,4 +455,4 @@ class MessageExchange extends MessageObserver {
419
455
 
420
456
  }
421
457
 
422
- module.exports = MessageExchange;
458
+ module.exports = MessageExchange;
@@ -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,17 +24,44 @@ class MessageMemoryCache {
24
24
  /**
25
25
  * @constructor
26
26
  * @param {string} identifier The connection identifier for the Redis connection.
27
+ * @returns {MessageMemoryCache}
27
28
  */
28
29
  constructor( identifier ) {
30
+ this.#redisClient = redis.createRedisClient( identifier );
31
+ }
32
+
33
+ /* Public interface */
34
+
35
+ /**
36
+ * Used to initialize the cache service.
37
+ *
38
+ * @method
39
+ * @returns {Promise}
40
+ * @public
41
+ */
42
+ initialize() {
29
43
  let host = config.getSetting( config.setting.MEMORY_CACHE_REDIS_HOST );
30
44
  let port = config.getSetting( config.setting.MEMORY_CACHE_REDIS_PORT );
31
45
  let db = config.getSetting( config.setting.MEMORY_CACHE_REDIS_DB );
32
46
  let authKey = config.getSetting( config.setting.MEMORY_CACHE_AUTH_KEY );
33
47
  let user = config.getSetting( config.setting.MEMORY_CACHE_USER );
34
- this.#redisClient = redis.createRedisClient( identifier, host, port, authKey, user, db );
48
+ let retryMaxAttempts = config.getSetting( config.setting.MEMORY_CACHE_RETRY_MAX_ATTEMPTS );
49
+ let retryMaxInterval = config.getSetting( config.setting.MEMORY_CACHE_RETRY_MAX_INTERVAL );
50
+
51
+ return this.#redisClient.initialize( host, port, authKey, user, db, retryMaxInterval, retryMaxAttempts );
35
52
  }
36
53
 
37
- /* Public interface */
54
+ /**
55
+ * Used to gracefully shut down the cache service.
56
+ *
57
+ * @method
58
+ * @param {number} [timeoutMs]
59
+ * @returns {Promise}
60
+ * @public
61
+ */
62
+ shutDown( timeoutMs ) {
63
+ return this.#redisClient.shutDown( timeoutMs );
64
+ }
38
65
 
39
66
  /**
40
67
  * Used to register a new {@link ConnectionObserver} for events related to the Redis connection state.
@@ -151,4 +178,4 @@ class MessageMemoryCache {
151
178
  */
152
179
  module.exports.create = ( identifier ) => {
153
180
  return Object.freeze( new MessageMemoryCache( identifier ) );
154
- };
181
+ };