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