@ti-engine/core 1.3.11 → 1.3.13

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
@@ -2,30 +2,53 @@
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.13
6
+
7
+ * feat(exceptions): add new exception code `E_GEN_UNALLOWED_OVERRIDE` (1009) for detecting attempts to override protected or private methods/properties
8
+ * feat(auditing): enhance console data formatting with recursive, depth-limited formatting (max depth: 5), circular reference detection and handling, improved readability for nested objects and multi-line strings, and proper indentation and prefixing for structured output
9
+ * fix(start instance): modified `start-instance.js` to initialize serviceConfig as undefined instead of an empty object when `TI_INSTANCE_CONFIG` environment variable is not provided
10
+ * build(npm): update npm dependencies to their latest versions
11
+
12
+ ## Version 1.3.12
13
+
14
+ * feat(exceptions): add new exception code `E_COM_SERVICE_EXEC_FAILED`
15
+ * feat(localization): add new public method `getAllLabels`
16
+ * feat(localization): use new `deepFreeze` method to freeze all label trees
17
+ * feat(tools): add new public method `deepFreeze` for freezing an object and all its sub-objects recursively
18
+ * feat(config): use new `deepFreeze` method to freeze all configuration settings
19
+ * docs: add more information about the `localization` module in `README.md` file
20
+ * build(npm): update npm dependencies to their latest versions
21
+
5
22
  ## Version 1.3.11
23
+
6
24
  * feat(tools): add `arrayUniques` method to the tools module that helps extract only the unique array values
7
25
  * fix(start instance): fix `undefined` instance ID in the log message about starting the instance
8
26
 
9
27
  ## Version 1.3.10
28
+
10
29
  * chore: fix various minor issues reported by the linter
11
30
  * build(npm): add more information and options to the `package.json` file
12
31
  * docs: add some JSDoc descriptions to classes and methods
13
32
 
14
33
  ## Version 1.3.9
34
+
15
35
  * feat(exceptions): add new exception code `E_GEN_NOT_INITIALIZED`
16
36
  * docs: fix image links in the `README.md` file
17
37
 
18
38
  ## Version 1.3.8
39
+
19
40
  * fix(tester): fix the relative paths of various files in the tester service. The configuration now assumes that the working directory is the root of the tester package instead of the repository itself
20
41
  * build(npm): update npm dependencies to their latest versions
21
42
  * docs: fix some outdated information in the `README.md` file and add some more details
22
43
 
23
44
  ## Version 1.3.7
45
+
24
46
  * feat(exceptions): add new exception code `E_SEC_UNRECOGNIZED_AUTH_METHOD`
25
47
  * fix(config): fix potential issue with `TI_GCLOUD_ENABLED` parsing as boolean
26
48
  * docs: adjust some of the JSDoc descriptions and definitions
27
49
 
28
50
  ## Version 1.3.6
51
+
29
52
  * feat(exceptions): add new exception code `E_GEN_INVALID_ARGUMENT_TYPE`
30
53
  * feat(exceptions): add public enum with all HTTP codes exported as `httpCode` and typedef `TiHttpCode`. Use it as the type for the `httpCode` exception property
31
54
  * feat(tools): add `description` property to enum objects
@@ -41,19 +64,23 @@ This document will contain the list of changes made to the framework. The format
41
64
  * build(npm)!: bump the minimum supported Node.js version to 18.0.0
42
65
 
43
66
  ## Version 1.3.5
67
+
44
68
  * fix(redis integration): fix a duplicated log entry on connection ready event if multiple observers are registered
45
69
  * feat(service caller)!: change log level of error result in `process` method from `ERROR` to `DEBUG`. Implementers are expected to handle this and decide if the error should be propagated further or not
46
70
  * docs: update and fix various issues with the `README.md` file
47
71
 
48
72
  ## Version 1.3.4
73
+
49
74
  * fix(service instance): fix the way `ServiceConfiguration` is propagated via child classes and remove unnecessary defaults. Also update the relevant JSDoc
50
75
 
51
76
  ## Version 1.3.3
77
+
52
78
  * feat(exceptions): add new parameter `includeData` to `Exception.asJSON` method which allows the exclusion of the data parameter from the returned JSON
53
79
  * feat(exceptions): remove several excessive exception codes that were unlikely to be used
54
80
  * fix(localization): add several missing exception labels
55
81
 
56
82
  ## Version 1.3.2
83
+
57
84
  * feat(exceptions): add a set of new exception codes for the needs of any wrapping `web-server` standard communication
58
85
  * feat(exception)!: change the default prefix path for exception labels to `system.exceptions.`
