topkat-utils 1.0.60 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/index.d.ts +34 -0
  3. package/dist/index.js +56 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/src/array-utils.d.ts +56 -0
  6. package/dist/src/array-utils.js +138 -0
  7. package/dist/src/array-utils.js.map +1 -0
  8. package/dist/src/date-utils.d.ts +100 -0
  9. package/dist/src/date-utils.js +357 -0
  10. package/dist/src/date-utils.js.map +1 -0
  11. package/dist/src/env-utils.d.ts +8 -0
  12. package/dist/src/env-utils.js +38 -0
  13. package/dist/src/env-utils.js.map +1 -0
  14. package/dist/src/error-utils.d.ts +8 -0
  15. package/dist/src/error-utils.js +99 -0
  16. package/dist/src/error-utils.js.map +1 -0
  17. package/dist/src/is-empty.d.ts +1 -0
  18. package/dist/src/is-empty.js +13 -0
  19. package/dist/src/is-empty.js.map +1 -0
  20. package/dist/src/is-object.d.ts +2 -0
  21. package/dist/src/is-object.js +7 -0
  22. package/dist/src/is-object.js.map +1 -0
  23. package/dist/src/isset.d.ts +1 -0
  24. package/dist/src/isset.js +8 -0
  25. package/dist/src/isset.js.map +1 -0
  26. package/dist/src/logger-utils.d.ts +76 -0
  27. package/dist/src/logger-utils.js +355 -0
  28. package/dist/src/logger-utils.js.map +1 -0
  29. package/dist/src/loop-utils.d.ts +37 -0
  30. package/dist/src/loop-utils.js +105 -0
  31. package/dist/src/loop-utils.js.map +1 -0
  32. package/dist/src/math-utils.d.ts +23 -0
  33. package/dist/src/math-utils.js +43 -0
  34. package/dist/src/math-utils.js.map +1 -0
  35. package/dist/src/mongo-utils.d.ts +11 -0
  36. package/dist/src/mongo-utils.js +49 -0
  37. package/dist/src/mongo-utils.js.map +1 -0
  38. package/dist/src/object-utils.d.ts +96 -0
  39. package/dist/src/object-utils.js +369 -0
  40. package/dist/src/object-utils.js.map +1 -0
  41. package/dist/src/private/config.d.ts +44 -0
  42. package/dist/src/private/config.js +55 -0
  43. package/dist/src/private/config.js.map +1 -0
  44. package/dist/src/private/error-handler.d.ts +10 -0
  45. package/dist/src/private/error-handler.js +18 -0
  46. package/dist/src/private/error-handler.js.map +1 -0
  47. package/dist/src/private/types.d.ts +4 -0
  48. package/dist/src/private/types.js +3 -0
  49. package/dist/src/private/types.js.map +1 -0
  50. package/dist/src/regexp-utils.d.ts +12 -0
  51. package/dist/src/regexp-utils.js +44 -0
  52. package/dist/src/regexp-utils.js.map +1 -0
  53. package/dist/src/remove-circular-json-stringify.d.ts +1 -0
  54. package/dist/src/remove-circular-json-stringify.js +20 -0
  55. package/dist/src/remove-circular-json-stringify.js.map +1 -0
  56. package/dist/src/string-utils.d.ts +77 -0
  57. package/dist/src/string-utils.js +209 -0
  58. package/dist/src/string-utils.js.map +1 -0
  59. package/dist/src/tests-utils.js +77 -0
  60. package/dist/src/tests-utils.js.map +1 -0
  61. package/dist/src/timer-utils.d.ts +16 -0
  62. package/dist/src/timer-utils.js +79 -0
  63. package/dist/src/timer-utils.js.map +1 -0
  64. package/dist/src/transaction-utils.d.ts +14 -0
  65. package/dist/src/transaction-utils.js +87 -0
  66. package/dist/src/transaction-utils.js.map +1 -0
  67. package/dist/src/validation-utils.d.ts +89 -0
  68. package/dist/src/validation-utils.js +192 -0
  69. package/dist/src/validation-utils.js.map +1 -0
  70. package/dist/src/wtf-utils.d.ts +7 -0
  71. package/dist/src/wtf-utils.js +83 -0
  72. package/dist/src/wtf-utils.js.map +1 -0
  73. package/index.ts +38 -0
  74. package/package.json +2 -2
  75. package/src/array-utils.ts +128 -0
  76. package/src/date-utils.ts +377 -0
  77. package/src/env-utils.ts +29 -0
  78. package/src/error-utils.ts +77 -0
  79. package/src/is-empty.ts +5 -0
  80. package/src/is-object.ts +3 -0
  81. package/src/isset.ts +3 -0
  82. package/src/logger-utils.ts +349 -0
  83. package/src/loop-utils.ts +101 -0
  84. package/src/math-utils.ts +38 -0
  85. package/src/mongo-utils.ts +38 -0
  86. package/src/object-utils.ts +356 -0
  87. package/src/private/config.ts +85 -0
  88. package/src/private/error-handler.ts +21 -0
  89. package/src/private/types.ts +6 -0
  90. package/src/regexp-utils.ts +37 -0
  91. package/src/remove-circular-json-stringify.ts +17 -0
  92. package/src/string-utils.ts +212 -0
  93. package/src/tests-utils.ts +70 -0
  94. package/src/timer-utils.ts +58 -0
  95. package/src/transaction-utils.ts +63 -0
  96. package/src/validation-utils.ts +253 -0
  97. package/src/wtf-utils.ts +88 -0
  98. package/tsconfig.json +11 -4
  99. package/utils.d.ts +0 -694
  100. package/utils.js +0 -2227
  101. package/utils.js.map +0 -1
  102. package/utils.ts +0 -2304
