@ti-engine/core 1.0.1 → 1.0.5

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/README.md CHANGED
@@ -2,11 +2,24 @@
2
2
  Flexible framework for the creation of microservices with [node.js](https://nodejs.org/).
3
3
 
4
4
  ## introduction
5
- The **@ti-engine/core** is an open source, free to use - both for personal and commercial projects - framework for the creation of microservice-based solutions using **node.js**. The general architectural concept of the framework is based on a standard messenger system that allows certain customization but also provides predictability and traceability of its behavior.
5
+ 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**. The general architectural concept of the framework is based on a standard messenger system that allows certain customization but also provides predictability and traceability of its behavior.
6
6
 
7
- Being a messenger system, the **@ti-engine/core** 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.
7
+ Being a messenger 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.
8
8
 
9
- Please be aware, that this framework is under active development and will expand in the near future. Make sure to keep an eye on the changes in case you want to use it in the meantime.
9
+ Please be aware, that this framework is under active development and will expand in the near future. Also, this documentation is still in the process of being created and refined. Make sure to keep an eye on the changes in case you want to use it in the meantime.
10
+
11
+ ## why ti-engine?
12
+ The framework is created based on a decade of professional experience with the utilized technologies and architectural approach. It's primary goal is to provide you with a lightweight and flexible solution that can help you build quickly a microservice ecosystem with any degree of size and complexity.
13
+
14
+ This is what you gain by using **ti-engine** in your project:
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
+
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.
10
23
 
11
24
  ## prerequisites & installation
12
25
  In order to run the basic ti-engine framework you will need a couple of things:
@@ -16,6 +29,8 @@ In order to run the basic ti-engine framework you will need a couple of things:
16
29
  To get the framework itself, use the command `npm install @ti-engine/core`. And to include it directly in your package.json dependencies execute `npm install @ti-engine/core --save-prod`.
17
30
 
18
31
  ## getting started
32
+ To start using the ti-engine, you will have to make sure that the framework is properly configured and able to run. More details and instructions will be provided in the next version of this documentation. For now please take a look at the included file `start-instance.js`. It should give you an idea of how the engine operates and what to do in order to create and run your own microservice instance.
33
+
19
34
  Under development...
20
35
 
21
36
  ## architecture
File without changes
@@ -0,0 +1,108 @@
1
+ /*
2
+ * SPDX-FileCopyrightText: © 2021 Boris Kostadinov <kostadinov.boris@gmail.com>
3
+ * SPDX-License-Identifier: ICU
4
+ */
5
+
6
+ "use strict";
7
+
8
+ // load any ENV variables defined in a .env file:
9
+ require( "dotenv" ).config();
10
+
11
+ const _ = require( "lodash" );
12
+ const fs = require( "fs-extra" );
13
+ const tools = require( "#tools" );
14
+ const logger = require( "#logger" );
15
+
16
+ // configure the current instance variables before requiring any platform modules and store the necessary ones in memory cache:
17
+ process.env.TI_INSTANCE_ID = "ti-" + tools.getUUID();
18
+ process.env.TI_INSTANCE_CLASS = process.env.TI_INSTANCE_CLASS || "";
19
+ process.env.TI_INSTANCE_NAME = process.env.TI_INSTANCE_NAME || _.last( _.split( process.env.TI_INSTANCE_CLASS, "/" ) );
20
+
21
+ // configure the process error handlers:
22
+
23
+ /**
24
+ * Will be used to gracefully shut down the instance.
25
+ * <br/>
26
+ * NOTE: Will be overridden below after the instance creation.
27
+ *
28
+ * @method
29
+ * @param {number} exitCode
30
+ * @abstract
31
+ * @private
32
+ */
33
+ let shutDownInstance = ( exitCode ) => {
34
+ };
35
+
36
+ // this event will handle the process termination (Ctrl + C):
37
+ process.on( "SIGINT", () => {
38
+ logger.log( `SIGINT event detected in main instance process.`, logger.logSeverity.NOTICE );
39
+ shutDownInstance( 0 );
40
+ } );
41
+
42
+ // this event will handle the process termination (CMD close):
43
+ process.on( "SIGHUP", () => {
44
+ logger.log( `SIGHUP event detected in main instance process.`, logger.logSeverity.NOTICE );
45
+ shutDownInstance( 0 );
46
+ } );
47
+
48
+ // this event will handle the process termination:
49
+ process.on( "SIGTERM", () => {
50
+ logger.log( `SIGTERM event detected in main instance process.`, logger.logSeverity.NOTICE );
51
+ shutDownInstance( 0 );
52
+ } );
53
+
54
+ process.on( "unhandledRejection", ( reason, promise ) => {
55
+ logger.log( `Unhandled promise rejection identified! Make sure this isn't a software bug.`, logger.logSeverity.WARNING, {
56
+ reason: reason,
57
+ promise: tools.stringifyJSON( promise )
58
+ } );
59
+ } );
60
+
61
+ process.on( "multipleResolves", ( type, promise, reason ) => {
62
+ logger.log( `Multiple promise resolves detected! Make sure this isn't a software bug.`, logger.logSeverity.WARNING, {
63
+ reason: reason,
64
+ promise: tools.stringifyJSON( promise )
65
+ } );
66
+ } );
67
+
68
+ process.on( "uncaughtException", ( error ) => {
69
+ logger.log( `Some nasty and uncaught error just occurred in the application!`, logger.logSeverity.ALERT, error );
70
+ setImmediate( () => process.exit( 1 ) );
71
+ } );
72
+
73
+ // start the instance:
74
+ try {
75
+ logger.log( `Starting new instance of type '${ process.env.TI_INSTANCE_NAME }' with instance ID '${ process.env.TI_INSTANCE_ID }'.`, logger.logSeverity.NOTICE );
76
+
77
+ /** @type ServiceInstance */
78
+ const serviceConstructor = require( process.env.TI_INSTANCE_CLASS );
79
+ const serviceConfigPath = process.env.TI_INSTANCE_CONFIG;
80
+ let serviceConfig = {};
81
+ if ( fs.existsSync( serviceConfigPath ) ) {
82
+ serviceConfig = require( process.env.TI_INSTANCE_CONFIG );
83
+ }
84
+ const mainInstance = new serviceConstructor( process.env.TI_INSTANCE_NAME, serviceConfig );
85
+
86
+ /** @override */
87
+ shutDownInstance = ( code ) => {
88
+ mainInstance.stop().then( () => {
89
+ setImmediate( () => process.exit( code ) );
90
+ } ).catch( ( error ) => {
91
+ logger.log( `Error occurred during shut down of instance '${ process.env.TI_INSTANCE_ID }'! Exit code changed from '${ code }' to '1'.`, logger.logSeverity.ERROR, error );
92
+ setImmediate( () => process.exit( 1 ) );
93
+ } );
94
+ };
95
+
96
+ if ( mainInstance.isServiceInstance ) {
97
+ mainInstance.start().catch( ( error ) => {
98
+ logger.log( `Error detected during instance '${ process.env.TI_INSTANCE_ID }' startup!`, logger.logSeverity.ALERT, error );
99
+ setImmediate( () => process.exit( 1 ) );
100
+ } );
101
+ } else {
102
+ logger.log( `Attempting to start a module that does not implement the ServiceInstance abstract class!`, logger.logSeverity.ERROR );
103
+ setImmediate( () => process.exit( 1 ) );
104
+ }
105
+ } catch ( error ) {
106
+ logger.log( `Error detected in the instance startup script!`, logger.logSeverity.ALERT, error );
107
+ setImmediate( () => process.exit( 1 ) );
108
+ }
@@ -94,7 +94,7 @@ class ServiceConsumer extends ServiceInstance {
94
94
  }
95
95
 
96
96
  /**
97
- * Used to invoke a business service.
97
+ * Used to invoke a business service in any {@link ServiceInstance}.
98
98
  *
99
99
  * @method
100
100
  * @param {ServiceAddress} serviceAddress
@@ -20,14 +20,14 @@ const messageDispatcher = require( "#message-dispatcher" );
20
20
  */
21
21
 
22
22
  /**
23
- * @callback VerifyAccess
23
+ * @callback VerifyAccessMethod
24
24
  * @param {string} authToken
25
25
  * @param {ServiceAddress} serviceAddress
26
26
  * @returns {Promise}
27
27
  */
28
28
 
29
29
  /**
30
- * @callback ServiceHandler
30
+ * @callback ServiceHandlerMethod
31
31
  * @param {ServiceDefinition} serviceDefinition The service definition as provided during the service registration.
32
32
  * @param {Object} serviceParams Set of named parameters provided to the called service.
33
33
  * @param {ServiceExecContext} serviceExecContext The context in which the service call is being executed.
@@ -44,7 +44,7 @@ const messageDispatcher = require( "#message-dispatcher" );
44
44
  class ServiceExecutor extends MessageObserver {
45
45
 
46
46
  #serviceInterface = {};
47
- /** @type VerifyAccess */
47
+ /** @type VerifyAccessMethod */
48
48
  #verifyAccess;
49
49
 
50
50
  /**
@@ -114,7 +114,7 @@ class ServiceExecutor extends MessageObserver {
114
114
  * Used to setup the method for service access verification.
115
115
  *
116
116
  * @method
117
- * @param {VerifyAccess} verifyAccess
117
+ * @param {VerifyAccessMethod} verifyAccess
118
118
  * @public
119
119
  */
120
120
  configureVerifyAccess( verifyAccess ) {
@@ -131,18 +131,19 @@ class ServiceExecutor extends MessageObserver {
131
131
  * NOTE: If the same version of the service handler already exists, it will be overridden!
132
132
  *
133
133
  * @method
134
- * @param {ServiceHandler} serviceHandler
134
+ * @param {ServiceHandlerMethod} serviceHandler
135
135
  * @param {ServiceDefinition} serviceDefinition
136
+ * @param {ServiceInstance} serviceInstance This will be used as context to bind all business services.
136
137
  * @public
137
138
  */
138
- addServiceHandler( serviceHandler, serviceDefinition ) {
139
+ addServiceHandler( serviceHandler, serviceDefinition, serviceInstance ) {
139
140
  if ( !this.#serviceInterface[ serviceDefinition.serviceAlias ] ) {
140
141
  this.#serviceInterface[ serviceDefinition.serviceAlias ] = {};
141
142
  }
142
143
  if ( this.#serviceInterface[ serviceDefinition.serviceAlias ][ serviceDefinition.serviceVersion ] ) {
143
144
  logger.log( `Service handler for '${ serviceDefinition.serviceAlias }' version '${ serviceDefinition.serviceVersion }' already existed and will be overridden.`, logger.logSeverity.WARNING );
144
145
  }
145
- this.#serviceInterface[ serviceDefinition.serviceAlias ][ serviceDefinition.serviceVersion ] = serviceHandler.bind( this, _.cloneDeep( serviceDefinition ) );
146
+ this.#serviceInterface[ serviceDefinition.serviceAlias ][ serviceDefinition.serviceVersion ] = serviceHandler.bind( serviceInstance, _.cloneDeep( serviceDefinition ) );
146
147
  }
147
148
 
148
149
  /* Private interface */
@@ -203,7 +204,7 @@ class ServiceExecutor extends MessageObserver {
203
204
  *
204
205
  * @method
205
206
  * @param {ServiceAddress} serviceAddress
206
- * @returns {Promise<ServiceHandler>}
207
+ * @returns {Promise<ServiceHandlerMethod>}
207
208
  * @private
208
209
  */
209
210
  #identifyService( serviceAddress ) {
@@ -5,7 +5,6 @@
5
5
 
6
6
  const ServiceConsumer = require( "#service-consumer" );
7
7
  const _ = require( "lodash" );
8
- const fs = require( "fs-extra" );
9
8
  const exceptions = require( "#exceptions" );
10
9
  const logger = require( "#logger" );
11
10
  const messageDispatcher = require( "#message-dispatcher" );
@@ -128,25 +127,19 @@ class ServiceProvider extends ServiceConsumer {
128
127
  *
129
128
  * @method
130
129
  * @param {ServiceDefinition} serviceDefinition Full service definition object.
131
- * @param {ServiceHandler} [defaultServiceHandler=undefined] A default service handler in case there is one.
130
+ * @param {ServiceHandlerMethod} [defaultServiceHandler=undefined] A default service handler in case there is one.
132
131
  * @return {Promise}
133
132
  * @public
134
133
  */
135
134
  registerService( serviceDefinition, defaultServiceHandler = undefined ) {
136
135
  return new Promise( ( resolve, reject ) => {
137
- /** @type {ServiceHandler} */
136
+ /** @type {ServiceHandlerMethod} */
138
137
  let serviceHandler = null;
139
138
  if ( serviceDefinition.serviceFile ) {
140
- let filePath = process.cwd() + serviceDefinition.serviceFile + ( ( _.endsWith( serviceDefinition.serviceFile, ".js" ) ) ? "" : ".js" );
141
- if ( fs.existsSync( filePath ) ) {
142
- // try to load the service handler dynamically from the file:
143
- try {
144
- serviceHandler = require( filePath ).service;
145
- } catch ( error ) {
146
- logger.log( `Specified service handler file '${ serviceDefinition.serviceFile }' could not be loaded!`, logger.logSeverity.ERROR, error );
147
- }
148
- } else {
149
- logger.log( `Specified service handler file '${ serviceDefinition.serviceFile }' was not found in the specified location!`, logger.logSeverity.WARNING );
139
+ try {
140
+ serviceHandler = require( serviceDefinition.serviceFile ).service;
141
+ } catch ( error ) {
142
+ logger.log( `Specified service handler file '${ serviceDefinition.serviceFile }' could not be loaded!`, logger.logSeverity.ERROR, error );
150
143
  }
151
144
  } else {
152
145
  if ( typeof ( defaultServiceHandler ) === "function" ) {
@@ -160,7 +153,7 @@ class ServiceProvider extends ServiceConsumer {
160
153
  if ( typeof ( serviceHandler ) === "function" ) {
161
154
  // make sure we have a version and parent service provider specified:
162
155
  serviceDefinition.serviceVersion = serviceDefinition.serviceVersion || 1;
163
- this.#serviceExecutor.addServiceHandler( serviceHandler, serviceDefinition );
156
+ this.#serviceExecutor.addServiceHandler( serviceHandler, serviceDefinition, this );
164
157
  resolve();
165
158
  } else {
166
159
  reject( exceptions.raise( exceptions.exceptionCode.E_GEN_BAD_SERVICE_HANDLER ) );
@@ -173,7 +166,7 @@ class ServiceProvider extends ServiceConsumer {
173
166
  *
174
167
  * @method
175
168
  * @param {ServiceDefinition[]} serviceDefinitions
176
- * @param {ServiceHandler} [defaultServiceHandler=undefined]
169
+ * @param {ServiceHandlerMethod} [defaultServiceHandler=undefined]
177
170
  * @return {Promise}
178
171
  * @public
179
172
  */
package/package.json CHANGED
@@ -1,14 +1,15 @@
1
1
  {
2
2
  "name": "@ti-engine/core",
3
- "version": "1.0.1",
3
+ "version": "1.0.5",
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": "ISC",
7
7
  "exports": {
8
+ "./exceptions": "./utils/exceptions.js",
9
+ "./logger": "./utils/logger.js",
8
10
  "./service-consumer": "./components/service-consumer.js",
9
11
  "./service-instance": "./components/service-instance.js",
10
12
  "./service-provider": "./components/service-provider.js",
11
- "./exceptions": "./utils/exceptions.js",
12
13
  "./tools": "./utils/tools.js"
13
14
  },
14
15
  "imports": {
@@ -36,17 +37,22 @@
36
37
  "#service-executor": "./components/service-executor.js",
37
38
  "#service-instance": "./components/service-instance.js",
38
39
  "#service-provider": "./components/service-provider.js",
39
- "#settings": "./settings.json",
40
+ "#settings": "./bin/settings.json",
40
41
  "#tools": "./utils/tools.js"
41
42
  },
42
43
  "dependencies": {
44
+ "dotenv": "^10.0.0",
43
45
  "fs-extra": "^10.0.0",
44
46
  "lodash": "^4.17.21",
45
47
  "node-schedule": "^2.0.0",
46
- "ioredis": "^4.27.7"
48
+ "ioredis": "^4.28.1"
47
49
  },
48
50
  "optionalDependencies": {
49
- "@google-cloud/error-reporting": "^2.0.2"
51
+ "@google-cloud/error-reporting": "^2.0.4"
52
+ },
53
+ "repository": {
54
+ "type": "git",
55
+ "url": "https://github.com/Belleal/ti-engine.git"
50
56
  },
51
57
  "engines": {
52
58
  "node": ">=14.17.0"
package/utils/config.js CHANGED
@@ -23,6 +23,10 @@ const tools = require( "#tools" );
23
23
  * @property {EnvironmentVariable} env.TI_LOG_CONSOLE_ENABLED
24
24
  * @property {EnvironmentVariable} env.TI_LOG_MIN_LEVEL
25
25
  * @property {EnvironmentVariable} env.TI_LOG_USED_JSON
26
+ * @property {EnvironmentVariable} env.TI_MEMORY_CACHE_AUTH_KEY
27
+ * @property {EnvironmentVariable} env.TI_MEMORY_CACHE_DB
28
+ * @property {EnvironmentVariable} env.TI_MEMORY_CACHE_HOST
29
+ * @property {EnvironmentVariable} env.TI_MEMORY_CACHE_PORT
26
30
  * @property {EnvironmentVariable} env.TI_OPERATION_MODE
27
31
  */
28
32
 
@@ -116,10 +120,16 @@ const settings = require( "#settings" );
116
120
 
117
121
  // override remaining settings with ENV variables (if provided):
118
122
  if ( settings.auditing ) {
119
- settings.auditing.logMinLevel = ( process.env.TI_LOG_MIN_LEVEL !== undefined ) ? process.env.TI_LOG_CONSOLE_ENABLED : settings.auditing.logMinLevel;
123
+ settings.auditing.logMinLevel = ( process.env.TI_LOG_MIN_LEVEL !== undefined ) ? process.env.TI_LOG_MIN_LEVEL : settings.auditing.logMinLevel;
120
124
  settings.auditing.logConsoleEnabled = ( process.env.TI_LOG_CONSOLE_ENABLED !== undefined ) ? tools.toBool( process.env.TI_LOG_CONSOLE_ENABLED ) : settings.auditing.logConsoleEnabled;
121
125
  settings.auditing.logUsesJSON = ( process.env.TI_LOG_USED_JSON !== undefined ) ? tools.toBool( process.env.TI_LOG_USED_JSON ) : settings.auditing.logUsesJSON;
122
126
  }
127
+ if ( settings.memoryCache ) {
128
+ settings.memoryCache.authKey = ( process.env.TI_MEMORY_CACHE_AUTH_KEY !== undefined ) ? process.env.TI_MEMORY_CACHE_AUTH_KEY : settings.memoryCache.authKey;
129
+ settings.memoryCache.redisDB = ( process.env.TI_MEMORY_CACHE_DB !== undefined ) ? process.env.TI_MEMORY_CACHE_DB : settings.memoryCache.redisDB;
130
+ settings.memoryCache.redisHost = ( process.env.TI_MEMORY_CACHE_HOST !== undefined ) ? process.env.TI_MEMORY_CACHE_HOST : settings.memoryCache.redisHost;
131
+ settings.memoryCache.redisPort = ( process.env.TI_MEMORY_CACHE_PORT !== undefined ) ? process.env.TI_MEMORY_CACHE_PORT : settings.memoryCache.redisPort;
132
+ }
123
133
 
124
134
  // make sure GCloud is enabled before trying to setup it:
125
135
  if ( process.env.TI_GCLOUD_ENABLED === true && settings.gcloudIntegration ) {
@@ -69,8 +69,9 @@ class Exception {
69
69
  * @param {string} id The unique ID to be assigned to this exception.
70
70
  * @param {TiExceptionCode} exceptionCode An unique exception identifier. If this is not recognized, the default error code will be used instead.
71
71
  * @param {Object} [data] Any additional data to insert into the exception.
72
+ * @param {string} [description] Description of the exception.
72
73
  */
73
- constructor( id, exceptionCode, data ) {
74
+ constructor( id, exceptionCode, data, description ) {
74
75
  exceptionCode = ( exceptionCodeEnum.properties[ exceptionCode ] ) ? exceptionCode : module.exports.exceptionCode.E_UNKNOWN_ERROR;
75
76
 
76
77
  this.#id = id;
@@ -184,7 +185,8 @@ class Exception {
184
185
  code: this.code,
185
186
  httpCode: this.httpCode,
186
187
  label: this.label,
187
- description: this.description
188
+ description: this.description,
189
+ data: this.data
188
190
  };
189
191
  }
190
192
  }
@@ -211,6 +213,8 @@ module.exports.raise = ( source, data, exceptionID ) => {
211
213
  exception = new Exception( exceptionID || tools.getUUID(), module.exports.exceptionCode.E_GEN_JS_INTERNAL_ERROR, {
212
214
  message: source
213
215
  } );
216
+ } else if ( _.isObjectLike( source ) ) {
217
+ exception = new Exception( exceptionID || ( source.id || tools.getUUID() ), source.code || module.exports.exceptionCode.E_GEN_JS_INTERNAL_ERROR, source.data, source.description );
214
218
  } else {
215
219
  exception = new Exception( exceptionID || tools.getUUID(), ( exceptionCodeEnum.properties[ source ] ) ? source : module.exports.exceptionCode.E_UNKNOWN_ERROR );
216
220
  }