@ti-engine/core 1.2.0 → 1.2.4

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,6 +2,22 @@
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.2.4
6
+ * feat(cache): expose the cache module as export in `package.json`
7
+ * feat(cache): add method `hashDeleteField` to remove a hash-set field. This implements the `hdel` Redis command
8
+ * fix(tools): optimize method `stringifyJSON` not to call unnecessary decycling of the value if it's not an object
9
+ * fix(cache): replace `hmset` with `hset` Redis command in both methods that set hash-set values. Also remove unnecessary `_.isObjectLike` call in `hashSetFields` method
10
+
11
+ ## Version 1.2.3
12
+ * 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
13
+
14
+ ## Version 1.2.2
15
+ * 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/
16
+
17
+ ## Version 1.2.1
18
+ * feat(localization): add support for adding custom labels to the localization system. These have to follow the same format as the system labels
19
+ * docs: add section about localization to the `README.md` file
20
+
5
21
  ## Version 1.2.0
6
22
  * 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
7
23
  * feat(message memory cache): implement graceful exception handling during shut down procedure in `receiveMessage` and `sendMessage` methods
package/README.md CHANGED
@@ -497,4 +497,24 @@ OPERATION_MODE
497
497
 
498
498
  ## Advanced topics
499
499
 
500
- Under development...
500
+ ### Localization
501
+
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
+
504
+ The following is an example of a custom localization file (the names in brackets can be replaced with your own values):
505
+
506
+ ```json
507
+ {
508
+ "labels": {
509
+ "[category]": {
510
+ "[sub-category]": {
511
+ "[label]": {
512
+ "en": "[localized label text]"
513
+ }
514
+ }
515
+ }
516
+ }
517
+ }
518
+ ```
519
+
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.
@@ -1,65 +1,65 @@
1
- {
2
- "labels": {
3
- "general": {
4
- "exceptions": {
5
- "0": {
6
- "en": "Unidentified error encountered or unrecognized exception code provided."
7
- },
8
- "1000": {
9
- "en": "Error thrown by internal JS source."
10
- },
11
- "1001": {
12
- "en": "Attempt to construct an abstract class detected."
13
- },
14
- "1002": {
15
- "en": "Attempt to call an abstract method detected."
16
- },
17
- "1003": {
18
- "en": "Invalid or no service domain name provided at microservice startup."
19
- },
20
- "1004": {
21
- "en": "The system cache required for proper engine operation is unavailable."
22
- },
23
- "1005": {
24
- "en": "The provided service handler is not a proper function."
25
- },
26
- "1006": {
27
- "en": "The requested feature is not supported by current configuration or version."
28
- },
29
- "2000": {
30
- "en": "Invalid authorization token provided."
31
- },
32
- "2001": {
33
- "en": "Invalid or expired session encountered."
34
- },
35
- "2002": {
36
- "en": "Attempt for unauthorized access detected."
37
- },
38
- "2003": {
39
- "en": "The system detected tampering with the message received via message exchange."
40
- },
41
- "3000": {
42
- "en": "General error during cross-application communication."
43
- },
44
- "3001": {
45
- "en": "The message sender instance is currently unavailable."
46
- },
47
- "3002": {
48
- "en": "The execution of a service could not complete within the allowed timeout."
49
- },
50
- "3003": {
51
- "en": "The specified service is not found in the service registry."
52
- },
53
- "3004": {
54
- "en": "The specified service is not found in the service definition interface."
55
- },
56
- "3005": {
57
- "en": "No handler found in the interface for the specified service or service version."
58
- },
59
- "3010": {
60
- "en": "Connection retry attempts exceeded the configured limit."
61
- }
62
- }
63
- }
64
- }
1
+ {
2
+ "labels": {
3
+ "general": {
4
+ "exceptions": {
5
+ "0": {
6
+ "en": "Unidentified error encountered or unrecognized exception code provided."
7
+ },
8
+ "1000": {
9
+ "en": "Error thrown by internal JS source."
10
+ },
11
+ "1001": {
12
+ "en": "Attempt to construct an abstract class detected."
13
+ },
14
+ "1002": {
15
+ "en": "Attempt to call an abstract method detected."
16
+ },
17
+ "1003": {
18
+ "en": "Invalid or no service domain name provided at microservice startup."
19
+ },
20
+ "1004": {
21
+ "en": "The system cache required for proper engine operation is unavailable."
22
+ },
23
+ "1005": {
24
+ "en": "The provided service handler is not a proper function."
25
+ },
26
+ "1006": {
27
+ "en": "The requested feature is not supported by current configuration or version."
28
+ },
29
+ "2000": {
30
+ "en": "Invalid authorization token provided."
31
+ },
32
+ "2001": {
33
+ "en": "Invalid or expired session encountered."
34
+ },
35
+ "2002": {
36
+ "en": "Attempt for unauthorized access detected."
37
+ },
38
+ "2003": {
39
+ "en": "The system detected tampering with the message received via message exchange."
40
+ },
41
+ "3000": {
42
+ "en": "General error during cross-application communication."
43
+ },
44
+ "3001": {
45
+ "en": "The message sender instance is currently unavailable."
46
+ },
47
+ "3002": {
48
+ "en": "The execution of a service could not complete within the allowed timeout."
49
+ },
50
+ "3003": {
51
+ "en": "The specified service is not found in the service registry."
52
+ },
53
+ "3004": {
54
+ "en": "The specified service is not found in the service definition interface."
55
+ },
56
+ "3005": {
57
+ "en": "No handler found in the interface for the specified service or service version."
58
+ },
59
+ "3010": {
60
+ "en": "Connection retry attempts exceeded the configured limit."
61
+ }
62
+ }
63
+ }
64
+ }
65
65
  }
@@ -11,7 +11,28 @@
11
11
  const path = require( "path" );
12
12
 
13
13
  // Load any ENV variables defined in a .env file - before including any framework files:
14
- require( "dotenv" ).config( { path: path.join( process.cwd(), ".env" ) } );
14
+ // - If an env file path is provided via process arguments, use it; otherwise, default to CWD/.env
15
+ const envFilePath = ( () => {
16
+ let envPath = path.join( process.cwd(), ".env" );
17
+ try {
18
+ const argv = Array.isArray( process.argv ) ? process.argv.slice( 2 ) : [];
19
+ const aliases = [ "--env", "--env-file", "--dotenv", "--dotenv-path", "-e" ];
20
+ for ( const key of aliases ) {
21
+ const idx = argv.indexOf( key );
22
+ if ( idx !== -1 && idx + 1 < argv.length ) {
23
+ const value = argv[ idx + 1 ];
24
+ if ( typeof value === "string" && !value.startsWith( "-" ) ) {
25
+ envPath = path.join( process.cwd(), value.trim() );
26
+ break;
27
+ }
28
+ }
29
+ }
30
+ } catch {
31
+ }
32
+ return envPath;
33
+ } )();
34
+
35
+ require( "@dotenvx/dotenvx" ).config( { path: envFilePath } );
15
36
 
16
37
  const tools = require( "#tools" );
17
38
  const logger = require( "#logger" );
@@ -36,7 +57,7 @@ const defaultNameFromClass = ( () => {
36
57
 
37
58
  process.env.TI_INSTANCE_NAME = process.env.TI_INSTANCE_NAME || defaultNameFromClass;
38
59
 
39
- // Configure the process error handlers:
60
+ // Configure the process termination handlers to ensure a graceful shutdown:
40
61
 
41
62
  /**
42
63
  * Will be used to gracefully shut down the instance.
@@ -75,6 +96,8 @@ process.on( "SIGBREAK", () => {
75
96
  shutDownInstance( 0 );
76
97
  } );
77
98
 
99
+ // Configure the process general error handlers:
100
+
78
101
  process.on( "unhandledRejection", ( reason ) => {
79
102
  // Check if the fail-fast behavior has been forcefully disabled:
80
103
  const failFastDisabled = tools.toBool( process.env.TI_FAIL_FAST_ON_UNHANDLED_OFF || "" );
Binary file
Binary file
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@ti-engine/core",
3
- "version": "1.2.0",
3
+ "version": "1.2.4",
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
  "author": "Boris Kostadinov <kostadinov.boris@gmail.com>",
6
6
  "license": "GPL-3.0-or-later",
7
7
  "exports": {
8
8
  ".": "./bin/start-instance.js",
9
+ "./cache": "./utils/cache.js",
9
10
  "./exceptions": "./utils/exceptions.js",
10
11
  "./localization": "./utils/localization.js",
11
12
  "./logger": "./utils/logger.js",
@@ -46,7 +47,7 @@
46
47
  },
47
48
  "dependencies": {
48
49
  "blake2": "^5.0.0",
49
- "dotenv": "^17.2.1",
50
+ "@dotenvx/dotenvx": "^1.49.0",
50
51
  "fs-extra": "^11.3.1",
51
52
  "lodash": "^4.17.21",
52
53
  "node-schedule": "^2.1.1",
package/utils/cache.js CHANGED
@@ -508,7 +508,7 @@ class CommonMemoryCache extends ConnectionObserver {
508
508
  hashSetField( key, name, value ) {
509
509
  return new Promise( ( resolve, reject ) => {
510
510
  if ( this.#isOperational === true ) {
511
- let commandHashSetField = [ redis.cacheCommands.HASH_SET_MANY, key, name, tools.stringifyJSON( value ) ];
511
+ let commandHashSetField = [ redis.cacheCommands.HASH_SET, key, name, tools.stringifyJSON( value ) ];
512
512
  this.#redisClient.executeCommands( [ commandHashSetField ] ).then( () => {
513
513
  resolve();
514
514
  } ).catch( ( error ) => {
@@ -535,10 +535,10 @@ class CommonMemoryCache extends ConnectionObserver {
535
535
  hashSetFields( key, fields ) {
536
536
  return new Promise( ( resolve, reject ) => {
537
537
  if ( this.#isOperational === true ) {
538
- let commandHashSetFields = [ redis.cacheCommands.HASH_SET_MANY, key ];
538
+ let commandHashSetFields = [ redis.cacheCommands.HASH_SET, key ];
539
539
  _.forEach( fields, ( field ) => {
540
540
  commandHashSetFields.push( field.name );
541
- commandHashSetFields.push( _.isObjectLike( field.value ) ? tools.stringifyJSON( field.value ) : field.value );
541
+ commandHashSetFields.push( tools.stringifyJSON( field.value ) );
542
542
  } );
543
543
  this.#redisClient.executeCommands( [ commandHashSetFields ] ).then( () => {
544
544
  resolve();
@@ -576,6 +576,31 @@ class CommonMemoryCache extends ConnectionObserver {
576
576
  } );
577
577
  }
578
578
 
579
+ /**
580
+ * Used to remove a single field from a hash.
581
+ *
582
+ * @method
583
+ * @param {string} key
584
+ * @param {string} field
585
+ * @return {Promise<boolean>} Will return 'true' if the field was removed, 'false' otherwise.
586
+ * @public
587
+ */
588
+ hashDeleteField( key, field ) {
589
+ return new Promise( ( resolve, reject ) => {
590
+ if ( this.#isOperational === true ) {
591
+ let commandHashGetField = [ redis.cacheCommands.HASH_REMOVE, key, field ];
592
+ this.#redisClient.executeCommands( [ commandHashGetField ] ).then( ( results ) => {
593
+ results = results[ 0 ];
594
+ resolve( ( results && results.length > 1 ) ? tools.toBool( results[ 1 ] ) : false );
595
+ } ).catch( ( error ) => {
596
+ reject( error );
597
+ } );
598
+ } else {
599
+ reject( exceptions.raise( exceptions.exceptionCode.E_GEN_SYSTEM_CACHE_UNAVAILABLE ) );
600
+ }
601
+ } );
602
+ }
603
+
579
604
  /**
580
605
  * Used to store a JSON variable.
581
606
  * <br/>
@@ -248,4 +248,4 @@ module.exports.raise = ( source, data, exceptionID ) => {
248
248
  */
249
249
  module.exports.isException = ( object ) => {
250
250
  return ( object instanceof Exception );
251
- };
251
+ };
@@ -8,16 +8,26 @@
8
8
 