package/utils.js DELETED
@@ -1,2227 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.recursiveGenericFunctionSync = exports.recursiveGenericFunction = exports.unflattenObject = exports.flattenObject = exports.mergeDeepConfigurable = exports.mergeDeepOverrideArrays = exports.mergeDeep = exports.isObject = exports.deepClone = exports.cloneObject = exports.randomItemInArray = exports.isNotEmptyArray = exports.pushIfNotExist = exports.arrayToObjectSorted = exports.arrayCount = exports.noDuplicateFilter = exports.getArrayDiff = exports.getNotInArrayA = exports.getArrayInCommon = exports.compareArrays = exports.asArray = exports.strAsArray = exports.mergeMixins = exports.objForceWriteIfNotSet = exports.objForceWrite = exports.findByAddressAll = exports.findByAddress = exports.has = exports.registerConfig = exports.configFn = exports.dim = exports.parseBool = exports.ENV = exports.parseEnv = exports.simpleObjectMaskOrSelect = exports.miniTemplater = exports.sortUrlsByDeepnessInArrayOrObject = exports.urlPathJoin = exports.generateObjectId = exports.generateToken = exports.tryCatch = exports.minMax = exports.pad = exports.cln = exports.moyenne = exports.sumArray = exports.random = exports.isBetween = exports.round2 = exports.round = void 0;
4
- exports.getDateAsInt12 = exports.err422IfNotSet = exports.isEmpty = exports.validatorReturnErrArray = exports.isType = exports.isValid = exports.assert = exports.restTestMini = exports.validator = exports.checkCtxIntegrity = exports.checkAllObjectValuesAreEmpty = exports.isDateObject = exports.errXXXIfNotSet = exports.err500IfEmptyOrNotSet = exports.errIfEmptyOrNotSet = exports.err500IfNotSet = exports.errIfNotSet = exports.isEmptyOrNotSet = exports.issetOr = exports.getValuesBetweenStrings = exports.getValuesBetweenSeparator = exports.allMatches = exports.firstMatch = exports.escapeRegexp = exports.camelCaseToWords = exports.capitalize1st = exports.lowerCase = exports.upperCase = exports.titleCase = exports.pascalCase = exports.kebabCase = exports.snakeCase = exports.camelCase = exports.shuffleArray = exports.removeCircularJSONstringify = exports.isset = exports.cleanStackTrace = exports.forIasync = exports.forI = exports.getId = exports.deleteByAddress = exports.filterKeys = exports.ensureIsArrayAndPush = exports.ensureObjectProp = exports.sortObjKeyAccordingToValue = exports.objFilterUndefinedRecursive = exports.readOnlyForAll = exports.reassignForbidden = exports.readOnly = exports.objFilterUndefined = void 0;
5
- exports.removeItemFromQueue = exports.transaction = exports.executeInDelayedLoop = exports.waitUntilTrue = exports.runAsync = exports.timeout = exports.mongoPush = exports.mongoFilterMerger = exports.convertAccentedCharacters = exports.cliProgressBar = exports.stringifyExtraInfos = exports.stringifyInstanceOfError = exports.logErrPrivate = exports.C = exports.logger = exports.getDateAs = exports.differenceInWeeks = exports.differenceInDays = exports.differenceInHours = exports.differenceInMinutes = exports.differenceInSeconds = exports.differenceInMilliseconds = exports.firstDayOfMonth = exports.lastDayOfMonth = exports.getMinutes = exports.getHours = exports.getYear = exports.getDayOfMonth = exports.addYears = exports.addMonths = exports.addHours = exports.addMinutes = exports.addDays = exports.nextWeekDay = exports.doDateOverlap = exports.getDuration = exports.isTimeStringValid = exports.getIntAsTime = exports.getTimeAsInt = exports.dateOffset = exports.dateFormatted = exports.dateArrayInt = exports.dateArray = exports.dateStringToArray = exports.isDateIsoOrObjectValid = exports.isDateIntOrStringValid = exports.getDateAsObject = exports.getMonthAsInt = exports.getDateAsInt = exports.humanReadableTimestamp = void 0;
6
- exports.orIsset = exports.required = exports.underscoreCase = exports.dashCase = exports.randomizeArray = exports.removeUndefinedKeys = exports.JSONstringyParse = exports.arrayUniqueValue = exports.average = exports.int = exports.waitForTransaction = void 0;
7
- /** Round with custom number of decimals (default:0) */
8
- function round(number, decimals = 0) { return Math.round((typeof number === 'number' ? number : parseFloat(number)) * Math.pow(10, decimals)) / Math.pow(10, decimals); }
9
- exports.round = round;
10
- /** Round with custom number of decimals (default:2) */
11
- function round2(number, decimals = 2) { return round(number, decimals); }
12
- exports.round2 = round2;
13
- /** Is number between two numbers (including those numbers) */
14
- function isBetween(number, min, max, inclusive = true) { return inclusive ? number <= max && number >= min : number < max && number > min; }
15
- exports.isBetween = isBetween;
16
- /** Random number between two values with 0 decimals by default */
17
- function random(nb1, nb2, nbOfDecimals = 0) { return round(Math.random() * (nb2 - nb1) + nb1, nbOfDecimals); }
18
- exports.random = random;
19
- /** Sum all values of an array, all values MUST be numbers */
20
- function sumArray(array) {
21
- return array.filter(item => typeof item === 'number').reduce((sum, val) => isset(val) ? val + sum : sum, 0);
22
- }
23
- exports.sumArray = sumArray;
24
- /** Moyenne / average between array of values
25
- * @param {Number} round number of decimals to keep. Default:2
26
- */
27
- function moyenne(array, nbOfDecimals = 2) {
28
- return round(sumArray(array) / array.length, nbOfDecimals);
29
- }
30
- exports.moyenne = moyenne;
31
- /** Clean output for outside world. All undefined / null / NaN / Infinity values are changed to '-' */
32
- function cln(val, replacerInCaseItIsUndefinNaN = '-') { return ['undefined', undefined, 'indéfini', 'NaN', NaN, Infinity, null].includes(val) ? replacerInCaseItIsUndefinNaN : val; }
33
- exports.cln = cln;
34
- /** length default 2, shortcut for 1 to 01 */
35
- function pad(numberOrStr, length = 2) { return ('' + numberOrStr).padStart(length, '0'); }
36
- exports.pad = pad;
37
- /** return the number or the closest number of the range
38
- * * nb min max => returns
39
- * * 7 5 10 => 7 // in the range
40
- * * 2 5 10 => 5 // below the min value
41
- * * 99 5 10 => 10// above the max value
42
- */
43
- function minMax(nb, min, max) { return Math.max(min, Math.min(nb, max)); }
44
- exports.minMax = minMax;
45
- async function tryCatch(callback, onErr = () => { }) {
46
- try {
47
- return await callback();
48
- }
49
- catch (err) {
50
- return await onErr(err);
51
- }
52
- }
53
- exports.tryCatch = tryCatch;
54
- let generatedTokens = []; // cache to avoid collision
55
- let lastTs = new Date().getTime();
56
- /** minLength 8 if unique
57
- * @param {Number} length default: 20
58
- * @param {Boolean} unique default: true. Generate a real unique token base on the date. min length will be min 8 in this case
59
- * @param {string} mode one of ['alphanumeric', 'hexadecimal']
60
- * NOTE: to generate a mongoDB Random Id, use the params: 24, true, 'hexadecimal'
61
- */
62
- function generateToken(length = 20, unique = true, mode = 'alphanumeric') {
63
- let charConvNumeric = mode === 'alphanumeric' ? 36 : 16;
64
- if (unique && length < 8)
65
- length = 8;
66
- let token;
67
- let tokenTs;
68
- do {
69
- tokenTs = (new Date()).getTime();
70
- token = unique ? tokenTs.toString(charConvNumeric) : '';
71
- while (token.length < length)
72
- token += Math.random().toString(charConvNumeric).substr(2, 1); // char alphaNumeric aléatoire
73
- } while (generatedTokens.includes(token));
74
- if (lastTs < tokenTs)
75
- generatedTokens = []; // reset generated token on new timestamp because cannot collide
76
- generatedTokens.push(token);
77
- return token;
78
- }
79
- exports.generateToken = generateToken;
80
- function generateObjectId() {
81
- return generateToken(24, true, 'hexadecimal');
82
- }
83
- exports.generateObjectId = generateObjectId;
84
- /** Useful to join differents bits of url with normalizing slashes
85
- * * urlPathJoin('https://', 'www.kikou.lol/', '/user', '//2//') => https://www.kikou.lol/user/2/
86
- * * urlPathJoin('http:/', 'kikou.lol') => https://www.kikou.lol
87
- */
88
- function urlPathJoin(...bits) {
89
- return bits.join('/').replace(/\/+/g, '/').replace(/(https?:)\/\/?/, '$1//');
90
- }
91
- exports.urlPathJoin = urlPathJoin;
92
- /** path shall always be sorted before using in express
93
- * to avoid a generic route like /* to catch a specific one like /bonjour
94
- *
95
- * @param {Object[]} urlObjOrArr
96
- * @param {String} propInObjectOrIndexInArray
97
- * @return {Array} urls modified
98
- */
99
- function sortUrlsByDeepnessInArrayOrObject(urlObjOrArr, propInObjectOrIndexInArray) {
100
- // SORTING BY
101
- // Deepness => /user vs /user/contract
102
- // simply count the / occurence to sort deepest path first to avoid concurrence or routes mislead
103
- const deepness = route => (route.match(/\//g) || []).length;
104
- // AND params deepness
105
- // avoid `/user/:param` to take precedence over `/user/field` as `user/fields` will be catched by the first route
106
- // Eg: /user/:rr/name/:id => 14pts
107
- // /user/:id/:name/id => 15pts take precedence
108
- const paramPrecedencePts = route => route.split('/').reduce((pts, routeChunk, i) => /^:/.test(routeChunk) ? pts + (10 - i) : pts, 0);
109
- return urlObjOrArr.sort((a, b) => {
110
- const aUrl = a[propInObjectOrIndexInArray] || a;
111
- const bUrl = b[propInObjectOrIndexInArray] || b;
112
- if (!aUrl)
113
- return -1;
114
- if (!bUrl)
115
- return 1;
116
- return deepness(bUrl) - deepness(aUrl) || // /a/b vs /a
117
- paramPrecedencePts(aUrl) - paramPrecedencePts(bUrl) || // /:dynamicparam vs /param
118
- bUrl.length - aUrl.length; // help separating / vs /blah
119
- });
120
- }
121
- exports.sortUrlsByDeepnessInArrayOrObject = sortUrlsByDeepnessInArrayOrObject;
122
- /** Replace variables in a string like: `Hello {{userName}}!`
123
- * @param {String} content
124
- * @param {Object} varz object with key => value === toReplace => replacer
125
- * @param {Object} options
126
- * * valueWhenNotSet => replacer for undefined values. Default: ''
127
- * * regexp => must be 'g' and first capturing group matching the value to replace. Default: /{{\s*([^}]*)\s*}}/g
128
- */
129
- function miniTemplater(content, varz, options = {}) {
130
- options = {
131
- valueWhenNotSet: '',
132
- regexp: /{{\s*([^}]*)\s*}}/g,
133
- valueWhenContentUndefined: '',
134
- ...options,
135
- };
136
- return isset(content) ? content.replace(options.regexp, (m, $1) => isset(varz[$1]) ? varz[$1] : options.valueWhenNotSet) : options.valueWhenContentUndefined;
137
- }
138
- exports.miniTemplater = miniTemplater;
139
- /**
140
- *
141
- * @param {Object} object main object
142
- * @param {String[]} maskedOrSelectedFields array of fields
143
- * @param {Boolean} isMask default: true; determine the behavior of the function. If is mask, selected fields will not appear in the resulting object. If it's a select, only selected fields will appear.
144
- * @param {Boolean} deleteKeysInsteadOfReturningAnewObject default:false; modify the existing object instead of creating a new instance
145
- */
146
- function simpleObjectMaskOrSelect(object, maskedOrSelectedFields, isMask = true, deleteKeysInsteadOfReturningAnewObject = false) {
147
- const allKeys = Object.keys(object);
148
- const keysToMask = allKeys.filter(keyName => {
149
- if (isMask)
150
- return maskedOrSelectedFields.includes(keyName);
151
- else
152
- return !maskedOrSelectedFields.includes(keyName);
153
- });
154
- if (deleteKeysInsteadOfReturningAnewObject) {
155
- keysToMask.forEach(keyNameToDelete => delete object[keyNameToDelete]);
156
- return object;
157
- }
158
- else {
159
- return allKeys.reduce((newObject, key) => {
160
- if (!keysToMask.includes(key))
161
- newObject[key] = object[key];
162
- return newObject;
163
- }, {});
164
- }
165
- }
166
- exports.simpleObjectMaskOrSelect = simpleObjectMaskOrSelect;
167
- /** Parse one dimention object undefined, true, false, null represented as string will be converted to primitives */
168
- function parseEnv(env) {
169
- const newEnv = {};
170
- for (const k in env) {
171
- const val = env[k];
172
- if (val === 'undefined')
173
- newEnv[k] = undefined;
174
- else if (val === 'true')
175
- newEnv[k] = true;
176
- else if (val === 'false')
177
- newEnv[k] = false;
178
- else if (val === 'null')
179
- newEnv[k] = null;
180
- else
181
- newEnv[k] = env[k];
182
- }
183
- return newEnv;
184
- }
185
- exports.parseEnv = parseEnv;
186
- /** READ ONLY, output a parsed version of process.env
187
- * use it like ENV().myVar
188
- */
189
- function ENV() {
190
- const throwErr = () => { throw new Error('Please use process.env to write to env'); };
191
- return new Proxy(parseEnv(process.env), {
192
- set: throwErr,
193
- defineProperty: throwErr,
194
- deleteProperty: throwErr,
195
- });
196
- }
197
- exports.ENV = ENV;
198
- /**
199
- * @param {any} mayBeAstring
200
- * @return !!value
201
- */
202
- function parseBool(mayBeAstring) {
203
- if (typeof mayBeAstring === 'boolean')
204
- return mayBeAstring;
205
- else
206
- return mayBeAstring === 'true' ? true : mayBeAstring === 'false' ? false : !!mayBeAstring;
207
- }
208
- exports.parseBool = parseBool;
209
- function dim(str = '') {
210
- return configFn().terminal.noColor ? str : `\x1b[2m${str.toString().split('\n').join('\x1b[0m\n\x1b[2m')}\x1b[0m`;
211
- }
212
- exports.dim = dim;
213
- let config = {
214
- env: 'development',
215
- isProd: false,
216
- nbOfLogsToKeep: 25,
217
- customTypes: {},
218
- terminal: {
219
- noColor: false,
220
- theme: {
221
- primary: [0, 149, 250],
222
- shade1: [0, 90, 250],
223
- shade2: [0, 208, 245],
224
- paddingX: 2,
225
- paddingY: 2,
226
- pageWidth: 53,
227
- debugModeColor: [201, 27, 169],
228
- }
229
- },
230
- };
231
- /** Allow dynamic changing of config */
232
- function configFn() { return config; }
233
- exports.configFn = configFn;
234
- /** Register custom config
235
- * @param {object} customConfig { 'email': email => /.+@.+/.test(email), type2 : myTestFunction() }
236
- * * env: 'development',
237
- * * customTypes: {},
238
- * * terminal: {
239
- * * noColor: false, // disable colored escape sequences like /mOO35...etc
240
- * * theme: {
241
- * * primary: [61, 167, 32], // main color (title font)
242
- * * shade1: [127, 192, 39], // gradient shade 1
243
- * * shade2: [194, 218, 47], // gradient shade 2
244
- * * bgColor: false, // background color
245
- * * paddingX: 2, // nb spaces added before an outputted str
246
- * * paddingY: 2, //
247
- * * fontColor: false, // default font color
248
- * * pageWidth: 53, // page size in character
249
- * * debugModeColor: [147, 212, 6], // usually orange
250
- * * }
251
- * * },
252
- */
253
- function registerConfig(customConfig) {
254
- if (!isset(customConfig.terminal))
255
- customConfig.terminal = {};
256
- const newconfig = {
257
- ...config,
258
- ...customConfig
259
- };
260
- newconfig.terminal = {
261
- ...config.terminal,
262
- ...customConfig.terminal
263
- };
264
- newconfig.terminal.theme = {
265
- ...config.terminal.theme,
266
- ...(customConfig.terminal.theme || {})
267
- };
268
- config = newconfig;
269
- config.isProd = config.env.includes('prod');
270
- }
271
- exports.registerConfig = registerConfig;
272
- //----------------------------------------
273
- // ERROR HANDLER
274
- //----------------------------------------
275
- class dataValidationUtilErrorHandler extends Error {
276
- constructor(msg, code, extraInfos) {
277
- super(msg);
278
- this.message = msg;
279
- this.msg = msg;
280
- this.name = `${code} ${msg}`;
281
- this.fromDataValidation = true; // will be catched by express error handler
282
- this.code = code;
283
- this.extraInfos = extraInfos;
284
- this.errorDescription = {
285
- msg,
286
- code,
287
- ...extraInfos,
288
- };
289
- }
290
- }
291
- //----------------------------------------
292
- // Object utils shared
293
- //----------------------------------------
294
- /**
295
- * check if **object OR array** has property Safely (avoid cannot read property x of null and such)
296
- * @param {Object} obj object to test against
297
- * @param {string} addr `a.b.c.0.1` will test if myObject has props a that has prop b. Work wit arrays as well (like `arr.0`)
298
- */
299
- function has(obj, addr) {
300
- if (!isset(obj) || typeof obj !== 'object')
301
- return;
302
- let propsArr = addr.replace(/\.?\[(\d+)\]/g, '.$1').split('.'); // replace a[3] => a.3;
303
- let objChain = obj;
304
- return propsArr.every(prop => {
305
- objChain = objChain[prop];
306
- return isset(objChain);
307
- });
308
- }
309
- exports.has = has;
310
- /** Find address in an object "a.b.c" IN { a : { b : {c : 'blah' }}} RETURNS 'blah'
311
- * @param obj
312
- * @param addr accept syntax like "obj.subItem.[0].sub2" OR "obj.subItem.0.sub2" OR "obj.subItem[0].sub2"
313
- * @returns the last item of the chain OR undefined if not found
314
- */
315
- function findByAddress(obj, addr) {
316
- if (addr === '')
317
- return obj;
318
- if (!isset(obj) || typeof obj !== 'object')
319
- return console.warn('Main object in `findByAddress` function is undefined or has the wrong type');
320
- const propsArr = addr.replace(/\.?\[(\d+)\]/g, '.$1').split('.'); // replace .[4] AND [4] TO .4
321
- const objRef = propsArr.reduce((objChain, prop) => {
322
- if (!isset(objChain) || typeof objChain !== 'object' || !isset(objChain[prop]))
323
- return;
324
- else
325
- return objChain[prop];
326
- }, obj);
327
- return objRef;
328
- }
329
- exports.findByAddress = findByAddress;
330
- function findByAddressAll(obj, addr, returnAddresses = false) {
331
- err500IfNotSet({ obj, addr });
332
- if (addr === '')
333
- return obj;
334
- const addrRegexp = new RegExp('^' + addr
335
- .replace(/\.?\[(\d+)\]/g, '.$1') // replace .[4] AND [4] TO .4
336
- .replace(/\./g, '\\.')
337
- .replace(/\.\*/g, '.[^.]+') // replace * by [^. (all but a point)]
338
- + '$');
339
- const matchingItems = [];
340
- recursiveGenericFunctionSync(obj, (item, address) => {
341
- if (addrRegexp.test(address))
342
- matchingItems.push(returnAddresses ? [address, item] : item);
343
- });
344
- return matchingItems;
345
- }
346
- exports.findByAddressAll = findByAddressAll;
347
- /** Enforce writing subItems. Eg: user.name.blah will ensure all are set until the writing of the last item
348
- * NOTE: doesn't work with arrays
349
- */
350
- function objForceWrite(obj, addr, item) {
351
- const chunks = addr.replace(/\.?\[(\d+)\]/g, '.[$1').split('.');
352
- let lastItem = obj;
353
- chunks.forEach((chunkRaw, i) => {
354
- const chunk = chunkRaw.replace(/^\[/, '');
355
- if (i === chunks.length - 1)
356
- lastItem[chunk] = item;
357
- else if (!isset(lastItem[chunk])) {
358
- const nextChunk = chunks[i + 1];
359
- if (isset(nextChunk) && nextChunk.startsWith('['))
360
- lastItem[chunk] = [];
361
- else
362
- lastItem[chunk] = {};
363
- }
364
- else if (typeof lastItem[chunk] !== 'object')
365
- throw new dataValidationUtilErrorHandler(`itemNotTypeObjectOrArrayInAddrChainForObjForceWrite`, 500, { origin: 'Validator', chunks, actualValueOfItem: lastItem[chunk], actualChunk: chunk });
366
- lastItem = lastItem[chunk];
367
- });
368
- }
369
- exports.objForceWrite = objForceWrite;
370
- /** Enforce writing subItems, only if obj.addr is empty.
371
- * Eg: user.name.blah will ensure all are set until the writing of the last item
372
- * if user.name.blah has a value it will not change it.
373
- * NOTE: doesn't work with arrays
374
- */
375
- function objForceWriteIfNotSet(obj, addr, item) {
376
- if (!isset(findByAddress(obj, addr)))
377
- return objForceWrite(obj, addr, item);
378
- }
379
- exports.objForceWriteIfNotSet = objForceWriteIfNotSet;
380
- /** Merge mixins into class. Use it in the constructor like: mergeMixins(this, {myMixin: true}) */
381
- function mergeMixins(that, ...mixins) {
382
- mixins.forEach(mixin => {
383
- for (const method in mixin) {
384
- that[method] = mixin[method];
385
- }
386
- });
387
- }
388
- exports.mergeMixins = mergeMixins;
389
- /** If a string is provided, return it as array else return the value */
390
- function strAsArray(arrOrStr) {
391
- return typeof arrOrStr === 'string' ? [arrOrStr] : arrOrStr;
392
- }
393
- exports.strAsArray = strAsArray;
394
- /** If not an array provided, return the array with the value
395
- * /!\ NOTE /!\ In case the value is null or undefined, it will return that value
396
- */
397
- function asArray(item) {
398
- return (typeof item === 'undefined' ? item : Array.isArray(item) ? item : [item]);
399
- }
400
- exports.asArray = asArray;
401
- /** Array comparison
402
- * @return {object} { inCommon, notInB, notInA }
403
- */
404
- function compareArrays(arrayA, arrayB, compare = (a, b) => a === b) {
405
- return {
406
- inCommon: getArrayInCommon(arrayA, arrayB, compare),
407
- notInB: getNotInArrayA(arrayB, arrayA, compare),
408
- notInA: getNotInArrayA(arrayA, arrayB, compare),
409
- };
410
- }
411
- exports.compareArrays = compareArrays;
412
- /**
413
- * @return [] only elements that are both in arrayA and arrayB
414
- */
415
- function getArrayInCommon(arrayA = [], arrayB = [], compare = (a, b) => a === b) {
416
- if (!Array.isArray(arrayA) || !Array.isArray(arrayB))
417
- return [];
418
- else
419
- return arrayA.filter(a => arrayB.some(b => compare(a, b)));
420
- }
421
- exports.getArrayInCommon = getArrayInCommon;
422
- /**
423
- * @return [] only elements that are in arrayB and not in arrayA
424
- */
425
- function getNotInArrayA(arrayA = [], arrayB = [], compare = (a, b) => a === b) {
426
- if (!Array.isArray(arrayA) && Array.isArray(arrayB))
427
- return arrayB;
428
- else if (!Array.isArray(arrayB))
429
- return [];
430
- else
431
- return arrayB.filter(b => !arrayA.some(a => compare(a, b)));
432
- }
433
- exports.getNotInArrayA = getNotInArrayA;
434
- /**
435
- * @return [] only elements that are in neither arrayA and arrayB
436
- */
437
- function getArrayDiff(arrayA = [], arrayB = [], compare = (a, b) => a === b) {
438
- return [...getNotInArrayA(arrayA, arrayB, compare), ...getNotInArrayA(arrayB, arrayA, compare)];
439
- }
440
- exports.getArrayDiff = getArrayDiff;
441
- /** filter duplicate values in an array
442
- * @param {function} comparisonFn default:(a, b) => a === b. A function that shall return true if two values are considered equal
443
- * @return {array|function}
444
- */
445
- function noDuplicateFilter(arr, comparisonFn = (a, b) => a === b) {
446
- return arr.filter((a, i, arr) => arr.findIndex(b => comparisonFn(a, b)) === i);
447
- }
448
- exports.noDuplicateFilter = noDuplicateFilter;
449
- /** Count number of occurence of item in array */
450
- function arrayCount(item, arr) {
451
- return arr.reduce((total, item2) => item === item2 ? total + 1 : total, 0);
452
- }
453
- exports.arrayCount = arrayCount;
454
- /**
455
- * Sort an array in an object of subArrays, no duplicate.
456
- * @param {Array} array
457
- * @param {function} getFieldFromItem (itemOfArray) => field[String|Number]
458
- * tell me how you want to sort your Array
459
- */
460
- function arrayToObjectSorted(array, getFieldFromItem) {
461
- const res = {};
462
- array.forEach(item => {
463
- objForceWriteIfNotSet(res, getFieldFromItem(item), []);
464
- res[getFieldFromItem(item)].push(item);
465
- });
466
- return res;
467
- }
468
- exports.arrayToObjectSorted = arrayToObjectSorted;
469
- /**
470
- * @param {Function} comparisonFunction default: (itemToPush, itemAlreadyInArray) => itemToPush === itemAlreadyInArray; comparison function to consider the added item duplicate
471
- */
472
- function pushIfNotExist(arrayToPushInto, valueOrArrayOfValuesToBePushed, comparisonFunction = (a, b) => a === b) {
473
- const valuesToPush = asArray(valueOrArrayOfValuesToBePushed).filter(a => !arrayToPushInto.some(b => comparisonFunction(a, b)));
474
- arrayToPushInto.push(...valuesToPush);
475
- return arrayToPushInto;
476
- }
477
- exports.pushIfNotExist = pushIfNotExist;
478
- function isNotEmptyArray(arr) {
479
- return Array.isArray(arr) && !!arr.length;
480
- }
481
- exports.isNotEmptyArray = isNotEmptyArray;
482
- function randomItemInArray(array) {
483
- return array[Math.floor(Math.random() * array.length)];
484
- }
485
- exports.randomItemInArray = randomItemInArray;
486
- function cloneObject(o) {
487
- return JSON.parse(JSON.stringify(o));
488
- }
489
- exports.cloneObject = cloneObject;
490
- /** Deep clone. WILL REMOVE circular references */
491
- function deepClone(obj, cache = []) {
492
- let copy;
493
- // usefull to not modify 1st level objet by lower levels
494
- // this is required for the same object to be referenced not in a redundant way
495
- const newCache = [...cache];
496
- if (obj instanceof Date)
497
- return new Date(obj);
498
- // Handle Array
499
- if (Array.isArray(obj)) {
500
- if (newCache.includes(obj))
501
- return [];
502
- newCache.push(obj);
503
- copy = [];
504
- for (var i = 0, len = obj.length; i < len; i++) {
505
- copy[i] = deepClone(obj[i], newCache);
506
- }
507
- return copy;
508
- }
509
- if (typeof obj === 'object' && obj !== null && Object.getPrototypeOf(obj) === Object.prototype) {
510
- if (newCache.includes(obj))
511
- return {};
512
- newCache.push(obj);
513
- copy = {};
514
- for (var key in obj) {
515
- if (Object.prototype.hasOwnProperty.call(obj, key)) {
516
- copy[key] = deepClone(obj[key], newCache);
517
- }
518
- }
519
- return copy;
520
- }
521
- return obj; // number, string...
522
- }
523
- exports.deepClone = deepClone;
524
- /** test if object but not array and not null (null is an object in Js) */
525
- function isObject(o) { return o instanceof Object && [Object, Error].includes(o.constructor); }
526
- exports.isObject = isObject;
527
- /** object and array merge
528
- * @warn /!\ Array will be merged and duplicate values will be deleted /!\
529
- * @return {Object} new object result from merge
530
- * NOTE: objects in params will NOT be modified*/
531
- function mergeDeep(...objects) {
532
- return mergeDeepConfigurable((previousVal, currentVal) => [...previousVal, ...currentVal].filter((elm, i, arr) => arr.indexOf(elm) === i), (previousVal, currentVal) => mergeDeep(previousVal, currentVal), undefined, ...objects);
533
- }
534
- exports.mergeDeep = mergeDeep;
535
- /** object and array merge
536
- * @warn /!\ Array will be replaced by the latest object /!\
537
- * @return {Object} new object result from merge
538
- * NOTE: objects in params will NOT be modified */
539
- function mergeDeepOverrideArrays(...objects) {
540
- return mergeDeepConfigurable(undefined, (previousVal, currentVal) => mergeDeepOverrideArrays(previousVal, currentVal), undefined, ...objects);
541
- }
542
- exports.mergeDeepOverrideArrays = mergeDeepOverrideArrays;
543
- /** object and array merge
544
- * @param {Function} replacerForArrays item[key] = (prevValue, currentVal) => () When 2 values are arrays,
545
- * @param {Function} replacerForObjects item[key] = (prevValue, currentVal) => () When 2 values are objects,
546
- * @param {Function} replacerDefault item[key] = (prevValue, currentVal) => () For all other values
547
- * @param {...Object} objects
548
- * @return {Object} new object result from merge
549
- * NOTE: objects in params will NOT be modified
550
- */
551
- function mergeDeepConfigurable(replacerForArrays = (prev, curr) => curr, replacerForObjects, replacerDefault = (prev, curr) => curr, ...objects) {
552
- return objects.reduce((actuallyMerged, obj) => {
553
- Object.keys(obj).forEach(key => {
554
- const previousVal = actuallyMerged[key];
555
- const currentVal = obj[key];
556
- if (Array.isArray(previousVal) && Array.isArray(currentVal)) {
557
- actuallyMerged[key] = replacerForArrays(previousVal, currentVal);
558
- }
559
- else if (isObject(previousVal) && isObject(currentVal)) {
560
- actuallyMerged[key] = replacerForObjects(previousVal, currentVal);
561
- }
562
- else {
563
- actuallyMerged[key] = replacerDefault(previousVal, currentVal);
564
- }
565
- });
566
- return actuallyMerged;
567
- }, {});
568
- }
569
- exports.mergeDeepConfigurable = mergeDeepConfigurable;
570
- /** { a: {b:2}} => {'a.b':2} useful for translations
571
- * NOTE: will remove circular references
572
- */
573
- function flattenObject(data, config = {}) {
574
- const { withoutArraySyntax = false, withArraySyntaxMinified = false } = config;
575
- const result = {};
576
- const seenObjects = []; // avoidCircular reference to infinite loop
577
- const recurse = (cur, prop) => {
578
- if (Array.isArray(cur)) {
579
- let l = cur.length;
580
- let i = 0;
581
- if (withoutArraySyntax)
582
- recurse(cur[0], prop);
583
- else {
584
- for (; i < l; i++)
585
- recurse(cur[i], prop + (withArraySyntaxMinified ? `.${i}` : `[${i}]`));
586
- if (l == 0)
587
- result[prop] = [];
588
- }
589
- }
590
- else if (isObject(cur)) { // is object
591
- try {
592
- if (seenObjects.includes(cur))
593
- cur = deepClone(cur); // avoid circular ref but allow duplicate objects
594
- else
595
- seenObjects.push(cur);
596
- const isEmpty = Object.keys(cur).length === 0;
597
- for (const p in cur)
598
- recurse(cur[p], (prop ? prop + '.' : '') + p.replace(/\./g, '%')); // allow prop to contain special chars like points);
599
- if (isEmpty && prop)
600
- result[prop] = {};
601
- }
602
- catch (error) {
603
- exports.C.warning('Circular reference in flattenObject, impossible to parse');
604
- }
605
- }
606
- else
607
- result[prop] = cur;
608
- };
609
- recurse(data, '');
610
- return result;
611
- }
612
- exports.flattenObject = flattenObject;
613
- /** {'a.b':2} => { a: {b:2}} */
614
- function unflattenObject(data) {
615
- const newO = {};
616
- for (const [addr, value] of Object.entries(data))
617
- objForceWrite(newO, addr, value);
618
- return newO;
619
- }
620
- exports.unflattenObject = unflattenObject;
621
- /**
622
- * @param {any} item the first array or object or whatever you want to recursively browse
623
- * @param {function} callback the callback you want to apply on items including the main one
624
- * * this callback has 2 arguments: (item, address) =>
625
- * * `item` => the actual item
626
- * * `addr` => the address of the item, not including root (Eg: subItem1.sub2.[3].[2]) array indexes are juste written as numbers
627
- * * `lastElementKey` => the key of last item. May be a number if last item is an array
628
- * * `parent` => reference the parent object as this is the only way of reassigning a value for the item. Eg: parent[lastElementKey] = myNewItem
629
- * * **NOTE** => if a key of an item contains dots, they will be replaced by '%' in `addr`
630
- * * **NOTE2** => if false is returned by the callback it will stop all other iterations (but not in an array)
631
- * @param {string} addr$ optional, the base address for the callback function
632
- * @param lastElementKey technical field
633
- * NOTE: will remove circular references
634
- * /!\ check return values
635
- */
636
- async function recursiveGenericFunction(item, callback, addr$ = '', lastElementKey = '', parent, techFieldToAvoidCircularDependency = []) {
637
- err500IfNotSet({ callback });
638
- if (!techFieldToAvoidCircularDependency.includes(item)) {
639
- const result = addr$ === '' ? true : await callback(item, addr$, lastElementKey, parent);
640
- if (result !== false) {
641
- const addr = addr$ ? addr$ + '.' : '';
642
- if (isType(item, 'array')) {
643
- techFieldToAvoidCircularDependency.push(item);
644
- await Promise.all(item.map((e, i) => recursiveGenericFunction(e, callback, addr + '[' + i + ']', i, item, techFieldToAvoidCircularDependency)));
645
- }
646
- else if (isObject(item)) {
647
- techFieldToAvoidCircularDependency.push(item);
648
- await Promise.all(Object.entries(item).map(([key, val]) => recursiveGenericFunction(val, callback, addr + key.replace(/\./g, '%'), key, item, techFieldToAvoidCircularDependency)));
649
- }
650
- }
651
- }
652
- return item;
653
- }
654
- exports.recursiveGenericFunction = recursiveGenericFunction;
655
- /**
656
- * @param {any} item the first array or object or whatever you want to recursively browse
657
- * @param {function} callback the callback you want to apply on items including the main one
658
- * * this callback has 2 arguments: (item, address) =>
659
- * * `item` => the actual item
660
- * * `addr` => the address of the item, not including root (Eg: subItem1.sub2.[3].[2]) array indexes are juste written as numbers
661
- * * `lastElementKey` => the key of last item. May be a number if last item is an array
662
- * * `parent` => reference the parent object as this is the only way of reassigning a value for the item. Eg: parent[lastElementKey] = myNewItem
663
- * * **NOTE** => if a key of an item contains dots, they will be replaced by '%' in `addr`
664
- * * **NOTE2** => if false is returned by the callback it will stop all other iterations (but not in an array)
665
- * * **NOTE3** => to reassign a key use => parent[lastElementKey] = myNewItem
666
- * @param {string} addr$ optional, the base address for the callback function
667
- * @param lastElementKey technical field
668
- * NOTE: will remove circular references
669
- * /!\ check return values
670
- */
671
- function recursiveGenericFunctionSync(item, callback, addr$ = '', lastElementKey = '', parent, techFieldToAvoidCircularDependency = []) {
672
- err500IfNotSet({ callback });
673
- if (!techFieldToAvoidCircularDependency.includes(item)) {
674
- const result = addr$ === '' ? true : callback(item, addr$, lastElementKey, parent);
675
- if (result !== false) {
676
- const addr = addr$ ? addr$ + '.' : '';
677
- if (isType(item, 'array')) {
678
- techFieldToAvoidCircularDependency.push(item); // do not up one level
679
- item.forEach((e, i) => recursiveGenericFunctionSync(e, callback, addr + '[' + i + ']', i, item, techFieldToAvoidCircularDependency));
680
- }
681
- else if (isObject(item)) {
682
- techFieldToAvoidCircularDependency.push(item);
683
- Object.entries(item).forEach(([key, val]) => recursiveGenericFunctionSync(val, callback, addr + key.replace(/\./g, '%'), key, item, techFieldToAvoidCircularDependency));
684
- }
685
- }
686
- }
687
- return item;
688
- }
689
- exports.recursiveGenericFunctionSync = recursiveGenericFunctionSync;
690
- /** Remove all key/values pair if value is undefined */
691
- function objFilterUndefined(o) {
692
- Object.keys(o).forEach(k => !isset(o[k]) && delete o[k]);
693
- return o;
694
- }
695
- exports.objFilterUndefined = objFilterUndefined;
696
- /** Lock all 1st level props of an object to read only */
697
- function readOnly(o) {
698
- const throwErr = () => { throw new dataValidationUtilErrorHandler('Cannot modify object that is read only', 500); };
699
- return new Proxy(o, {
700
- set: throwErr,
701
- defineProperty: throwErr,
702
- deleteProperty: throwErr,
703
- });
704
- }
705
- exports.readOnly = readOnly;
706
- /** Fields of the object can be created BUT NOT reassignated */
707
- function reassignForbidden(o) {
708
- return new Proxy(o, {
709
- defineProperty: function (that, key, value) {
710
- if (key in that)
711
- throw new dataValidationUtilErrorHandler(`Cannot reassign the property ${key.toString()} of this object`, 500);
712
- else {
713
- that[key] = value;
714
- return true;
715
- }
716
- },
717
- deleteProperty: function (_, key) {
718
- throw new dataValidationUtilErrorHandler(`Cannot delete the property ${key.toString()} of this object`, 500);
719
- }
720
- });
721
- }
722
- exports.reassignForbidden = reassignForbidden;
723
- /** All fileds and subFields of the object will become readOnly */
724
- function readOnlyForAll(object) {
725
- recursiveGenericFunctionSync(object, (item, _, lastElementKey, parent) => {
726
- if (typeof item === 'object')
727
- parent[lastElementKey] = readOnly(item);
728
- });
729
- return object;
730
- }
731
- exports.readOnlyForAll = readOnlyForAll;
732
- function objFilterUndefinedRecursive(obj) {
733
- if (obj) {
734
- const flattenedObj = flattenObject(obj);
735
- Object.keys(flattenedObj).forEach(key => {
736
- if (!isset(flattenedObj[key])) {
737
- delete flattenedObj[key];
738
- }
739
- });
740
- return unflattenObject(flattenedObj);
741
- }
742
- else
743
- return obj;
744
- }
745
- exports.objFilterUndefinedRecursive = objFilterUndefinedRecursive;
746
- function sortObjKeyAccordingToValue(unorderedObj, ascending = true) {
747
- const orderedObj = {};
748
- const sortingConst = ascending ? 1 : -1;
749
- Object.keys(unorderedObj)
750
- .sort((keyA, keyB) => unorderedObj[keyA] < unorderedObj[keyB] ? sortingConst : -sortingConst)
751
- .forEach(key => { orderedObj[key] = unorderedObj[key]; });
752
- return orderedObj;
753
- }
754
- exports.sortObjKeyAccordingToValue = sortObjKeyAccordingToValue;
755
- /**
756
- * Make default value if object key do not exist
757
- * @param {object} obj
758
- * @param {string} addr
759
- * @param {any} defaultValue
760
- * @param {function} callback (obj[addr]) => processValue. Eg: myObjAddr => myObjAddr.push('bikou')
761
- * @return obj[addr] eventually processed by the callback
762
- */
763
- function ensureObjectProp(obj, addr, defaultValue, callback = o => o) {
764
- err500IfNotSet({ obj, addr, defaultValue, callback });
765
- if (!isset(obj[addr]))
766
- obj[addr] = defaultValue;
767
- callback(obj[addr]);
768
- return obj[addr];
769
- }
770
- exports.ensureObjectProp = ensureObjectProp;
771
- /**
772
- * Maye sure obj[addr] is an array and push a value to it
773
- * @param {Object} obj parent object
774
- * @param {String} addr field name in parent
775
- * @param {Any} valToPush
776
- * @param {Boolean} onlyUniqueValues default:false; may be true or a comparision function; (a,b) => return true if they are the same like (a, b) => a.name === b.name
777
- * @return obj[addr] eventually processed by the callback
778
- */
779
- function ensureIsArrayAndPush(obj, addr, valToPush, onlyUniqueValues) {
780
- return ensureObjectProp(obj, addr, [], objValue => {
781
- if (isset(onlyUniqueValues)) {
782
- let duplicateFound = false;
783
- if (typeof onlyUniqueValues === 'function')
784
- duplicateFound = objValue.some(a => onlyUniqueValues(a, valToPush));
785
- else
786
- duplicateFound = objValue.includes(valToPush);
787
- if (!duplicateFound)
788
- objValue.push(valToPush);
789
- }
790
- else
791
- objValue.push(valToPush);
792
- });
793
- }
794
- exports.ensureIsArrayAndPush = ensureIsArrayAndPush;
795
- /**
796
- * @param {Object} obj the object on which we want to filter the keys
797
- * @param {function} filterFunc function that returns true if the key match the wanted criteria
798
- */
799
- function filterKeys(obj, filter) {
800
- const clone = cloneObject(obj);
801
- recursiveGenericFunctionSync(obj, (item, addr, lastElementKey) => {
802
- if (!filter(lastElementKey))
803
- deleteByAddress(clone, addr.split('.'));
804
- });
805
- return clone;
806
- }
807
- exports.filterKeys = filterKeys;
808
- /**
809
- * @param {Object} obj the object on which we want to delete a property
810
- * @param {Array} addr addressArray on which to delete the property
811
- */
812
- function deleteByAddress(obj, addr) {
813
- let current = obj;
814
- for (let i = 0; i < addr.length - 2; i++)
815
- current = current[addr[i]];
816
- delete current[addr[addr.length - 1]];
817
- }
818
- exports.deleteByAddress = deleteByAddress;
819
- /** @return undefined if cannot find _id */
820
- function getId(obj = {}) {
821
- if (!obj)
822
- return; // null case
823
- if (obj._id)
824
- return obj._id.toString();
825
- else if (isType(obj, 'objectId'))
826
- return obj.toString();
827
- }
828
- exports.getId = getId;
829
- function forI(nbIterations, callback) {
830
- const results = [];
831
- for (let i = 0; i < nbIterations; i++) {
832
- const prevValue = results[results.length - 1];
833
- results.push(callback(i, prevValue, results));
834
- }
835
- return results;
836
- }
837
- exports.forI = forI;
838
- async function forIasync(nbIterations, callback) {
839
- const results = [];
840
- for (let i = 0; i < nbIterations; i++) {
841
- results.push(await callback(i));
842
- }
843
- return results;
844
- }
845
- exports.forIasync = forIasync;
846
- function cleanStackTrace(stack) {
847
- if (typeof stack !== 'string')
848
- return '';
849
- stack.replace(/home\/[^/]+\/[^/]+\//g, '');
850
- const lines = stack.split('\n');
851
- const removeIfContain = [
852
- 'logger-utils.js',
853
- 'TCP.onread',
854
- 'readableAddChunk',
855
- 'Socket.EventEmitter.emit (domain.js',
856
- 'Socket.emit (events.js',
857
- 'Connection.EventEmitter.emit (domain.js',
858
- 'Connection.emit (events.js',
859
- 'Socket.Readable.push (_stream_readable',
860
- 'model.Query',
861
- 'Object.promiseOrCallback',
862
- 'Connection.<anonymous>',
863
- 'process.topLevelDomainCallback',
864
- // internal
865
- 'internal/process',
866
- 'internal/timers',
867
- 'internal/modules',
868
- 'internal/main',
869
- 'DefaultError.throw',
870
- 'Object.throw',
871
- 'mongoose/lib/utils',
872
- 'at Array.forEach (<anonymous>)',
873
- ];
874
- const linesClean = lines
875
- .filter(l => !removeIfContain.some(text => l.includes(text)))
876
- .map((line, i) => {
877
- if (i === 0)
878
- return '';
879
- else {
880
- const [, start, fileName, end] = line.match(/(^.+\/)([^/]+:\d+:\d+)(.{0,3})/) || [];
881
- return fileName ? `\x1b[2m${start}\x1b[0m${fileName}\x1b[2m${end}\x1b[0m` : `\x1b[2m${line}\x1b[0m`;
882
- }
883
- })
884
- .join('\n');
885
- return linesClean;
886
- }
887
- exports.cleanStackTrace = cleanStackTrace;
888
- function isset(...elms) {
889
- return elms.every(elm => typeof elm !== 'undefined' && elm !== null);
890
- }
891
- exports.isset = isset;
892
- function removeCircularJSONstringify(object, indent = 2) {
893
- const getCircularReplacer = () => {
894
- const seen = new WeakSet();
895
- return (key, value) => {
896
- if (typeof value === 'object' && value !== null) {
897
- if (seen.has(value)) {
898
- return;
899
- }
900
- seen.add(value);
901
- }
902
- return value;
903
- };
904
- };
905
- return JSON.stringify(object, getCircularReplacer(), indent);
906
- }
907
- exports.removeCircularJSONstringify = removeCircularJSONstringify;
908
- function shuffleArray(array) {
909
- for (let i = array.length - 1; i > 0; i--) {
910
- const j = Math.floor(Math.random() * (i + 1));
911
- [array[i], array[j]] = [array[j], array[i]];
912
- }
913
- return array;
914
- }
915
- exports.shuffleArray = shuffleArray;
916
- /**Eg: camelCase */
917
- function camelCase(...wordBits) {
918
- return wordBits.filter(e => e).map((w, i) => i === 0 ? w : capitalize1st(w)).join('');
919
- }
920
- exports.camelCase = camelCase;
921
- /**Eg: snake_case
922
- * trimmed but not lowerCased
923
- */
924
- function snakeCase(...wordBits) {
925
- return wordBits.filter(e => e).map(w => w.trim()).join('_');
926
- }
927
- exports.snakeCase = snakeCase;
928
- /**Eg: kebab-case
929
- * trimmed AND lowerCased
930
- * undefined, null... => ''
931
- */
932
- function kebabCase(...wordBits) {
933
- return wordBits.filter(e => e).map(w => w.trim().toLowerCase()).join('-');
934
- }
935
- exports.kebabCase = kebabCase;
936
- /**Eg: PascalCase undefined, null... => '' */
937
- function pascalCase(...wordBits) {
938
- return wordBits.filter(e => e).map((w, i) => capitalize1st(w)).join('');
939
- }
940
- exports.pascalCase = pascalCase;
941
- /**Eg: Titlecase undefined, null... => '' */
942
- function titleCase(...wordBits) {
943
- return capitalize1st(wordBits.filter(e => e).map(w => w.trim()).join(''));
944
- }
945
- exports.titleCase = titleCase;
946
- /**Eg: UPPERCASE undefined, null... => '' */
947
- function upperCase(...wordBits) {
948
- return wordBits.filter(e => e).map(w => w.trim().toUpperCase()).join('');
949
- }
950
- exports.upperCase = upperCase;
951
- /**Eg: lowercase undefined, null... => '' */
952
- function lowerCase(...wordBits) {
953
- return wordBits.filter(e => e).map(w => w.trim().toLowerCase()).join('');
954
- }
955
- exports.lowerCase = lowerCase;
956
- function capitalize1st(str = '') { return str[0].toUpperCase() + str.slice(1); }
957
- exports.capitalize1st = capitalize1st;
958
- function camelCaseToWords(str) {
959
- return str ? str.trim().replace(/([A-Z])/g, '-$1').toLowerCase().split('-') : [];
960
- }
961
- exports.camelCaseToWords = camelCaseToWords;
962
- function escapeRegexp(str, config = {}) {
963
- const { parseStarChar = false } = config;
964
- if (parseStarChar)
965
- return str.replace(/[-[\]{}()+?.,\\^$|#\s]/g, '\\$&').replace(/\*/g, '.*');
966
- else
967
- return str.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
968
- }
969
- exports.escapeRegexp = escapeRegexp;
970
- /** Get first match of the first capturing group of regexp
971
- * Eg: const basePath = firstMatch(apiFile, /basePath = '(.*?)'/); will get what is inside quotes
972
- */
973
- function firstMatch(str, regExp) { return (str.match(regExp) || [])[1]; }
974
- exports.firstMatch = firstMatch;
975
- /** Get all matches from regexp with g flag
976
- * Eg: [ [full, match1, m2], [f, m1, m2]... ]
977
- * NOTE: the G flag will be appended to regexp
978
- */
979
- function allMatches(str, reg) {
980
- let i = 0;
981
- let matches;
982
- const arr = [];
983
- if (typeof str !== 'string')
984
- exports.C.error('Not a string provided as first argument for allMatches()');
985
- else {
986
- reg = new RegExp(reg, 'g');
987
- while ((matches = reg.exec(str))) {
988
- arr.push(matches);
989
- if (i++ > 99) {
990
- exports.C.error('error', 'Please provide the G flag in regexp for allMatches');
991
- break;
992
- }
993
- }
994
- }
995
- return arr;
996
- }
997
- exports.allMatches = allMatches;
998
- /** GIVEN A STRING '{ blah;2}, ['nested,(what,ever)']' AND A SEPARATOR ",""
999
- * This will return the content separated by first level of separators
1000
- * @return ["{ blah;2}", "['nested,(what,ever)']"]
1001
- */
1002
- function getValuesBetweenSeparator(str, separator, removeTrailingSpaces = true) {
1003
- err500IfEmptyOrNotSet({ separator, str });
1004
- const { outer } = getValuesBetweenStrings(str, separator, undefined, undefined, undefined, removeTrailingSpaces);
1005
- return outer;
1006
- }
1007
- exports.getValuesBetweenSeparator = getValuesBetweenSeparator;
1008
- /** GIVEN A STRING "a: [ 'str', /[^]]/, '[aa]]]str', () => [ nestedArray ] ], b: ['arr']"
1009
- * @return matching: [ "'str', /[^]]/, '[aa]]]str', () => [ nestedArray ]", "'arr'" ], between: [ "a:", ", b: " ]
1010
- * @param str base string
1011
- * @param openingOrSeparator opening character OR separator if closing not set
1012
- * @param closing
1013
- * @param ignoreBetweenOpen default ['\'', '`', '"', '/'], when reaching an opening char, it will ignore all until it find the corresponding closing char
1014
- * @param ignoreBetweenClose default ['\'', '`', '"', '/'] list of corresponding closing chars
1015
- */
1016
- function getValuesBetweenStrings(str, openingOrSeparator, closing, ignoreBetweenOpen = ['\'', '`', '"', '/'], ignoreBetweenClose = ['\'', '`', '"', '/'], removeTrailingSpaces = true) {
1017
- err500IfEmptyOrNotSet({ openingOrSeparator, str });
1018
- str = str.replace(/<</g, '§§"').replace(/>>/g, '"§§');
1019
- const arrayValues = [];
1020
- const betweenArray = [];
1021
- let level = 0;
1022
- let ignoreUntil = false;
1023
- let actualValue = '';
1024
- let precedingChar = '';
1025
- let separatorMode = false;
1026
- if (!closing)
1027
- separatorMode = true;
1028
- const separator = separatorMode ? openingOrSeparator : false;
1029
- const openingChars = separatorMode ? ['(', '{', '['] : [openingOrSeparator];
1030
- const closingChars = separatorMode ? [')', '}', ']'] : [closing];
1031
- const pushActualValue = () => {
1032
- if (level === 0)
1033
- betweenArray.push(removeTrailingSpaces ? actualValue.replace(/(?:^\s+|\s+$)/g, '') : actualValue);
1034
- else
1035
- arrayValues.push(removeTrailingSpaces ? actualValue.replace(/(?:^\s+|\s+$)/g, '') : actualValue);
1036
- actualValue = '';
1037
- };
1038
- str.split('').forEach(char => {
1039
- // handle unwanted nested structure like characters in a strings that may be a unmatched closing / opening character
1040
- // Eg: {'azer}aze'}
1041
- if (ignoreUntil && char === ignoreUntil && precedingChar !== '\\')
1042
- ignoreUntil = false;
1043
- else if (ignoreUntil && char !== ignoreUntil)
1044
- true;
1045
- else if (ignoreBetweenOpen.includes(char)) {
1046
- const indexChar = ignoreBetweenOpen.findIndex(char2 => char2 === char);
1047
- ignoreUntil = ignoreBetweenClose[indexChar];
1048
- }
1049
- else if (openingChars.includes(char)) {
1050
- // handle nested structures
1051
- if (!separatorMode && level === 0)
1052
- pushActualValue();
1053
- level++;
1054
- if (!separatorMode)
1055
- return;
1056
- }
1057
- else if (closingChars.includes(char)) {
1058
- // handle nested structures
1059
- if (!separatorMode && level === 1)
1060
- pushActualValue();
1061
- level--;
1062
- }
1063
- else if (separatorMode && level === 0 && char === separator) {
1064
- // SEPARATOR MODE
1065
- pushActualValue();
1066
- return;
1067
- }
1068
- actualValue += char;
1069
- precedingChar = char;
1070
- });
1071
- pushActualValue();
1072
- const replaceValz = arr => arr.map(v => v.replace(/§§"/g, '<<').replace(/"§§/g, '>>')).filter(v => v);
1073
- return { inner: replaceValz(arrayValues), outer: replaceValz(betweenArray) };
1074
- }
1075
- exports.getValuesBetweenStrings = getValuesBetweenStrings;
1076
- function issetOr(...elms) { return elms.some(elm => typeof elm !== 'undefined' && elm !== null); }
1077
- exports.issetOr = issetOr;
1078
- function isEmptyOrNotSet(...elms) { return elms.some(elm => !isset(elm) || isEmpty(elm)); }
1079
- exports.isEmptyOrNotSet = isEmptyOrNotSet;
1080
- function errIfNotSet(objOfVarNamesWithValues, additionalMessage) { return errXXXIfNotSet(422, false, objOfVarNamesWithValues); }
1081
- exports.errIfNotSet = errIfNotSet;
1082
- function err500IfNotSet(objOfVarNamesWithValues) { return errXXXIfNotSet(500, false, objOfVarNamesWithValues); }
1083
- exports.err500IfNotSet = err500IfNotSet;
1084
- function errIfEmptyOrNotSet(objOfVarNamesWithValues) { return errXXXIfNotSet(422, true, objOfVarNamesWithValues); }
1085
- exports.errIfEmptyOrNotSet = errIfEmptyOrNotSet;
1086
- function err500IfEmptyOrNotSet(objOfVarNamesWithValues) { return errXXXIfNotSet(500, true, objOfVarNamesWithValues); }
1087
- exports.err500IfEmptyOrNotSet = err500IfEmptyOrNotSet;
1088
- function errXXXIfNotSet(errCode, checkEmpty, objOfVarNamesWithValues) {
1089
- let missingVars = [];
1090
- for (let prop in objOfVarNamesWithValues) {
1091
- if (!isset(objOfVarNamesWithValues[prop]) || (checkEmpty && isEmpty(objOfVarNamesWithValues[prop])))
1092
- missingVars.push(prop);
1093
- }
1094
- if (missingVars.length)
1095
- throw new dataValidationUtilErrorHandler(`requiredVariableEmptyOrNotSet`, errCode, { origin: 'Validator', varNames: missingVars.join(', ') });
1096
- }
1097
- exports.errXXXIfNotSet = errXXXIfNotSet;
1098
- function isDateObject(variable) { return variable instanceof Date; }
1099
- exports.isDateObject = isDateObject;
1100
- /** Check all values are set */
1101
- function checkAllObjectValuesAreEmpty(o) { return Object.values(o).every(value => !isset(value)); }
1102
- exports.checkAllObjectValuesAreEmpty = checkAllObjectValuesAreEmpty;
1103
- /** Throw an error in case data passed is not a valid ctx */
1104
- function checkCtxIntegrity(ctx) {
1105
- if (!isset(ctx) || !isset(ctx.user))
1106
- throw new dataValidationUtilErrorHandler('ctxNotSet', 500);
1107
- }
1108
- exports.checkCtxIntegrity = checkCtxIntegrity;
1109
- function validator(...paramsToValidate) {
1110
- const errArray = validatorReturnErrArray(...paramsToValidate);
1111
- if (errArray.length)
1112
- throw new dataValidationUtilErrorHandler(...errArray);
1113
- }
1114
- exports.validator = validator;
1115
- exports.restTestMini = {
1116
- throwOnErr: false,
1117
- reset(throwOnErr = false) {
1118
- exports.restTestMini.nbSuccess = 0;
1119
- exports.restTestMini.nbError = 0;
1120
- exports.restTestMini.lastErrors = [];
1121
- exports.restTestMini.throwOnErr = throwOnErr;
1122
- },
1123
- newErr(err) {
1124
- exports.restTestMini.nbError++;
1125
- exports.restTestMini.lastErrors.push(err);
1126
- if (exports.restTestMini.throwOnErr)
1127
- throw new Error(err);
1128
- else
1129
- exports.C.error(false, err);
1130
- },
1131
- printStats() {
1132
- // TODO print last errz
1133
- exports.C.info(`ERRORS RESUME =========`);
1134
- if (exports.restTestMini.lastErrors.length)
1135
- exports.C.log('\n\n\n');
1136
- for (const lastErr of exports.restTestMini.lastErrors)
1137
- exports.C.error(false, lastErr);
1138
- exports.C.log('\n\n\n');
1139
- exports.C.info(`STATS =========`);
1140
- exports.C.info(`Total: ${exports.restTestMini.nbSuccess + exports.restTestMini.nbError}`);
1141
- exports.C.success(`Success: ${exports.restTestMini.nbSuccess}`);
1142
- exports.C.error(false, ` Errors: ${exports.restTestMini.nbError}`);
1143
- },
1144
- nbSuccess: 0,
1145
- nbError: 0,
1146
- lastErrors: []
1147
- };
1148
- function assert(msg, value, validatorObject = {}) {
1149
- try {
1150
- if (typeof validatorObject !== 'object')
1151
- validatorObject = { eq: validatorObject };
1152
- const issetCheck = isEmpty(validatorObject);
1153
- validatorObject.value = value;
1154
- validatorObject.name = msg;
1155
- const [errMsg, , extraInfos] = validatorReturnErrArray(validatorObject);
1156
- const msg2 = msg + ` ${issetCheck ? 'isset' : `${JSON.stringify({ ...validatorObject, name: undefined, value: undefined })}`}`;
1157
- if (!isset(errMsg)) {
1158
- exports.restTestMini.nbSuccess++;
1159
- exports.C.success(msg2);
1160
- }
1161
- else {
1162
- const err = msg2 + `\n ${errMsg}\n ${JSON.stringify(extraInfos)}`;
1163
- exports.restTestMini.newErr(err);
1164
- }
1165
- }
1166
- catch (err) {
1167
- exports.restTestMini.newErr(err);
1168
- }
1169
- }
1170
- exports.assert = assert;
1171
- /** Same as validator but return a boolean
1172
- * See {@link validator}
1173
- */
1174
- function isValid(...paramsToValidate) {
1175
- const errArray = validatorReturnErrArray(...paramsToValidate);
1176
- return errArray.length ? false : true;
1177
- }
1178
- exports.isValid = isValid;
1179
- /** Default types + custom types
1180
- * 'objectId','dateInt6','dateInt','dateInt8','dateInt12','time','humanReadableTimestamp','date','array','object','buffer','string','function','boolean','number','bigint',
1181
- */
1182
- function isType(value, type) { return isValid({ name: 'Is type check', value, type }); }
1183
- exports.isType = isType;
1184
- function validatorReturnErrArray(...paramsToValidate) {
1185
- let paramsFormatted = [];
1186
- // support for multiple names with multiple values for one rule. Eg: {name: [{startDate:'20180101'}, {endDate:'20180101'}], type: 'dateInt8'}
1187
- paramsToValidate.forEach(param => {
1188
- if (typeof param !== 'object' || Array.isArray(param))
1189
- throw new dataValidationUtilErrorHandler(`wrongTypeForDataValidatorArgument`, 500, { origin: 'Generic validator', expected: 'object', actualType: Array.isArray(param) ? 'array' : typeof param });
1190
- // parse => name: {myVar1: 'blah, myvar2: myvar2}
1191
- if (typeof param.name === 'object' && !Array.isArray(param.name))
1192
- Object.keys(param.name).forEach(name => paramsFormatted.push(Object.assign({}, param, { name: name, value: param.name[name] })));
1193
- else
1194
- paramsFormatted.push(param);
1195
- });
1196
- for (const paramObj of paramsFormatted) {
1197
- let name = paramObj.name;
1198
- let value = paramObj.value;
1199
- let optional = paramObj.optional || false;
1200
- let emptyAllowed = optional || paramObj.emptyAllowed || false;
1201
- const errMess = (msg, extraInfos = {}, errCode = 422) => [msg, errCode, { origin: 'Generic validator', varName: name, gotValue: isset(value) && isset(value.data) && isset(value.data.data) ? { ...value, data: 'Buffer' } : value, ...extraInfos }];
1202
- // accept syntax { 'myVar.var2': myVar.var2, ... }
1203
- if (!isset(name)) {
1204
- name = Object.keys(paramObj).find(param => !['in', 'eq', 'lte', 'gte', 'name', 'value', 'type', 'regexp', 'minLength', 'maxLength', 'optional', 'emptyAllowed', 'mustNotBeSet', 'includes', 'length'].includes(param));
1205
- if (isset(name))
1206
- value = paramObj[name]; // throw new dataValidationUtilErrorHandler('noNameProvidedForDataValidator', 500, { origin: 'Generic validator', });
1207
- }
1208
- // if nameString ends by $ sign it is optional
1209
- if (isset(name) && /.*\$$/.test(name)) {
1210
- name = name.substr(0, name.length - 1);
1211
- optional = true;
1212
- }
1213
- // DEFINED AND NOT EMPTY
1214
- if (!isset(value) && optional)
1215
- continue;
1216
- if (isset(value) && paramObj.mustNotBeSet)
1217
- return errMess('variableMustNotBeSet');
1218
- if (paramObj.mustNotBeSet)
1219
- continue; // exit
1220
- if (!isset(value))
1221
- return errMess('requiredVariableEmptyOrNotSet');
1222
- if (!emptyAllowed && value === '')
1223
- return errMess('requiredVariableEmpty');
1224
- const isArray = paramObj.isArray;
1225
- if (isArray && !Array.isArray(value))
1226
- return errMess('wrongTypeForVar', { expectedTypes: 'array', gotType: typeof value });
1227
- // TYPE
1228
- if (isset(paramObj.type)) {
1229
- const types = asArray(paramObj.type); // support for multiple type
1230
- const areSomeTypeValid = types.some(type => {
1231
- if (type.endsWith('[]')) {
1232
- if (!Array.isArray(value))
1233
- errMess('wrongTypeForVar', { expectedTypes: 'array', gotType: typeof value });
1234
- type = type.replace('[]', '');
1235
- }
1236
- const allTypes = [
1237
- 'objectId',
1238
- 'dateInt6',
1239
- 'dateInt',
1240
- 'dateInt8',
1241
- 'dateInt12',
1242
- 'time',
1243
- 'humanReadableTimestamp',
1244
- 'date',
1245
- 'dateObject',
1246
- 'array',
1247
- 'object',
1248
- 'buffer',
1249
- 'string',
1250
- 'function',
1251
- 'boolean',
1252
- 'number',
1253
- 'bigint',
1254
- 'year',
1255
- 'any',
1256
- 'email',
1257
- //...Object.keys(configFn().customTypes)
1258
- ];
1259
- if (!allTypes.includes(type))
1260
- throw new dataValidationUtilErrorHandler('typeDoNotExist', 500, { type });
1261
- const basicTypeCheck = {
1262
- objectId: val => /^[0-9a-fA-F-]{24,}$/.test(val),
1263
- dateInt6: val => isDateIntOrStringValid(parseInt(val + '01'), true, 8),
1264
- dateInt: val => isDateIntOrStringValid(val, true, 8),
1265
- dateInt8: val => isDateIntOrStringValid(val, true, 8),
1266
- dateInt12: val => isDateIntOrStringValid(val, true, 12),
1267
- time: val => /^\d\d:\d\d$/.test(val) && isTimeStringValid(val),
1268
- humanReadableTimestamp: val => (val + '').length === 17,
1269
- date: val => isDateIsoOrObjectValid(val, true),
1270
- dateObject: val => isDateIsoOrObjectValid(val, true),
1271
- array: val => Array.isArray(val),
1272
- object: val => !Array.isArray(val) && val !== null && typeof val === type,
1273
- buffer: val => Buffer.isBuffer(val),
1274
- year: val => /^\d\d\d\d$/.test(val),
1275
- email: val => /^[^\s@]+@([^\s@.,]+\.)+[^\s@.,]+$/.test(val),
1276
- any: () => true,
1277
- };
1278
- return isset(basicTypeCheck[type]) && basicTypeCheck[type](value) ||
1279
- typeof value === type && type !== 'object' || // for string, number, boolean...
1280
- isset(configFn().customTypes[type]) && configFn().customTypes[type].test(value);
1281
- });
1282
- if (!areSomeTypeValid)
1283
- return errMess(`wrongTypeForVar`, { expectedTypes: types.join(', '), gotType: Object.prototype.toString.call(value) });
1284
- }
1285
- // GREATER / LESS
1286
- if (isset(paramObj.gte) && value < paramObj.gte)
1287
- return errMess(`valueShouldBeSuperiorOrEqualForVar`, { shouldBeSupOrEqTo: paramObj.gte });
1288
- if (isset(paramObj.lte) && value > paramObj.lte)
1289
- return errMess(`valueShouldBeInferiorOrEqualForVar`, { shouldBeInfOrEqTo: paramObj.lte });
1290
- if (isset(paramObj.gt) && value <= paramObj.gt)
1291
- return errMess(`valueShouldBeSuperiorForVar`, { shouldBeSupOrEqTo: paramObj.gt });
1292
- if (isset(paramObj.lt) && value >= paramObj.lt)
1293
- return errMess(`valueShouldBeInferiorForVar`, { shouldBeInfOrEqTo: paramObj.lt });
1294
- // IN VALUES
1295
- if (isset(paramObj.in)) {
1296
- const equals = Array.isArray(paramObj.in) ? paramObj.in : [paramObj.in];
1297
- if (!equals.some(equalVal => equalVal === value))
1298
- return errMess(`wrongValueForVar`, { supportedValues: JSON.stringify(equals) });
1299
- }
1300
- // EQUAL (exact copy of .in)
1301
- if (paramObj.hasOwnProperty('eq')) {
1302
- const equals = Array.isArray(paramObj.eq) ? paramObj.eq : [paramObj.eq];
1303
- if (!equals.some(equalVal => equalVal === value))
1304
- return errMess(`wrongValueForVar`, { supportedValues: equals.join(', '), });
1305
- }
1306
- // NOT EQUAL
1307
- if (paramObj.hasOwnProperty('neq')) {
1308
- const notEquals = Array.isArray(paramObj.neq) ? paramObj.neq : [paramObj.neq];
1309
- if (notEquals.some(equalVal => equalVal === value))
1310
- return errMess(`wrongValueForVar`, { NOTsupportedValues: notEquals.join(', ') });
1311
- }
1312
- // INCLUDES
1313
- if (isset(paramObj.includes) && !value.includes(paramObj.includes))
1314
- return errMess(`wrongValueForVar`, { shouldIncludes: paramObj.includes });
1315
- // REGEXP
1316
- if (isset(paramObj.regexp) && !paramObj.regexp.test(value))
1317
- return errMess(`wrongValueForVar`, { shouldMatchRegexp: paramObj.regexp.toString() });
1318
- // MIN / MAX LENGTH works for number length. Eg: 20180101.length == 8
1319
- if (isset(paramObj.minLength) && paramObj.minLength > (typeof value == 'number' ? value + '' : value).length)
1320
- return errMess(`wrongLengthForVar`, { minLength: paramObj.minLength });
1321
- if (isset(paramObj.maxLength) && paramObj.maxLength < (typeof value == 'number' ? value + '' : value).length)
1322
- return errMess(`wrongLengthForVar`, { maxLength: paramObj.maxLength });
1323
- if (isset(paramObj.length) && paramObj.length !== (typeof value == 'number' ? value + '' : value).length)
1324
- return errMess(`wrongLengthForVar`, { length: paramObj.length });
1325
- }
1326
- return [];
1327
- }
1328
- exports.validatorReturnErrArray = validatorReturnErrArray;
1329
- function isEmpty(objOrArr) {
1330
- if (Array.isArray(objOrArr) || typeof objOrArr === 'string')
1331
- return objOrArr.length === 0;
1332
- else if (typeof objOrArr == 'object' && objOrArr !== null && !(objOrArr instanceof Date))
1333
- return Object.keys(objOrArr).length === 0;
1334
- else
1335
- false;
1336
- }
1337
- exports.isEmpty = isEmpty;
1338
- function err422IfNotSet(o) {
1339
- let m = [];
1340
- for (let p in o)
1341
- if (!isset(o[p]))
1342
- m.push(p);
1343
- if (m.length)
1344
- throw new dataValidationUtilErrorHandler(`requiredVariableEmptyOrNotSet`, 422, { origin: 'Validator', varNames: m.join(', ') });
1345
- }
1346
- exports.err422IfNotSet = err422IfNotSet;
1347
- function getDateAsInt12(dateAllFormat, errIfNotValid) { return getDateAsInt(dateAllFormat, errIfNotValid, true); } // alias
1348
- exports.getDateAsInt12 = getDateAsInt12;
1349
- function humanReadableTimestamp(dateAllFormat) {
1350
- if (isset(dateAllFormat))
1351
- dateAllFormat = getDateAsObject(dateAllFormat);
1352
- return parseInt(getDateAsInt12(dateAllFormat) + pad((dateAllFormat || new Date()).getUTCSeconds()) + pad((dateAllFormat || new Date()).getUTCMilliseconds(), 3));
1353
- }
1354
- exports.humanReadableTimestamp = humanReadableTimestamp;
1355
- /** format for 6/8/2018 => 20180806
1356
- * @param dateAllFormat multiple format allowed 2012, 20120101, 201201011200, new Date(), "2019-12-08T16:19:10.341Z" and all string that new Date() can parse
1357
- */
1358
- function getDateAsInt(dateAllFormat = new Date(), errIfNotValid$ = false, withHoursAndMinutes$ = false) {
1359
- let dateInt;
1360
- if (typeof dateAllFormat === 'string' && dateAllFormat.includes('/')) {
1361
- // 01/01/2020 format
1362
- const [d, m, y] = dateAllFormat.split('/');
1363
- return y + m.toString().padStart(2, '0') + d.toString().padStart(2, '0');
1364
- }
1365
- else if (isDateIntOrStringValid(dateAllFormat)) {
1366
- // we can pass an int or string format (20180106)
1367
- dateInt = (dateAllFormat + '00000000').substr(0, 12); // add default 000000 for "month days minutes:sec" if not set
1368
- }
1369
- else {
1370
- let date = dateAllFormat;
1371
- if (typeof date === 'string')
1372
- date = new Date(date);
1373
- const realDate = date;
1374
- //let dateArr = dateAllFormat.toString().split(); // we cannot use ISOString
1375
- dateInt = '' + realDate.getUTCFullYear() + pad(realDate.getUTCMonth() + 1) + pad(realDate.getUTCDate()) + pad(realDate.getUTCHours()) + pad(realDate.getUTCMinutes());
1376
- }
1377
- isDateIntOrStringValid(dateInt, errIfNotValid$);
1378
- return (withHoursAndMinutes$ ? dateInt : dateInt.substr(0, 8));
1379
- }
1380
- exports.getDateAsInt = getDateAsInt;
1381
- function getMonthAsInt(dateAllFormat = new Date()) {
1382
- let dateInt;
1383
- if (isDateIntOrStringValid(dateAllFormat)) {
1384
- // we can pass an int or string format (20180106)
1385
- dateInt = (dateAllFormat + '').substr(0, 6);
1386
- }
1387
- else {
1388
- let date = dateAllFormat;
1389
- if (typeof date === 'string')
1390
- date = new Date(date);
1391
- //let dateArr = dateAllFormat.toString().split(); // we cannot use ISOString
1392
- dateInt = '' + date.getUTCFullYear() + pad(date.getUTCMonth() + 1);
1393
- }
1394
- return (0, exports.int)(dateInt);
1395
- }
1396
- exports.getMonthAsInt = getMonthAsInt;
1397
- /**
1398
- * @param dateAllFormat multiple format allowed 2012, 20120101, 201201011200, new Date(), "2019-12-08T16:19:10.341Z" and all string that new Date() can parse
1399
- */
1400
- function getDateAsObject(dateAllFormat = new Date(), errIfNotValid$ = true) {
1401
- let dateObj = dateAllFormat;
1402
- if (isDateIntOrStringValid(dateAllFormat)) {
1403
- const [y, M, d, h, m] = dateStringToArray(dateAllFormat);
1404
- dateObj = new Date(`${y}-${M}-${d}T${h}:${m}`);
1405
- }
1406
- else if (typeof dateAllFormat === 'string') {
1407
- dateObj = new Date(dateAllFormat);
1408
- }
1409
- else {
1410
- dateObj = new Date(dateAllFormat.getTime()); // clone
1411
- }
1412
- isDateIsoOrObjectValid(dateObj, errIfNotValid$);
1413
- return dateObj;
1414
- }
1415
- exports.getDateAsObject = getDateAsObject;
1416
- function isDateIntOrStringValid(dateStringOrInt, outputAnError = false, length) {
1417
- if (!isset(dateStringOrInt))
1418
- return false;
1419
- const dateStr = dateStringOrInt.toString();
1420
- if (length && dateStr.length !== length)
1421
- throw new dataValidationUtilErrorHandler(`wrongLengthForDateInt`, 422, { origin: 'Date Int validator', dateStringOrInt: dateStringOrInt, extraInfo: `${dateStringOrInt} length !== ${length}` });
1422
- if ((typeof dateStringOrInt === 'object' && isNaN((0, exports.int)(dateStr))) || ![4, 6, 8, 10, 12, 17].includes(dateStr.length))
1423
- return false;
1424
- const dateArr = dateStringToArray(dateStringOrInt);
1425
- const [y, M, d, h, m] = dateArr;
1426
- const test1 = dateArr.length >= 3 && (0, exports.int)(y) >= 1000; // Y
1427
- const test2 = (0, exports.int)(M) <= 12 && (0, exports.int)(M) > 0; // M
1428
- const test3 = !isset(d) || (0, exports.int)(d) <= 31 && (0, exports.int)(d) > 0; // D
1429
- const test4 = !isset(h) || ((0, exports.int)(h) <= 23 && (0, exports.int)(h) >= 0); // H
1430
- const test5 = !isset(m) || ((0, exports.int)(m) <= 59 && (0, exports.int)(m) >= 0); // M
1431
- if (outputAnError && !(test1 && test2 && test3 && test4 && test5))
1432
- throw new dataValidationUtilErrorHandler(`dateStringOrIntFormatInvalid`, 422, { origin: 'Date Int validator', dateStringOrInt: dateStringOrInt, extraInfo: 'Needs YYYYMMDD[HHMM] between 100001010000 and 999912312359', dateArr, isYearValid: test1, isMonthValid: test2, isDayValid: test3, isHourValid: test4, isMinutesValid: test5 });
1433
- return true;
1434
- }
1435
- exports.isDateIntOrStringValid = isDateIntOrStringValid;
1436
- function isDateIsoOrObjectValid(dateIsoOrObj, outputAnError = false) {
1437
- let dateObj = dateIsoOrObj;
1438
- if (typeof dateIsoOrObj === 'string')
1439
- dateObj = new Date(dateIsoOrObj);
1440
- let valid = dateObj instanceof Date;
1441
- if (outputAnError && !valid)
1442
- throw new dataValidationUtilErrorHandler('dateIsoStringOrObjectIsNotValid', 422, { origin: 'Date Object validator', isoDate: dateIsoOrObj });
1443
- return valid;
1444
- }
1445
- exports.isDateIsoOrObjectValid = isDateIsoOrObjectValid;
1446
- /** [2018,01,06] */
1447
- function dateStringToArray(strOrInt) {
1448
- err422IfNotSet({ strOrInt });
1449
- const dateStr = strOrInt.toString();
1450
- return [
1451
- dateStr.substr(0, 4),
1452
- dateStr.substr(4, 2) || '01',
1453
- dateStr.substr(6, 2) || '01',
1454
- dateStr.substr(8, 2) || '12',
1455
- dateStr.substr(10, 2) || '00',
1456
- dateStr.substr(12, 2) || '00',
1457
- dateStr.substr(14, 3) || '000', // MS
1458
- ];
1459
- }
1460
- exports.dateStringToArray = dateStringToArray;
1461
- /**
1462
- * @param dateAllFormat default: actualDate
1463
- * @returns ['01', '01', '2019'] OR **string** if separator is provided */
1464
- function dateArray(dateAllFormat = getDateAsInt()) {
1465
- const dateStr = getDateAsInt(dateAllFormat).toString();
1466
- return [
1467
- dateStr.substr(6, 2),
1468
- dateStr.substr(4, 2),
1469
- dateStr.substr(0, 4), // Y
1470
- ];
1471
- }
1472
- exports.dateArray = dateArray;
1473
- /**
1474
- * @param dateAllFormat default: actualDate
1475
- * @returns ['01', '01', '2019'] OR **string** if separator is provided */
1476
- function dateArrayInt(dateAllFormat = getDateAsInt()) {
1477
- const dateStr = getDateAsInt(dateAllFormat).toString();
1478
- return [
1479
- (0, exports.int)(dateStr.substr(6, 2)),
1480
- (0, exports.int)(dateStr.substr(4, 2)),
1481
- (0, exports.int)(dateStr.substr(0, 4)), // Y
1482
- ];
1483
- }
1484
- exports.dateArrayInt = dateArrayInt;
1485
- /**
1486
- * @return 01/01/2012 (alias of dateArrayFormatted(date, '/'))
1487
- */
1488
- function dateFormatted(dateAllFormat, separator = '/') { return dateArray(dateAllFormat).join(separator); }
1489
- exports.dateFormatted = dateFormatted;
1490
- /** Date with custom offset (Ex: +2 for France) */
1491
- function dateOffset(offsetHours, dateObj = new Date()) {
1492
- var utc = Date.UTC(dateObj.getUTCFullYear(), dateObj.getUTCMonth(), dateObj.getUTCDate(), dateObj.getUTCHours(), dateObj.getUTCMinutes(), dateObj.getUTCSeconds());
1493
- return new Date(utc + (3600000 * offsetHours));
1494
- }
1495
- exports.dateOffset = dateOffset;
1496
- //----------------------------------------
1497
- // TIME UTILS
1498
- //----------------------------------------
1499
- /** */
1500
- function getTimeAsInt(timeOrDateInt = getDateAsInt12()) {
1501
- if (isDateIntOrStringValid(timeOrDateInt)) {
1502
- const tl = timeOrDateInt.toString().length;
1503
- return (0, exports.int)(timeOrDateInt.toString().substring(tl - 4, tl));
1504
- }
1505
- else if (typeof timeOrDateInt === 'string' && timeOrDateInt.length === 5 && timeOrDateInt.includes(':'))
1506
- return (0, exports.int)(timeOrDateInt.replace(':', ''));
1507
- else
1508
- return 'dateInvalid';
1509
- }
1510
- exports.getTimeAsInt = getTimeAsInt;
1511
- /**
1512
- * @param {timeInt|dateInt12} Eg: 2222 OR 201201012222. Default, actual dateInt12
1513
- * @param {String} separator default: ":"
1514
- */
1515
- function getIntAsTime(intOrDateTimeInt = getDateAsInt12(), separator = ':') {
1516
- const time = intOrDateTimeInt.toString().padStart(4, '0');
1517
- const tl = time.length;
1518
- return time.substring(tl - 4, tl - 2) + separator + time.substring(tl - 2, tl);
1519
- }
1520
- exports.getIntAsTime = getIntAsTime;
1521
- function isTimeStringValid(timeStr, outputAnError$ = false) {
1522
- let timeArr = timeStr.split(':');
1523
- let h = (0, exports.int)(timeArr[0]);
1524
- let m = (0, exports.int)(timeArr[1]);
1525
- let test1 = h >= 0 && h < 24;
1526
- let test2 = m >= 0 && m < 60;
1527
- if (outputAnError$ && !(test1 && test2))
1528
- throw new dataValidationUtilErrorHandler('timeStringOutOfRange', 422, { origin: 'Time validator' });
1529
- else
1530
- return test1 && test2;
1531
- }
1532
- exports.isTimeStringValid = isTimeStringValid;
1533
- //----------------------------------------
1534
- // DURATIONS
1535
- //----------------------------------------
1536
- function getDuration(startDate, endDate, inMinutes = false) {
1537
- const startDateO = getDateAsObject(startDate);
1538
- const endDateO = getDateAsObject(endDate);
1539
- let diffInSec = Math.floor(endDateO.getTime() / 1000) - Math.floor(startDateO.getTime() / 1000);
1540
- if (inMinutes)
1541
- return Math.floor(diffInSec / 60);
1542
- else
1543
- return [
1544
- Math.floor(diffInSec / (24 * 3600)),
1545
- Math.floor((diffInSec % (24 * 3600)) / 3600),
1546
- Math.floor(((diffInSec % (24 * 3600)) % 3600) / 60), // M
1547
- ];
1548
- }
1549
- exports.getDuration = getDuration;
1550
- /** compare two object with DATE INT, if they overlap return true
1551
- * @param {Object} event1 {startDate, endDate}
1552
- * @param {Object} event2 {startDate, endDate}
1553
- * @param {String} fieldNameForStartDate$ replace startDate with this string
1554
- * @param {String} fieldNameForEndDate$ replace endDate with this string
1555
- * @param {Boolean} allowNull$ if false, retrun false if any of the startdates or enddates are not set
1556
- * @param {Boolean} strict$ if true,
1557
- */
1558
- function doDateOverlap(event1, event2, fieldNameForStartDate$ = 'startDate', fieldNameForEndDate$ = 'endDate', allowNull$ = true, strict$ = false) {
1559
- if (!allowNull$ && !isset(event1[fieldNameForStartDate$], event1[fieldNameForEndDate$], event2[fieldNameForStartDate$], event2[fieldNameForEndDate$]))
1560
- return false;
1561
- if (strict$)
1562
- return (!event2[fieldNameForEndDate$] || event1[fieldNameForStartDate$] < event2[fieldNameForEndDate$]) && (!event1[fieldNameForEndDate$] || event1[fieldNameForEndDate$] > event2[fieldNameForStartDate$]);
1563
- return (!event2[fieldNameForEndDate$] || event1[fieldNameForStartDate$] <= event2[fieldNameForEndDate$]) && (!event1[fieldNameForEndDate$] || event1[fieldNameForEndDate$] >= event2[fieldNameForStartDate$]);
1564
- }
1565
- exports.doDateOverlap = doDateOverlap;
1566
- function nextWeekDay(fromDate, weekDayInt, outputFormat = 'date', sameDayAllowed = false) {
1567
- const date = getDateAsObject(fromDate);
1568
- if (!isset(weekDayInt))
1569
- weekDayInt = date.getDay();
1570
- const toAdd = !sameDayAllowed && date.getDay() === weekDayInt ? 7 : 0;
1571
- date.setUTCDate(date.getUTCDate() + toAdd + (7 + weekDayInt - date.getUTCDay()) % 7);
1572
- return getDateAs(date, outputFormat);
1573
- }
1574
- exports.nextWeekDay = nextWeekDay;
1575
- function addDays(dateAllFormat = getDateAsInt(), numberOfDays = 1, outputFormat = 'date') {
1576
- let date = getDateAsObject(dateAllFormat);
1577
- date.setTime(date.getTime() + numberOfDays * 24 * 60 * 60 * 1000);
1578
- return getDateAs(date, outputFormat);
1579
- }
1580
- exports.addDays = addDays;
1581
- function addMinutes(dateAllFormat = getDateAsInt(), numberOfMinutes = 1, outputFormat = 'date') {
1582
- let date = getDateAsObject(dateAllFormat);
1583
- date.setTime(date.getTime() + 1 * numberOfMinutes * 60 * 1000);
1584
- return getDateAs(date, outputFormat);
1585
- }
1586
- exports.addMinutes = addMinutes;
1587
- function addHours(dateAllFormat = getDateAsInt(), numberOfHours = 1, outputFormat = 'date') {
1588
- let date = getDateAsObject(dateAllFormat);
1589
- date.setTime(date.getTime() + 1 * numberOfHours * 60 * 60 * 1000);
1590
- return getDateAs(date, outputFormat);
1591
- }
1592
- exports.addHours = addHours;
1593
- function addMonths(dateAllFormat = getDateAsInt(), numberOfMonths = 1, outputFormat = 'date') {
1594
- let date = getDateAsObject(dateAllFormat);
1595
- date.setUTCMonth(date.getUTCMonth() + numberOfMonths);
1596
- return getDateAs(date, outputFormat);
1597
- }
1598
- exports.addMonths = addMonths;
1599
- /**
1600
- * @param {String} outputFormat dateInt, dateInt8, dateInt12, date, humanReadableTimestamp, int (dateInt8)
1601
- */
1602
- function addYears(dateAllFormat = getDateAsInt(), numberOfYears = 1, outputFormat = 'date') {
1603
- let date = getDateAsObject(dateAllFormat);
1604
- date.setUTCFullYear(date.getUTCFullYear() + numberOfYears);
1605
- return getDateAs(date, outputFormat);
1606
- }
1607
- exports.addYears = addYears;
1608
- function getDayOfMonth(dateAllFormat = getDateAsInt()) {
1609
- let dateAsInt = getDateAsInt(dateAllFormat);
1610
- const [, , d] = dateStringToArray(dateAsInt);
1611
- return d;
1612
- }
1613
- exports.getDayOfMonth = getDayOfMonth;
1614
- function getYear(dateAllFormat = getDateAsInt()) {
1615
- let dateAsInt = getDateAsInt(dateAllFormat);
1616
- const [y] = dateStringToArray(dateAsInt);
1617
- return y;
1618
- }
1619
- exports.getYear = getYear;
1620
- function getHours(dateAllFormat = getDateAsInt()) {
1621
- let dateAsInt = getDateAsInt(dateAllFormat);
1622
- const [, , , h,] = dateStringToArray(dateAsInt);
1623
- return h;
1624
- }
1625
- exports.getHours = getHours;
1626
- function getMinutes(dateAllFormat = getDateAsInt()) {
1627
- let dateAsInt = getDateAsInt(dateAllFormat);
1628
- const [, , , , m] = dateStringToArray(dateAsInt);
1629
- return m;
1630
- }
1631
- exports.getMinutes = getMinutes;
1632
- /**
1633
- * @param {String} outputFormat dateInt, dateInt8, dateInt12, date, humanReadableTimestamp, int (dateInt8)
1634
- */
1635
- function lastDayOfMonth(dateAllFormat = getDateAsInt(), outputFormat = 'date') {
1636
- let date = getDateAsObject(dateAllFormat);
1637
- const lastDay = new Date(date.getUTCFullYear(), date.getUTCMonth() + 1, 0);
1638
- lastDay.setUTCHours(date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds(), date.getUTCMilliseconds());
1639
- return getDateAs(lastDay, outputFormat);
1640
- }
1641
- exports.lastDayOfMonth = lastDayOfMonth;
1642
- /**
1643
- * @param {String} outputFormat dateInt, dateInt8, dateInt12, date, humanReadableTimestamp, int (dateInt8)
1644
- */
1645
- function firstDayOfMonth(dateAllFormat = getDateAsInt(), outputFormat = 'date') {
1646
- let date = getDateAsObject(dateAllFormat);
1647
- const firstDay = new Date(date.getUTCFullYear(), date.getUTCMonth(), 1);
1648
- firstDay.setUTCHours(date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds(), date.getUTCMilliseconds());
1649
- return getDateAs(firstDay, outputFormat);
1650
- }
1651
- exports.firstDayOfMonth = firstDayOfMonth;
1652
- function differenceInMilliseconds(startDateAllFormat, endDateAllFormat) {
1653
- const startDate = getDateAsObject(startDateAllFormat);
1654
- const endDate = getDateAsObject(endDateAllFormat);
1655
- return endDate.getTime() - startDate.getTime();
1656
- }
1657
- exports.differenceInMilliseconds = differenceInMilliseconds;
1658
- function differenceInSeconds(startDateAllFormat, endDateAllFormat) {
1659
- return differenceInMilliseconds(startDateAllFormat, endDateAllFormat) / 1000;
1660
- }
1661
- exports.differenceInSeconds = differenceInSeconds;
1662
- function differenceInMinutes(startDateAllFormat, endDateAllFormat) {
1663
- return differenceInSeconds(startDateAllFormat, endDateAllFormat) / 60;
1664
- }
1665
- exports.differenceInMinutes = differenceInMinutes;
1666
- function differenceInHours(startDateAllFormat, endDateAllFormat) {
1667
- return differenceInMinutes(startDateAllFormat, endDateAllFormat) / 60;
1668
- }
1669
- exports.differenceInHours = differenceInHours;
1670
- function differenceInDays(startDateAllFormat, endDateAllFormat) {
1671
- return differenceInHours(startDateAllFormat, endDateAllFormat) / 24;
1672
- }
1673
- exports.differenceInDays = differenceInDays;
1674
- function differenceInWeeks(startDateAllFormat, endDateAllFormat) {
1675
- return differenceInDays(startDateAllFormat, endDateAllFormat) / 7;
1676
- }
1677
- exports.differenceInWeeks = differenceInWeeks;
1678
- function getDateAs(dateAllFormat = new Date(), outputDateFormat = 'date') {
1679
- switch (outputDateFormat) {
1680
- case 'dateInt8':
1681
- return getDateAsInt(dateAllFormat);
1682
- case 'dateInt12':
1683
- return getDateAsInt12(dateAllFormat);
1684
- case 'humanReadableTimestamp':
1685
- return humanReadableTimestamp(dateAllFormat);
1686
- case 'date':
1687
- default:
1688
- return getDateAsObject(dateAllFormat);
1689
- }
1690
- }
1691
- exports.getDateAs = getDateAs;
1692
- //----------------------------------------
1693
- // LOGGER
1694
- //----------------------------------------
1695
- exports.logger = {
1696
- log(str, type = 'log') {
1697
- const { preprocessLog } = configFn();
1698
- if (typeof preprocessLog === 'function')
1699
- str = preprocessLog(str) || str;
1700
- if (isset(console[type]))
1701
- console[type](str);
1702
- else if (type === 'appError')
1703
- console.warn(str);
1704
- else
1705
- console.log(str);
1706
- exports.logger.raw.push(str + `\n`);
1707
- exports.logger.raw = exports.logger.raw.slice(exports.logger.raw.length - configFn().nbOfLogsToKeep, exports.logger.raw.length);
1708
- },
1709
- /**
1710
- * @param {String[]|String} inputLogs
1711
- */
1712
- toHtml(inputLogs = [...exports.logger.raw]) {
1713
- if (!Array.isArray(inputLogs))
1714
- inputLogs = [inputLogs];
1715
- const code2css = {
1716
- 2: `opacity:.5`,
1717
- 32: `color:#679933`,
1718
- 31: `color:#A8383B`,
1719
- 33: `color:#D7941C`,
1720
- 35: `color:#C21949`,
1721
- 36: `color:#128C6D`,
1722
- 34: `color:#1B568B`, // blue
1723
- };
1724
- let htmlLogs = '';
1725
- inputLogs.join('<br/>').split('\x1b[').forEach((bit, i) => {
1726
- // the first doesn't have a preceding (may be undef)
1727
- if (bit) {
1728
- if (i !== 0) {
1729
- const [, code, R, G, B, content] = bit.match(/(^\d\d?)(?:;|m)(?:\d;(\d+);(\d+);(\d+)m)?(.*$)/) || [];
1730
- if (content) {
1731
- const style = !isset(R) ? isset(code) ? code2css[code] : undefined : `color:rgb(${R},${G},${B})`;
1732
- htmlLogs += isset(style) ? `<span style='${style}'>${content.replace(/\n/g, '<br>')}</span>` : content.replace(/\n/g, '<br>');
1733
- }
1734
- }
1735
- else
1736
- htmlLogs += bit.replace(/\n/g, '<br>');
1737
- }
1738
- });
1739
- return `<div style='color:#ccc'>${htmlLogs}<br></div>`;
1740
- },
1741
- toText(inputLogs = [...exports.logger.raw]) {
1742
- const str = Array.isArray(inputLogs) ? inputLogs.join('\n') : inputLogs;
1743
- return str.replace(/\x1b\[.*?m/g, '');
1744
- },
1745
- raw: [],
1746
- json: [],
1747
- };
1748
- /**
1749
- // console colored output
1750
- // * console.log(C.green(C.dim('Hey bro !')))
1751
- // * or C.log() // will use padding and color defined by themes
1752
- // * or C.line('MY TITLE', 53)
1753
- // * or C.gradientize(myLongString)
1754
- */
1755
- exports.C = {
1756
- dim: str => exports.C.output(2, str),
1757
- green: str => exports.C.output(32, str),
1758
- red: str => exports.C.output(31, str),
1759
- yellow: str => exports.C.output(33, str),
1760
- grey: str => exports.C.output(2, str),
1761
- magenta: str => exports.C.output(35, str),
1762
- cyan: str => exports.C.output(36, str),
1763
- blue: str => exports.C.output(34, str),
1764
- primary: str => {
1765
- const primary = configFn().terminal.theme.primary;
1766
- return exports.C.rgb(...primary) + str + exports.C.reset;
1767
- },
1768
- reset: '\x1b[0m',
1769
- output: (code, str = '') => configFn().terminal.noColor ? str : `\x1b[${code}m${str.toString().split('\n').join('\x1b[0m\n\x1b[2m')}\x1b[0m`,
1770
- // true RGB colors B-*
1771
- rgb: (r, g = 0, b = 0) => configFn().terminal.noColor || !isset(r, g, b) ? '' : `\x1b[38;2;${r};${g};${b}m`,
1772
- bg: (r, g, b) => configFn().terminal.noColor || !isset(r, g, b) ? '' : `${'\x1b['}48;2;${r};${g};${b}m`,
1773
- /** Output a line of title */
1774
- line(title = '', length = configFn().terminal.theme.pageWidth, clr = configFn().terminal.theme.primary, char = '=', paddingX = configFn().terminal.theme.paddingX) {
1775
- const padX = ' '.repeat(paddingX);
1776
- this.log('\u00A0\n' + padX + this.rgb(...clr) + (title + ' ').padEnd(length, char) + this.reset + padX + '\u00A0\n');
1777
- },
1778
- /** Eg: ['cell1', 'cell2', 'cell3'], [25, 15] will start cell2 at 25 and cell 3 at 25 + 15
1779
- * @param {Array} limits default divide the viewport
1780
- */
1781
- cols(strings, limits = [], clr = configFn().terminal.theme.fontColor, paddingX = configFn().terminal.theme.paddingX) {
1782
- if (!limits.length) {
1783
- const colWidth = Math.round(configFn().terminal.theme.pageWidth / strings.length);
1784
- limits = Array(strings.length - 1).fill(2).map((itm, i) => colWidth * i + 1);
1785
- }
1786
- const str = strings.reduce((glob, str = '', i) => {
1787
- const realCharLength = str.toString().replace(/\x1b\[.*?m/, '').length;
1788
- const charLength = str.toString().length;
1789
- const realLimit = limits[i] + charLength - realCharLength;
1790
- return glob + str.toString().substring(0, realLimit || 999).padEnd(realLimit || 0, ' ');
1791
- }, '');
1792
- this.logClr(str, clr, paddingX);
1793
- },
1794
- /** Console log alias */
1795
- log(...stringsCtxMayBeFirstParam) {
1796
- stringsCtxMayBeFirstParam.forEach(str => this.logClr(str, undefined, undefined));
1797
- },
1798
- logClr(str, clr = configFn().terminal.theme.fontColor, paddingX = configFn().terminal.theme.paddingX) {
1799
- if (!isset(str))
1800
- return;
1801
- const padX = ' '.repeat(paddingX);
1802
- str = padX + (isset(clr) ? this.rgb(...clr) : '') + str.toString().replace(/\n/g, '\n' + padX + (isset(clr) ? this.rgb(...clr) : ''));
1803
- exports.logger.log(str + this.reset, 'log');
1804
- },
1805
- info(...str) {
1806
- str.forEach((s, i) => {
1807
- if (i === 0)
1808
- this.logClr('ⓘ ' + s, configFn().terminal.theme.primary);
1809
- else
1810
- this.log(this.dimStrSplit(s));
1811
- });
1812
- this.log(' ');
1813
- },
1814
- success(...str) {
1815
- str.forEach((s, i) => {
1816
- if (i === 0)
1817
- this.log(this.green('✓ ' + s));
1818
- else
1819
- this.log(this.dimStrSplit(s));
1820
- });
1821
- },
1822
- /** First param **false** to avoid logging stack trace */
1823
- error: (...errors) => logErrPrivate('error', [255, 0, 0], ...errors),
1824
- /** First param **false** to avoid logging stack trace */
1825
- warning: (...str) => logErrPrivate('warn', [255, 122, 0], ...str),
1826
- customError: (color, ...str) => logErrPrivate('error', color, ...str),
1827
- customWarning: (color, ...str) => logErrPrivate('warn', color, ...str),
1828
- applicationError: (color, ...str) => logErrPrivate('appError', color, ...str),
1829
- warningLight: (color, ...str) => logErrPrivate('warn', [196, 120, 52], ...str),
1830
- dimStrSplit(...logs) {
1831
- let logsStr = [];
1832
- logs.filter(isset).forEach(log => log.toString().split('\n').forEach(line => line && logsStr.push(this.dim(` ${line}`))));
1833
- return logsStr.join('\n');
1834
- },
1835
- notifShow() {
1836
- console.log('\n\u00A0');
1837
- this.notifications.forEach(fn => fn());
1838
- this.notifications = [];
1839
- console.log('\n\u00A0');
1840
- },
1841
- /** Keep in memory the logs to show when needed with C.notifShow()
1842
- * Ex: C.notification('info', str); */
1843
- notification(type, ...messages) {
1844
- this.notifications.push(() => {
1845
- if (isset(this[type])) {
1846
- this[type](...messages);
1847
- }
1848
- else {
1849
- this.warning('Wrong param for C.notification');
1850
- }
1851
- });
1852
- },
1853
- notifications: [],
1854
- /** Gratientize lines of text (separated by \n) */
1855
- gradientize(str = '', rgb1 = configFn().terminal.theme.shade1, rgb2 = configFn().terminal.theme.shade2, bgRgb = configFn().terminal.theme.bgColor, paddingY = configFn().terminal.theme.paddingY, paddingX = configFn().terminal.theme.paddingX) {
1856
- const lines = str.split('\n');
1857
- const largestLine = lines.reduce((sum, line) => sum < line.length ? line.length : sum, 0);
1858
- const rgbParts = rgb1.map((val, i) => (val - rgb2[i]) / (lines.length));
1859
- const bg = bgRgb ? this.bg(bgRgb[0], bgRgb[1], bgRgb[2]) : '';
1860
- const padX = ' '.repeat(paddingX);
1861
- const padLine = bg + padX + ' '.padEnd(largestLine, ' ') + padX + '\x1b[0m\n';
1862
- console.log(padLine.repeat(paddingY) +
1863
- lines.reduce((s, line, i) => {
1864
- return s + bg + padX + this.rgb(...rgb1.map((val, i2) => Math.round(val - i * rgbParts[i2]))) + line.padEnd(largestLine, ' ') + padX + '\x1b[0m\n';
1865
- }, '') +
1866
- padLine.repeat(paddingY));
1867
- },
1868
- debugModeLog(title, ...string) {
1869
- this.logClr('🐞 ' + title, configFn().terminal.theme.debugModeColor, 0);
1870
- this.log(this.dimStrSplit(...string));
1871
- },
1872
- // DEPRECATED
1873
- useTheme() { },
1874
- };
1875
- function logErrPrivate(type, color, ...errors) {
1876
- const { isProd } = configFn();
1877
- if (errors.length === 1 && typeof errors[0].log === 'function')
1878
- return errors[0].log();
1879
- let stackTrace = (new Error('')).stack || '';
1880
- const displayStack = errors[0] === false ? errors.shift() : true;
1881
- const symbol = type === 'error' ? '✘ ' : '⚠ ';
1882
- if (errors.length > 1 && !isset(errors[0]))
1883
- errors.shift();
1884
- const getStringFromErr = (err, i) => {
1885
- if (!isset(err))
1886
- return '';
1887
- else if (typeof err === 'string') {
1888
- if (i === 0)
1889
- return exports.C.rgb(...color) + symbol + err + exports.C.reset;
1890
- else
1891
- return err.split('\n').map(val => exports.C.dim(val)).join('\n');
1892
- }
1893
- else if (err instanceof Error) {
1894
- const { str, stackTrace: stkTrc } = stringifyInstanceOfError(err, type, color);
1895
- if (stkTrc)
1896
- stackTrace = stkTrc;
1897
- return str;
1898
- }
1899
- else if (typeof err === 'object') {
1900
- let msg = '';
1901
- msg += removeCircularJSONstringify(err, 2).split('\n').map(val => exports.C.dim(val)).join('\n') + '\n';
1902
- const { str, stackTrace: stkTrc } = stringifyExtraInfos(err.extraInfo || err, type, color);
1903
- if (stkTrc)
1904
- stackTrace = stkTrc;
1905
- msg += str;
1906
- return msg;
1907
- }
1908
- else
1909
- return '';
1910
- };
1911
- if (errors.length && errors[0]) {
1912
- const messages = errors.map((e, i) => {
1913
- if (typeof e.log === 'function') {
1914
- e.log();
1915
- return '';
1916
- }
1917
- else
1918
- return getStringFromErr(e, i);
1919
- });
1920
- // Stack
1921
- if (displayStack) {
1922
- messages.push(isProd ? stackTrace : cleanStackTrace(stackTrace) + '\n');
1923
- }
1924
- exports.logger.log(messages.join(''), type);
1925
- }
1926
- }
1927
- exports.logErrPrivate = logErrPrivate;
1928
- function stringifyInstanceOfError(err, type = 'error', color = [255, 0, 0], level = 0) {
1929
- if (level > 5)
1930
- return { str: '' };
1931
- let str = '';
1932
- let stackTrace;
1933
- const symbol = type === 'error' ? '✘ ' : '⚠ ';
1934
- const title = err.msg || err.message || err.id || (err.stack ? err.stack.split('\n')[0] : 'Error');
1935
- // Err mess
1936
- str += exports.C.rgb(...color) + symbol + title + exports.C.reset + '\n';
1937
- if (err.stack)
1938
- stackTrace = err.stack; // more relevant
1939
- // ExtraInfos
1940
- if (isset(err.extraInfo)) {
1941
- const { str: str2, stackTrace: stkTrc } = stringifyExtraInfos(err.extraInfo, type, color, level++);
1942
- if (stkTrc)
1943
- stackTrace = stkTrc;
1944
- str += str2;
1945
- }
1946
- return { str, stackTrace };
1947
- }
1948
- exports.stringifyInstanceOfError = stringifyInstanceOfError;
1949
- function stringifyExtraInfos(extraInfoOriginal, type, color, level = 0) {
1950
- let stackTrace;
1951
- const originalError = [exports.C.dim(`ORIGINAL ERROR ${'-'.repeat(39)}\n`)];
1952
- if (extraInfoOriginal instanceof Error) { // case where error is passed directly to extraInfos
1953
- return stringifyInstanceOfError(extraInfoOriginal, type, color);
1954
- }
1955
- else {
1956
- const extraInfo = { ...extraInfoOriginal };
1957
- const extraInfos = [exports.C.dim(`EXTRA INFOS ${'-'.repeat(41)}\n`)];
1958
- if (typeof extraInfo === 'object' && Object.keys(extraInfo).length) {
1959
- for (const itemName in extraInfo) {
1960
- if (extraInfo[itemName] instanceof Error) {
1961
- const { str, stackTrace: stkTrc } = stringifyInstanceOfError(extraInfo[itemName], type, color, level++);
1962
- originalError.push(str);
1963
- stackTrace = stkTrc;
1964
- delete extraInfo[itemName];
1965
- }
1966
- }
1967
- }
1968
- if (Object.keys(extraInfo).length) {
1969
- extraInfos.push(removeCircularJSONstringify(extraInfo, 2)
1970
- .replace(/(?:^\s*{(?:\n {2})?|}\s*$)/g, '')
1971
- .replace(/\n {2}/g, '\n')
1972
- .split('\n')
1973
- .map(val => exports.C.dim(val)).join('\n') + '\n');
1974
- }
1975
- return {
1976
- str: (extraInfos.length > 1 ? extraInfos.join('') : '') + (originalError.length > 1 ? originalError.join('') + '\n' : ''),
1977
- stackTrace
1978
- };
1979
- }
1980
- }
1981
- exports.stringifyExtraInfos = stringifyExtraInfos;
1982
- /**
1983
- * Call this at each steps of your progress and change the step value
1984
- * @param {Number} step Number of "char" to output
1985
- * @param {String} char Default: '.'
1986
- * @param {String} msg String before char. Final output will be `${str}${char.repeat(step)}`
1987
- */
1988
- function cliProgressBar(step, char = '.', msg = `\x1b[2mⓘ Waiting response`) {
1989
- if (isset(process) && isset(process.stdout) && isset(process.stdout.clearLine)) {
1990
- process.stdout.clearLine(0);
1991
- process.stdout.cursorTo(0);
1992
- process.stdout.write(`${msg}${char.repeat(step)}\x1b[0m`); // \x1b[0m == reset color
1993
- }
1994
- }
1995
- exports.cliProgressBar = cliProgressBar;
1996
- /** This allow an intuitive inline loading spinner with a check mark when loading as finished or a red cross for errors */
1997
- class cliLoadingSpinner {
1998
- constructor(type = 'dots', activeProcess = process) {
1999
- const anims = {
2000
- arrow: {
2001
- interval: 120,
2002
- frames: ['▹▹▹▹▹', '▸▹▹▹▹', '▹▸▹▹▹', '▹▹▸▹▹', '▹▹▹▸▹', '▹▹▹▹▸']
2003
- },
2004
- dots: {
2005
- interval: 80,
2006
- frames: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
2007
- }
2008
- };
2009
- this.frameRate = anims[type].interval;
2010
- this.animFrames = anims[type].frames;
2011
- this.activeProcess = activeProcess;
2012
- }
2013
- start(msg) {
2014
- this.frameNb = 0;
2015
- this.progressMessage = msg;
2016
- this.interval = setInterval(() => {
2017
- this.activeProcess.stdout.clearLine();
2018
- this.activeProcess.stdout.cursorTo(0);
2019
- const symbol = this.animFrames[this.frameNb++ % this.animFrames.length];
2020
- this.activeProcess.stdout.write(exports.C.primary(symbol) + ' ' + this.progressMessage);
2021
- }, this.frameRate);
2022
- }
2023
- end(error = false) {
2024
- clearInterval(this.interval);
2025
- this.activeProcess.stdout.clearLine();
2026
- this.activeProcess.stdout.cursorTo(0);
2027
- this.activeProcess.stdout.write(error ? exports.C.red('✘ ' + this.progressMessage + '\n\n')
2028
- : '\x1b[32m✓ ' + this.progressMessage + '\n\n');
2029
- this.progressMessage = '';
2030
- }
2031
- error() {
2032
- return this.end(true);
2033
- }
2034
- }
2035
- //----------------------------------------
2036
- // STRING UTILS
2037
- //----------------------------------------
2038
- /** Remove accentued character from string and eventually special chars and numbers
2039
- * @param {String} str input string
2040
- * @param {Object} config { removeSpecialChars: false, removeNumbers: false, removeSpaces: false }
2041
- * @returns String with all accentued char replaced by their non accentued version + config formattting
2042
- */
2043
- function convertAccentedCharacters(str, config = {}) {
2044
- let output = str
2045
- .replace(/[àáâãäå]/g, 'a')
2046
- .replace(/ç/g, 'c')
2047
- .replace(/[èéêë]/g, 'e')
2048
- .replace(/[ìíîï]/g, 'i')
2049
- .replace(/[ôö]/g, 'o')
2050
- .replace(/[ùúû]/g, 'u')
2051
- .replace(/(^\s*|\s*$)/g, '');
2052
- if (config.removeNumbers === true)
2053
- output = output.replace(/\d+/g, '');
2054
- if (config.removeSpecialChars === true)
2055
- output = output.replace(/[^A-Za-z0-9 ]/g, '');
2056
- if (config.removeSpaces === true)
2057
- output = output.replace(/\s+/g, '');
2058
- return output;
2059
- }
2060
- exports.convertAccentedCharacters = convertAccentedCharacters;
2061
- //----------------------------------------
2062
- // MONGO UTILS
2063
- //----------------------------------------
2064
- /** Merge filter with correct handling of OR and AND
2065
- * @param {Object} filterA
2066
- * @param {Object} filterB
2067
- * @param {Boolean} assignToFilterA defualt false: if true, it will modify filterA, else it will return merged filters as a new object
2068
- */
2069
- function mongoFilterMerger(filterA, filterB, assignToFilterA = false) {
2070
- if (isset(filterA.$and) && isset(filterB.$and)) {
2071
- filterA.$and.push(...filterB.$and);
2072
- delete filterB.$and;
2073
- }
2074
- if (isset(filterA.$or) && isset(filterB.$or)) {
2075
- if (!isset(filterA.$and))
2076
- filterA.$and = [];
2077
- filterA.$and.push({ $or: filterA.$or }, { $or: filterA.$or });
2078
- delete filterB.$or;
2079
- }
2080
- if (assignToFilterA) {
2081
- Object.assign(filterA, filterB);
2082
- return filterA;
2083
- }
2084
- else
2085
- return { ...filterA, ...filterB };
2086
- }
2087
- exports.mongoFilterMerger = mongoFilterMerger;
2088
- function mongoPush(field, value, fields) {
2089
- if (!isset(fields.$push))
2090
- fields.$push = {};
2091
- fields.$push[field] = value;
2092
- }
2093
- exports.mongoPush = mongoPush;
2094
- //----------------------------------------
2095
- // TIMEOUT UTILS
2096
- //----------------------------------------
2097
- async function timeout(ms, fn = () => { }) { return new Promise(res => setTimeout(res, ms)).then(fn); }
2098
- exports.timeout = timeout;
2099
- async function runAsync(callback, milliseconds$ = 1) { return timeout(milliseconds$, callback); }
2100
- exports.runAsync = runAsync;
2101
- /**
2102
- *
2103
- * @param {Function} callback function that shall return ===true asynchronously
2104
- * @param {Number} timeoutSec default:10; general timeout in seconds
2105
- * @param {Boolean|String} errorAfterNSeconds default:true output an error in case of timeout, can be the displayed error message
2106
- * @param {*} cliOutput write a cli progress to show that a process is running
2107
- */
2108
- async function waitUntilTrue(callback, timeoutSec = 10, errorAfterNSeconds = true, cliOutput = true) {
2109
- let generalTimeout = true;
2110
- let step = 3;
2111
- const errMess = typeof errorAfterNSeconds === 'string' ? 'Timeout: ' + errorAfterNSeconds : 'Timeout for waitUntilTrue() callback';
2112
- if (timeoutSec)
2113
- setTimeout(() => generalTimeout = false, timeoutSec * 1000);
2114
- while (callback() !== true && generalTimeout) {
2115
- if (cliOutput)
2116
- cliProgressBar(step++);
2117
- await timeout(300);
2118
- }
2119
- if (cliOutput)
2120
- process.stdout.write(`\n`);
2121
- if (!generalTimeout && errorAfterNSeconds)
2122
- throw new dataValidationUtilErrorHandler(errMess, 500);
2123
- }
2124
- exports.waitUntilTrue = waitUntilTrue;
2125
- const delayedLoopParams = [];
2126
- let isExecuting = false;
2127
- /** Allow to perform an action in a delayed loop, useful for example to avoid reaching limits on servers. This function can be securely called multiple times.
2128
- * @param {Function} callback
2129
- * @param {Number} time default: 500ms;
2130
- * @param {Function} errorCallback default: e => C.error(e)
2131
- */
2132
- async function executeInDelayedLoop(callback, time = 500, errorCallback = e => exports.C.error(e)) {
2133
- delayedLoopParams.push([callback, time, errorCallback]);
2134
- if (isExecuting)
2135
- return;
2136
- isExecuting = true;
2137
- while (delayedLoopParams.length) {
2138
- const [callback, time, errorCallback] = delayedLoopParams.shift();
2139
- try {
2140
- await callback();
2141
- await timeout(time);
2142
- }
2143
- catch (err) {
2144
- errorCallback(err);
2145
- }
2146
- }
2147
- isExecuting = false;
2148
- }
2149
- exports.executeInDelayedLoop = executeInDelayedLoop;
2150
- //----------------------------------------
2151
- // TRANSACTION
2152
- //----------------------------------------
2153
- const transactionRunning = { __default: false };
2154
- const queue = { __default: [] };
2155
- /** Allow to perform async functions in a defined order
2156
- * This adds the callback to a queue and is resolved when ALL previous callbacks with same name are executed
2157
- * Use it like: await transaction('nameOfTheFlow', async () => { ...myFunction })
2158
- * @param {String|Function} name name for the actions that should never happen concurrently
2159
- * @param {Function} asyncCallback
2160
- * @param {Number} timeout default: 120000 (120s) will throw an error if transaction time is higher that this amount of ms
2161
- * @returns {Promise}
2162
- */
2163
- async function transaction(name, asyncCallback, timeout = 120000, doNotThrow = false) {
2164
- if (typeof name === 'function') {
2165
- asyncCallback = name;
2166
- name = '__default';
2167
- }
2168
- if (!isset(queue[name]))
2169
- queue[name] = [];
2170
- if (!isset(transactionRunning[name]))
2171
- transactionRunning[name] = false;
2172
- return await new Promise((resolve, reject) => {
2173
- if (doNotThrow)
2174
- reject = exports.C.error;
2175
- queue[name].push(async () => {
2176
- try {
2177
- setTimeout(() => {
2178
- exports.C.warning('Transaction Timout'); // in case not catched
2179
- reject(new Error('transactionTimeout'));
2180
- }, timeout);
2181
- const res = await asyncCallback();
2182
- resolve(res);
2183
- }
2184
- catch (err) {
2185
- reject(err);
2186
- }
2187
- });
2188
- removeItemFromQueue(name);
2189
- });
2190
- }
2191
- exports.transaction = transaction;
2192
- async function removeItemFromQueue(name) {
2193
- if (transactionRunning[name] === true)
2194
- return; // v
2195
- transactionRunning[name] = true; // A A /\
2196
- while (queue[name].length)
2197
- await queue[name].shift()(); // II
2198
- transactionRunning[name] = false; // \==/_______II
2199
- } // l v_v_v _I
2200
- exports.removeItemFromQueue = removeItemFromQueue;
2201
- // 11 11
2202
- /** Wait for a transaction to complete without creating a new transaction
2203
- *
2204
- */
2205
- async function waitForTransaction(transactionName, forceReleaseInSeconds = 30) {
2206
- let brk = false;
2207
- setTimeout(() => brk = true, forceReleaseInSeconds * 1000);
2208
- while (isset(transactionRunning[transactionName]) && transactionRunning[transactionName] === true) {
2209
- if (brk)
2210
- break;
2211
- await timeout(15);
2212
- }
2213
- }
2214
- exports.waitForTransaction = waitForTransaction;
2215
- // ALIASES
2216
- exports.int = parseInt;
2217
- exports.average = moyenne;
2218
- exports.arrayUniqueValue = noDuplicateFilter;
2219
- const JSONstringyParse = o => JSON.parse(removeCircularJSONstringify(o));
2220
- exports.JSONstringyParse = JSONstringyParse;
2221
- exports.removeUndefinedKeys = objFilterUndefinedRecursive;
2222
- exports.randomizeArray = shuffleArray;
2223
- exports.dashCase = kebabCase;
2224
- exports.underscoreCase = snakeCase;
2225
- exports.required = validator; // alias for readability
2226
- exports.orIsset = issetOr;
2227
- //# sourceMappingURL=utils.js.map