59
86
  * feat(localization): add an Enum list of all language codes based on ISO 639-1 standard
@@ -67,6 +94,7 @@ This document will contain the list of changes made to the framework. The format
67
94
  * 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
68
95
 
69
96
  ## Version 1.3.1
97
+
70
98
  * feat(service caller): refactor the entire service call execution flow for clarity and better performance
71
99
  * feat(service caller): create a new private class `ServiceCallProcessor` to handle individual service calls in a contained scope
72
100
  * 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
@@ -77,6 +105,7 @@ This document will contain the list of changes made to the framework. The format
77
105
  * fix(redis integration): fix an issue which was setting the client status to `DISCONNECTED` during a normal shut down procedure
78
106
 
79
107
  ## Version 1.3.0
108
+
80
109
  * feat(redis integration)!: change `reconnectOnError` behavior to also resubmit the failed command in case the error was of type `READONLY`
81
110
  * feat(redis integration): improve the reliability and usage of the event notification mechanism for connection observers
82
111
  * feat(redis integration): implement listener to the `end` event on Redis connection to capture when connection can no longer be recovered
@@ -90,25 +119,31 @@ This document will contain the list of changes made to the framework. The format
90
119
  * fix(redis integration): fix broken event propagation on `disrupted` events to some connection observers
91
120
 
92
121
  ## Version 1.2.5
122
+
93
123
  * feat(cache): extend method `expireValue` to work with has set fields as well
94
124
 
95
125
  ## Version 1.2.4
126
+
96
127
  * feat(cache): expose the cache module as export in `package.json`
97
128
  * feat(cache): add method `hashDeleteField` to remove a hash-set field. This implements the `hdel` Redis command
98
129
  * fix(tools): optimize method `stringifyJSON` not to call unnecessary decycling of the value if it's not an object
99
130
  * fix(cache): replace `hmset` with `hset` Redis command in both methods that set hash-set values. Also remove unnecessary `_.isObjectLike` call in `hashSetFields` method
100
131
 
101
132
  ## Version 1.2.3
133
+
102
134
  * feat(start instance): add support for providing a custom path to the `.env` file to be used at service startup as process argument. Accepted arguments are `--env`, `--env-file`, `--dotenv`, `--dotenv-path`, and `-e`. The path itself should be relative to the working directory and should include the file name
103
135
 
104
136
  ## Version 1.2.2
137
+
105
138
  * feat(start instance): change package `dotenv` to `@dotenvx/dotenvx` for loading of ENV variables. The new package supports encrypting the ENV variables. For more information see https://dotenvx.com/docs/
106
139
 
107
140
  ## Version 1.2.1
141
+
108
142
  * feat(localization): add support for adding custom labels to the localization system. These have to follow the same format as the system labels
109
143
  * docs: add section about localization to the `README.md` file
110
144
 
111
145
  ## Version 1.2.0
146
+
112
147
  * feat(config)!: change the setting `localization.labelsPath` to be an array of strings. It can now be used to supply any additional custom labels in one or more files to the framework
113
148
  * feat(message memory cache): implement graceful exception handling during shut down procedure in `receiveMessage` and `sendMessage` methods
114
149
  * fix(default message sender): add missing initialization of the memory cache on enable
@@ -121,21 +156,25 @@ This document will contain the list of changes made to the framework. The format
121
156
  * fix(service executor)!: change the individual service registration process to ensure service registration does actually happen before the service provider finishes its initialization
122
157
 
123
158
  ## Version 1.1.10
159
+
124
160
  * fix(cache): fix redis client creation sequence. It is now created inside the constructor as intended. It still needs to be initialized explicitly using the `initialize` method.
125
161
 
126
162
  ## Version 1.1.9
163
+
127
164
  * 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.
128
165
  * feat(cache): change the way the main cache instance is initialized in compliance with the new redis integration
129
166
  * 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.
130
167
  * fix(message dispatcher): fix the way the `messageExchange` is initialized in the `MessageDispatcher` class (was not returning a promise)
131
168
 
132
169
  ## Version 1.1.8
170
+
133
171
  * feat(auditing): change export of the singleton class in an `instance` variable for consistency and clarity
134
172
  * feat(message dispatcher): change export of the singleton class in an `instance` variable for consistency and clarity
135
173
  * feat(cache): change export of the singleton class in an `instance` variable for consistency and clarity
136
174
  * feat(service executor): implement service registration retry policy
137
175
 
138
176
  ## Version 1.1.7
177
+
139
178
  * feat(start instance): add support for the detection of `SIGBREAK` events and graceful shutdown on Windows
