@ti-engine/core 1.6.1 → 1.7.2

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.
Files changed (33) hide show
  1. package/CHANGELOG.md +383 -364
  2. package/LICENSE.md +321 -321
  3. package/README.md +597 -548
  4. package/bin/localization/labels.json +122 -122
  5. package/bin/settings.json +41 -41
  6. package/bin/start-instance.js +164 -156
  7. package/components/auditing.js +191 -191
  8. package/components/connection-observer.js +72 -72
  9. package/components/definitions.types.js +248 -248
  10. package/components/exchange/default/default-message-exchange.js +136 -136
  11. package/components/exchange/default/default-message-receiver.js +101 -101
  12. package/components/exchange/default/default-message-sender.js +100 -100
  13. package/components/exchange/message-dispatcher.js +168 -168
  14. package/components/exchange/message-exchange.js +449 -449
  15. package/components/exchange/message-handler.js +235 -234
  16. package/components/exchange/message-memory-cache.js +190 -190
  17. package/components/exchange/message-observer.js +126 -126
  18. package/components/exchange/message-receiver.js +181 -181
  19. package/components/exchange/message-sender.js +143 -143
  20. package/components/exchange/message-tracer.js +212 -212
  21. package/components/service-caller.js +370 -370
  22. package/components/service-consumer.js +131 -131
  23. package/components/service-executor.js +278 -278
  24. package/components/service-instance.js +316 -316
  25. package/components/service-provider.js +251 -251
  26. package/integrations/redis-integration.js +591 -591
  27. package/package.json +89 -90
  28. package/utils/cache.js +772 -772
  29. package/utils/config.js +103 -103
  30. package/utils/exceptions.js +368 -368
  31. package/utils/localization.js +298 -298
  32. package/utils/logger.js +82 -82
  33. package/utils/tools.js +632 -632