9
9
  const _ = require( "lodash" );
10
10
  const path = require( "path" );
11
+ const labels = require( "#labels" );
11
12
  const config = require( "#config" );
13
+ const logger = require( '#logger' );
14
+ const exceptions = require( '#exceptions' );
12
15
 
13
- // const labelsPath = path.normalize( path.join( process.cwd(), config.getSetting( config.setting.LOCALIZATION_LABELS_PATH ) ) );
14
- // const labels = require( labelsPath );
15
- const labels = require( "#labels" );
16
16
  const defaultEmptyLabel = "!!! label not found !!!";
17
17
 
18
- // TODO: Add functionality to import additional custom labels.
18
+ // Load any custom labels defined in the configuration:
19
+ try {
20
+ const labelsPaths = config.getSetting( config.setting.LOCALIZATION_LABELS_PATH );
21
+ _.forEach( labelsPaths, ( labelsPath ) => {
22
+ let filePath = path.normalize( path.join( process.cwd(), labelsPath ) );
23
+ let fileLabels = require( filePath );
24
+ _.merge( labels, fileLabels );
25
+ } );
26
+ } catch ( error ) {
27
+ logger.log( `Error while trying to load custom labels.`, logger.logSeverity.ERROR, exceptions.raise( error ) );
28
+ }
19
29
 
20
- // prevent further modifications to the labels object:
30
+ // Prevent further modifications to the labels object:
21
31
  Object.freeze( labels );
22
32
 
23
33
  /**
package/utils/tools.js CHANGED
@@ -319,8 +319,7 @@ module.exports.retrocycle = ( $ ) => {
319
319
  * @public
320
320
  */
321
321
  module.exports.stringifyJSON = ( value ) => {
322
- let transformed = module.exports.decycle( value );
323
- return _.isObjectLike( transformed ) ? JSON.stringify( _.toPlainObject( transformed ) ) : value;
322
+ return _.isObjectLike( value ) ? JSON.stringify( _.toPlainObject( module.exports.decycle( value ) ) ) : value;
324
323
  };
325
324
 
326
325
  /**
@@ -537,4 +536,4 @@ class RetryPolicy {
537
536
 
538
537
  }
539
538
 
540
- module.exports.RetryPolicy = RetryPolicy;
539
+ module.exports.RetryPolicy = RetryPolicy;