@ti-engine/core 1.3.3 → 1.3.6

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/utils/tools.js CHANGED
@@ -7,19 +7,21 @@
7
7
  */
8
8
 
9
9
  const _ = require( "lodash" );
10
- const fs = require( "fs-extra" );
11
10
  const crypto = require( "node:crypto" );
12
11
 
13
12
  /**
14
13
  * @typedef {Object} TiEnumValue
15
14
  * @property {number|string} value
16
15
  * @property {string} name
17
- * @property {string} description
16
+ * @property {string} [description]
18
17
  */
19
18
 
20
19
  /**
21
20
  * @typedef {Object} TiEnum
22
21
  * @property {Object.<number|string,TiEnumValue>} properties
22
+ * @property {function( (number|string), [string] ): (string|undefined)} name
23
+ * @property {function( (number|string), [string] ): (string|undefined)} description
24
+ * @property {function( (number|string) ): boolean} contains
23
25
  */
24
26
 
25
27
  /**
@@ -38,38 +40,113 @@ module.exports.getUUID = () => {
38
40
  *
39
41
  * @method
40
42
  * @param {Object} seed
41
- * @returns {Object}
43
+ * @returns {Object} This is a {@link TiEnum} object. Setting the proper reference here would unfortunately break IDE support.
42
44
  * @public
43
45
  */
