@ti-engine/core 1.2.4 → 1.3.3
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 +44 -0
- package/README.md +7 -9
- package/bin/localization/labels.json +94 -60
- package/bin/start-instance.js +1 -1
- package/components/auditing.js +14 -13
- package/components/connection-observer.js +19 -4
- package/components/exchange/message-exchange.js +16 -1
- package/components/exchange/message-handler.js +37 -7
- package/components/exchange/message-observer.js +51 -4
- package/components/exchange/message-receiver.js +1 -1
- package/components/service-caller.js +248 -147
- package/components/service-executor.js +19 -4
- package/components/service-instance.js +8 -3
- package/integrations/redis-integration.js +86 -23
- package/package.json +1 -1
- package/utils/cache.js +24 -2
- package/utils/config.js +5 -5
- package/utils/exceptions.js +41 -35
- package/utils/localization.js +249 -12
- package/utils/logger.js +3 -3
- package/utils/tools.js +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,50 @@
|
|
|
2
2
|
|
|
3
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
4
|
|
|
5
|
+
## Version 1.3.3
|
|
6
|
+
* feat(exceptions): add new parameter `includeData` to `Exception.asJSON` method which allows the exclusion of the data parameter from the returned JSON
|
|
7
|
+
* feat(exceptions): remove several excessive exception codes that were unlikely to be used
|
|
8
|
+
* fix(localization): add several missing exception labels
|
|
9
|
+
|
|
10
|
+
## Version 1.3.2
|
|
11
|
+
* feat(exceptions): add a set of new exception codes for the needs of any wrapping `web-server` standard communication
|
|
12
|
+
* feat(exception)!: change the default prefix path for exception labels to `system.exceptions.`
|
|
13
|
+
* feat(localization): add an Enum list of all language codes based on ISO 639-1 standard
|
|
14
|
+
* feat(localization): add an option to the `getLabel` method to provide the language code to use for localization. If not provided, the system will use the language code from the `localization.language` setting
|
|
15
|
+
* feat(localization)!: change the default structure of system labels from `labels.general.[...]` to `system.[...]`
|
|
16
|
+
* refactor(localization): add typedefs and JSDoc structure for the objects used in localization
|
|
17
|
+
* refactor(exceptions): refactor and fix various JSDoc descriptions
|
|
18
|
+
* refactor(auditing): change the type name of `LogEntry` to `TiLogEntry` and fix some JSDoc descriptions
|
|
19
|
+
* fix(exceptions): fix an issue which caused the exception to disregard the `description` value sent in the constructor and use the default one instead
|
|
20
|
+
* fix(config): fix an issue with the `TI_LOCALIZATION_LABELS_PATH` variable not respecting that the underlying setting expected an array. Now it is expected to be a single path and it will be wrapped into an array automatically
|
|
21
|
+
* fix(auditing): fix a potential problem with setting the log entry reporter from an ENV variable; instead, the system will now use the `ServiceInstance.instanceID` property
|
|
22
|
+
|
|
23
|
+
## Version 1.3.1
|
|
24
|
+
* feat(service caller): refactor the entire service call execution flow for clarity and better performance
|
|
25
|
+
* feat(service caller): create a new private class `ServiceCallProcessor` to handle individual service calls in a contained scope
|
|
26
|
+
* feat(message observer): add new property `priority` to the message observer class. It is used to determine the order in which the observers are notified about the messages
|
|
27
|
+
* feat(message observer)!: change method `onMessage` to now return the message it received. This allows for the message to be modified before it is passed to the next observer based on `priority`
|
|
28
|
+
* feat(message handler)!: change method `onMessage` to `notifyMessageObservers` for clarity. It now implements the `MessageObserver` functionality for prioritization and message modification
|
|
29
|
+
* fix(service caller): fix an issue which did not allow for a service call to be marked as completed thus being entered into the trace log as still pending
|
|
30
|
+
* fix(redis integration): remove hardcoded `#retryMaxAttempts` value in `#setupClient` method
|
|
31
|
+
* fix(redis integration): fix an issue which was setting the client status to `DISCONNECTED` during a normal shut down procedure
|
|
32
|
+
|
|
33
|
+
## Version 1.3.0
|
|
34
|
+
* feat(redis integration)!: change `reconnectOnError` behavior to also resubmit the failed command in case the error was of type `READONLY`
|
|
35
|
+
* feat(redis integration): improve the reliability and usage of the event notification mechanism for connection observers
|
|
36
|
+
* feat(redis integration): implement listener to the `end` event on Redis connection to capture when connection can no longer be recovered
|
|
37
|
+
* feat(redis integration): notify connection observers `onConnectionLost` event
|
|
38
|
+
* feat(redis integration): add redis client platform-specific status. It is used internally by the platform and can also be accessed via `redisClient.clientStatus` property
|
|
39
|
+
* feat(connection observer): add new event handler `onConnectionLost` that will be invoked when the observed connection is irrevocably lost
|
|
40
|
+
* feat(cache)!: implement `onConnectionLost` handler that will cause the service instance to immediately stop since it cannot work without the cache
|
|
41
|
+
* fix(service caller): fix multiple promise reject condition when a service call timed out and the service handler still attempted to complete with subsequent error
|
|
42
|
+
* fix(service instance): limit health check reporting to one attempt at a time to avoid unnecessary cache requests and log spam
|
|
43
|
+
* fix(redis integration): fix a multiple promise resolve condition on Redis `reconnect` event
|
|
44
|
+
* fix(redis integration): fix broken event propagation on `disrupted` events to some connection observers
|
|
45
|
+
|
|
46
|
+
## Version 1.2.5
|
|
47
|
+
* feat(cache): extend method `expireValue` to work with has set fields as well
|
|
48
|
+
|
|
5
49
|
## Version 1.2.4
|
|
6
50
|
* feat(cache): expose the cache module as export in `package.json`
|
|
7
51
|
* feat(cache): add method `hashDeleteField` to remove a hash-set field. This implements the `hdel` Redis command
|
package/README.md
CHANGED
|
@@ -397,7 +397,7 @@ GCLOUD_PROJECT_ID (Alpha)
|
|
|
397
397
|
LOCALIZATION_LABELS_PATH
|
|
398
398
|
: JSON path `localization.labelsPath`, type `Array<string>`, default `[]`
|
|
399
399
|
: ENV variable `TI_LOCALIZATION_LABELS_PATH`
|
|
400
|
-
: This setting holds a list of paths to custom `.json` files containing additional localization information. By default, the framework also provides such a file with english texts that can be customized further. All additional JSONs in these files have to follow the rules and structure of the `localization` module.
|
|
400
|
+
: This setting holds a list of paths to custom `.json` files containing additional localization information. By default, the framework also provides such a file with english texts that can be customized further. All additional JSONs in these files have to follow the rules and structure of the `localization` module. The ENV variable currently supports providing only a single custom path.
|
|
401
401
|
|
|
402
402
|
LOCALIZATION_LANGUAGE
|
|
403
403
|
: JSON path `localization.language`, type `string`, default `en`
|
|
@@ -501,20 +501,18 @@ OPERATION_MODE
|
|
|
501
501
|
|
|
502
502
|
The **ti-engine** framework provides a localization mechanism that allows you to translate labels into localized text. The framework comes with a default set of labels that can be found in the `localization` module. You can add your own custom labels to this set by providing one or more JSON files with the same structure as the default one (see below). The path to these files should be specified in the `LOCALIZATION_LABELS_PATH` setting. On startup, the framework will load all the JSON files and merge them into a single repository.
|
|
503
503
|
|
|
504
|
-
The following is an example of a custom localization file
|
|
504
|
+
The following is an example of a custom localization file. The names in brackets can be replaced with your own values. The depth of the three is unlimited and can be used to create a hierarchy of labels.
|
|
505
505
|
|
|
506
506
|
```json
|
|
507
507
|
{
|
|
508
|
-
"
|
|
509
|
-
"[category]": {
|
|
510
|
-
"[
|
|
511
|
-
"[
|
|
512
|
-
"en": "[localized label text]"
|
|
513
|
-
}
|
|
508
|
+
"[category]": {
|
|
509
|
+
"[sub-category]": {
|
|
510
|
+
"[label]": {
|
|
511
|
+
"[language code]": "[localized label text]"
|
|
514
512
|
}
|
|
515
513
|
}
|
|
516
514
|
}
|
|
517
515
|
}
|
|
518
516
|
```
|
|
519
517
|
|
|
520
|
-
The individual languages are specified with a two-letter code. The default language is `en` (English). If you want to use a different language, you can set the `LOCALIZATION_LANGUAGE` setting to the desired language code.
|
|
518
|
+
The individual languages are specified with a two-letter code according to ISO 639-1. The default language is `en` (English). If you want to use a different language, you can set the `LOCALIZATION_LANGUAGE` setting to the desired language code.
|
|
@@ -1,64 +1,98 @@
|
|
|
1
1
|
{
|
|
2
|
-
"
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
"
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
"
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
"
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
"
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
"
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
"
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
"
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
"
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
"
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
"
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
"
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
"
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
"
|
|
60
|
-
|
|
61
|
-
|
|
2
|
+
"system": {
|
|
3
|
+
"exceptions": {
|
|
4
|
+
"0": {
|
|
5
|
+
"en": "Unidentified error encountered or unrecognized exception code provided."
|
|
6
|
+
},
|
|
7
|
+
"1000": {
|
|
8
|
+
"en": "Error thrown by internal JS source."
|
|
9
|
+
},
|
|
10
|
+
"1001": {
|
|
11
|
+
"en": "Attempt to construct an abstract class detected."
|
|
12
|
+
},
|
|
13
|
+
"1002": {
|
|
14
|
+
"en": "Attempt to call an abstract method detected."
|
|
15
|
+
},
|
|
16
|
+
"1003": {
|
|
17
|
+
"en": "Invalid or no service domain name provided at microservice startup."
|
|
18
|
+
},
|
|
19
|
+
"1004": {
|
|
20
|
+
"en": "The system cache required for proper engine operation is unavailable."
|
|
21
|
+
},
|
|
22
|
+
"1005": {
|
|
23
|
+
"en": "The provided service handler is not a proper function."
|
|
24
|
+
},
|
|
25
|
+
"1006": {
|
|
26
|
+
"en": "The requested feature is not supported by current configuration or version."
|
|
27
|
+
},
|
|
28
|
+
"2000": {
|
|
29
|
+
"en": "Invalid authorization token provided."
|
|
30
|
+
},
|
|
31
|
+
"2001": {
|
|
32
|
+
"en": "Invalid or expired session encountered."
|
|
33
|
+
},
|
|
34
|
+
"2002": {
|
|
35
|
+
"en": "Attempt for unauthorized access detected."
|
|
36
|
+
},
|
|
37
|
+
"2003": {
|
|
38
|
+
"en": "The system detected tampering with the message received via message exchange."
|
|
39
|
+
},
|
|
40
|
+
"3000": {
|
|
41
|
+
"en": "General error during cross-application communication."
|
|
42
|
+
},
|
|
43
|
+
"3001": {
|
|
44
|
+
"en": "The message sender instance is currently unavailable."
|
|
45
|
+
},
|
|
46
|
+
"3002": {
|
|
47
|
+
"en": "The execution of a service could not complete within the allowed timeout."
|
|
48
|
+
},
|
|
49
|
+
"3003": {
|
|
50
|
+
"en": "The specified service is not found in the service registry."
|
|
51
|
+
},
|
|
52
|
+
"3004": {
|
|
53
|
+
"en": "The specified service is not found in the service definition interface."
|
|
54
|
+
},
|
|
55
|
+
"3005": {
|
|
56
|
+
"en": "No handler found in the interface for the specified service or service version."
|
|
57
|
+
},
|
|
58
|
+
"3006": {
|
|
59
|
+
"en": "The message receiver instance is currently unavailable."
|
|
60
|
+
},
|
|
61
|
+
"3007": {
|
|
62
|
+
"en": "The message exchange is irrevocably broken and cannot be used any longer."
|
|
63
|
+
},
|
|
64
|
+
"3010": {
|
|
65
|
+
"en": "Connection retry attempts exceeded the configured limit."
|
|
66
|
+
},
|
|
67
|
+
"4000": {
|
|
68
|
+
"en": "The request method is not recognized or not supported."
|
|
69
|
+
},
|
|
70
|
+
"4001": {
|
|
71
|
+
"en": "The request URI is not recognized or not supported."
|
|
72
|
+
},
|
|
73
|
+
"4002": {
|
|
74
|
+
"en": "The request body is not recognized or not supported."
|
|
75
|
+
},
|
|
76
|
+
"4003": {
|
|
77
|
+
"en": "The request query is not recognized or not supported."
|
|
78
|
+
},
|
|
79
|
+
"4004": {
|
|
80
|
+
"en": "The request headers are not recognized or not supported."
|
|
81
|
+
},
|
|
82
|
+
"4005": {
|
|
83
|
+
"en": "The request parameters are not recognized or not supported."
|
|
84
|
+
},
|
|
85
|
+
"4006": {
|
|
86
|
+
"en": "The request format is not recognized or not supported."
|
|
87
|
+
},
|
|
88
|
+
"4007": {
|
|
89
|
+
"en": "The request content type is not recognized or not supported."
|
|
90
|
+
},
|
|
91
|
+
"4008": {
|
|
92
|
+
"en": "The request content length is not recognized or not supported."
|
|
93
|
+
},
|
|
94
|
+
"4009": {
|
|
95
|
+
"en": "The request content encoding is not recognized or not supported."
|
|
62
96
|
}
|
|
63
97
|
}
|
|
64
98
|
}
|
package/bin/start-instance.js
CHANGED
|
@@ -151,7 +151,7 @@ try {
|
|
|
151
151
|
setImmediate( () => process.exit( 1 ) );
|
|
152
152
|
} );
|
|
153
153
|
} else {
|
|
154
|
-
logger.log( `Attempting to start a module that does not implement the ServiceInstance abstract class!`, logger.logSeverity.
|
|
154
|
+
logger.log( `Attempting to start a module that does not implement the ServiceInstance abstract class!`, logger.logSeverity.CRITICAL );
|
|
155
155
|
setImmediate( () => process.exit( 1 ) );
|
|
156
156
|
}
|
|
157
157
|
} catch ( error ) {
|
package/components/auditing.js
CHANGED
|
@@ -11,9 +11,10 @@ const tools = require( "#tools" );
|
|
|
11
11
|
const logger = require( "#logger" );
|
|
12
12
|
const config = require( "#config" );
|
|
13
13
|
const gcloud = require( "#gcloud-integration" );
|
|
14
|
+
const ServiceInstance = require( "#service-instance" );
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
|
-
* @typedef {Object}
|
|
17
|
+
* @typedef {Object} TiLogEntry
|
|
17
18
|
* @property {string} _id Unique identifier that can be used to identify the document in a NoSQL database.
|
|
18
19
|
* @property {TiLogSeverity} severity The log severity level.
|
|
19
20
|
* @property {string} thread The categorization of the log message.
|
|
@@ -59,19 +60,19 @@ class Auditing {
|
|
|
59
60
|
*/
|
|
60
61
|
log( message, severity = logger.logSeverity.DEFAULT, thread = "main", data = {} ) {
|
|
61
62
|
try {
|
|
62
|
-
//
|
|
63
|
+
// Log entries below the minimum allowed level will be directly ignored:
|
|
63
64
|
if ( severity >= config.getSetting( config.setting.AUDITING_LOG_MIN_LEVEL ) ) {
|
|
64
|
-
//
|
|
65
|
-
//
|
|
65
|
+
// Obscure any passwords that might have landed in the data object;
|
|
66
|
+
// Also make sure to convert a potential Error object to a JSON:
|
|
66
67
|
let copyOfData = ( config.getSetting( config.setting.AUDITING_LOG_DETAILS ) === true ) ? _.cloneDeep( data ) : undefined;
|
|
67
68
|
let logEntry = Auditing.#createLogEntry( severity, thread, message, copyOfData );
|
|
68
69
|
|
|
69
|
-
//
|
|
70
|
+
// Make sure there is a console available (especially important for Cloud or containerized environments):
|
|
70
71
|
if ( config.getSetting( config.setting.AUDITING_LOG_CONSOLE_ENABLED ) === true && console ) {
|
|
71
72
|
Auditing.#logToConsole( logEntry );
|
|
72
73
|
}
|
|
73
74
|
|
|
74
|
-
//
|
|
75
|
+
// If this is an actual error, then send it to GCloud error reporting system as well:
|
|
75
76
|
if ( logEntry.severity >= logger.logSeverity.WARNING && data instanceof Error && gcloud.isEnabled() ) {
|
|
76
77
|
gcloud.reportError( data );
|
|
77
78
|
}
|
|
@@ -91,14 +92,14 @@ class Auditing {
|
|
|
91
92
|
* @param {string} thread
|
|
92
93
|
* @param {string} message
|
|
93
94
|
* @param {Object} data
|
|
94
|
-
* @returns {
|
|
95
|
+
* @returns {TiLogEntry}
|
|
95
96
|
* @private
|
|
96
97
|
*/
|
|
97
98
|
static #createLogEntry( severity, thread, message, data ) {
|
|
98
99
|
let currentDate = new Date();
|
|
99
100
|
let logDate = tools.getUTCDateString( currentDate );
|
|
100
101
|
let logTime = tools.getUTCTimeString( currentDate, true );
|
|
101
|
-
let reporter =
|
|
102
|
+
let reporter = ServiceInstance.instanceID;
|
|
102
103
|
|
|
103
104
|
return {
|
|
104
105
|
_id: `${ logDate }-${ logTime }-${ thread }-${ reporter }-${ logger.getSeverityName( severity ) }-${ tools.getUUID() }`,
|
|
@@ -112,13 +113,13 @@ class Auditing {
|
|
|
112
113
|
}
|
|
113
114
|
|
|
114
115
|
/**
|
|
115
|
-
* Used to write the log entries to the system console (i.e
|
|
116
|
+
* Used to write the log entries to the system console (i.e., STD OUT and STD ERR).
|
|
116
117
|
* <br/>
|
|
117
118
|
* NOTE: There was an issue in previous Node versions with console that can crash the application if the number of
|
|
118
|
-
*
|
|
119
|
+
* outputs exceeds several thousands per second. To be monitored and adjusted as necessary!
|
|
119
120
|
*
|
|
120
121
|
* @method
|
|
121
|
-
* @param {
|
|
122
|
+
* @param {TiLogEntry} logEntry
|
|
122
123
|
* @private
|
|
123
124
|
*/
|
|
124
125
|
static #logToConsole( logEntry ) {
|
|
@@ -140,7 +141,7 @@ class Auditing {
|
|
|
140
141
|
* Used to format a log entry for the Node console.
|
|
141
142
|
*
|
|
142
143
|
* @method
|
|
143
|
-
* @param {
|
|
144
|
+
* @param {TiLogEntry} logEntry
|
|
144
145
|
* @returns {string}
|
|
145
146
|
* @private
|
|
146
147
|
*/
|
|
@@ -153,7 +154,7 @@ class Auditing {
|
|
|
153
154
|
* Used to format a log entry data payload for the Node console.
|
|
154
155
|
*
|
|
155
156
|
* @method
|
|
156
|
-
* @param {
|
|
157
|
+
* @param {TiLogEntry} logEntry
|
|
157
158
|
* @returns {string}
|
|
158
159
|
* @private
|
|
159
160
|
*/
|
|
@@ -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/>.
|
|
@@ -37,7 +37,8 @@ class ConnectionObserver {
|
|
|
37
37
|
* @virtual
|
|
38
38
|
* @public
|
|
39
39
|
*/
|
|
40
|
-
onConnectionDisrupted( identifier ) {
|
|
40
|
+
onConnectionDisrupted( identifier ) {
|
|
41
|
+
}
|
|
41
42
|
|
|
42
43
|
/**
|
|
43
44
|
* Needs to be invoked by the connection handler when the connection is recovered.
|
|
@@ -49,8 +50,22 @@ class ConnectionObserver {
|
|
|
49
50
|
* @virtual
|
|
50
51
|
* @public
|
|
51
52
|
*/
|
|
52
|
-
onConnectionRecovered( identifier ) {
|
|
53
|
+
onConnectionRecovered( identifier ) {
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Needs to be invoked by the connection handler when the connection is irrevocably lost.
|
|
58
|
+
* <br/>
|
|
59
|
+
* NOTE: Override this to add custom functionality.
|
|
60
|
+
*
|
|
61
|
+
* @method
|
|
62
|
+
* @param {string} identifier The identifier of the observed connection.
|
|
63
|
+
* @virtual
|
|
64
|
+
* @public
|
|
65
|
+
*/
|
|
66
|
+
onConnectionLost( identifier ) {
|
|
67
|
+
}
|
|
53
68
|
|
|
54
69
|
}
|
|
55
70
|
|
|
56
|
-
module.exports = ConnectionObserver;
|
|
71
|
+
module.exports = ConnectionObserver;
|
|
@@ -74,7 +74,7 @@ class MessageExchange extends MessageObserver {
|
|
|
74
74
|
* @param {string} serviceDomainName The domain name of the microservice using the message exchange.
|
|
75
75
|
*/
|
|
76
76
|
constructor( instanceID, serviceDomainName ) {
|
|
77
|
-
super();
|
|
77
|
+
super( 9 );
|
|
78
78
|
|
|
79
79
|
// make sure this abstract class cannot be instantiated:
|
|
80
80
|
if ( new.target === MessageExchange ) {
|
|
@@ -394,12 +394,25 @@ class MessageExchange extends MessageObserver {
|
|
|
394
394
|
_.pull( this.#disruptedConnections, identifier );
|
|
395
395
|
}
|
|
396
396
|
|
|
397
|
+
/**
|
|
398
|
+
* Used to mark the connection with the provided identifier as disrupted.
|
|
399
|
+
*
|
|
400
|
+
* @method
|
|
401
|
+
* @param {string} identifier The identifier of the observed connection.
|
|
402
|
+
* @override
|
|
403
|
+
* @public
|
|
404
|
+
*/
|
|
405
|
+
onConnectionLost( identifier ) {
|
|
406
|
+
// TODO: implement triggering of a graceful shutdown of the service instance
|
|
407
|
+
}
|
|
408
|
+
|
|
397
409
|
/**
|
|
398
410
|
* Used only for the purposes of the message tracer.
|
|
399
411
|
*
|
|
400
412
|
* @method
|
|
401
413
|
* @param {string} identifier The identifier of the observed connection.
|
|
402
414
|
* @param {Message} message The message for processing.
|
|
415
|
+
* @returns {Message} The message that was received.
|
|
403
416
|
* @override
|
|
404
417
|
* @public
|
|
405
418
|
*/
|
|
@@ -411,6 +424,8 @@ class MessageExchange extends MessageObserver {
|
|
|
411
424
|
} else if ( MessageExchange.connectionNameResponsesIn === identifier ) {
|
|
412
425
|
messageTracer.instance.recordTraceEntry( message, messageTracer.messageType.MESSAGE_RESPONSE, messageTracer.dispatchEvent.RECEIVED, messageTracer.messageState.PROCESSED );
|
|
413
426
|
}
|
|
427
|
+
|
|
428
|
+
return message;
|
|
414
429
|
}
|
|
415
430
|
|
|
416
431
|
/**
|
|
@@ -54,7 +54,9 @@ class MessageHandler extends ConnectionObserver {
|
|
|
54
54
|
* @returns {boolean}
|
|
55
55
|
* @public
|
|
56
56
|
*/
|
|
57
|
-
get isAvailable() {
|
|
57
|
+
get isAvailable() {
|
|
58
|
+
return this.#isAvailable;
|
|
59
|
+
}
|
|
58
60
|
|
|
59
61
|
/**
|
|
60
62
|
* Used to set the isAvailable flag.
|
|
@@ -65,7 +67,9 @@ class MessageHandler extends ConnectionObserver {
|
|
|
65
67
|
* @param {boolean} value
|
|
66
68
|
* @public
|
|
67
69
|
*/
|
|
68
|
-
set isAvailable( value ) {
|
|
70
|
+
set isAvailable( value ) {
|
|
71
|
+
this.#isAvailable = value;
|
|
72
|
+
}
|
|
69
73
|
|
|
70
74
|
/**
|
|
71
75
|
* Returns the connection identifier.
|
|
@@ -74,7 +78,9 @@ class MessageHandler extends ConnectionObserver {
|
|
|
74
78
|
* @returns {string}
|
|
75
79
|
* @public
|
|
76
80
|
*/
|
|
77
|
-
get connectionIdentifier() {
|
|
81
|
+
get connectionIdentifier() {
|
|
82
|
+
return this.#connectionIdentifier;
|
|
83
|
+
}
|
|
78
84
|
|
|
79
85
|
/**
|
|
80
86
|
* Used to initialize and enable the communication capabilities of the handler.
|
|
@@ -128,9 +134,9 @@ class MessageHandler extends ConnectionObserver {
|
|
|
128
134
|
*/
|
|
129
135
|
addMessageObserver( messageObserver ) {
|
|
130
136
|
const MessageObserver = require( "#message-observer" );
|
|
131
|
-
|
|
132
137
|
if ( messageObserver instanceof MessageObserver ) {
|
|
133
138
|
this.#messageObservers.push( messageObserver );
|
|
139
|
+
this.#messageObservers = _.orderBy( this.#messageObservers, [ "priority" ], [ "desc" ] );
|
|
134
140
|
} else {
|
|
135
141
|
logger.log( `Attempting to add '${ messageObserver.constructor.name }' as message observer but it's not a child-class of 'MessageObserver'!`, logger.logSeverity.WARNING );
|
|
136
142
|
}
|
|
@@ -138,19 +144,23 @@ class MessageHandler extends ConnectionObserver {
|
|
|
138
144
|
|
|
139
145
|
/**
|
|
140
146
|
* An event-triggered method that will notify any observers about a new message for handling.
|
|
147
|
+
* <br/>
|
|
148
|
+
* NOTE: Each observer will be notified in the order of their priority via their {@link MessageObserver.onMessage} method. Additionally, the message will be
|
|
149
|
+
* passed through each observer in the order of their priority. If the observer returns a modified message, it will be used instead of the original message!
|
|
141
150
|
*
|
|
142
151
|
* @method
|
|
143
152
|
* @param {Message} message
|
|
144
153
|
* @public
|
|
145
154
|
*/
|
|
146
|
-
|
|
155
|
+
notifyMessageObservers( message ) {
|
|
156
|
+
let modifiedMessage = message;
|
|
147
157
|
_.forEach( this.#messageObservers, ( messageObserver ) => {
|
|
148
|
-
messageObserver.onMessage( this.#connectionIdentifier,
|
|
158
|
+
modifiedMessage = messageObserver.onMessage( this.#connectionIdentifier, modifiedMessage );
|
|
149
159
|
} );
|
|
150
160
|
}
|
|
151
161
|
|
|
152
162
|
/**
|
|
153
|
-
* An event-triggered method that will notify any observers about primary connection recovered state.
|
|
163
|
+
* An event-triggered method that will notify any observers about the primary connection recovered state.
|
|
154
164
|
* <br/>
|
|
155
165
|
* NOTE: You can override this to add custom functionality but make sure to also call the base method
|
|
156
166
|
* using: super.onConnectionRecovered( identifier )
|
|
@@ -189,6 +199,26 @@ class MessageHandler extends ConnectionObserver {
|
|
|
189
199
|
}
|
|
190
200
|
}
|
|
191
201
|
|
|
202
|
+
/**
|
|
203
|
+
* An event-triggered method that will notify any observers about the primary connection having been lost.
|
|
204
|
+
* <br/>
|
|
205
|
+
* NOTE: You can override this to add custom functionality but make sure to also call the base method
|
|
206
|
+
* using: super.onConnectionLost( identifier )
|
|
207
|
+
*
|
|
208
|
+
* @method
|
|
209
|
+
* @param {string} identifier The identifier of the observed connection.
|
|
210
|
+
* @override
|
|
211
|
+
* @private
|
|
212
|
+
*/
|
|
213
|
+
onConnectionLost( identifier ) {
|
|
214
|
+
if ( identifier === this.#connectionIdentifier ) {
|
|
215
|
+
this.#isAvailable = false;
|
|
216
|
+
_.forEach( this.#messageObservers, ( messageObserver ) => {
|
|
217
|
+
messageObserver.onConnectionLost( this.#connectionIdentifier );
|
|
218
|
+
} );
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
192
222
|
}
|
|
193
223
|
|
|
194
224
|
module.exports = MessageHandler;
|
|
@@ -1,12 +1,13 @@
|
|
|
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/>.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
const ConnectionObserver = require( "#connection-observer" );
|
|
10
|
+
const _ = require( "lodash" );
|
|
10
11
|
const exceptions = require( "#exceptions" );
|
|
11
12
|
|
|
12
13
|
/**
|
|
@@ -21,16 +22,45 @@ const exceptions = require( "#exceptions" );
|
|
|
21
22
|
*/
|
|
22
23
|
class MessageObserver extends ConnectionObserver {
|
|
23
24
|
|
|
25
|
+
#priority = 0;
|
|
26
|
+
|
|
24
27
|
/**
|
|
28
|
+
* @param {number} [priority=0] The priority of this observer. Higher values indicate higher priority.
|
|
25
29
|
* @constructor
|
|
26
30
|
*/
|
|
27
|
-
constructor() {
|
|
31
|
+
constructor( priority = 0 ) {
|
|
28
32
|
super();
|
|
29
33
|
|
|
30
34
|
// make sure this abstract class cannot be instantiated:
|
|
31
35
|
if ( new.target === MessageObserver ) {
|
|
32
36
|
throw exceptions.raise( exceptions.exceptionCode.E_GEN_ABSTRACT_CLASS_INIT, { name: this.constructor.name } );
|
|
33
37
|
}
|
|
38
|
+
|
|
39
|
+
this.#priority = _.isNumber( priority ) ? priority : 0;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Returns the priority of this observer.
|
|
44
|
+
* <br/>
|
|
45
|
+
* NOTE: Higher values indicate higher priority.
|
|
46
|
+
*
|
|
47
|
+
* @property
|
|
48
|
+
* @returns {number}
|
|
49
|
+
*/
|
|
50
|
+
get priority() {
|
|
51
|
+
return this.#priority;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Used to set the priority of this observer.
|
|
56
|
+
* <br/>
|
|
57
|
+
* NOTE: Higher values indicate higher priority.
|
|
58
|
+
*
|
|
59
|
+
* @property
|
|
60
|
+
* @param {number} value
|
|
61
|
+
*/
|
|
62
|
+
set priority( value ) {
|
|
63
|
+
this.#priority = _.isNumber( value ) ? value : 0;
|
|
34
64
|
}
|
|
35
65
|
|
|
36
66
|
/**
|
|
@@ -41,10 +71,13 @@ class MessageObserver extends ConnectionObserver {
|
|
|
41
71
|
* @method
|
|
42
72
|
* @param {string} identifier The identifier of the observed connection.
|
|
43
73
|
* @param {Message} message The message for processing.
|
|
74
|
+
* @returns {Message} The message that was received.
|
|
44
75
|
* @virtual
|
|
45
76
|
* @public
|
|
46
77
|
*/
|
|
47
|
-
onMessage( identifier, message ) {
|
|
78
|
+
onMessage( identifier, message ) {
|
|
79
|
+
return message;
|
|
80
|
+
}
|
|
48
81
|
|
|
49
82
|
/**
|
|
50
83
|
* Needs to be invoked by the connection handler when the connection is disrupted.
|
|
@@ -74,6 +107,20 @@ class MessageObserver extends ConnectionObserver {
|
|
|
74
107
|
super.onConnectionRecovered( identifier );
|
|
75
108
|
}
|
|
76
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Needs to be invoked by the connection handler when the connection is irrevocably lost.
|
|
112
|
+
* <br/>
|
|
113
|
+
* NOTE: Override this to add custom functionality.
|
|
114
|
+
*
|
|
115
|
+
* @method
|
|
116
|
+
* @param {string} identifier The identifier of the observed connection.
|
|
117
|
+
* @virtual
|
|
118
|
+
* @public
|
|
119
|
+
*/
|
|
120
|
+
onConnectionLost( identifier ) {
|
|
121
|
+
super.onConnectionLost( identifier );
|
|
122
|
+
}
|
|
123
|
+
|
|
77
124
|
}
|
|
78
125
|
|
|
79
|
-
module.exports = MessageObserver;
|
|
126
|
+
module.exports = MessageObserver;
|
|
@@ -117,7 +117,7 @@ class MessageReceiver extends MessageHandler {
|
|
|
117
117
|
this.onReceive().then( ( message ) => {
|
|
118
118
|
return this.#postReceive( message );
|
|
119
119
|
} ).then( ( message ) => {
|
|
120
|
-
this.
|
|
120
|
+
this.notifyMessageObservers( message );
|
|
121
121
|
} ).catch( ( error ) => {
|
|
122
122
|
if ( error.code !== exceptions.exceptionCode.E_COM_MESSAGE_RECEIVER_UNAVAILABLE ) {
|
|
123
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 );
|