140
179
  * feat(service instance)!: prevent the initialization of multiple `ServiceInstance` within the same process
141
180
  * feat(message tracer)!: covert to singleton instance and add initialization method
@@ -145,6 +184,7 @@ This document will contain the list of changes made to the framework. The format
145
184
  * fix(redis integration): update links to official commands documentation
146
185
 
147
186
  ## Version 1.1.6
187
+
148
188
  * feat(start instance): add fail-fast mode as default behavior on promise unhandled rejections
149
189
  * feat(start instance): implement functionality to derive safe default service domain name when none is provided in the configuration
150
190
  * feat(config): add support for new ENV variable `TI_FAIL_FAST_ON_UNHANDLED_OFF` that controls the fail-fast mode
package/README.md CHANGED
@@ -544,4 +544,6 @@ The following is an example of a custom localization file. The names in brackets
544
544
  }
545
545
  ```
546
546
 
547
- 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.
547
+ 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.
548
+
549
+ If you want to localize the system labels of the framework, you can do so by providing a custom localization file with the same structure as the default one but containing only the languages you want to add. For example, the default English label for `E_UNKNOWN_ERROR` is found in JSON path `system.exceptions.0.en`. To add an entry for German, your file needs to contain a `system.exceptions.0.de` property. The localization module will handle the rest. You can use the same approach to modify the existing English labels as well. The full list of system labels is found in the `bin/localization/labels.json` file. Avoid modifying the default file since it might get overwritten by future updates.
@@ -31,6 +31,9 @@
31
31
  "1008": {
32
32
  "en": "The invoked framework component is not initialized."
33
33
  },
34
+ "1009": {
35
+ "en": "Attempt to override a protected or private method or property detected."
36
+ },
34
37
  "2000": {
35
38
  "en": "Invalid authorization token provided."
36
39
  },
@@ -70,6 +73,9 @@
70
73
  "3007": {
71
74
  "en": "The message exchange is irrevocably broken and cannot be used any longer."
72
75
  },
76
+ "3008": {
77
+ "en": "The execution of a service reported a failure. This might be a business logic error or a system error."
78
+ },
73
79
  "3010": {
74
80
  "en": "Connection retry attempts exceeded the configured limit."
75
81
  },
@@ -124,7 +124,7 @@ try {
124
124
  const serviceConstructor = require( path.join( process.cwd(), process.env.TI_INSTANCE_CLASS ) );
125
125
  const serviceConfigPath = process.env.TI_INSTANCE_CONFIG;
126
126
  /** @type ServiceConfiguration */
127
- let serviceConfig = {};
127
+ let serviceConfig;
128
128
  if ( serviceConfigPath ) {
129
129
  serviceConfig = require( path.join( process.cwd(), process.env.TI_INSTANCE_CONFIG ) );
130
130
  }
@@ -6,6 +6,7 @@
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
+ const { EOL } = require( "node:os" )
9
10
  const _ = require( "lodash" );
10
11
  const tools = require( "#tools" );
11
12
  const logger = require( "#logger" );
@@ -132,7 +133,7 @@ class Auditing {
132
133
  } else {
133
134
  console.log( Auditing.#formatConsoleMessage( logEntry ) );
134
135
  if ( !_.isEmpty( logEntry.data ) ) {
135
- console.log( ` » ${ Auditing.#formatConsoleData( logEntry ) }` );
136
+ console.log( Auditing.#formatConsoleData( logEntry.data, " " ) );
136
137
  }
137
138
  }
138
139
  }
