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