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.
- package/CHANGELOG.md +4 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +56 -0
- package/dist/index.js.map +1 -0
- package/dist/src/array-utils.d.ts +56 -0
- package/dist/src/array-utils.js +138 -0
- package/dist/src/array-utils.js.map +1 -0
- package/dist/src/date-utils.d.ts +100 -0
- package/dist/src/date-utils.js +357 -0
- package/dist/src/date-utils.js.map +1 -0
- package/dist/src/env-utils.d.ts +8 -0
- package/dist/src/env-utils.js +38 -0
- package/dist/src/env-utils.js.map +1 -0
- package/dist/src/error-utils.d.ts +8 -0
- package/dist/src/error-utils.js +99 -0
- package/dist/src/error-utils.js.map +1 -0
- package/dist/src/is-empty.d.ts +1 -0
- package/dist/src/is-empty.js +13 -0
- package/dist/src/is-empty.js.map +1 -0
- package/dist/src/is-object.d.ts +2 -0
- package/dist/src/is-object.js +7 -0
- package/dist/src/is-object.js.map +1 -0
- package/dist/src/isset.d.ts +1 -0
- package/dist/src/isset.js +8 -0
- package/dist/src/isset.js.map +1 -0
- package/dist/src/logger-utils.d.ts +76 -0
- package/dist/src/logger-utils.js +355 -0
- package/dist/src/logger-utils.js.map +1 -0
- package/dist/src/loop-utils.d.ts +37 -0
- package/dist/src/loop-utils.js +105 -0
- package/dist/src/loop-utils.js.map +1 -0
- package/dist/src/math-utils.d.ts +23 -0
- package/dist/src/math-utils.js +43 -0
- package/dist/src/math-utils.js.map +1 -0
- package/dist/src/mongo-utils.d.ts +11 -0
- package/dist/src/mongo-utils.js +49 -0
- package/dist/src/mongo-utils.js.map +1 -0
- package/dist/src/object-utils.d.ts +96 -0
- package/dist/src/object-utils.js +369 -0
- package/dist/src/object-utils.js.map +1 -0
- package/dist/src/private/config.d.ts +44 -0
- package/dist/src/private/config.js +55 -0
- package/dist/src/private/config.js.map +1 -0
- package/dist/src/private/error-handler.d.ts +10 -0
- package/dist/src/private/error-handler.js +18 -0
- package/dist/src/private/error-handler.js.map +1 -0
- package/dist/src/private/types.d.ts +4 -0
- package/dist/src/private/types.js +3 -0
- package/dist/src/private/types.js.map +1 -0
- package/dist/src/regexp-utils.d.ts +12 -0
- package/dist/src/regexp-utils.js +44 -0
- package/dist/src/regexp-utils.js.map +1 -0
- package/dist/src/remove-circular-json-stringify.d.ts +1 -0
- package/dist/src/remove-circular-json-stringify.js +20 -0
- package/dist/src/remove-circular-json-stringify.js.map +1 -0
- package/dist/src/string-utils.d.ts +77 -0
- package/dist/src/string-utils.js +209 -0
- package/dist/src/string-utils.js.map +1 -0
- package/dist/src/tests-utils.js +77 -0
- package/dist/src/tests-utils.js.map +1 -0
- package/dist/src/timer-utils.d.ts +16 -0
- package/dist/src/timer-utils.js +79 -0
- package/dist/src/timer-utils.js.map +1 -0
- package/dist/src/transaction-utils.d.ts +14 -0
- package/dist/src/transaction-utils.js +87 -0
- package/dist/src/transaction-utils.js.map +1 -0
- package/dist/src/validation-utils.d.ts +89 -0
- package/dist/src/validation-utils.js +192 -0
- package/dist/src/validation-utils.js.map +1 -0
- package/dist/src/wtf-utils.d.ts +7 -0
- package/dist/src/wtf-utils.js +83 -0
- package/dist/src/wtf-utils.js.map +1 -0
- package/index.ts +38 -0
- package/package.json +2 -2
- package/src/array-utils.ts +128 -0
- package/src/date-utils.ts +377 -0
- package/src/env-utils.ts +29 -0
- package/src/error-utils.ts +77 -0
- package/src/is-empty.ts +5 -0
- package/src/is-object.ts +3 -0
- package/src/isset.ts +3 -0
- package/src/logger-utils.ts +349 -0
- package/src/loop-utils.ts +101 -0
- package/src/math-utils.ts +38 -0
- package/src/mongo-utils.ts +38 -0
- package/src/object-utils.ts +356 -0
- package/src/private/config.ts +85 -0
- package/src/private/error-handler.ts +21 -0
- package/src/private/types.ts +6 -0
- package/src/regexp-utils.ts +37 -0
- package/src/remove-circular-json-stringify.ts +17 -0
- package/src/string-utils.ts +212 -0
- package/src/tests-utils.ts +70 -0
- package/src/timer-utils.ts +58 -0
- package/src/transaction-utils.ts +63 -0
- package/src/validation-utils.ts +253 -0
- package/src/wtf-utils.ts +88 -0
- package/tsconfig.json +11 -4
- package/utils.d.ts +0 -694
- package/utils.js +0 -2227
- package/utils.js.map +0 -1
- 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
|