@@ -154,12 +155,53 @@ class Auditing {
154
155
  * Used to format a log entry data payload for the Node console.
155
156
  *
156
157
  * @method
157
- * @param {TiLogEntry} logEntry
158
+ * @param {*} data
159
+ * @param {string} [prefix=""]
160
+ * @param {number} [currentDepth=0]
161
+ * @param {number} [maxDepth=5]
162
+ * @param {Set} [visited=new Set()]
158
163
  * @returns {string}
159
164
  * @private
160
165
  */
161
- static #formatConsoleData( logEntry ) {
162
- return tools.stringifyJSON( logEntry.data );
166
+ static #formatConsoleData( data, prefix = "", currentDepth = 0, maxDepth = 5, visited = new Set() ) {
167
+ if ( data === null || data === undefined || !_.isObjectLike( data ) ) {
168
+ // Handle null, undefined, and primitive values:
169
+ return prefix + "» " + String( data );
170
+ } else if ( currentDepth >= maxDepth ) {
171
+ // Check max depth:
172
+ return prefix + "! [max data depth reached]";
173
+ } else if ( visited.has( data ) ) {
174
+ // Check for circular references:
175
+ return prefix + "! [circular reference detected]";
176
+ } else {
177
+ let formattedData = "";
178
+ visited.add( data );
179
+
180
+ _.forOwn( data, ( value, key ) => {
181
+ let stackLines = ( _.isString( value ) ) ? value.split( "\n" ) : [ value ];
182
+ if ( stackLines.length > 1 ) {
183
+ _.forEach( stackLines, ( line, idx ) => {
184
+ if ( idx === 0 ) {
185
+ formattedData += prefix + `» ${ key }: ${ _.trim( line ) }` + EOL;
186
+ } else {
187
+ formattedData += prefix + `- ${ _.trim( line ) }` + EOL;
188
+ }
189
+ } );
190
+ } else {
191
+ if ( _.isObjectLike( value ) ) {
192
+ formattedData += prefix + `» ${ key }:` + EOL + Auditing.#formatConsoleData( value, " " + prefix, currentDepth + 1, maxDepth, visited ) + EOL;
193
+ } else {
194
+ formattedData += prefix + `» ${ key }: ${ value }` + EOL;
195
+ }
196
+ }
197
+ } );
198
+
199
+ if ( formattedData.endsWith( EOL ) ) {
200
+ formattedData = formattedData.slice( 0, -EOL.length );
201
+ }
202
+
203
+ return formattedData;
204
+ }
163
205
  }
164
206
  }
165
207
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ti-engine/core",
3
- "version": "1.3.11",
3
+ "version": "1.3.13",
4
4
  "description": "The ti-engine is an open source, free to use—both for personal and commercial projects—framework for the creation of microservice-based solutions using node.js.",
5
5
  "keywords": [
6
6
  "microservices",
@@ -57,9 +57,9 @@
57
57
  "#tools": "./utils/tools.js"
58
58
  },
59
59
  "dependencies": {
60
- "@dotenvx/dotenvx": "^1.51.0",
60
+ "@dotenvx/dotenvx": "^1.51.1",
61
61
  "blake2": "^5.0.0",
62
- "ioredis": "^5.8.0",
62
+ "ioredis": "^5.8.2",
63
63
  "lodash": "^4.17.21",
64
64
  "node-schedule": "^2.1.1"
65
65
  },