@@ -1,252 +1,252 @@
1
- /*
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-2026 Boris Kostadinov <kostadinov.boris@gmail.com>
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
- * 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
- * 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
- */
8
-
9
- const ServiceConsumer = require( "#service-consumer" );
10
- const _ = require( "lodash" );
11
- const path = require( "path" );
12
- const exceptions = require( "#exceptions" );
13
- const logger = require( "#logger" );
14
- const messageDispatcher = require( "#message-dispatcher" );
15
-
16
- /**
17
- * Abstract class used to define a Service Provider behavior.
18
- * <br/>
19
- * NOTE: Inherit this to create a module that can be started as a microservice provider instance.
20
- * <br/>
21
- * NOTE: A service provider is a microservice that offers an API of named business services that can be invoked by other
22
- * microservices using {@link ServiceCall} objects. The provider will take care of the actual execution of that service and
23
- * therefore acts as a "black box". The only necessary items are the service address and optional inbound parameters to be
24
- * used in that service's logic. The result of the service's execution will be bundled in an {@link ServiceCallResult}
25
- * object and returned to the caller.
26
- *
27
- * @class ServiceProvider
28
- * @extends ServiceConsumer
29
- * @abstract
30
- * @public
31
- */
32
- class ServiceProvider extends ServiceConsumer {
33
-
34
- /** @type ServiceExecutor */
35
- #serviceExecutor;
36
-
37
- /**
38
- * @constructor
39
- * @param {string} serviceDomainName The service domain name for this service instance.
40
- * @param {ServiceConfiguration} [serviceConfig] The JSON configuration for this service.
41
- * @throws {TiException.E_GEN_ABSTRACT_CLASS_INIT} If this class is instantiated directly.
42
- */
43
- constructor( serviceDomainName, serviceConfig ) {
44
- super( serviceDomainName, serviceConfig );
45
-
46
- // make sure this abstract class cannot be instantiated:
47
- if ( new.target === ServiceProvider ) {
48
- throw exceptions.raise( exceptions.exceptionCode.E_GEN_ABSTRACT_CLASS_INIT, { name: this.constructor.name } );
49
- }
50
- }
51
-
52
- /* Public interface */
53
-
54
- /**
55
- * Perform initialization tasks when the service provider starts.
56
- * <br/>
57
- * NOTE: This method will be invoked automatically.
58
- * <br/>
59
- * NOTE: If you need to add more onStart logic, you can override this method but make sure to call it in the
60
- * overriding method using: super.onStart()
61
- *
62
- * @method
63
- * @returns {Promise}
64
- * @override
65
- * @public
66
- */
67
- onStart() {
68
- return new Promise( ( resolve, reject ) => {
69
- const ServiceExecutor = require( "#service-executor" );
70
-
71
- this.#serviceExecutor = new ServiceExecutor();
72
- this.#serviceExecutor.configureVerifyAccess( this.verifyAccess );
73
-
74
- super.onStart().then( () => {
75
- let serviceDefinitions = this.serviceConfig.services;
76
- return this.registerServices( serviceDefinitions );
77
- } ).then( () => {
78
- messageDispatcher.instance.addMessageObserverRequestsIn( this.#serviceExecutor );
79
- resolve();
80
- } ).catch( ( error ) => {
81
- reject( exceptions.raise( error ) );
82
- } );
83
- } );
84
- }
85
-
86
- /**
87
- * Perform shut down and cleanup tasks when the service provider stops.
88
- * <br/>
89
- * NOTE: This method will be invoked automatically.
90
- * <br/>
91
- * NOTE: If you need to add more onStop logic, you can override this method but make sure to call it in the
92
- * overriding method using: super.onStop()
93
- *
94
- * @method
95
- * @returns {Promise}
96
- * @override
97
- * @public
98
- */
99
- onStop() {
100
- return new Promise( ( resolve, reject ) => {
101
- super.onStop().then( () => {
102
- resolve();
103
- } ).catch( ( error ) => {
104
- reject( exceptions.raise( error ) );
105
- } );
106
- } );
107
- }
108
-
109
- /**
110
- * Used to report health status of the service instance for external monitoring.
111
- * This is a scheduled job that will be executed at SERVICE_HEALTH_CHECK_INTERVAL time.
112
- * <br/>
113
- * NOTE: By default, this method will update a Redis key with an expiration timer. You can override this
114
- * functionality with something custom like calling an HTTP endpoint.
115
- *
116
- * @method
117
- * @override
118
- * @virtual
119
- * @public
120
- */
121
- reportHealthy() {
122
- super.reportHealthy();
123
- }
124
-
125
- /**
126
- * Used to verify whether the service caller has authorization to access the service.
127
- * <br/>
128
- * NOTE: Override this to implement authorization check. By default, this method simply returns.
129
- *
130
- * @method
131
- * @param {string} authToken
132
- * @param {ServiceAddress} serviceAddress
133
- * @return {Promise}
134
- * @virtual
135
- * @public
136
- */
137
- verifyAccess( authToken, serviceAddress ) {
138
- return Promise.resolve();
139
- }
140
-
141
- /**
142
- * Used to register a single service to the service provider's API. One service can have multiple versions accessible at the same time.
143
- * <br/>
144
- * NOTE: This will actually bind the serviceDefinition as the first parameter of the service handler function. When creating default service handlers,
145
- * keep in mind that your first param must always be the 'serviceDefinition' and the second one will be the general 'serviceParams' object.
146
- * <br/>
147
- * NOTE: Additionally, if you intend to call another service inside the service handler, then you have to use a normal function for the handler and not
148
- * an arrow function! Arrow functions cannot bind the scope of the parent class to themselves, and you won't have access to it and its methods.
149
- *
150
- * @method
151
- * @param {ServiceDefinition} serviceDefinition Full service definition object.
152
- * @param {ServiceHandlerMethod} [defaultServiceHandler=undefined] A default service handler in case there is one.
153
- * @return {Promise}
154
- * @public
155
- */
156
- registerService( serviceDefinition, defaultServiceHandler = undefined ) {
157
- return new Promise( ( resolve, reject ) => {
158
- /** @type {ServiceHandlerMethod} */
159
- let serviceHandler = null;
160
- if ( serviceDefinition.serviceFile ) {
161
- let serviceFilePath = path.normalize( path.join( process.cwd(), serviceDefinition.serviceFile ) );
162
- try {
163
- serviceHandler = require( serviceFilePath ).service;
164
- } catch ( error ) {
165
- logger.log( `Specified service handler file '${ serviceFilePath }' could not be loaded!`, logger.logSeverity.ERROR, error );
166
- }
167
- } else {
168
- if ( typeof ( defaultServiceHandler ) === "function" ) {
169
- serviceHandler = defaultServiceHandler;
170
- } else {
171
- logger.log( "A service cannot be registered without provided default service handler at the very least!", logger.logSeverity.WARNING, serviceDefinition );
172
- }
173
- }
174
-
175
- // if we have a valid service handler, proceed with the registration:
176
- if ( typeof ( serviceHandler ) === "function" ) {
177
- // make sure we have a version and parent service provider specified:
178
- serviceDefinition.serviceVersion = serviceDefinition.serviceVersion || 1;
179
- this.#serviceExecutor.addServiceHandler( serviceHandler, serviceDefinition, this ).then( () => {
180
- resolve();
181
- } ).catch( ( error ) => {
182
- reject( exceptions.raise( error ) );
183
- } );
184
- } else {
185
- reject( exceptions.raise( exceptions.exceptionCode.E_GEN_BAD_SERVICE_HANDLER ) );
186
- }
187
- } );
188
- }
189
-
190
- /**
191
- * Used to register multiple services from the provided service definitions.
192
- *
193
- * @method
194
- * @param {ServiceDefinition[]} serviceDefinitions
195
- * @param {ServiceHandlerMethod} [defaultServiceHandler=undefined]
196
- * @return {Promise}
197
- * @public
198
- */
199
- registerServices( serviceDefinitions, defaultServiceHandler = undefined ) {
200
- return new Promise( ( resolve, reject ) => {
201
- if ( serviceDefinitions ) {
202
- logger.log( `Starting service registration process. There is ${ ( ( defaultServiceHandler ) ? "" : "NO" ) } default service handler provided.`, logger.logSeverity.INFO );
203
-
204
- let promises = [];
205
- _.forEach( serviceDefinitions, ( serviceDefinition ) => {
206
- // NOTE: we are not going to interrupt the service interface loading if one of the services fails to load or is not found!
207
- // If this happens, a corresponding log entry will be created, but the loading process will continue. Therefore, the following
208
- // promise will always be resolved (unless a programming error occurs in it, of course).
209
- let registrationPromise = ( serviceDefinition, defaultServiceHandler ) => {
210
- return new Promise( ( resolve ) => {
211
- this.registerService( serviceDefinition, defaultServiceHandler ).then( () => {
212
- resolve( true );
213
- } ).catch( ( error ) => {
214
- logger.log( `Service registration failed for '${ serviceDefinition?.serviceAlias || "unknown-service" }'.`, logger.logSeverity.ERROR, error );
215
- resolve( false );
216
- } );
217
- } );
218
- };
219
- promises.push( registrationPromise( serviceDefinition, defaultServiceHandler ) );
220
- } );
221
-
222
- Promise.all( promises ).then( ( result ) => {
223
- let registrationResults = _.countBy( result, ( value ) => {
224
- return value === true;
225
- } );
226
- logger.log( `Registration of defined services completed with ${ registrationResults[ "true" ] || 0 } successful out of ${ serviceDefinitions.length } total.`, logger.logSeverity.INFO );
227
-
228
- resolve();
229
- } ).catch( ( error ) => {
230
- reject( exceptions.raise( error ) );
231
- } );
232
- } else {
233
- logger.log( `Service registration process skipped as there are no service definitions provided.`, logger.logSeverity.NOTICE );
234
- resolve();
235
- }
236
- } );
237
- }
238
-
239
- /**
240
- * Used to get an ordered list of all currently registered services. This does not include the service versions.
241
- *
242
- * @method
243
- * @returns {string[]}
244
- * @public
245
- */
246
- getRegisteredServices() {
247
- return _.sortBy( _.keys( this.#serviceExecutor.serviceInterface ) );
248
- }
249
-
250
- }
251
-
1
+ /*
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-2026 Boris Kostadinov <kostadinov.boris@gmail.com>
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
+ * 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
+ * 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
+ */
8
+
9
+ const ServiceConsumer = require( "#service-consumer" );
10
+ const _ = require( "lodash" );
11
+ const path = require( "path" );
12
+ const exceptions = require( "#exceptions" );
13
+ const logger = require( "#logger" );
14
+ const messageDispatcher = require( "#message-dispatcher" );
15
+
16
+ /**
17
+ * Abstract class used to define a Service Provider behavior.
18
+ * <br/>
19
+ * NOTE: Inherit this to create a module that can be started as a microservice provider instance.
20
+ * <br/>
21
+ * NOTE: A service provider is a microservice that offers an API of named business services that can be invoked by other
22
+ * microservices using {@link ServiceCall} objects. The provider will take care of the actual execution of that service and
23
+ * therefore acts as a "black box". The only necessary items are the service address and optional inbound parameters to be
24
+ * used in that service's logic. The result of the service's execution will be bundled in an {@link ServiceCallResult}
25
+ * object and returned to the caller.
26
+ *
27
+ * @class ServiceProvider
28
+ * @extends ServiceConsumer
29
+ * @abstract
30
+ * @public
31
+ */
32
+ class ServiceProvider extends ServiceConsumer {
33
+
34
+ /** @type ServiceExecutor */
35
+ #serviceExecutor;
36
+
37
+ /**
38
+ * @constructor
39
+ * @param {string} serviceDomainName The service domain name for this service instance.
40
+ * @param {ServiceConfiguration} [serviceConfig] The JSON configuration for this service.
41
+ * @throws {TiException.E_GEN_ABSTRACT_CLASS_INIT} If this class is instantiated directly.
42
+ */
43
+ constructor( serviceDomainName, serviceConfig ) {
44
+ super( serviceDomainName, serviceConfig );
45
+
46
+ // make sure this abstract class cannot be instantiated:
47
+ if ( new.target === ServiceProvider ) {
48
+ throw exceptions.raise( exceptions.exceptionCode.E_GEN_ABSTRACT_CLASS_INIT, { name: this.constructor.name } );
49
+ }
50
+ }
51
+
52
+ /* Public interface */
53
+
54
+ /**
55
+ * Perform initialization tasks when the service provider starts.
56
+ * <br/>
57
+ * NOTE: This method will be invoked automatically.
58
+ * <br/>
59
+ * NOTE: If you need to add more onStart logic, you can override this method but make sure to call it in the
60
+ * overriding method using: super.onStart()
61
+ *
62
+ * @method
63
+ * @returns {Promise}
64
+ * @override
65
+ * @public
66
+ */
67
+ onStart() {
68
+ return new Promise( ( resolve, reject ) => {
69
+ const ServiceExecutor = require( "#service-executor" );
70
+
71
+ this.#serviceExecutor = new ServiceExecutor();
72
+ this.#serviceExecutor.configureVerifyAccess( this.verifyAccess );
73
+
74
+ super.onStart().then( () => {
75
+ let serviceDefinitions = this.serviceConfig.services;
76
+ return this.registerServices( serviceDefinitions );
77
+ } ).then( () => {
78
+ messageDispatcher.instance.addMessageObserverRequestsIn( this.#serviceExecutor );
79
+ resolve();
80
+ } ).catch( ( error ) => {
81
+ reject( exceptions.raise( error ) );
82
+ } );
83
+ } );
84
+ }
85
+
86
+ /**
87
+ * Perform shut down and cleanup tasks when the service provider stops.
88
+ * <br/>
89
+ * NOTE: This method will be invoked automatically.
90
+ * <br/>
91
+ * NOTE: If you need to add more onStop logic, you can override this method but make sure to call it in the
92
+ * overriding method using: super.onStop()
93
+ *
94
+ * @method
95
+ * @returns {Promise}
96
+ * @override
97
+ * @public
98
+ */
99
+ onStop() {
100
+ return new Promise( ( resolve, reject ) => {
101
+ super.onStop().then( () => {
102
+ resolve();
103
+ } ).catch( ( error ) => {
104
+ reject( exceptions.raise( error ) );
105
+ } );
106
+ } );
107
+ }
108
+
109
+ /**
110
+ * Used to report health status of the service instance for external monitoring.
111
+ * This is a scheduled job that will be executed at SERVICE_HEALTH_CHECK_INTERVAL time.
112
+ * <br/>
113
+ * NOTE: By default, this method will update a Redis key with an expiration timer. You can override this
114
+ * functionality with something custom like calling an HTTP endpoint.
115
+ *
116
+ * @method
117
+ * @override
118
+ * @virtual
119
+ * @public
120
+ */
121
+ reportHealthy() {
122
+ super.reportHealthy();
123
+ }
124
+
125
+ /**
126
+ * Used to verify whether the service caller has authorization to access the service.
127
+ * <br/>
128
+ * NOTE: Override this to implement authorization check. By default, this method simply returns.
129
+ *
130
+ * @method
131
+ * @param {string} authToken
132
+ * @param {ServiceAddress} serviceAddress
133
+ * @return {Promise}
134
+ * @virtual
135
+ * @public
136
+ */
137
+ verifyAccess( authToken, serviceAddress ) {
138
+ return Promise.resolve();
139
+ }
140
+
141
+ /**
142
+ * Used to register a single service to the service provider's API. One service can have multiple versions accessible at the same time.
143
+ * <br/>
144
+ * NOTE: This will actually bind the serviceDefinition as the first parameter of the service handler function. When creating default service handlers,
145
+ * keep in mind that your first param must always be the 'serviceDefinition' and the second one will be the general 'serviceParams' object.
146
+ * <br/>
147
+ * NOTE: Additionally, if you intend to call another service inside the service handler, then you have to use a normal function for the handler and not
148
+ * an arrow function! Arrow functions cannot bind the scope of the parent class to themselves, and you won't have access to it and its methods.
149
+ *
150
+ * @method
151
+ * @param {ServiceDefinition} serviceDefinition Full service definition object.
152
+ * @param {ServiceHandlerMethod} [defaultServiceHandler=undefined] A default service handler in case there is one.
153
+ * @return {Promise}
154
+ * @public
155
+ */
156
+ registerService( serviceDefinition, defaultServiceHandler = undefined ) {
157
+ return new Promise( ( resolve, reject ) => {
158
+ /** @type {ServiceHandlerMethod} */
159
+ let serviceHandler = null;
160
+ if ( serviceDefinition.serviceFile ) {
161
+ let serviceFilePath = path.normalize( path.join( process.cwd(), serviceDefinition.serviceFile ) );
162
+ try {
163
+ serviceHandler = require( serviceFilePath ).service;
164
+ } catch ( error ) {
165
+ logger.log( `Specified service handler file '${ serviceFilePath }' could not be loaded!`, logger.logSeverity.ERROR, error );
166
+ }
167
+ } else {
168
+ if ( typeof ( defaultServiceHandler ) === "function" ) {
169
+ serviceHandler = defaultServiceHandler;
170
+ } else {
171
+ logger.log( "A service cannot be registered without provided default service handler at the very least!", logger.logSeverity.WARNING, serviceDefinition );
172
+ }
173
+ }
174
+
175
+ // if we have a valid service handler, proceed with the registration:
176
+ if ( typeof ( serviceHandler ) === "function" ) {
177
+ // make sure we have a version and parent service provider specified:
178
+ serviceDefinition.serviceVersion = serviceDefinition.serviceVersion || 1;
179
+ this.#serviceExecutor.addServiceHandler( serviceHandler, serviceDefinition, this ).then( () => {
180
+ resolve();
181
+ } ).catch( ( error ) => {
182
+ reject( exceptions.raise( error ) );
183
+ } );
184
+ } else {
185
+ reject( exceptions.raise( exceptions.exceptionCode.E_GEN_BAD_SERVICE_HANDLER ) );
186
+ }
187
+ } );
188
+ }
189
+
190
+ /**
191
+ * Used to register multiple services from the provided service definitions.
192
+ *
193
+ * @method
194
+ * @param {ServiceDefinition[]} serviceDefinitions
195
+ * @param {ServiceHandlerMethod} [defaultServiceHandler=undefined]
196
+ * @return {Promise}
197
+ * @public
198
+ */
199
+ registerServices( serviceDefinitions, defaultServiceHandler = undefined ) {
200
+ return new Promise( ( resolve, reject ) => {
201
+ if ( serviceDefinitions ) {
202
+ logger.log( `Starting service registration process. There is ${ ( ( defaultServiceHandler ) ? "" : "NO" ) } default service handler provided.`, logger.logSeverity.INFO );
203
+
204
+ let promises = [];
205
+ _.forEach( serviceDefinitions, ( serviceDefinition ) => {
206
+ // NOTE: we are not going to interrupt the service interface loading if one of the services fails to load or is not found!
207
+ // If this happens, a corresponding log entry will be created, but the loading process will continue. Therefore, the following
208
+ // promise will always be resolved (unless a programming error occurs in it, of course).
209
+ let registrationPromise = ( serviceDefinition, defaultServiceHandler ) => {
210
+ return new Promise( ( resolve ) => {
211
+ this.registerService( serviceDefinition, defaultServiceHandler ).then( () => {
212
+ resolve( true );
213
+ } ).catch( ( error ) => {
214
+ logger.log( `Service registration failed for '${ serviceDefinition?.serviceAlias || "unknown-service" }'.`, logger.logSeverity.ERROR, error );
215
+ resolve( false );
216
+ } );
217
+ } );
218
+ };
219
+ promises.push( registrationPromise( serviceDefinition, defaultServiceHandler ) );
220
+ } );
221
+
222
+ Promise.all( promises ).then( ( result ) => {
223
+ let registrationResults = _.countBy( result, ( value ) => {
224
+ return value === true;
225
+ } );
226
+ logger.log( `Registration of defined services completed with ${ registrationResults[ "true" ] || 0 } successful out of ${ serviceDefinitions.length } total.`, logger.logSeverity.INFO );
227
+
228
+ resolve();
229
+ } ).catch( ( error ) => {
230
+ reject( exceptions.raise( error ) );
231
+ } );
232
+ } else {
233
+ logger.log( `Service registration process skipped as there are no service definitions provided.`, logger.logSeverity.NOTICE );
234
+ resolve();
235
+ }
236
+ } );
237
+ }
238
+
239
+ /**
240
+ * Used to get an ordered list of all currently registered services. This does not include the service versions.
241
+ *
242
+ * @method
243
+ * @returns {string[]}
244
+ * @public
245
+ */
246
+ getRegisteredServices() {
247
+ return _.sortBy( _.keys( this.#serviceExecutor.serviceInterface ) );
248
+ }
249
+
250
+ }
251
+
252
252
  module.exports = ServiceProvider;