44
46
  module.exports.enum = ( seed ) => {
45
- let properties = {};
47
+ const enumObject = Object.create( null );
48
+ const properties = Object.create( null );
49
+ const reserved = new Set( [ "properties", "name", "description", "contains", "__proto__", "prototype", "constructor" ] );
46
50
 
47
51
  _.forOwn( seed, ( value, key ) => {
48
- if ( value instanceof Array ) {
49
- seed[ key ] = value[ 0 ];
50
- properties[ value[ 0 ] ] = {
51
- value: value[ 0 ],
52
- name: value[ 1 ],
53
- description: value[ 2 ]
54
- };
52
+ if ( !reserved.has( key ) ) {
53
+ if ( Array.isArray( value ) ) {
54
+ enumObject[ key ] = value[ 0 ];
55
+ properties[ value[ 0 ] ] = {
56
+ value: value[ 0 ],
57
+ name: value[ 1 ],
58
+ description: value[ 2 ]
59
+ };
60
+ } else {
61
+ enumObject[ key ] = value;
62
+ properties[ value ] = {
63
+ value: value,
64
+ name: key
65
+ };
66
+ }
67
+ }
68
+ } );
69
+ Object.values( properties ).forEach( Object.freeze );
70
+ Object.freeze( properties );
71
+
72
+ /**
73
+ * Used to get the name of an {@link TiEnumValue} if such value exists.
74
+ *
75
+ * @method
76
+ * @param {number|string} value
77
+ * @param {string} [placeholder=undefined] If provided it will be returned when the enum value does not have a name defined.
78
+ * @returns {string|undefined}
79
+ * @public
80
+ */
81
+ const name = ( value, placeholder = undefined ) => {
82
+ return ( properties[ value ] ) ? properties[ value ].name : placeholder;
83
+ };
84
+
85
+ /**
86
+ * Used to get the description of an {@link TiEnumValue} if such value exists.
87
+ *
88
+ * @method
89
+ * @param {number|string} value
90
+ * @param {string} [placeholder=undefined] If provided it will be returned when the enum value does not have a description defined.
91
+ * @returns {string|undefined}
92
+ * @public
93
+ */
94
+ const description = ( value, placeholder = undefined ) => {
95
+ if ( !properties[ value ] ) {
96
+ return placeholder;
55
97
  } else {
56
- properties[ value ] = {
57
- value: value,
58
- name: key.toLowerCase(),
59
- description: ""
60
- };
98
+ return ( properties[ value ].description !== undefined ) ? properties[ value ].description : placeholder;
99
+ }
100
+ };
101
+
102
+ /**
103
+ * Used to check if the provided value is contained in the provided {@link TiEnum} list.
104
+ *
105
+ * @method
106
+ * @param {number|string} value
107
+ * @returns {boolean}
108
+ * @public
109
+ */
110
+ const contains = ( value ) => {
111
+ return !!( properties[ value ] );
112
+ };
113
+
114
+ Object.defineProperties( enumObject, {
115
+ contains: {
116
+ enumerable: false,
117
+ configurable: false,
118
+ writable: false,
119
+ value: contains
120
+ },
121
+ description: {
122
+ enumerable: false,
123
+ configurable: false,
124
+ writable: false,
125
+ value: description
126
+ },
127
+ name: {
128
+ enumerable: false,
129
+ configurable: false,
130
+ writable: false,
131
+ value: name
132
+ },
133
+ properties: {
134
+ enumerable: false,
135
+ configurable: false,
136
+ writable: false,
137
+ value: properties
61
138
  }
62
139
  } );
63
- seed.properties = properties;
140
+ Object.freeze( enumObject );
64
141
 
65
- Object.freeze( seed );
66
- return seed;
142
+ return enumObject;
67
143
  };
68
144
 
69
145
  /**
70
146
  * Used to get the name of an {@link TiEnum} value if such exists.
71
147
  *
72
148
  * @method
149
+ * @deprecated Use the 'name' property of the provided {@link TiEnum} instead.
73
150
  * @param {TiEnum} enumList
74
151
  * @param {number|string} enumValue
75
152
  * @param {string} [placeholder=undefined] If provided it will be returned when the enum value does not have a name defined.
@@ -81,7 +158,7 @@ module.exports.getEnumName = ( enumList, enumValue, placeholder = undefined ) =>
81
158
  };
82
159
 
83
160
  /**
84
- * Convert an Error to JSON object.
161
+ * Convert an Error to a JSON object.
85
162
  * <br/>
86
163
  * NOTE: If the value provided is not an error, then it will just be cloned.
87
164
  *
@@ -123,7 +200,7 @@ module.exports.toBool = ( value ) => {
123
200
  };
124
201
 
125
202
  /**
126
- * Will return UTC date string in format YYYY-MM-DD from the provided date.
203
+ * Will return a UTC date string in format YYYY-MM-DD from the provided date.
127
204
  *
128
205
  * @method
129
206
  * @param {Date} date
@@ -131,15 +208,14 @@ module.exports.toBool = ( value ) => {
131
208
  * @public
132
209
  */
133
210
  module.exports.getUTCDateString = ( date ) => {
134
- let year = date.getUTCFullYear();
135
- let month = ( "00" + ( date.getUTCMonth() + 1 ) ).match( /\d{2}$/ );
136
- let day = ( "00" + date.getUTCDate() ).match( /\d{2}$/ );
137
-
138
- return String( year + "-" + month + "-" + day );
211
+ const year = date.getUTCFullYear();
212
+ const month = String( date.getUTCMonth() + 1 ).padStart( 2, "0" );
213
+ const day = String( date.getUTCDate() ).padStart( 2, "0" );
214
+ return `${ year }-${ month }-${ day }`;
139
215
  };
140
216
 
141
217
  /**
142
- * Will return UTC time string in format hh:mm:ss.MMM from the provided date.
218
+ * Will return a UTC time string in format hh:mm:ss, or hh:mm:ss.MMM when useMilliseconds is true.
143
219
  *
144
220
  * @method
145
221
  * @param {Date} date
@@ -147,13 +223,12 @@ module.exports.getUTCDateString = ( date ) => {
147
223
  * @returns {string}
148
224
  * @public
149
225
  */
150
- module.exports.getUTCTimeString = ( date, useMilliseconds ) => {
151
- let hours = ( "00" + date.getUTCHours() ).match( /\d{2}$/ );
152
- let minutes = ( "00" + date.getUTCMinutes() ).match( /\d{2}$/ );
153
- let seconds = ( "00" + date.getUTCSeconds() ).match( /\d{2}$/ );
154
- let milliseconds = ( "000" + date.getUTCMilliseconds() ).match( /\d{3}$/ );
155
-
156
- return String( hours + ":" + minutes + ":" + seconds + ( ( useMilliseconds ) ? "." + milliseconds : "" ) );
226
+ module.exports.getUTCTimeString = ( date, useMilliseconds = false ) => {
227
+ const hours = String( date.getUTCHours() ).padStart( 2, "0" );
228
+ const minutes = String( date.getUTCMinutes() ).padStart( 2, "0" );
229
+ const seconds = String( date.getUTCSeconds() ).padStart( 2, "0" );
230
+ const milliseconds = useMilliseconds ? `${ String( date.getUTCMilliseconds() ).padStart( 3, "0" ) }` : "";
231
+ return `${ hours }:${ minutes }:${ seconds }${ useMilliseconds ? `.${ milliseconds }` : "" }`;
157
232
  };
158
233
 
159
234
  /**
@@ -181,7 +256,7 @@ module.exports.getUTCTimeString = ( date, useMilliseconds ) => {
181
256
  *
182
257
  * @method
183
258
  * @param {Object} object
184
- * @param {function} [replacer]
259
+ * @param {function( Object ): Object} [replacer]
185
260
  * @returns {Object}
186
261
  * @public
187
262
  */
@@ -315,7 +390,7 @@ module.exports.retrocycle = ( $ ) => {
315
390
  *
316
391
  * @method
317
392
  * @param {Object} value
318
- * @return {string}
393
+ * @returns {string|*}
319
394
  * @public
320
395
  */
321
396
  module.exports.stringifyJSON = ( value ) => {
@@ -345,7 +420,7 @@ module.exports.isJsonString = ( string ) => {
345
420
  *
346
421
  * @method
347
422
  * @param {string} value
348
- * @return {Object}
423
+ * @returns {Object|string}
349
424
  * @public
350
425
  */
351
426
  module.exports.parseJSON = ( value ) => {
@@ -364,7 +439,7 @@ module.exports.parseJSON = ( value ) => {
364
439
  *
365
440
  * @param {Object} input
366
441
  * @recursion
367
- * @return {string|null}
442
+ * @returns {string|null}
368
443
  * @public
369
444
  */
370
445
  module.exports.decomposeJSON = ( input ) => {
@@ -399,50 +474,6 @@ module.exports.decomposeJSON = ( input ) => {
399
474
  return decomposed;
400
475
  };
401
476
 
402
- /**
403
- * Used to create a CSV file from the provided data.
404
- *
405
- * @method
406
- * @param {Object[]} data
407
- * @param {string} filePath
408
- * @param {string} fileName
409
- * @return {Promise}
410
- * @public
411
- */
412
- module.exports.createCSVFile = ( data, filePath, fileName ) => {
413
- return new Promise( ( resolve, reject ) => {
414
- let fileData = "";
415
- if ( data && data.length > 0 ) {
416
- let keys = [];
417
- _.forOwn( data[ 0 ], ( value, key ) => {
418
- keys.push( key );
419
- } );
420
- keys.sort();
421
-
422
- _.forEach( keys, ( key, idx ) => {
423
- fileData += key + ( ( idx < keys.length - 1 ) ? "," : "" );
424
- } );
425
- fileData += "\n";
426
-
427
- _.forEach( data, ( entry ) => {
428
- _.forEach( keys, ( key, idx ) => {
429
- fileData += entry[ key ] + ( ( idx < keys.length - 1 ) ? "," : "" );
430
- } );
431
- fileData += "\n";
432
- } );
433
- }
434
-
435
- fs.ensureDir( filePath ).then( () => {
436
- const fullPath = filePath + "/" + Date.now() + "-" + fileName + ".csv";
437
- return fs.appendFile( fullPath, fileData );
438
- } ).then( () => {
439
- resolve();
440
- } ).catch( ( error ) => {
441
- reject( error );
442
- } );
443
- } );
444
- };
445
-
446
477
  /**
447
478
  * Used to create retry policy for the execution of an operation.
448
479
  *
@@ -457,8 +488,12 @@ class RetryPolicy {
457
488
 
458
489
  /**
459
490
  * @constructor
491
+ * @param {number} maxAttempts The maximum number of attempts to execute the operation.
460
492
  */
461
493
  constructor( maxAttempts ) {
494
+ if ( !Number.isInteger( maxAttempts ) || maxAttempts < 1 ) {
495
+ throw new TypeError( "maxAttempts must be a positive integer" );
496
+ }
462
497
  this.#maxAttempts = maxAttempts;
463
498
  }
464
499
 
@@ -468,13 +503,13 @@ class RetryPolicy {
468
503
  * Used to start execution of the provided operation.
469
504
  *
470
505
  * @method
471
- * @param {Object} context The context in which the operation will be executed (i.e. this reference).
472
- * @param {function} operation Operation to be executed; has to return a Promise.
473
- * @param {Array} params The arguments to be provided to the operation upon execution.
506
+ * @param {Object} context The context in which the operation will be executed (i.e., this reference).
507
+ * @param {function( ...* ): Promise<*>} operation Operation to be executed; must return a Promise.
508
+ * @param {Array<*>} [params=[]] The arguments to be provided to the operation upon execution.
474
509
  * @returns {Promise}
475
510
  * @public
476
511
  */
477
- execute( context, operation, params ) {
512
+ execute( context, operation, params = [] ) {
478
513
  return this.#retry( context, operation, params, 1, undefined );
479
514
  }
480
515
 
@@ -495,7 +530,7 @@ class RetryPolicy {
495
530
  * Used to register a method that will be automatically called on each execution retry (after the initial one).
496
531
  *
497
532
  * @method
498
- * @param {function( number )} action The current attempt number will be provided as an argument.
533
+ * @param {function( number, (Error|undefined) )} action The current attempt and last error are provided.
499
534
  * @public
500
535
  */
501
536
  onRetry( action ) {
@@ -511,8 +546,8 @@ class RetryPolicy {
511
546
  *
512
547
  * @method
513
548
  * @param {Object} context
514
- * @param {function} operation
515
- * @param {Array} params
549
+ * @param {function( ...* ): Promise<*>} operation Operation to be executed; must return a Promise.
550
+ * @param {Array<*>} params The arguments to be provided to the operation upon execution.
516
551
  * @param {number} attempt
517
552
  * @param {Error} error
518
553
  * @returns {Promise}
@@ -523,14 +558,25 @@ class RetryPolicy {
523
558
  return Promise.reject( error );
524
559
  } else {
525
560
  if ( attempt > 1 && this.#onRetry ) {
526
- this.#onRetry( attempt );
527
- }
528
- return operation.apply( context, params ).catch( error => {
529
- if ( this.#onFailedAttempt ) {
530
- this.#onFailedAttempt( error );
561
+ try {
562
+ this.#onRetry( attempt, error );
563
+ } catch ( _ ) { /* ignore observer errors */
531
564
  }
532
- return this.#retry( context, operation, params, ( attempt - 1 ), error );
533
- } );
565
+ }
566
+ return Promise
567
+ .resolve()
568
+ .then( () => {
569
+ return operation.apply( context, params );
570
+ } )
571
+ .catch( ( error ) => {
572
+ if ( this.#onFailedAttempt ) {
573
+ try {
574
+ this.#onFailedAttempt( error );
575
+ } catch ( _ ) { /* ignore observer errors */
576
+ }
577
+ }
578
+ return this.#retry( context, operation, params, ( attempt + 1 ), error );
579
+ } );
534
580
  }
535
581
  }
536
582