package/utils/config.js CHANGED
@@ -185,7 +185,7 @@ if ( tools.toBool( process.env.TI_GCLOUD_ENABLED ) === true && settings.gcloudIn
185
185
  settings.operationMode = process.env.NODE_ENV || settings.operationMode;
186
186
 
187
187
  // Prevent further modifications to the settings object:
188
- Object.freeze( settings );
188
+ tools.deepFreeze( settings );
189
189
 
190
190
  /**
191
191
  * A standard getter method for fetching a setting.
@@ -28,6 +28,7 @@ const exceptionCodeEnum = tools.enum( {
28
28
  E_GEN_FEATURE_UNSUPPORTED: [ 1006, "feature unsupported", "The requested feature is not supported by current configuration or version." ],
29
29
  E_GEN_INVALID_ARGUMENT_TYPE: [ 1007, "invalid argument type", "The provided argument is not of the expected type." ],
30
30
  E_GEN_NOT_INITIALIZED: [ 1008, "not initialized", "The invoked framework component is not initialized." ],
31
+ E_GEN_UNALLOWED_OVERRIDE: [ 1009, "unallowed override", "Attempt to override a protected or private method or property detected." ],
31
32
  /** Security & Administration exceptions - codes under 2xxx */
32
33
  E_SEC_INVALID_AUTH_TOKEN: [ 2000, "invalid auth token", "Invalid authorization token provided." ],
33
34
  E_SEC_INVALID_EXPIRED_SESSION: [ 2001, "invalid or expired session", "Invalid or expired session encountered." ],
@@ -43,6 +44,7 @@ const exceptionCodeEnum = tools.enum( {
43
44
  E_COM_SERVICE_HANDLER_NOT_FOUND: [ 3005, "service handler not found", "No handler found in the interface for the specified service or service version." ],
44
45
  E_COM_MESSAGE_RECEIVER_UNAVAILABLE: [ 3006, "message receiver unavailable", "The message receiver instance is currently unavailable." ],
45
46
  E_COM_MESSAGE_EXCHANGE_BROKEN: [ 3007, "message exchange broken", "The message exchange is irrevocably broken and cannot be used any longer." ],
47
+ E_COM_SERVICE_EXEC_FAILED: [ 3008, "service exec failed", "The execution of a service reported a failure. This might be a business logic error or a system error." ],
46
48
  E_COM_RETRY_ATTEMPTS_EXCEEDED: [ 3010, "retry attempts exceeded", "Connection retry attempts exceeded the configured limit." ],
47
49
  /** Web server exceptions - codes under 4xxx */
48
50
  E_WEB_INVALID_REQUEST_METHOD: [ 4000, "invalid request method", "The request method is not recognized or not supported." ],
@@ -245,12 +245,7 @@ module.exports.localizationLanguage = localizationLanguageEnum;
245
245
  * @typedef {Object<string, TiLocalizedLabel | TiLabelsTree>} TiLabelsTree
246
246
  */
247
247
 
248
- /**
249
- * @typedef {Object} TiLabels
250
- * @property {TiLabelsTree} labels
251
- */
252
-
253
- /** @type {TiLabels} */
248
+ /** @type {TiLabelsTree} */
254
249
  const labels = require( "#labels" );
255
250
 
256
251
  // Load any custom labels defined in the configuration:
@@ -264,7 +259,7 @@ if ( labelsPaths && _.isArray( labelsPaths ) && labelsPaths.length > 0 ) {
264
259
  }
265
260
 
266
261
  // Prevent further modifications to the TiLabels object:
267
- Object.freeze( labels );
262
+ tools.deepFreeze( labels );
268
263
 
269
264
  /**
270
265
  * Used to return the textual value for a label based on the current system language by default or the specified language code if provided.
@@ -277,4 +272,43 @@ Object.freeze( labels );
277
272
  */
278
273
  module.exports.getLabel = ( label, language ) => {
279
274
  return _.get( labels, label + "." + ( ( language ) ? language : config.getSetting( config.setting.LOCALIZATION_LANGUAGE ) ), defaultEmptyLabel );
275
+ };
276
+
277
+ const labelsCacheByLanguage = new Map();
278
+
279
+ /**
280
+ * Used to return the entire labels tree.
281
+ * <br/>
282
+ * NOTE: The result will be a modified copy of the label tree that has no "language" end-nodes and keeps only the appropriate labels for the requested language.
283
+ * For example, if the original JSON path is "path.to.label.language", the returned path will be just "path.to.label" corresponding to the text label.
284
+ *
285
+ * @method
286
+ * @param {TiLocalizationLanguage} [language] The language code to use for the lookup. If not provided, the current system language will be used.
287
+ * @returns {TiLabelsTree}
288
+ * @public
289
+ */
290
+ module.exports.getAllLabels = ( language ) => {
291
+ const usedLanguage = language || config.getSetting( config.setting.LOCALIZATION_LANGUAGE );
292
+
293
+ if ( labelsCacheByLanguage.has( usedLanguage ) ) {
294
+ return labelsCacheByLanguage.get( usedLanguage );
295
+ } else {
296
+ const labelsByLanguage = _.cloneDeepWith( labels, ( value ) => {
297
+ if ( _.isPlainObject( value ) ) {
298
+ const values = Object.values( value );
299
+ const isLeaf = values.length > 0 && values.every( v => _.isString( v ) || _.isNil( v ) );
300
+ if ( isLeaf ) {
301
+ // Return only the desired language or default placeholder if missing:
302
+ return _.get( value, usedLanguage, defaultEmptyLabel );
303
+ }
304
+ } else {
305
+ // Return undefined to let lodash handle default deep cloning for non-leaves:
306
+ return undefined;
307
+ }
308
+ } );
309
+
310
+ tools.deepFreeze( labelsByLanguage );
311
+ labelsCacheByLanguage.set( usedLanguage, labelsByLanguage );
312
+ return labelsByLanguage;
313
+ }
280
314
  };
package/utils/tools.js CHANGED
@@ -35,6 +35,27 @@ module.exports.getUUID = () => {
35
35
  return crypto.randomUUID( { disableEntropyCache: true } );
36
36
  };
37
37
 
38
+ /**
39
+ * Used to deep-freeze an object.
40
+ *
41
+ * @method
42
+ * @param {Object} object
43
+ * @param {WeakSet} [seen]
44
+ * @return {Object}
45
+ * @public
46
+ */
47
+ module.exports.deepFreeze = ( object, seen = new WeakSet() ) => {
48
+ if ( object === null || typeof object !== "object" || seen.has( object ) ) {
49
+ return object;
50
+ } else {
51
+ seen.add( object );
52
+ _.forOwn( object, ( value ) => {
53
+ module.exports.deepFreeze( value, seen );
54
+ } );
55
+ return Object.freeze( object );
56
+ }
57
+ };
58
+
38
59
  /**
39
60
  * Used to create a custom Enum list.
40
61
  *