@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.
- package/CHANGELOG.md +383 -364
- package/LICENSE.md +321 -321
- package/README.md +597 -548
- package/bin/localization/labels.json +122 -122
- package/bin/settings.json +41 -41
- package/bin/start-instance.js +164 -156
- package/components/auditing.js +191 -191
- package/components/connection-observer.js +72 -72
- package/components/definitions.types.js +248 -248
- package/components/exchange/default/default-message-exchange.js +136 -136
- package/components/exchange/default/default-message-receiver.js +101 -101
- package/components/exchange/default/default-message-sender.js +100 -100
- package/components/exchange/message-dispatcher.js +168 -168
- package/components/exchange/message-exchange.js +449 -449
- package/components/exchange/message-handler.js +235 -234
- package/components/exchange/message-memory-cache.js +190 -190
- package/components/exchange/message-observer.js +126 -126
- package/components/exchange/message-receiver.js +181 -181
- package/components/exchange/message-sender.js +143 -143
- package/components/exchange/message-tracer.js +212 -212
- package/components/service-caller.js +370 -370
- package/components/service-consumer.js +131 -131
- package/components/service-executor.js +278 -278
- package/components/service-instance.js +316 -316
- package/components/service-provider.js +251 -251
- package/integrations/redis-integration.js +591 -591
- package/package.json +89 -90
- package/utils/cache.js +772 -772
- package/utils/config.js +103 -103
- package/utils/exceptions.js +368 -368
- package/utils/localization.js +298 -298
- package/utils/logger.js +82 -82
- package/utils/tools.js +632 -632
package/utils/tools.js
CHANGED
|
@@ -1,633 +1,633 @@
|
|
|
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 _ = require( "lodash" );
|
|
10
|
-
const crypto = require( "node:crypto" );
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Used to generate and return new UUID.
|
|
14
|
-
*
|
|
15
|
-
* @method
|
|
16
|
-
* @returns {string}
|
|
17
|
-
* @public
|
|
18
|
-
*/
|
|
19
|
-
module.exports.getUUID = () => {
|
|
20
|
-
return crypto.randomUUID( { disableEntropyCache: true } );
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Used to deep-freeze an object.
|
|
25
|
-
*
|
|
26
|
-
* @method
|
|
27
|
-
* @param {Object} object
|
|
28
|
-
* @param {WeakSet} [seen]
|
|
29
|
-
* @returns {Object}
|
|
30
|
-
* @public
|
|
31
|
-
*/
|
|
32
|
-
module.exports.deepFreeze = ( object, seen = new WeakSet() ) => {
|
|
33
|
-
if ( object === null || typeof object !== "object" || seen.has( object ) ) {
|
|
34
|
-
return object;
|
|
35
|
-
} else {
|
|
36
|
-
seen.add( object );
|
|
37
|
-
_.forOwn( object, ( value ) => {
|
|
38
|
-
module.exports.deepFreeze( value, seen );
|
|
39
|
-
} );
|
|
40
|
-
return Object.freeze( object );
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Used to create a custom Enum list.
|
|
46
|
-
*
|
|
47
|
-
* @method
|
|
48
|
-
* @param {Object} seed
|
|
49
|
-
* @returns {Object} This is a {@link TiEnum} object. Setting the proper reference here would unfortunately break IDE support.
|
|
50
|
-
* @public
|
|
51
|
-
*/
|
|
52
|
-
module.exports.enum = ( seed ) => {
|
|
53
|
-
const enumObject = Object.create( null );
|
|
54
|
-
const properties = Object.create( null );
|
|
55
|
-
const reserved = new Set( [ "properties", "name", "description", "contains", "__proto__", "prototype", "constructor" ] );
|
|
56
|
-
|
|
57
|
-
_.forOwn( seed, ( value, key ) => {
|
|
58
|
-
if ( !reserved.has( key ) ) {
|
|
59
|
-
if ( Array.isArray( value ) ) {
|
|
60
|
-
enumObject[ key ] = value[ 0 ];
|
|
61
|
-
properties[ value[ 0 ] ] = {
|
|
62
|
-
value: value[ 0 ],
|
|
63
|
-
name: value[ 1 ],
|
|
64
|
-
description: value[ 2 ]
|
|
65
|
-
};
|
|
66
|
-
} else {
|
|
67
|
-
enumObject[ key ] = value;
|
|
68
|
-
properties[ value ] = {
|
|
69
|
-
value: value,
|
|
70
|
-
name: key
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
} );
|
|
75
|
-
Object.values( properties ).forEach( Object.freeze );
|
|
76
|
-
Object.freeze( properties );
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Used to get the name of an {@link TiEnumValue} if such value exists.
|
|
80
|
-
*
|
|
81
|
-
* @method
|
|
82
|
-
* @param {number|string} value
|
|
83
|
-
* @param {string} [placeholder=undefined] If provided it will be returned when the enum value does not have a name defined.
|
|
84
|
-
* @returns {string|undefined}
|
|
85
|
-
* @public
|
|
86
|
-
*/
|
|
87
|
-
const name = ( value, placeholder = undefined ) => {
|
|
88
|
-
return ( properties[ value ] ) ? properties[ value ].name : placeholder;
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Used to get the description of an {@link TiEnumValue} if such value exists.
|
|
93
|
-
*
|
|
94
|
-
* @method
|
|
95
|
-
* @param {number|string} value
|
|
96
|
-
* @param {string} [placeholder=undefined] If provided it will be returned when the enum value does not have a description defined.
|
|
97
|
-
* @returns {string|undefined}
|
|
98
|
-
* @public
|
|
99
|
-
*/
|
|
100
|
-
const description = ( value, placeholder = undefined ) => {
|
|
101
|
-
if ( !properties[ value ] ) {
|
|
102
|
-
return placeholder;
|
|
103
|
-
} else {
|
|
104
|
-
return ( properties[ value ].description !== undefined ) ? properties[ value ].description : placeholder;
|
|
105
|
-
}
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Used to check if the provided value is contained in the provided {@link TiEnum} list.
|
|
110
|
-
*
|
|
111
|
-
* @method
|
|
112
|
-
* @param {number|string} value
|
|
113
|
-
* @returns {boolean}
|
|
114
|
-
* @public
|
|
115
|
-
*/
|
|
116
|
-
const contains = ( value ) => {
|
|
117
|
-
return !!( properties[ value ] );
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
Object.defineProperties( enumObject, {
|
|
121
|
-
contains: {
|
|
122
|
-
enumerable: false,
|
|
123
|
-
configurable: false,
|
|
124
|
-
writable: false,
|
|
125
|
-
value: contains
|
|
126
|
-
},
|
|
127
|
-
description: {
|
|
128
|
-
enumerable: false,
|
|
129
|
-
configurable: false,
|
|
130
|
-
writable: false,
|
|
131
|
-
value: description
|
|
132
|
-
},
|
|
133
|
-
name: {
|
|
134
|
-
enumerable: false,
|
|
135
|
-
configurable: false,
|
|
136
|
-
writable: false,
|
|
137
|
-
value: name
|
|
138
|
-
},
|
|
139
|
-
properties: {
|
|
140
|
-
enumerable: false,
|
|
141
|
-
configurable: false,
|
|
142
|
-
writable: false,
|
|
143
|
-
value: properties
|
|
144
|
-
}
|
|
145
|
-
} );
|
|
146
|
-
Object.freeze( enumObject );
|
|
147
|
-
|
|
148
|
-
return enumObject;
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Used to get the name of an {@link TiEnum} value if such exists.
|
|
153
|
-
*
|
|
154
|
-
* @method
|
|
155
|
-
* @deprecated Use the 'name' property of the provided {@link TiEnum} instead.
|
|
156
|
-
* @param {TiEnum} enumList
|
|
157
|
-
* @param {number|string} enumValue
|
|
158
|
-
* @param {string} [placeholder=undefined] If provided it will be returned when the enum value does not have a name defined.
|
|
159
|
-
* @returns {string|undefined}
|
|
160
|
-
* @public
|
|
161
|
-
*/
|
|
162
|
-
module.exports.getEnumName = ( enumList, enumValue, placeholder = undefined ) => {
|
|
163
|
-
return ( enumList.properties[ enumValue ] ) ? enumList.properties[ enumValue ].name : placeholder;
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Convert an Error to a JSON object.
|
|
168
|
-
* <br/>
|
|
169
|
-
* NOTE: If the value provided is not an error, then it will just be cloned.
|
|
170
|
-
*
|
|
171
|
-
* @method
|
|
172
|
-
* @param {Error} value
|
|
173
|
-
* @returns {Object}
|
|
174
|
-
* @public
|
|
175
|
-
*/
|
|
176
|
-
module.exports.errorToJSON = ( value ) => {
|
|
177
|
-
let error = {};
|
|
178
|
-
|
|
179
|
-
if ( value instanceof Error ) {
|
|
180
|
-
Object.getOwnPropertyNames( value ).forEach( ( key ) => {
|
|
181
|
-
error[ key ] = value[ key ];
|
|
182
|
-
} );
|
|
183
|
-
} else {
|
|
184
|
-
error = _.cloneDeep( value );
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
return error;
|
|
188
|
-
};
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Used to parse a value and return its boolean representation (if possible).
|
|
192
|
-
*
|
|
193
|
-
* @param {*} value
|
|
194
|
-
* @returns {boolean}
|
|
195
|
-
* @public
|
|
196
|
-
*/
|
|
197
|
-
module.exports.toBool = ( value ) => {
|
|
198
|
-
let result = true;
|
|
199
|
-
let regexp = /^false$|^0$|^no$/i;
|
|
200
|
-
|
|
201
|
-
if ( !value || regexp.test( value ) || value === "N" || value === "0" || ( _.isObjectLike( value ) && _.size( value ) === 0 ) ) {
|
|
202
|
-
result = false;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
return result;
|
|
206
|
-
};
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Used to fetch only the unique values from the provided array.
|
|
210
|
-
* <br/>
|
|
211
|
-
* Note: For objects and arrays, uniqueness is determined by reference equality. Primitives are compared by their value.
|
|
212
|
-
*
|
|
213
|
-
* @method
|
|
214
|
-
* @param {Array} array
|
|
215
|
-
* @returns {Array}
|
|
216
|
-
* @throws {TypeError} If the provided parameter is not an array
|
|
217
|
-
* @public
|
|
218
|
-
*/
|
|
219
|
-
module.exports.arrayUniques = ( array ) => {
|
|
220
|
-
if ( !Array.isArray( array ) ) {
|
|
221
|
-
throw new TypeError( "Expected an array" );
|
|
222
|
-
}
|
|
223
|
-
return [ ...new Set( array ) ];
|
|
224
|
-
};
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* Will return a UTC date string in format YYYY-MM-DD from the provided date.
|
|
228
|
-
*
|
|
229
|
-
* @method
|
|
230
|
-
* @param {Date} date
|
|
231
|
-
* @returns {string}
|
|
232
|
-
* @public
|
|
233
|
-
*/
|
|
234
|
-
module.exports.getUTCDateString = ( date ) => {
|
|
235
|
-
const year = date.getUTCFullYear();
|
|
236
|
-
const month = String( date.getUTCMonth() + 1 ).padStart( 2, "0" );
|
|
237
|
-
const day = String( date.getUTCDate() ).padStart( 2, "0" );
|
|
238
|
-
return `${ year }-${ month }-${ day }`;
|
|
239
|
-
};
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* Will return a UTC time string in format hh:mm:ss, or hh:mm:ss.MMM when useMilliseconds is true.
|
|
243
|
-
*
|
|
244
|
-
* @method
|
|
245
|
-
* @param {Date} date
|
|
246
|
-
* @param {boolean} [useMilliseconds=false]
|
|
247
|
-
* @returns {string}
|
|
248
|
-
* @public
|
|
249
|
-
*/
|
|
250
|
-
module.exports.getUTCTimeString = ( date, useMilliseconds = false ) => {
|
|
251
|
-
const hours = String( date.getUTCHours() ).padStart( 2, "0" );
|
|
252
|
-
const minutes = String( date.getUTCMinutes() ).padStart( 2, "0" );
|
|
253
|
-
const seconds = String( date.getUTCSeconds() ).padStart( 2, "0" );
|
|
254
|
-
const milliseconds = useMilliseconds ? `${ String( date.getUTCMilliseconds() ).padStart( 3, "0" ) }` : "";
|
|
255
|
-
return `${ hours }:${ minutes }:${ seconds }${ useMilliseconds ? `.${ milliseconds }` : "" }`;
|
|
256
|
-
};
|
|
257
|
-
|
|
258
|
-
/**
|
|
259
|
-
* Used to remove any circular dependencies from JSON objects.
|
|
260
|
-
* <br/>
|
|
261
|
-
* NOTE: Original file from here - https://github.com/douglascrockford/JSON-js/blob/master/cycle.js
|
|
262
|
-
*
|
|
263
|
-
* Make a deep copy of an object or array, assuring that there is at most one instance of each object or array in the
|
|
264
|
-
* resulting structure. The duplicate references (which might be forming cycles) are replaced with an object of the
|
|
265
|
-
* form of {"$ref": PATH} where the PATH is a JSONPath string that locates the first occurrence.
|
|
266
|
-
*
|
|
267
|
-
* So,
|
|
268
|
-
*
|
|
269
|
-
* var a = [];
|
|
270
|
-
* a[0] = a;
|
|
271
|
-
* return JSON.stringify(JSON.decycle(a));
|
|
272
|
-
*
|
|
273
|
-
* produces the string '[{"$ref":"$"}]'.
|
|
274
|
-
*
|
|
275
|
-
* If a replacer function is provided, then it will be called for each value. A replacer function receives a value
|
|
276
|
-
* and returns a replacement value.
|
|
277
|
-
*
|
|
278
|
-
* JSONPath is used to locate the unique object. $ indicates the top level of the object or array. [NUMBER] or [STRING]
|
|
279
|
-
* indicates a child element or property.
|
|
280
|
-
*
|
|
281
|
-
* @method
|
|
282
|
-
* @param {Object} object
|
|
283
|
-
* @param {function( Object ): Object} [replacer]
|
|
284
|
-
* @returns {Object}
|
|
285
|
-
* @public
|
|
286
|
-
*/
|
|
287
|
-
module.exports.decycle = ( object, replacer ) => {
|
|
288
|
-
"use strict";
|
|
289
|
-
|
|
290
|
-
let objects = new WeakMap();
|
|
291
|
-
|
|
292
|
-
// The derez function recurse through the object, producing the deep copy.
|
|
293
|
-
return ( function derez( value, path ) {
|
|
294
|
-
let oldPath; // The path of an earlier occurrence of value
|
|
295
|
-
let newItem; // The new object or array
|
|
296
|
-
|
|
297
|
-
// If a replacer function was provided, then call it to get a replacement value.
|
|
298
|
-
if ( replacer !== undefined ) {
|
|
299
|
-
value = replacer( value );
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// typeof null === "object", so go on if this value is really an object but not
|
|
303
|
-
// one of the weird builtin objects.
|
|
304
|
-
if (
|
|
305
|
-
typeof value === "object"
|
|
306
|
-
&& value !== null
|
|
307
|
-
&& !( value instanceof Boolean )
|
|
308
|
-
&& !( value instanceof Date )
|
|
309
|
-
&& !( value instanceof Number )
|
|
310
|
-
&& !( value instanceof RegExp )
|
|
311
|
-
&& !( value instanceof String )
|
|
312
|
-
) {
|
|
313
|
-
// If the value is an object or array, look to see if we have already
|
|
314
|
-
// encountered it. If so, return a {"$ref":PATH} object. This uses an
|
|
315
|
-
// ES6 WeakMap.
|
|
316
|
-
oldPath = objects.get( value );
|
|
317
|
-
if ( oldPath !== undefined ) {
|
|
318
|
-
return { $ref: oldPath };
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
// Otherwise, accumulate the unique value and its path.
|
|
322
|
-
objects.set( value, path );
|
|
323
|
-
|
|
324
|
-
// If it is an array, replicate the array.
|
|
325
|
-
if ( Array.isArray( value ) ) {
|
|
326
|
-
newItem = [];
|
|
327
|
-
value.forEach( ( element, i ) => {
|
|
328
|
-
newItem[ i ] = derez( element, path + "[" + i + "]" );
|
|
329
|
-
} );
|
|
330
|
-
} else {
|
|
331
|
-
// If it is an object, replicate the object.
|
|
332
|
-
newItem = {};
|
|
333
|
-
Object.keys( value ).forEach( ( name ) => {
|
|
334
|
-
newItem[ name ] = derez(
|
|
335
|
-
value[ name ],
|
|
336
|
-
path + "[" + JSON.stringify( name ) + "]"
|
|
337
|
-
);
|
|
338
|
-
} );
|
|
339
|
-
}
|
|
340
|
-
return newItem;
|
|
341
|
-
}
|
|
342
|
-
return value;
|
|
343
|
-
}( object, "$" ) );
|
|
344
|
-
};
|
|
345
|
-
|
|
346
|
-
/**
|
|
347
|
-
* Used to restore any circular dependencies from JSON objects after using the 'decycle' method.
|
|
348
|
-
* <br/>
|
|
349
|
-
* NOTE: Original file from here - https://github.com/douglascrockford/JSON-js/blob/master/cycle.js
|
|
350
|
-
*
|
|
351
|
-
* Restore an object that was reduced by decycle. Members whose values are objects of the form of {$ref: PATH} are
|
|
352
|
-
* replaced with references to the value found by the PATH. This will restore cycles. The object will be mutated.
|
|
353
|
-
*
|
|
354
|
-
* The eval function is used to locate the values described by a PATH. The root object is kept in a $ variable. A
|
|
355
|
-
* regular expression is used to ensure that the PATH is extremely well-formed. The regexp contains nested quantifiers.
|
|
356
|
-
* That has been known to have extremely bad performance problems on some browsers for very long strings. A PATH is
|
|
357
|
-
* expected to be reasonably short. A PATH is allowed to belong to a very restricted subset of Goessner's JSONPath.
|
|
358
|
-
*
|
|
359
|
-
* So,
|
|
360
|
-
*
|
|
361
|
-
* var s = '[{"$ref":"$"}]';
|
|
362
|
-
* return JSON.retrocycle(JSON.parse(s));
|
|
363
|
-
*
|
|
364
|
-
* produces an array containing a single element which is the array itself.
|
|
365
|
-
*
|
|
366
|
-
* @method
|
|
367
|
-
* @param {Object} $
|
|
368
|
-
* @returns {Object}
|
|
369
|
-
* @public
|
|
370
|
-
*/
|
|
371
|
-
module.exports.retrocycle = ( $ ) => {
|
|
372
|
-
"use strict";
|
|
373
|
-
|
|
374
|
-
// eslint-disable-next-line no-control-regex
|
|
375
|
-
let px = /^\$(?:\[(?:\d+|"(?:[^\\"\u0000-\u001f]|\\(?:[\\"/bfnrt]|u[0-9a-zA-Z]{4}))*")])*$/;
|
|
376
|
-
|
|
377
|
-
// The rez function walks recursively through the object looking for $ref
|
|
378
|
-
// properties. When it finds one that has a value that is a path, then it
|
|
379
|
-
// replaces the $ref object with a reference to the value that is found by
|
|
380
|
-
// the path.
|
|
381
|
-
( function rez( value ) {
|
|
382
|
-
if ( value && typeof value === "object" ) {
|
|
383
|
-
if ( Array.isArray( value ) ) {
|
|
384
|
-
value.forEach( ( element, i ) => {
|
|
385
|
-
if ( typeof element === "object" && element !== null ) {
|
|
386
|
-
let path = element.$ref;
|
|
387
|
-
if ( typeof path === "string" && px.test( path ) ) {
|
|
388
|
-
value[ i ] = eval( path );
|
|
389
|
-
} else {
|
|
390
|
-
rez( element );
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
} );
|
|
394
|
-
} else {
|
|
395
|
-
Object.keys( value ).forEach( ( name ) => {
|
|
396
|
-
let item = value[ name ];
|
|
397
|
-
if ( typeof item === "object" && item !== null ) {
|
|
398
|
-
let path = item.$ref;
|
|
399
|
-
if ( typeof path === "string" && px.test( path ) ) {
|
|
400
|
-
value[ name ] = eval( path );
|
|
401
|
-
} else {
|
|
402
|
-
rez( item );
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
} );
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
}( $ ) );
|
|
409
|
-
return $;
|
|
410
|
-
};
|
|
411
|
-
|
|
412
|
-
/**
|
|
413
|
-
* Use this to stringify any JSON object for internal system purposes as it ensures no potential circular dependencies
|
|
414
|
-
* will cause it to throw exception.
|
|
415
|
-
*
|
|
416
|
-
* @method
|
|
417
|
-
* @param {Object} value
|
|
418
|
-
* @returns {string|*}
|
|
419
|
-
* @public
|
|
420
|
-
*/
|
|
421
|
-
module.exports.stringifyJSON = ( value ) => {
|
|
422
|
-
return _.isObjectLike( value ) ? JSON.stringify( _.toPlainObject( module.exports.decycle( value ) ) ) : value;
|
|
423
|
-
};
|
|
424
|
-
|
|
425
|
-
/**
|
|
426
|
-
* Use this to verify if the provided string can be parsed as a JSON.
|
|
427
|
-
*
|
|
428
|
-
* @method
|
|
429
|
-
* @param {string} string
|
|
430
|
-
* @returns {boolean}
|
|
431
|
-
* @public
|
|
432
|
-
*/
|
|
433
|
-
module.exports.isJsonString = ( string ) => {
|
|
434
|
-
try {
|
|
435
|
-
JSON.parse( string );
|
|
436
|
-
} catch {
|
|
437
|
-
return false;
|
|
438
|
-
}
|
|
439
|
-
return true;
|
|
440
|
-
};
|
|
441
|
-
|
|
442
|
-
/**
|
|
443
|
-
* Use this to parse any JSON string into a JSON object for internal system purposes as it ensures to restore any
|
|
444
|
-
* circular dependencies obscured with 'stringifyJSON'.
|
|
445
|
-
*
|
|
446
|
-
* @method
|
|
447
|
-
* @param {string} value
|
|
448
|
-
* @returns {Object|string}
|
|
449
|
-
* @public
|
|
450
|
-
*/
|
|
451
|
-
module.exports.parseJSON = ( value ) => {
|
|
452
|
-
try {
|
|
453
|
-
let transformed = JSON.parse( value );
|
|
454
|
-
return module.exports.retrocycle( transformed );
|
|
455
|
-
} catch {
|
|
456
|
-
return value;
|
|
457
|
-
}
|
|
458
|
-
};
|
|
459
|
-
|
|
460
|
-
/**
|
|
461
|
-
* Use this to decompose a JSON object into a sorted string. The values will be ordered alphabetically and combined with
|
|
462
|
-
* their keys, where applicable, starting from the bottom and moving up. Null or undefined values will be ignored, and
|
|
463
|
-
* their keys will not be included in the final string.
|
|
464
|
-
*
|
|
465
|
-
* @param {Object} input
|
|
466
|
-
* @recursion
|
|
467
|
-
* @returns {string|null}
|
|
468
|
-
* @public
|
|
469
|
-
*/
|
|
470
|
-
module.exports.decomposeJSON = ( input ) => {
|
|
471
|
-
let decomposed;
|
|
472
|
-
|
|
473
|
-
if ( !_.isNil( input ) ) {
|
|
474
|
-
if ( _.isArray( input ) ) {
|
|
475
|
-
decomposed = [];
|
|
476
|
-
_.forEach( input, ( value ) => {
|
|
477
|
-
let decomposedValue = module.exports.decomposeJSON( value );
|
|
478
|
-
if ( decomposedValue !== undefined ) {
|
|
479
|
-
decomposed.push( decomposedValue );
|
|
480
|
-
}
|
|
481
|
-
} );
|
|
482
|
-
decomposed = decomposed.sort();
|
|
483
|
-
decomposed = decomposed.join( ":" );
|
|
484
|
-
} else if ( _.isPlainObject( input ) ) {
|
|
485
|
-
decomposed = [];
|
|
486
|
-
_.forOwn( input, ( value, key ) => {
|
|
487
|
-
let decomposedValue = module.exports.decomposeJSON( value );
|
|
488
|
-
if ( decomposedValue !== undefined ) {
|
|
489
|
-
decomposed.push( _.toString( key ) + ":" + decomposedValue );
|
|
490
|
-
}
|
|
491
|
-
} );
|
|
492
|
-
decomposed = decomposed.sort();
|
|
493
|
-
decomposed = decomposed.join( ":" );
|
|
494
|
-
} else {
|
|
495
|
-
decomposed = _.toString( input );
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
return decomposed;
|
|
500
|
-
};
|
|
501
|
-
|
|
502
|
-
/**
|
|
503
|
-
* Constant-time string comparison. Mirrors the web-framework safe-compare idiom: coerces inputs to
|
|
504
|
-
* utf8 buffers, short-circuits on length mismatch, and never throws on hostile/non-string input.
|
|
505
|
-
*
|
|
506
|
-
* @method
|
|
507
|
-
* @param {*} a
|
|
508
|
-
* @param {*} b
|
|
509
|
-
* @returns {boolean} True only when both inputs coerce to equal-length, byte-identical strings.
|
|
510
|
-
* @public
|
|
511
|
-
*/
|
|
512
|
-
module.exports.constantTimeEquals = ( a, b ) => {
|
|
513
|
-
try {
|
|
514
|
-
const ba = Buffer.from( String( a || "" ), "utf8" );
|
|
515
|
-
const bb = Buffer.from( String( b || "" ), "utf8" );
|
|
516
|
-
return ( ba.length !== bb.length ) ? false : crypto.timingSafeEqual( ba, bb );
|
|
517
|
-
} catch {
|
|
518
|
-
return false;
|
|
519
|
-
}
|
|
520
|
-
};
|
|
521
|
-
|
|
522
|
-
/**
|
|
523
|
-
* Used to create retry policy for the execution of an operation.
|
|
524
|
-
*
|
|
525
|
-
* @class RetryPolicy
|
|
526
|
-
* @public
|
|
527
|
-
*/
|
|
528
|
-
class RetryPolicy {
|
|
529
|
-
|
|
530
|
-
#maxAttempts;
|
|
531
|
-
#onFailedAttempt;
|
|
532
|
-
#onRetry;
|
|
533
|
-
|
|
534
|
-
/**
|
|
535
|
-
* @constructor
|
|
536
|
-
* @param {number} maxAttempts The maximum number of attempts to execute the operation.
|
|
537
|
-
* @throws {TypeError} maxAttempts must be a positive integer.
|
|
538
|
-
*/
|
|
539
|
-
constructor( maxAttempts ) {
|
|
540
|
-
if ( !Number.isInteger( maxAttempts ) || maxAttempts < 1 ) {
|
|
541
|
-
throw new TypeError( "maxAttempts must be a positive integer" );
|
|
542
|
-
}
|
|
543
|
-
this.#maxAttempts = maxAttempts;
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
/* Public interface */
|
|
547
|
-
|
|
548
|
-
/**
|
|
549
|
-
* Used to start execution of the provided operation.
|
|
550
|
-
*
|
|
551
|
-
* @method
|
|
552
|
-
* @param {Object} context The context in which the operation will be executed (i.e., this reference).
|
|
553
|
-
* @param {function( ...* ): Promise<*>} operation Operation to be executed; must return a Promise.
|
|
554
|
-
* @param {Array<*>} [params=[]] The arguments to be provided to the operation upon execution.
|
|
555
|
-
* @returns {Promise}
|
|
556
|
-
* @public
|
|
557
|
-
*/
|
|
558
|
-
execute( context, operation, params = [] ) {
|
|
559
|
-
return this.#retry( context, operation, params, 1, undefined );
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
/**
|
|
563
|
-
* Used to register a method that will be automatically called on a failed execution attempt.
|
|
564
|
-
*
|
|
565
|
-
* @method
|
|
566
|
-
* @param {function( Error )} action The execution error will be provided as an argument.
|
|
567
|
-
* @public
|
|
568
|
-
*/
|
|
569
|
-
onFailedAttempt( action ) {
|
|
570
|
-
if ( typeof ( action ) === "function" ) {
|
|
571
|
-
this.#onFailedAttempt = action;
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
/**
|
|
576
|
-
* Used to register a method that will be automatically called on each execution retry (after the initial one).
|
|
577
|
-
*
|
|
578
|
-
* @method
|
|
579
|
-
* @param {function( number, (Error|undefined) )} action The current attempt and last error are provided.
|
|
580
|
-
* @public
|
|
581
|
-
*/
|
|
582
|
-
onRetry( action ) {
|
|
583
|
-
if ( typeof ( action ) === "function" ) {
|
|
584
|
-
this.#onRetry = action;
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
/* Private interface */
|
|
589
|
-
|
|
590
|
-
/**
|
|
591
|
-
* Will retry the execution of operation up to max attempts.
|
|
592
|
-
*
|
|
593
|
-
* @method
|
|
594
|
-
* @param {Object} context
|
|
595
|
-
* @param {function( ...* ): Promise<*>} operation Operation to be executed; must return a Promise.
|
|
596
|
-
* @param {Array<*>} params The arguments to be provided to the operation upon execution.
|
|
597
|
-
* @param {number} attempt
|
|
598
|
-
* @param {Error} error
|
|
599
|
-
* @returns {Promise}
|
|
600
|
-
* @private
|
|
601
|
-
*/
|
|
602
|
-
#retry( context, operation, params, attempt, error ) {
|
|
603
|
-
if ( attempt > this.#maxAttempts ) {
|
|
604
|
-
return Promise.reject( error );
|
|
605
|
-
} else {
|
|
606
|
-
if ( attempt > 1 && this.#onRetry ) {
|
|
607
|
-
try {
|
|
608
|
-
this.#onRetry( attempt, error );
|
|
609
|
-
} catch {
|
|
610
|
-
// ignore observer errors...
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
return Promise
|
|
614
|
-
.resolve()
|
|
615
|
-
.then( () => {
|
|
616
|
-
return operation.apply( context, params );
|
|
617
|
-
} )
|
|
618
|
-
.catch( ( error ) => {
|
|
619
|
-
if ( this.#onFailedAttempt ) {
|
|
620
|
-
try {
|
|
621
|
-
this.#onFailedAttempt( error );
|
|
622
|
-
} catch {
|
|
623
|
-
// ignore observer errors...
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
return this.#retry( context, operation, params, ( attempt + 1 ), error );
|
|
627
|
-
} );
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
}
|
|
632
|
-
|
|
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 _ = require( "lodash" );
|
|
10
|
+
const crypto = require( "node:crypto" );
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Used to generate and return new UUID.
|
|
14
|
+
*
|
|
15
|
+
* @method
|
|
16
|
+
* @returns {string}
|
|
17
|
+
* @public
|
|
18
|
+
*/
|
|
19
|
+
module.exports.getUUID = () => {
|
|
20
|
+
return crypto.randomUUID( { disableEntropyCache: true } );
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Used to deep-freeze an object.
|
|
25
|
+
*
|
|
26
|
+
* @method
|
|
27
|
+
* @param {Object} object
|
|
28
|
+
* @param {WeakSet} [seen]
|
|
29
|
+
* @returns {Object}
|
|
30
|
+
* @public
|
|
31
|
+
*/
|
|
32
|
+
module.exports.deepFreeze = ( object, seen = new WeakSet() ) => {
|
|
33
|
+
if ( object === null || typeof object !== "object" || seen.has( object ) ) {
|
|
34
|
+
return object;
|
|
35
|
+
} else {
|
|
36
|
+
seen.add( object );
|
|
37
|
+
_.forOwn( object, ( value ) => {
|
|
38
|
+
module.exports.deepFreeze( value, seen );
|
|
39
|
+
} );
|
|
40
|
+
return Object.freeze( object );
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Used to create a custom Enum list.
|
|
46
|
+
*
|
|
47
|
+
* @method
|
|
48
|
+
* @param {Object} seed
|
|
49
|
+
* @returns {Object} This is a {@link TiEnum} object. Setting the proper reference here would unfortunately break IDE support.
|
|
50
|
+
* @public
|
|
51
|
+
*/
|
|
52
|
+
module.exports.enum = ( seed ) => {
|
|
53
|
+
const enumObject = Object.create( null );
|
|
54
|
+
const properties = Object.create( null );
|
|
55
|
+
const reserved = new Set( [ "properties", "name", "description", "contains", "__proto__", "prototype", "constructor" ] );
|
|
56
|
+
|
|
57
|
+
_.forOwn( seed, ( value, key ) => {
|
|
58
|
+
if ( !reserved.has( key ) ) {
|
|
59
|
+
if ( Array.isArray( value ) ) {
|
|
60
|
+
enumObject[ key ] = value[ 0 ];
|
|
61
|
+
properties[ value[ 0 ] ] = {
|
|
62
|
+
value: value[ 0 ],
|
|
63
|
+
name: value[ 1 ],
|
|
64
|
+
description: value[ 2 ]
|
|
65
|
+
};
|
|
66
|
+
} else {
|
|
67
|
+
enumObject[ key ] = value;
|
|
68
|
+
properties[ value ] = {
|
|
69
|
+
value: value,
|
|
70
|
+
name: key
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
} );
|
|
75
|
+
Object.values( properties ).forEach( Object.freeze );
|
|
76
|
+
Object.freeze( properties );
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Used to get the name of an {@link TiEnumValue} if such value exists.
|
|
80
|
+
*
|
|
81
|
+
* @method
|
|
82
|
+
* @param {number|string} value
|
|
83
|
+
* @param {string} [placeholder=undefined] If provided it will be returned when the enum value does not have a name defined.
|
|
84
|
+
* @returns {string|undefined}
|
|
85
|
+
* @public
|
|
86
|
+
*/
|
|
87
|
+
const name = ( value, placeholder = undefined ) => {
|
|
88
|
+
return ( properties[ value ] ) ? properties[ value ].name : placeholder;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Used to get the description of an {@link TiEnumValue} if such value exists.
|
|
93
|
+
*
|
|
94
|
+
* @method
|
|
95
|
+
* @param {number|string} value
|
|
96
|
+
* @param {string} [placeholder=undefined] If provided it will be returned when the enum value does not have a description defined.
|
|
97
|
+
* @returns {string|undefined}
|
|
98
|
+
* @public
|
|
99
|
+
*/
|
|
100
|
+
const description = ( value, placeholder = undefined ) => {
|
|
101
|
+
if ( !properties[ value ] ) {
|
|
102
|
+
return placeholder;
|
|
103
|
+
} else {
|
|
104
|
+
return ( properties[ value ].description !== undefined ) ? properties[ value ].description : placeholder;
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Used to check if the provided value is contained in the provided {@link TiEnum} list.
|
|
110
|
+
*
|
|
111
|
+
* @method
|
|
112
|
+
* @param {number|string} value
|
|
113
|
+
* @returns {boolean}
|
|
114
|
+
* @public
|
|
115
|
+
*/
|
|
116
|
+
const contains = ( value ) => {
|
|
117
|
+
return !!( properties[ value ] );
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
Object.defineProperties( enumObject, {
|
|
121
|
+
contains: {
|
|
122
|
+
enumerable: false,
|
|
123
|
+
configurable: false,
|
|
124
|
+
writable: false,
|
|
125
|
+
value: contains
|
|
126
|
+
},
|
|
127
|
+
description: {
|
|
128
|
+
enumerable: false,
|
|
129
|
+
configurable: false,
|
|
130
|
+
writable: false,
|
|
131
|
+
value: description
|
|
132
|
+
},
|
|
133
|
+
name: {
|
|
134
|
+
enumerable: false,
|
|
135
|
+
configurable: false,
|
|
136
|
+
writable: false,
|
|
137
|
+
value: name
|
|
138
|
+
},
|
|
139
|
+
properties: {
|
|
140
|
+
enumerable: false,
|
|
141
|
+
configurable: false,
|
|
142
|
+
writable: false,
|
|
143
|
+
value: properties
|
|
144
|
+
}
|
|
145
|
+
} );
|
|
146
|
+
Object.freeze( enumObject );
|
|
147
|
+
|
|
148
|
+
return enumObject;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Used to get the name of an {@link TiEnum} value if such exists.
|
|
153
|
+
*
|
|
154
|
+
* @method
|
|
155
|
+
* @deprecated Use the 'name' property of the provided {@link TiEnum} instead.
|
|
156
|
+
* @param {TiEnum} enumList
|
|
157
|
+
* @param {number|string} enumValue
|
|
158
|
+
* @param {string} [placeholder=undefined] If provided it will be returned when the enum value does not have a name defined.
|
|
159
|
+
* @returns {string|undefined}
|
|
160
|
+
* @public
|
|
161
|
+
*/
|
|
162
|
+
module.exports.getEnumName = ( enumList, enumValue, placeholder = undefined ) => {
|
|
163
|
+
return ( enumList.properties[ enumValue ] ) ? enumList.properties[ enumValue ].name : placeholder;
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Convert an Error to a JSON object.
|
|
168
|
+
* <br/>
|
|
169
|
+
* NOTE: If the value provided is not an error, then it will just be cloned.
|
|
170
|
+
*
|
|
171
|
+
* @method
|
|
172
|
+
* @param {Error} value
|
|
173
|
+
* @returns {Object}
|
|
174
|
+
* @public
|
|
175
|
+
*/
|
|
176
|
+
module.exports.errorToJSON = ( value ) => {
|
|
177
|
+
let error = {};
|
|
178
|
+
|
|
179
|
+
if ( value instanceof Error ) {
|
|
180
|
+
Object.getOwnPropertyNames( value ).forEach( ( key ) => {
|
|
181
|
+
error[ key ] = value[ key ];
|
|
182
|
+
} );
|
|
183
|
+
} else {
|
|
184
|
+
error = _.cloneDeep( value );
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return error;
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Used to parse a value and return its boolean representation (if possible).
|
|
192
|
+
*
|
|
193
|
+
* @param {*} value
|
|
194
|
+
* @returns {boolean}
|
|
195
|
+
* @public
|
|
196
|
+
*/
|
|
197
|
+
module.exports.toBool = ( value ) => {
|
|
198
|
+
let result = true;
|
|
199
|
+
let regexp = /^false$|^0$|^no$/i;
|
|
200
|
+
|
|
201
|
+
if ( !value || regexp.test( value ) || value === "N" || value === "0" || ( _.isObjectLike( value ) && _.size( value ) === 0 ) ) {
|
|
202
|
+
result = false;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return result;
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Used to fetch only the unique values from the provided array.
|
|
210
|
+
* <br/>
|
|
211
|
+
* Note: For objects and arrays, uniqueness is determined by reference equality. Primitives are compared by their value.
|
|
212
|
+
*
|
|
213
|
+
* @method
|
|
214
|
+
* @param {Array} array
|
|
215
|
+
* @returns {Array}
|
|
216
|
+
* @throws {TypeError} If the provided parameter is not an array
|
|
217
|
+
* @public
|
|
218
|
+
*/
|
|
219
|
+
module.exports.arrayUniques = ( array ) => {
|
|
220
|
+
if ( !Array.isArray( array ) ) {
|
|
221
|
+
throw new TypeError( "Expected an array" );
|
|
222
|
+
}
|
|
223
|
+
return [ ...new Set( array ) ];
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Will return a UTC date string in format YYYY-MM-DD from the provided date.
|
|
228
|
+
*
|
|
229
|
+
* @method
|
|
230
|
+
* @param {Date} date
|
|
231
|
+
* @returns {string}
|
|
232
|
+
* @public
|
|
233
|
+
*/
|
|
234
|
+
module.exports.getUTCDateString = ( date ) => {
|
|
235
|
+
const year = date.getUTCFullYear();
|
|
236
|
+
const month = String( date.getUTCMonth() + 1 ).padStart( 2, "0" );
|
|
237
|
+
const day = String( date.getUTCDate() ).padStart( 2, "0" );
|
|
238
|
+
return `${ year }-${ month }-${ day }`;
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Will return a UTC time string in format hh:mm:ss, or hh:mm:ss.MMM when useMilliseconds is true.
|
|
243
|
+
*
|
|
244
|
+
* @method
|
|
245
|
+
* @param {Date} date
|
|
246
|
+
* @param {boolean} [useMilliseconds=false]
|
|
247
|
+
* @returns {string}
|
|
248
|
+
* @public
|
|
249
|
+
*/
|
|
250
|
+
module.exports.getUTCTimeString = ( date, useMilliseconds = false ) => {
|
|
251
|
+
const hours = String( date.getUTCHours() ).padStart( 2, "0" );
|
|
252
|
+
const minutes = String( date.getUTCMinutes() ).padStart( 2, "0" );
|
|
253
|
+
const seconds = String( date.getUTCSeconds() ).padStart( 2, "0" );
|
|
254
|
+
const milliseconds = useMilliseconds ? `${ String( date.getUTCMilliseconds() ).padStart( 3, "0" ) }` : "";
|
|
255
|
+
return `${ hours }:${ minutes }:${ seconds }${ useMilliseconds ? `.${ milliseconds }` : "" }`;
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Used to remove any circular dependencies from JSON objects.
|
|
260
|
+
* <br/>
|
|
261
|
+
* NOTE: Original file from here - https://github.com/douglascrockford/JSON-js/blob/master/cycle.js
|
|
262
|
+
*
|
|
263
|
+
* Make a deep copy of an object or array, assuring that there is at most one instance of each object or array in the
|
|
264
|
+
* resulting structure. The duplicate references (which might be forming cycles) are replaced with an object of the
|
|
265
|
+
* form of {"$ref": PATH} where the PATH is a JSONPath string that locates the first occurrence.
|
|
266
|
+
*
|
|
267
|
+
* So,
|
|
268
|
+
*
|
|
269
|
+
* var a = [];
|
|
270
|
+
* a[0] = a;
|
|
271
|
+
* return JSON.stringify(JSON.decycle(a));
|
|
272
|
+
*
|
|
273
|
+
* produces the string '[{"$ref":"$"}]'.
|
|
274
|
+
*
|
|
275
|
+
* If a replacer function is provided, then it will be called for each value. A replacer function receives a value
|
|
276
|
+
* and returns a replacement value.
|
|
277
|
+
*
|
|
278
|
+
* JSONPath is used to locate the unique object. $ indicates the top level of the object or array. [NUMBER] or [STRING]
|
|
279
|
+
* indicates a child element or property.
|
|
280
|
+
*
|
|
281
|
+
* @method
|
|
282
|
+
* @param {Object} object
|
|
283
|
+
* @param {function( Object ): Object} [replacer]
|
|
284
|
+
* @returns {Object}
|
|
285
|
+
* @public
|
|
286
|
+
*/
|
|
287
|
+
module.exports.decycle = ( object, replacer ) => {
|
|
288
|
+
"use strict";
|
|
289
|
+
|
|
290
|
+
let objects = new WeakMap();
|
|
291
|
+
|
|
292
|
+
// The derez function recurse through the object, producing the deep copy.
|
|
293
|
+
return ( function derez( value, path ) {
|
|
294
|
+
let oldPath; // The path of an earlier occurrence of value
|
|
295
|
+
let newItem; // The new object or array
|
|
296
|
+
|
|
297
|
+
// If a replacer function was provided, then call it to get a replacement value.
|
|
298
|
+
if ( replacer !== undefined ) {
|
|
299
|
+
value = replacer( value );
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// typeof null === "object", so go on if this value is really an object but not
|
|
303
|
+
// one of the weird builtin objects.
|
|
304
|
+
if (
|
|
305
|
+
typeof value === "object"
|
|
306
|
+
&& value !== null
|
|
307
|
+
&& !( value instanceof Boolean )
|
|
308
|
+
&& !( value instanceof Date )
|
|
309
|
+
&& !( value instanceof Number )
|
|
310
|
+
&& !( value instanceof RegExp )
|
|
311
|
+
&& !( value instanceof String )
|
|
312
|
+
) {
|
|
313
|
+
// If the value is an object or array, look to see if we have already
|
|
314
|
+
// encountered it. If so, return a {"$ref":PATH} object. This uses an
|
|
315
|
+
// ES6 WeakMap.
|
|
316
|
+
oldPath = objects.get( value );
|
|
317
|
+
if ( oldPath !== undefined ) {
|
|
318
|
+
return { $ref: oldPath };
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Otherwise, accumulate the unique value and its path.
|
|
322
|
+
objects.set( value, path );
|
|
323
|
+
|
|
324
|
+
// If it is an array, replicate the array.
|
|
325
|
+
if ( Array.isArray( value ) ) {
|
|
326
|
+
newItem = [];
|
|
327
|
+
value.forEach( ( element, i ) => {
|
|
328
|
+
newItem[ i ] = derez( element, path + "[" + i + "]" );
|
|
329
|
+
} );
|
|
330
|
+
} else {
|
|
331
|
+
// If it is an object, replicate the object.
|
|
332
|
+
newItem = {};
|
|
333
|
+
Object.keys( value ).forEach( ( name ) => {
|
|
334
|
+
newItem[ name ] = derez(
|
|
335
|
+
value[ name ],
|
|
336
|
+
path + "[" + JSON.stringify( name ) + "]"
|
|
337
|
+
);
|
|
338
|
+
} );
|
|
339
|
+
}
|
|
340
|
+
return newItem;
|
|
341
|
+
}
|
|
342
|
+
return value;
|
|
343
|
+
}( object, "$" ) );
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Used to restore any circular dependencies from JSON objects after using the 'decycle' method.
|
|
348
|
+
* <br/>
|
|
349
|
+
* NOTE: Original file from here - https://github.com/douglascrockford/JSON-js/blob/master/cycle.js
|
|
350
|
+
*
|
|
351
|
+
* Restore an object that was reduced by decycle. Members whose values are objects of the form of {$ref: PATH} are
|
|
352
|
+
* replaced with references to the value found by the PATH. This will restore cycles. The object will be mutated.
|
|
353
|
+
*
|
|
354
|
+
* The eval function is used to locate the values described by a PATH. The root object is kept in a $ variable. A
|
|
355
|
+
* regular expression is used to ensure that the PATH is extremely well-formed. The regexp contains nested quantifiers.
|
|
356
|
+
* That has been known to have extremely bad performance problems on some browsers for very long strings. A PATH is
|
|
357
|
+
* expected to be reasonably short. A PATH is allowed to belong to a very restricted subset of Goessner's JSONPath.
|
|
358
|
+
*
|
|
359
|
+
* So,
|
|
360
|
+
*
|
|
361
|
+
* var s = '[{"$ref":"$"}]';
|
|
362
|
+
* return JSON.retrocycle(JSON.parse(s));
|
|
363
|
+
*
|
|
364
|
+
* produces an array containing a single element which is the array itself.
|
|
365
|
+
*
|
|
366
|
+
* @method
|
|
367
|
+
* @param {Object} $
|
|
368
|
+
* @returns {Object}
|
|
369
|
+
* @public
|
|
370
|
+
*/
|
|
371
|
+
module.exports.retrocycle = ( $ ) => {
|
|
372
|
+
"use strict";
|
|
373
|
+
|
|
374
|
+
// eslint-disable-next-line no-control-regex
|
|
375
|
+
let px = /^\$(?:\[(?:\d+|"(?:[^\\"\u0000-\u001f]|\\(?:[\\"/bfnrt]|u[0-9a-zA-Z]{4}))*")])*$/;
|
|
376
|
+
|
|
377
|
+
// The rez function walks recursively through the object looking for $ref
|
|
378
|
+
// properties. When it finds one that has a value that is a path, then it
|
|
379
|
+
// replaces the $ref object with a reference to the value that is found by
|
|
380
|
+
// the path.
|
|
381
|
+
( function rez( value ) {
|
|
382
|
+
if ( value && typeof value === "object" ) {
|
|
383
|
+
if ( Array.isArray( value ) ) {
|
|
384
|
+
value.forEach( ( element, i ) => {
|
|
385
|
+
if ( typeof element === "object" && element !== null ) {
|
|
386
|
+
let path = element.$ref;
|
|
387
|
+
if ( typeof path === "string" && px.test( path ) ) {
|
|
388
|
+
value[ i ] = eval( path );
|
|
389
|
+
} else {
|
|
390
|
+
rez( element );
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
} );
|
|
394
|
+
} else {
|
|
395
|
+
Object.keys( value ).forEach( ( name ) => {
|
|
396
|
+
let item = value[ name ];
|
|
397
|
+
if ( typeof item === "object" && item !== null ) {
|
|
398
|
+
let path = item.$ref;
|
|
399
|
+
if ( typeof path === "string" && px.test( path ) ) {
|
|
400
|
+
value[ name ] = eval( path );
|
|
401
|
+
} else {
|
|
402
|
+
rez( item );
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
} );
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}( $ ) );
|
|
409
|
+
return $;
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Use this to stringify any JSON object for internal system purposes as it ensures no potential circular dependencies
|
|
414
|
+
* will cause it to throw exception.
|
|
415
|
+
*
|
|
416
|
+
* @method
|
|
417
|
+
* @param {Object} value
|
|
418
|
+
* @returns {string|*}
|
|
419
|
+
* @public
|
|
420
|
+
*/
|
|
421
|
+
module.exports.stringifyJSON = ( value ) => {
|
|
422
|
+
return _.isObjectLike( value ) ? JSON.stringify( _.toPlainObject( module.exports.decycle( value ) ) ) : value;
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Use this to verify if the provided string can be parsed as a JSON.
|
|
427
|
+
*
|
|
428
|
+
* @method
|
|
429
|
+
* @param {string} string
|
|
430
|
+
* @returns {boolean}
|
|
431
|
+
* @public
|
|
432
|
+
*/
|
|
433
|
+
module.exports.isJsonString = ( string ) => {
|
|
434
|
+
try {
|
|
435
|
+
JSON.parse( string );
|
|
436
|
+
} catch {
|
|
437
|
+
return false;
|
|
438
|
+
}
|
|
439
|
+
return true;
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Use this to parse any JSON string into a JSON object for internal system purposes as it ensures to restore any
|
|
444
|
+
* circular dependencies obscured with 'stringifyJSON'.
|
|
445
|
+
*
|
|
446
|
+
* @method
|
|
447
|
+
* @param {string} value
|
|
448
|
+
* @returns {Object|string}
|
|
449
|
+
* @public
|
|
450
|
+
*/
|
|
451
|
+
module.exports.parseJSON = ( value ) => {
|
|
452
|
+
try {
|
|
453
|
+
let transformed = JSON.parse( value );
|
|
454
|
+
return module.exports.retrocycle( transformed );
|
|
455
|
+
} catch {
|
|
456
|
+
return value;
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Use this to decompose a JSON object into a sorted string. The values will be ordered alphabetically and combined with
|
|
462
|
+
* their keys, where applicable, starting from the bottom and moving up. Null or undefined values will be ignored, and
|
|
463
|
+
* their keys will not be included in the final string.
|
|
464
|
+
*
|
|
465
|
+
* @param {Object} input
|
|
466
|
+
* @recursion
|
|
467
|
+
* @returns {string|null}
|
|
468
|
+
* @public
|
|
469
|
+
*/
|
|
470
|
+
module.exports.decomposeJSON = ( input ) => {
|
|
471
|
+
let decomposed;
|
|
472
|
+
|
|
473
|
+
if ( !_.isNil( input ) ) {
|
|
474
|
+
if ( _.isArray( input ) ) {
|
|
475
|
+
decomposed = [];
|
|
476
|
+
_.forEach( input, ( value ) => {
|
|
477
|
+
let decomposedValue = module.exports.decomposeJSON( value );
|
|
478
|
+
if ( decomposedValue !== undefined ) {
|
|
479
|
+
decomposed.push( decomposedValue );
|
|
480
|
+
}
|
|
481
|
+
} );
|
|
482
|
+
decomposed = decomposed.sort();
|
|
483
|
+
decomposed = decomposed.join( ":" );
|
|
484
|
+
} else if ( _.isPlainObject( input ) ) {
|
|
485
|
+
decomposed = [];
|
|
486
|
+
_.forOwn( input, ( value, key ) => {
|
|
487
|
+
let decomposedValue = module.exports.decomposeJSON( value );
|
|
488
|
+
if ( decomposedValue !== undefined ) {
|
|
489
|
+
decomposed.push( _.toString( key ) + ":" + decomposedValue );
|
|
490
|
+
}
|
|
491
|
+
} );
|
|
492
|
+
decomposed = decomposed.sort();
|
|
493
|
+
decomposed = decomposed.join( ":" );
|
|
494
|
+
} else {
|
|
495
|
+
decomposed = _.toString( input );
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
return decomposed;
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Constant-time string comparison. Mirrors the web-framework safe-compare idiom: coerces inputs to
|
|
504
|
+
* utf8 buffers, short-circuits on length mismatch, and never throws on hostile/non-string input.
|
|
505
|
+
*
|
|
506
|
+
* @method
|
|
507
|
+
* @param {*} a
|
|
508
|
+
* @param {*} b
|
|
509
|
+
* @returns {boolean} True only when both inputs coerce to equal-length, byte-identical strings.
|
|
510
|
+
* @public
|
|
511
|
+
*/
|
|
512
|
+
module.exports.constantTimeEquals = ( a, b ) => {
|
|
513
|
+
try {
|
|
514
|
+
const ba = Buffer.from( String( a || "" ), "utf8" );
|
|
515
|
+
const bb = Buffer.from( String( b || "" ), "utf8" );
|
|
516
|
+
return ( ba.length !== bb.length ) ? false : crypto.timingSafeEqual( ba, bb );
|
|
517
|
+
} catch {
|
|
518
|
+
return false;
|
|
519
|
+
}
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Used to create retry policy for the execution of an operation.
|
|
524
|
+
*
|
|
525
|
+
* @class RetryPolicy
|
|
526
|
+
* @public
|
|
527
|
+
*/
|
|
528
|
+
class RetryPolicy {
|
|
529
|
+
|
|
530
|
+
#maxAttempts;
|
|
531
|
+
#onFailedAttempt;
|
|
532
|
+
#onRetry;
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* @constructor
|
|
536
|
+
* @param {number} maxAttempts The maximum number of attempts to execute the operation.
|
|
537
|
+
* @throws {TypeError} maxAttempts must be a positive integer.
|
|
538
|
+
*/
|
|
539
|
+
constructor( maxAttempts ) {
|
|
540
|
+
if ( !Number.isInteger( maxAttempts ) || maxAttempts < 1 ) {
|
|
541
|
+
throw new TypeError( "maxAttempts must be a positive integer" );
|
|
542
|
+
}
|
|
543
|
+
this.#maxAttempts = maxAttempts;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/* Public interface */
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* Used to start execution of the provided operation.
|
|
550
|
+
*
|
|
551
|
+
* @method
|
|
552
|
+
* @param {Object} context The context in which the operation will be executed (i.e., this reference).
|
|
553
|
+
* @param {function( ...* ): Promise<*>} operation Operation to be executed; must return a Promise.
|
|
554
|
+
* @param {Array<*>} [params=[]] The arguments to be provided to the operation upon execution.
|
|
555
|
+
* @returns {Promise}
|
|
556
|
+
* @public
|
|
557
|
+
*/
|
|
558
|
+
execute( context, operation, params = [] ) {
|
|
559
|
+
return this.#retry( context, operation, params, 1, undefined );
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* Used to register a method that will be automatically called on a failed execution attempt.
|
|
564
|
+
*
|
|
565
|
+
* @method
|
|
566
|
+
* @param {function( Error )} action The execution error will be provided as an argument.
|
|
567
|
+
* @public
|
|
568
|
+
*/
|
|
569
|
+
onFailedAttempt( action ) {
|
|
570
|
+
if ( typeof ( action ) === "function" ) {
|
|
571
|
+
this.#onFailedAttempt = action;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* Used to register a method that will be automatically called on each execution retry (after the initial one).
|
|
577
|
+
*
|
|
578
|
+
* @method
|
|
579
|
+
* @param {function( number, (Error|undefined) )} action The current attempt and last error are provided.
|
|
580
|
+
* @public
|
|
581
|
+
*/
|
|
582
|
+
onRetry( action ) {
|
|
583
|
+
if ( typeof ( action ) === "function" ) {
|
|
584
|
+
this.#onRetry = action;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/* Private interface */
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* Will retry the execution of operation up to max attempts.
|
|
592
|
+
*
|
|
593
|
+
* @method
|
|
594
|
+
* @param {Object} context
|
|
595
|
+
* @param {function( ...* ): Promise<*>} operation Operation to be executed; must return a Promise.
|
|
596
|
+
* @param {Array<*>} params The arguments to be provided to the operation upon execution.
|
|
597
|
+
* @param {number} attempt
|
|
598
|
+
* @param {Error} error
|
|
599
|
+
* @returns {Promise}
|
|
600
|
+
* @private
|
|
601
|
+
*/
|
|
602
|
+
#retry( context, operation, params, attempt, error ) {
|
|
603
|
+
if ( attempt > this.#maxAttempts ) {
|
|
604
|
+
return Promise.reject( error );
|
|
605
|
+
} else {
|
|
606
|
+
if ( attempt > 1 && this.#onRetry ) {
|
|
607
|
+
try {
|
|
608
|
+
this.#onRetry( attempt, error );
|
|
609
|
+
} catch {
|
|
610
|
+
// ignore observer errors...
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
return Promise
|
|
614
|
+
.resolve()
|
|
615
|
+
.then( () => {
|
|
616
|
+
return operation.apply( context, params );
|
|
617
|
+
} )
|
|
618
|
+
.catch( ( error ) => {
|
|
619
|
+
if ( this.#onFailedAttempt ) {
|
|
620
|
+
try {
|
|
621
|
+
this.#onFailedAttempt( error );
|
|
622
|
+
} catch {
|
|
623
|
+
// ignore observer errors...
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
return this.#retry( context, operation, params, ( attempt + 1 ), error );
|
|
627
|
+
} );
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
}
|
|
632
|
+
|
|
633
633
|
module.exports.RetryPolicy = RetryPolicy;
|