@ti-engine/core 1.0.0

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.
@@ -0,0 +1,241 @@
1
+ /*
2
+ * SPDX-FileCopyrightText: © 2021 Boris Kostadinov <kostadinov.boris@gmail.com>
3
+ * SPDX-License-Identifier: ICU
4
+ */
5
+
6
+ const _ = require( "lodash" );
7
+ const tools = require( "#tools" );
8
+
9
+ /**
10
+ * Enum for listing all system-recognized exceptions.
11
+ *
12
+ * @readonly
13
+ * @enum {number}
14
+ */
15
+ let exceptionCodeEnum = tools.enum( {
16
+ E_UNKNOWN_ERROR: [ 0, "unknown error", "Unidentified error encountered or unrecognized exception code provided." ],
17
+ /** General exceptions - codes under 1xxx */
18
+ E_GEN_JS_INTERNAL_ERROR: [ 1000, "js internal error", "Error thrown by internal JS source." ],
19
+ E_GEN_ABSTRACT_CLASS_INIT: [ 1001, "abstract class init", "Attempt to construct an abstract class detected." ],
20
+ E_GEN_ABSTRACT_METHOD_CALL: [ 1002, "abstract method call", "Attempt to call an abstract method detected." ],
21
+ E_GEN_INVALID_SERVICE_DOMAIN_NAME: [ 1003, "invalid service domain name", "Invalid or no service domain name provided at microservice startup." ],
22
+ E_GEN_SYSTEM_CACHE_UNAVAILABLE: [ 1004, "system cache unavailable", "The system cache required for proper engine operation is unavailable." ],
23
+ E_GEN_BAD_SERVICE_HANDLER: [ 1005, "bad service handler", "The provided service handler is not a proper function." ],
24
+ /** Security & Administration related exceptions - codes under 2xxx */
25
+ E_SEC_INVALID_AUTH_TOKEN: [ 2000, "invalid auth token", "Invalid authorization token provided." ],
26
+ E_SEC_INVALID_EXPIRED_SESSION: [ 2001, "invalid or expired session", "Invalid or expired session encountered." ],
27
+ E_SEC_UNAUTHORIZED_ACCESS: [ 2002, "unauthorized access", "Attempt for unauthorized access detected." ],
28
+ /** Cross-Application Communication exceptions - codes under 3xxx */
29
+ E_COM_GENERAL_ERROR: [ 3000, "general communication error", "General error during cross-application communication." ],
30
+ E_COM_MESSAGE_SENDER_UNAVAILABLE: [ 3001, "message sender unavailable", "The message sender instance is currently unavailable." ],
31
+ E_COM_SERVICE_EXEC_TIMEOUT: [ 3002, "service exec timeout", "The execution of a service could not complete within the allowed timeout." ],
32
+ E_COM_SERVICE_NOT_REGISTERED: [ 3003, "service not registered", "The specified service is not found in the service registry." ],
33
+ E_COM_SERVICE_NOT_FOUND: [ 3004, "service not found", "The specified service is not found in the service definition interface." ],
34
+ E_COM_SERVICE_HANDLER_NOT_FOUND: [ 3005, "service handler not found", "No handler found in the interface for the specified service or service version." ],
35
+ // E_COM_UNRECOGNIZED_API_URL: [ 301, "unrecognized api url", "Attempt to access unrecognized or invalid API URL." ],
36
+ // E_COM_MISSING_REQUIRED_ARGUMENTS: [ 302, "missing required arguments", "Attempt to execute operation without all required arguments." ],
37
+ // E_COM_UNRECOGNIZED_RESPONSE_STRUCTURE: [ 303, "unrecognized response structure", "The received response has unrecognized structure and cannot be parsed or examined." ],
38
+ // E_COM_RECEIVED_ERROR_RESPONSE: [ 304, "received error response", "The received response indicates error in the external system." ],
39
+ // E_COM_JSON_RPC_DATA_INVALID: [ 305, "json rpc data invalid", "The JSON RPC 2.0 data being verified is not valid." ],
40
+ // E_COM_NO_OPEN_CONNECTION: [ 306, "no open connection", "Attempting to do communication request while there is no open connection available." ],
41
+ // E_COM_REQUEST_ERROR_RESPONSE: [ 307, "received error response", "The received response indicates error in the external system." ],
42
+ // E_COM_CONNECTION_TIMEOUT: [ 308, "connection timeout", "Attempting to do communication request but request timeout." ],
43
+ // E_COM_INVALID_API_MAPPING: [ 309, "invalid api mapping", "Attempt to access API URL without proper controller mapping." ],
44
+ E_COM_RETRY_ATTEMPTS_EXCEEDED: [ 3010, "retry attempts exceeded", "Connection retry attempts exceeded the configured limit." ]
45
+ } );
46
+
47
+ /**
48
+ * @typedef {number} TiExceptionCode
49
+ */
50
+ module.exports.exceptionCode = exceptionCodeEnum;
51
+
52
+ /**
53
+ * Represents an exception.
54
+ *
55
+ * @class Exception
56
+ * @public
57
+ */
58
+ class Exception {
59
+
60
+ #id = undefined;
61
+ #code = undefined;
62
+ #httpCode = undefined;
63
+ #label = undefined;
64
+ #description = undefined;
65
+ #data = undefined;
66
+
67
+ /**
68
+ * @constructor
69
+ * @param {string} id The unique ID to be assigned to this exception.
70
+ * @param {TiExceptionCode} exceptionCode An unique exception identifier. If this is not recognized, the default error code will be used instead.
71
+ * @param {Object} [data] Any additional data to insert into the exception.
72
+ */
73
+ constructor( id, exceptionCode, data ) {
74
+ exceptionCode = ( exceptionCodeEnum.properties[ exceptionCode ] ) ? exceptionCode : module.exports.exceptionCode.E_UNKNOWN_ERROR;
75
+
76
+ this.#id = id;
77
+ this.#code = exceptionCode;
78
+ this.#httpCode = undefined;
79
+ this.#label = "labels.general.exceptions." + exceptionCode;
80
+ this.#description = exceptionCodeEnum.properties[ exceptionCode ].description;
81
+ this.#data = data || {};
82
+ }
83
+
84
+ /* Public interface */
85
+
86
+ /**
87
+ * Unique identifier of the exception instance. Can be used for tracing problems with customer support cases.
88
+ *
89
+ * @method
90
+ * @return {string}
91
+ * @public
92
+ */
93
+ get id() {
94
+ return this.#id;
95
+ }
96
+
97
+ /**
98
+ * Identifier code of the exception type.
99
+ *
100
+ * @method
101
+ * @return {TiExceptionCode}
102
+ * @public
103
+ */
104
+ get code() {
105
+ return this.#code;
106
+ }
107
+
108
+ /**
109
+ * HTTP error code if relevant.
110
+ *
111
+ * @method
112
+ * @return {number}
113
+ * @public
114
+ */
115
+ get httpCode() {
116
+ return this.#httpCode;
117
+ }
118
+
119
+ /**
120
+ * HTTP error code if relevant.
121
+ *
122
+ * @method
123
+ * @param {number} httpCode
124
+ * @public
125
+ */
126
+ set httpCode( httpCode ) {
127
+ this.#httpCode = httpCode;
128
+ }
129
+
130
+ /**
131
+ * Localized label identifier.
132
+ *
133
+ * @method
134
+ * @return {string}
135
+ * @public
136
+ */
137
+ get label() {
138
+ return this.#label;
139
+ }
140
+
141
+ /**
142
+ * Description or additional technical information that is NOT localized.
143
+ *
144
+ * @method
145
+ * @return {string}
146
+ * @public
147
+ */
148
+ get description() {
149
+ return this.#description;
150
+ }
151
+
152
+ /**
153
+ * JSON containing any additional data that has relevance for the exception. Can be converted JavaScript {@link Error} object as well.
154
+ *
155
+ * @method
156
+ * @return {Object}
157
+ * @public
158
+ */
159
+ get data() {
160
+ return this.#data;
161
+ }
162
+
163
+ /**
164
+ * JSON containing any additional data that has relevance for the exception. Can be converted JavaScript {@link Error} object as well.
165
+ *
166
+ * @method
167
+ * @param {Object} data
168
+ * @public
169
+ */
170
+ set data( data ) {
171
+ this.#data = data;
172
+ }
173
+
174
+ /**
175
+ * Extracts the essential information about the Exception and returns it as JSON.
176
+ *
177
+ * @method
178
+ * @returns {Object}
179
+ * @public
180
+ */
181
+ asJSON() {
182
+ return {
183
+ id: this.id,
184
+ code: this.code,
185
+ httpCode: this.httpCode,
186
+ label: this.label,
187
+ description: this.description
188
+ };
189
+ }
190
+ }
191
+
192
+ /**
193
+ * Used to raise an exception from the provided source.
194
+ *
195
+ * @method
196
+ * @param {Error|TiExceptionCode|Exception} source Could be a standard JS Error, an ExceptionCode, or another Exception (in which case it will be raised further).
197
+ * @param {Object} [data] Additional JSON data that can accompany the exception. If more data is added on subsequent Raise calls, it will be merged with the existing one.
198
+ * @param {string} [exceptionID] Should be used only in cases when we have a recognizable exception ID beforehand. Should not be entered otherwise!
199
+ * @returns {Exception}
200
+ * @public
201
+ */
202
+ module.exports.raise = ( source, data, exceptionID ) => {
203
+ /** @type Exception */
204
+ let exception;
205
+
206
+ if ( source instanceof Error ) {
207
+ exception = new Exception( exceptionID || tools.getUUID(), module.exports.exceptionCode.E_GEN_JS_INTERNAL_ERROR, tools.errorToJSON( source ) );
208
+ } else if ( source instanceof Exception ) {
209
+ exception = source;
210
+ } else if ( _.isString( source ) ) {
211
+ exception = new Exception( exceptionID || tools.getUUID(), module.exports.exceptionCode.E_GEN_JS_INTERNAL_ERROR, {
212
+ message: source
213
+ } );
214
+ } else {
215
+ exception = new Exception( exceptionID || tools.getUUID(), ( exceptionCodeEnum.properties[ source ] ) ? source : module.exports.exceptionCode.E_UNKNOWN_ERROR );
216
+ }
217
+
218
+ // merge the default exception data with the additional one, if it's provided:
219
+ if ( data ) {
220
+ exception.data = _.mergeWith( ( exception.data || {} ), _.cloneDeep( data ), ( objValue, srcValue ) => {
221
+ return ( _.isArray( objValue ) ) ? objValue.concat( srcValue ) : undefined;
222
+ } );
223
+
224
+ // make sure to eliminate any circular dependencies inside the data object (these should never be needed for an error description):
225
+ exception.data = tools.decycle( exception.data );
226
+ }
227
+
228
+ return exception;
229
+ };
230
+
231
+ /**
232
+ * Verifies if the passed object is an Exception.
233
+ *
234
+ * @method
235
+ * @param object
236
+ * @returns {boolean}
237
+ * @public
238
+ */
239
+ module.exports.isException = ( object ) => {
240
+ return ( object instanceof Exception );
241
+ };
@@ -0,0 +1,69 @@
1
+ /*
2
+ * SPDX-FileCopyrightText: © 2021 Boris Kostadinov <kostadinov.boris@gmail.com>
3
+ * SPDX-License-Identifier: ICU
4
+ */
5
+
6
+ const _ = require( "lodash" );
7
+ const tools = require( "#tools" );
8
+ const exceptions = require( "#exceptions" );
9
+
10
+ /**
11
+ * Enum for specifying the log entry severity. This is based on the Google Stackdriver severity levels.
12
+ *
13
+ * @readonly
14
+ * @enum {number}
15
+ */
16
+ let logSeverityEnum = tools.enum( {
17
+ DEFAULT: [ 0, "default", "The log entry has no assigned severity level." ],
18
+ DEBUG: [ 100, "debug", "Debug or trace information." ],
19
+ INFO: [ 200, "info", "Routine information, such as ongoing status or performance." ],
20
+ NOTICE: [ 300, "notice", "Normal but significant events, such as start up, shut down, or a configuration change." ],
21
+ WARNING: [ 400, "warning", "Warning events might cause problems." ],
22
+ ERROR: [ 500, "error", "Error events are likely to cause problems." ],
23
+ CRITICAL: [ 600, "critical", "Critical events cause more severe problems or outages." ],
24
+ ALERT: [ 700, "alert", "A person must take an action immediately." ],
25
+ EMERGENCY: [ 800, "emergency", "One or more systems are unusable." ]
26
+ } );
27
+
28
+ /**
29
+ * @typedef {number} TiLogSeverity
30
+ */
31
+ module.exports.logSeverity = logSeverityEnum;
32
+
33
+ /**
34
+ * Used to safely return the name of a severity code.
35
+ *
36
+ * @method
37
+ * @param {TiLogSeverity} severity
38
+ * @returns {string}
39
+ */
40
+ module.exports.getSeverityName = ( severity ) => {
41
+ return tools.getEnumName( logSeverityEnum, severity, "unknown" );
42
+ };
43
+
44
+ /**
45
+ * Used to generate and store a log entry in the active cache.
46
+ *
47
+ * @method
48
+ * @param {string} message The primary log message.
49
+ * @param {TiLogSeverity} [level=DEFAULT] The log severity level. If the current log filtering setting is higher than this then the log entry will be ignored.
50
+ * @param {Object|Error|Exception} [data={}] Optional JSON data containing details of the log entry.
51
+ * @param {string} [thread='main'] The logging thread to which the log entry belongs.
52
+ * @public
53
+ */
54
+ module.exports.log = ( message, level = logSeverityEnum.DEFAULT, data = {}, thread = "main" ) => {
55
+ /** @type {Auditing} */
56
+ const auditing = require( "#auditing" );
57
+
58
+ if ( data instanceof Error ) {
59
+ data = tools.errorToJSON( data );
60
+ } else if ( exceptions.isException( data ) ) {
61
+ data = {
62
+ id: data.id,
63
+ description: data.description,
64
+ details: !_.isEmpty( data.data ) ? data.data : undefined
65
+ };
66
+ }
67
+
68
+ auditing.log( message, level, thread, data );
69
+ };