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
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
//----------------------------------------
|
|
2
|
+
// STRING UTILS
|
|
3
|
+
//----------------------------------------
|
|
4
|
+
import { err500IfEmptyOrNotSet } from "./error-utils"
|
|
5
|
+
import { ObjectGeneric } from "./private/types"
|
|
6
|
+
import { isset } from "./isset"
|
|
7
|
+
|
|
8
|
+
/**Eg: camelCase */
|
|
9
|
+
export function camelCase(...wordBits) {
|
|
10
|
+
return wordBits.filter(e => e).map((w, i) => i === 0 ? w : capitalize1st(w)).join('')
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**Eg: snake_case
|
|
14
|
+
* trimmed but not lowerCased
|
|
15
|
+
*/
|
|
16
|
+
export function snakeCase(...wordBits) {
|
|
17
|
+
return wordBits.filter(e => e).map(w => w.trim()).join('_')
|
|
18
|
+
}
|
|
19
|
+
/**Eg: kebab-case
|
|
20
|
+
* trimmed AND lowerCased
|
|
21
|
+
* undefined, null... => ''
|
|
22
|
+
*/
|
|
23
|
+
export function kebabCase(...wordBits) {
|
|
24
|
+
return wordBits.filter(e => e).map(w => w.trim().toLowerCase()).join('-')
|
|
25
|
+
}
|
|
26
|
+
/**Eg: PascalCase undefined, null... => '' */
|
|
27
|
+
export function pascalCase(...wordBits) {
|
|
28
|
+
return wordBits.filter(e => e).map((w, i) => capitalize1st(w)).join('')
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**Eg: Titlecase undefined, null... => '' */
|
|
32
|
+
export function titleCase(...wordBits) {
|
|
33
|
+
return capitalize1st(wordBits.filter(e => e).map(w => w.trim()).join(''))
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**Eg: UPPERCASE undefined, null... => '' */
|
|
37
|
+
export function upperCase(...wordBits) {
|
|
38
|
+
return wordBits.filter(e => e).map(w => w.trim().toUpperCase()).join('')
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**Eg: lowercase undefined, null... => '' */
|
|
42
|
+
export function lowerCase(...wordBits) {
|
|
43
|
+
return wordBits.filter(e => e).map(w => w.trim().toLowerCase()).join('')
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function capitalize1st(str = '') { return str[0].toUpperCase() + str.slice(1) }
|
|
47
|
+
|
|
48
|
+
export function camelCaseToWords(str) {
|
|
49
|
+
return str ? str.trim().replace(/([A-Z])/g, '-$1').toLowerCase().split('-') : []
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** GIVEN A STRING '{ blah;2}, ['nested,(what,ever)']' AND A SEPARATOR ",""
|
|
53
|
+
* This will return the content separated by first level of separators
|
|
54
|
+
* @return ["{ blah;2}", "['nested,(what,ever)']"]
|
|
55
|
+
*/
|
|
56
|
+
export function getValuesBetweenSeparator(str: string, separator: string, removeTrailingSpaces = true) {
|
|
57
|
+
err500IfEmptyOrNotSet({ separator, str })
|
|
58
|
+
const { outer } = getValuesBetweenStrings(str, separator, undefined, undefined, undefined, removeTrailingSpaces)
|
|
59
|
+
return outer
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
/** GIVEN A STRING "a: [ 'str', /[^]]/, '[aa]]]str', () => [ nestedArray ] ], b: ['arr']"
|
|
64
|
+
* @return matching: [ "'str', /[^]]/, '[aa]]]str', () => [ nestedArray ]", "'arr'" ], between: [ "a:", ", b: " ]
|
|
65
|
+
* @param str base string
|
|
66
|
+
* @param openingOrSeparator opening character OR separator if closing not set
|
|
67
|
+
* @param closing
|
|
68
|
+
* @param ignoreBetweenOpen default ['\'', '`', '"', '/'], when reaching an opening char, it will ignore all until it find the corresponding closing char
|
|
69
|
+
* @param ignoreBetweenClose default ['\'', '`', '"', '/'] list of corresponding closing chars
|
|
70
|
+
*/
|
|
71
|
+
export function getValuesBetweenStrings(str: string, openingOrSeparator, closing, ignoreBetweenOpen = ['\'', '`', '"', '/'], ignoreBetweenClose = ['\'', '`', '"', '/'], removeTrailingSpaces = true) {
|
|
72
|
+
err500IfEmptyOrNotSet({ openingOrSeparator, str })
|
|
73
|
+
|
|
74
|
+
str = str.replace(/<</g, '§§"').replace(/>>/g, '"§§')
|
|
75
|
+
|
|
76
|
+
const arrayValues = []
|
|
77
|
+
const betweenArray = []
|
|
78
|
+
let level = 0
|
|
79
|
+
let ignoreUntil: boolean | string = false
|
|
80
|
+
let actualValue = ''
|
|
81
|
+
let precedingChar = ''
|
|
82
|
+
|
|
83
|
+
let separatorMode = false
|
|
84
|
+
if (!closing) separatorMode = true
|
|
85
|
+
|
|
86
|
+
const separator = separatorMode ? openingOrSeparator : false
|
|
87
|
+
const openingChars = separatorMode ? ['(', '{', '['] : [openingOrSeparator]
|
|
88
|
+
const closingChars = separatorMode ? [')', '}', ']'] : [closing]
|
|
89
|
+
|
|
90
|
+
const pushActualValue = () => {
|
|
91
|
+
if (level === 0) betweenArray.push(removeTrailingSpaces ? actualValue.replace(/(?:^\s+|\s+$)/g, '') : actualValue)
|
|
92
|
+
else arrayValues.push(removeTrailingSpaces ? actualValue.replace(/(?:^\s+|\s+$)/g, '') : actualValue)
|
|
93
|
+
actualValue = ''
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
str.split('').forEach(char => {
|
|
97
|
+
// handle unwanted nested structure like characters in a strings that may be a unmatched closing / opening character
|
|
98
|
+
// Eg: {'azer}aze'}
|
|
99
|
+
if (ignoreUntil && char === ignoreUntil && precedingChar !== '\\') ignoreUntil = false
|
|
100
|
+
else if (ignoreUntil && char !== ignoreUntil) true
|
|
101
|
+
else if (ignoreBetweenOpen.includes(char)) {
|
|
102
|
+
const indexChar = ignoreBetweenOpen.findIndex(char2 => char2 === char)
|
|
103
|
+
ignoreUntil = ignoreBetweenClose[indexChar]
|
|
104
|
+
} else if (openingChars.includes(char)) {
|
|
105
|
+
// handle nested structures
|
|
106
|
+
if (!separatorMode && level === 0) pushActualValue()
|
|
107
|
+
level++
|
|
108
|
+
if (!separatorMode) return
|
|
109
|
+
} else if (closingChars.includes(char)) {
|
|
110
|
+
// handle nested structures
|
|
111
|
+
if (!separatorMode && level === 1) pushActualValue()
|
|
112
|
+
level--
|
|
113
|
+
} else if (separatorMode && level === 0 && char === separator) {
|
|
114
|
+
// SEPARATOR MODE
|
|
115
|
+
pushActualValue()
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
actualValue += char
|
|
119
|
+
precedingChar = char
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
pushActualValue()
|
|
123
|
+
|
|
124
|
+
const replaceValz = arr => arr.map(v => v.replace(/§§"/g, '<<').replace(/"§§/g, '>>')).filter(v => v)
|
|
125
|
+
|
|
126
|
+
return { inner: replaceValz(arrayValues), outer: replaceValz(betweenArray) }
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Remove accentued character from string and eventually special chars and numbers
|
|
130
|
+
* @param {String} str input string
|
|
131
|
+
* @param {Object} config { removeSpecialChars: false, removeNumbers: false, removeSpaces: false }
|
|
132
|
+
* @returns String with all accentued char replaced by their non accentued version + config formattting
|
|
133
|
+
*/
|
|
134
|
+
export function convertAccentedCharacters(str, config: { removeNumbers?: boolean, removeSpecialChars?: boolean, removeSpaces?: boolean } = {}) {
|
|
135
|
+
let output = str
|
|
136
|
+
.replace(/[àáâãäå]/g, 'a')
|
|
137
|
+
.replace(/ç/g, 'c')
|
|
138
|
+
.replace(/[èéêë]/g, 'e')
|
|
139
|
+
.replace(/[ìíîï]/g, 'i')
|
|
140
|
+
.replace(/[ôö]/g, 'o')
|
|
141
|
+
.replace(/[ùúû]/g, 'u')
|
|
142
|
+
.replace(/(^\s*|\s*$)/g, '')
|
|
143
|
+
if (config.removeNumbers === true) output = output.replace(/\d+/g, '')
|
|
144
|
+
if (config.removeSpecialChars === true) output = output.replace(/[^A-Za-z0-9 ]/g, '')
|
|
145
|
+
if (config.removeSpaces === true) output = output.replace(/\s+/g, '')
|
|
146
|
+
return output
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
let generatedTokens = [] // cache to avoid collision
|
|
151
|
+
let lastTs = new Date().getTime()
|
|
152
|
+
/** minLength 8 if unique
|
|
153
|
+
* @param {Number} length default: 20
|
|
154
|
+
* @param {Boolean} unique default: true. Generate a real unique token base on the date. min length will be min 8 in this case
|
|
155
|
+
* @param {string} mode one of ['alphanumeric', 'hexadecimal']
|
|
156
|
+
* NOTE: to generate a mongoDB Random Id, use the params: 24, true, 'hexadecimal'
|
|
157
|
+
*/
|
|
158
|
+
export function generateToken(length = 20, unique = true, mode: 'alphanumeric' | 'hexadecimal' = 'alphanumeric') {
|
|
159
|
+
let charConvNumeric = mode === 'alphanumeric' ? 36 : 16
|
|
160
|
+
if (unique && length < 8) length = 8
|
|
161
|
+
let token
|
|
162
|
+
let tokenTs
|
|
163
|
+
do {
|
|
164
|
+
tokenTs = (new Date()).getTime()
|
|
165
|
+
token = unique ? tokenTs.toString(charConvNumeric) : ''
|
|
166
|
+
while (token.length < length) token += Math.random().toString(charConvNumeric).substr(2, 1) // char alphaNumeric aléatoire
|
|
167
|
+
} while (generatedTokens.includes(token))
|
|
168
|
+
if (lastTs < tokenTs) generatedTokens = [] // reset generated token on new timestamp because cannot collide
|
|
169
|
+
generatedTokens.push(token)
|
|
170
|
+
return token
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function generateObjectId() {
|
|
174
|
+
return generateToken(24, true, 'hexadecimal')
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
/** Useful to join differents bits of url with normalizing slashes
|
|
179
|
+
* * urlPathJoin('https://', 'www.kikou.lol/', '/user', '//2//') => https://www.kikou.lol/user/2/
|
|
180
|
+
* * urlPathJoin('http:/', 'kikou.lol') => https://www.kikou.lol
|
|
181
|
+
*/
|
|
182
|
+
export function urlPathJoin(...bits: string[]) {
|
|
183
|
+
return bits.join('/').replace(/\/+/g, '/').replace(/(https?:)\/\/?/, '$1//')
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
export type MiniTemplaterOptions = {
|
|
189
|
+
valueWhenNotSet?: string
|
|
190
|
+
regexp?: RegExp
|
|
191
|
+
valueWhenContentUndefined?: string
|
|
192
|
+
}
|
|
193
|
+
/** Replace variables in a string like: `Hello {{userName}}!`
|
|
194
|
+
* @param {String} content
|
|
195
|
+
* @param {Object} varz object with key => value === toReplace => replacer
|
|
196
|
+
* @param {Object} options
|
|
197
|
+
* * valueWhenNotSet => replacer for undefined values. Default: ''
|
|
198
|
+
* * regexp => must be 'g' and first capturing group matching the value to replace. Default: /{{\s*([^}]*)\s*}}/g
|
|
199
|
+
*/
|
|
200
|
+
export function miniTemplater(content: string, varz: ObjectGeneric, options: MiniTemplaterOptions = {}): string {
|
|
201
|
+
options = {
|
|
202
|
+
valueWhenNotSet: '',
|
|
203
|
+
regexp: /{{\s*([^}]*)\s*}}/g,
|
|
204
|
+
valueWhenContentUndefined: '',
|
|
205
|
+
...options,
|
|
206
|
+
}
|
|
207
|
+
return isset(content) ? content.replace(options.regexp, (m, $1) => isset(varz[$1]) ? varz[$1] : options.valueWhenNotSet) : options.valueWhenContentUndefined
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
/** Clean output for outside world. All undefined / null / NaN / Infinity values are changed to '-' */
|
|
212
|
+
export function cln(val, replacerInCaseItIsUndefinNaN = '-') { return ['undefined', undefined, 'indéfini', 'NaN', NaN, Infinity, null].includes(val) ? replacerInCaseItIsUndefinNaN : val }
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
//----------------------------------------
|
|
2
|
+
// TESTS UTILS
|
|
3
|
+
//----------------------------------------
|
|
4
|
+
import { C } from "./logger-utils"
|
|
5
|
+
import { ValidatorObject } from "./validation-utils"
|
|
6
|
+
import { isset } from "./isset"
|
|
7
|
+
import { validatorReturnErrArray } from "./validation-utils"
|
|
8
|
+
import { Override } from "./private/types"
|
|
9
|
+
import { isObject } from "./is-object"
|
|
10
|
+
|
|
11
|
+
export const restTestMini = {
|
|
12
|
+
throwOnErr: false,
|
|
13
|
+
reset(throwOnErr = false) {
|
|
14
|
+
restTestMini.nbSuccess = 0
|
|
15
|
+
restTestMini.nbError = 0
|
|
16
|
+
restTestMini.lastErrors = []
|
|
17
|
+
restTestMini.throwOnErr = throwOnErr
|
|
18
|
+
},
|
|
19
|
+
newErr(err) {
|
|
20
|
+
restTestMini.nbError++
|
|
21
|
+
restTestMini.lastErrors.push(err)
|
|
22
|
+
if (restTestMini.throwOnErr) throw new Error(err)
|
|
23
|
+
else C.error(false, err)
|
|
24
|
+
},
|
|
25
|
+
printStats() {
|
|
26
|
+
// TODO print last errz
|
|
27
|
+
C.info(`ERRORS RESUME =========`)
|
|
28
|
+
if (restTestMini.lastErrors.length) C.log('\n\n\n')
|
|
29
|
+
for (const lastErr of restTestMini.lastErrors) C.error(false, lastErr)
|
|
30
|
+
C.log('\n\n\n')
|
|
31
|
+
C.info(`STATS =========`)
|
|
32
|
+
C.info(`Total: ${restTestMini.nbSuccess + restTestMini.nbError}`)
|
|
33
|
+
C.success(`Success: ${restTestMini.nbSuccess}`)
|
|
34
|
+
C.error(false, ` Errors: ${restTestMini.nbError}`)
|
|
35
|
+
},
|
|
36
|
+
nbSuccess: 0,
|
|
37
|
+
nbError: 0,
|
|
38
|
+
lastErrors: []
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** if validatorObject param is not set then it will consider checking that the value is set
|
|
42
|
+
*
|
|
43
|
+
* @param description
|
|
44
|
+
* @param value
|
|
45
|
+
* @param validatorObject
|
|
46
|
+
*/
|
|
47
|
+
export function assert(description: string, value: any, validatorObject?: Override<ValidatorObject, { value?: never, name?: never }> | number | boolean | string) {
|
|
48
|
+
try {
|
|
49
|
+
const validatorObjReal: ValidatorObject = {
|
|
50
|
+
value,
|
|
51
|
+
name: description,
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (isObject(validatorObject)) Object.assign(validatorObjReal, validatorObject)
|
|
55
|
+
else if (isset(validatorObject)) validatorObjReal.eq = validatorObject
|
|
56
|
+
|
|
57
|
+
const issetCheck = !isset(validatorObjReal) // isEmpty()
|
|
58
|
+
const [errMsg, , extraInfos] = validatorReturnErrArray(validatorObjReal)
|
|
59
|
+
const msg2 = description + ` ${issetCheck ? 'isset' : `${JSON.stringify(validatorObject)}`}`
|
|
60
|
+
if (isset(errMsg)) {
|
|
61
|
+
const err = msg2 + `\n ${errMsg}\n ${JSON.stringify(extraInfos)}`
|
|
62
|
+
restTestMini.newErr(err)
|
|
63
|
+
} else {
|
|
64
|
+
restTestMini.nbSuccess++
|
|
65
|
+
C.success(msg2)
|
|
66
|
+
}
|
|
67
|
+
} catch (err) {
|
|
68
|
+
restTestMini.newErr(err)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
|
|
2
|
+
//----------------------------------------
|
|
3
|
+
// TIMEOUT UTILS
|
|
4
|
+
//----------------------------------------
|
|
5
|
+
import { cliProgressBar, C } from "./logger-utils"
|
|
6
|
+
import { dataValidationUtilErrorHandler } from "./private/error-handler"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
export async function timeout(ms, fn = () => { }) { return new Promise(res => setTimeout(res, ms)).then(fn) }
|
|
10
|
+
|
|
11
|
+
export async function runAsync(callback, milliseconds$ = 1) { return timeout(milliseconds$, callback) }
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
*
|
|
15
|
+
* @param {Function} callback function that shall return ===true asynchronously
|
|
16
|
+
* @param {Number} timeoutSec default:10; general timeout in seconds
|
|
17
|
+
* @param {Boolean|String} errorAfterNSeconds default:true output an error in case of timeout, can be the displayed error message
|
|
18
|
+
* @param {*} cliOutput write a cli progress to show that a process is running
|
|
19
|
+
*/
|
|
20
|
+
export async function waitUntilTrue(callback, timeoutSec = 10, errorAfterNSeconds = true, cliOutput = true) {
|
|
21
|
+
let generalTimeout = true
|
|
22
|
+
let step = 3
|
|
23
|
+
const errMess = typeof errorAfterNSeconds === 'string' ? 'Timeout: ' + errorAfterNSeconds : 'Timeout for waitUntilTrue() callback'
|
|
24
|
+
|
|
25
|
+
if (timeoutSec) setTimeout(() => generalTimeout = false, timeoutSec * 1000)
|
|
26
|
+
|
|
27
|
+
while (callback() !== true && generalTimeout) {
|
|
28
|
+
if (cliOutput) cliProgressBar(step++)
|
|
29
|
+
await timeout(300)
|
|
30
|
+
}
|
|
31
|
+
if (cliOutput) process.stdout.write(`\n`)
|
|
32
|
+
if (!generalTimeout && errorAfterNSeconds) throw new dataValidationUtilErrorHandler(errMess, 500)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
const delayedLoopParams = []
|
|
37
|
+
let isExecuting = false
|
|
38
|
+
|
|
39
|
+
/** 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.
|
|
40
|
+
* @param {Function} callback
|
|
41
|
+
* @param {Number} time default: 500ms;
|
|
42
|
+
* @param {Function} errorCallback default: e => C.error(e)
|
|
43
|
+
*/
|
|
44
|
+
export async function executeInDelayedLoop(callback, time = 500, errorCallback = e => C.error(e)) {
|
|
45
|
+
delayedLoopParams.push([callback, time, errorCallback])
|
|
46
|
+
if (isExecuting) return
|
|
47
|
+
isExecuting = true
|
|
48
|
+
while (delayedLoopParams.length) {
|
|
49
|
+
const [callback, time, errorCallback] = delayedLoopParams.shift()
|
|
50
|
+
try {
|
|
51
|
+
await callback()
|
|
52
|
+
await timeout(time)
|
|
53
|
+
} catch (err) {
|
|
54
|
+
errorCallback(err)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
isExecuting = false
|
|
58
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
//----------------------------------------
|
|
2
|
+
// TRANSACTION
|
|
3
|
+
//----------------------------------------
|
|
4
|
+
import { isset } from "./isset"
|
|
5
|
+
import { C } from "./logger-utils"
|
|
6
|
+
import { timeout } from "./timer-utils"
|
|
7
|
+
|
|
8
|
+
const transactionRunning = { __default: false }
|
|
9
|
+
const queue = { __default: [] }
|
|
10
|
+
|
|
11
|
+
/** Allow to perform async functions in a defined order
|
|
12
|
+
* This adds the callback to a queue and is resolved when ALL previous callbacks with same name are executed
|
|
13
|
+
* Use it like: await transaction('nameOfTheFlow', async () => { ...myFunction })
|
|
14
|
+
* @param {String|Function} name name for the actions that should never happen concurrently
|
|
15
|
+
* @param {Function} asyncCallback
|
|
16
|
+
* @param {Number} timeout default: 120000 (120s) will throw an error if transaction time is higher that this amount of ms
|
|
17
|
+
* @returns {Promise}
|
|
18
|
+
*/
|
|
19
|
+
export async function transaction(name, asyncCallback, timeout = 120000, doNotThrow = false) {
|
|
20
|
+
if (typeof name === 'function') {
|
|
21
|
+
asyncCallback = name
|
|
22
|
+
name = '__default'
|
|
23
|
+
}
|
|
24
|
+
if (!isset(queue[name])) queue[name] = []
|
|
25
|
+
if (!isset(transactionRunning[name])) transactionRunning[name] = false
|
|
26
|
+
|
|
27
|
+
return await new Promise((resolve, reject) => {
|
|
28
|
+
if (doNotThrow) reject = C.error
|
|
29
|
+
queue[name].push(async () => {
|
|
30
|
+
try {
|
|
31
|
+
setTimeout(() => {
|
|
32
|
+
C.warning('Transaction Timout') // in case not catched
|
|
33
|
+
reject(new Error('transactionTimeout'))
|
|
34
|
+
}, timeout)
|
|
35
|
+
const res = await asyncCallback()
|
|
36
|
+
resolve(res)
|
|
37
|
+
} catch (err) {
|
|
38
|
+
reject(err)
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
removeItemFromQueue(name)
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function removeItemFromQueue(name) {// meoww!
|
|
46
|
+
if (transactionRunning[name] === true) return // v
|
|
47
|
+
transactionRunning[name] = true // A A /\
|
|
48
|
+
while (queue[name].length) await queue[name].shift()() // II
|
|
49
|
+
transactionRunning[name] = false // \==/_______II
|
|
50
|
+
} // l v_v_v _I
|
|
51
|
+
// 11 11
|
|
52
|
+
|
|
53
|
+
/** Wait for a transaction to complete without creating a new transaction
|
|
54
|
+
*
|
|
55
|
+
*/
|
|
56
|
+
export async function waitForTransaction(transactionName, forceReleaseInSeconds = 30) {
|
|
57
|
+
let brk = false
|
|
58
|
+
setTimeout(() => brk = true, forceReleaseInSeconds * 1000)
|
|
59
|
+
while (isset(transactionRunning[transactionName]) && transactionRunning[transactionName] === true) {
|
|
60
|
+
if (brk) break
|
|
61
|
+
await timeout(15)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
//----------------------------------------
|
|
2
|
+
// VALIDATION UTILS
|
|
3
|
+
//----------------------------------------
|
|
4
|
+
import { dataValidationUtilErrorHandler } from "./private/error-handler"
|
|
5
|
+
import { isset } from "./isset"
|
|
6
|
+
import { isDateIsoOrObjectValid, isDateIntOrStringValid, isTimeStringValid } from "./date-utils"
|
|
7
|
+
import { asArray } from "./array-utils"
|
|
8
|
+
import { configFn } from "./private/config"
|
|
9
|
+
import { isEmpty } from "./is-empty"
|
|
10
|
+
|
|
11
|
+
export type BaseTypes = 'objectId' | 'dateInt6' | 'dateInt' | 'dateInt8' | 'dateInt12' | 'time' | 'humanReadableTimestamp' | 'date' | 'dateObject' | 'array' | 'object' | 'buffer' | 'string' | 'function' | 'boolean' | 'number' | 'bigint' | 'year' | 'email' | 'any'
|
|
12
|
+
|
|
13
|
+
export function issetOr(...elms) { return elms.some(elm => typeof elm !== 'undefined' && elm !== null) }
|
|
14
|
+
|
|
15
|
+
export function isEmptyOrNotSet(...elms) { return elms.some(elm => !isset(elm) || isEmpty(elm)) }
|
|
16
|
+
|
|
17
|
+
export function isDateObject(variable) { return variable instanceof Date }
|
|
18
|
+
|
|
19
|
+
/** Check all values are set */
|
|
20
|
+
export function checkAllObjectValuesAreEmpty(o) { return Object.values(o).every(value => !isset(value)) }
|
|
21
|
+
|
|
22
|
+
/** Throw an error in case data passed is not a valid ctx */
|
|
23
|
+
export function checkCtxIntegrity(ctx) {
|
|
24
|
+
if (!isset(ctx) || !isset(ctx.user)) throw new dataValidationUtilErrorHandler('ctxNotSet', 500)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
## VALIDATOR
|
|
29
|
+
|
|
30
|
+
@name validator
|
|
31
|
+
|
|
32
|
+
@description support multiple names, multiple values and multiple type check
|
|
33
|
+
@option if nameString ends by $ sign it is considered optional
|
|
34
|
+
|
|
35
|
+
@function validator([Objects])
|
|
36
|
+
@return {error|true/false|testMode} depend on mode (see prop mode)
|
|
37
|
+
@param {} mode normal (default) | test (TODO) | boolean
|
|
38
|
+
@param {} name 'myName' || [{myVar1: 'blah, myvar2: myvar2}], support multiple names / values
|
|
39
|
+
@param {} value myVar,
|
|
40
|
+
@param {string} myVar myVar, instead of name / value
|
|
41
|
+
@param {array} in ['blah', 'otherPossibleValue', true], equal ONE OF THESE values
|
|
42
|
+
@param {any} eq exactly equal to in, both support string or array of values
|
|
43
|
+
@param {any} neq not in, both support string or array of values
|
|
44
|
+
@param {number} lte 3, less than or equal
|
|
45
|
+
@param {number} gte 1, greater or equal
|
|
46
|
+
@param {number} lt 3, less than
|
|
47
|
+
@param {number} gt 1, greater
|
|
48
|
+
@param {string|string[]} type
|
|
49
|
+
* possibleTypes: object, number, string, boolean, array, date, dateInt8, dateInt12, dateInt6, time, objectId (mongo), humanReadableTimestamp, buffer
|
|
50
|
+
* Notes: multiples value is an OR, /!\ Array is type 'array' and not 'object' like in real JS /!\
|
|
51
|
+
@param {regExp} regexp /regexp/, test against regexp
|
|
52
|
+
@param {number} minLength for string, array or number length
|
|
53
|
+
@param {number} maxLength
|
|
54
|
+
@param {number} length
|
|
55
|
+
@param {boolean} optional default false
|
|
56
|
+
@param {boolean} emptyAllowed default false (to use if must be set but can be empty)
|
|
57
|
+
@param {boolean} mustNotBeSet this one must not be set
|
|
58
|
+
@param {any} includes check if array or string includes value (like js .includes())
|
|
59
|
+
|
|
60
|
+
@example
|
|
61
|
+
validator(
|
|
62
|
+
{ myNumber : 3, type: 'number', gte: 1, lte: 3 }, // use the name directly as a param
|
|
63
|
+
{ name: 'email', value: 'nameATsite.com', regexp: /[^\sAT]+AT[^\sAT]+\.[^\sAT]/},
|
|
64
|
+
{ name: [{'blahVar': blahVarValue, 'myOtherVar': myOtherVarValue}], type: 'string'} // multiple names for same check
|
|
65
|
+
)
|
|
66
|
+
----------------------------------------*/
|
|
67
|
+
|
|
68
|
+
export type ValidatorObject = {
|
|
69
|
+
name?: string
|
|
70
|
+
value?: any
|
|
71
|
+
type?: BaseTypes
|
|
72
|
+
eq?: any
|
|
73
|
+
neq?: any
|
|
74
|
+
in?: any[]
|
|
75
|
+
lt?: number
|
|
76
|
+
gt?: number
|
|
77
|
+
lte?: number
|
|
78
|
+
gte?: number
|
|
79
|
+
length?: number
|
|
80
|
+
minLength?: number
|
|
81
|
+
maxLength?: number
|
|
82
|
+
emptyAllowed?: boolean
|
|
83
|
+
regexp?: RegExp
|
|
84
|
+
mustNotBeSet?: boolean
|
|
85
|
+
optional?: boolean
|
|
86
|
+
isArray?: boolean
|
|
87
|
+
[k: string]: any
|
|
88
|
+
}
|
|
89
|
+
export function validator(...paramsToValidate: ValidatorObject[]) {
|
|
90
|
+
const errArray = validatorReturnErrArray(...paramsToValidate)
|
|
91
|
+
if (errArray.length) throw new dataValidationUtilErrorHandler(...errArray)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
/** Same as validator but return a boolean
|
|
96
|
+
* See {@link validator}
|
|
97
|
+
*/
|
|
98
|
+
export function isValid(...paramsToValidate) {
|
|
99
|
+
const errArray = validatorReturnErrArray(...paramsToValidate)
|
|
100
|
+
return errArray.length ? false : true
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Default types + custom types
|
|
104
|
+
* 'objectId','dateInt6','dateInt','dateInt8','dateInt12','time','humanReadableTimestamp','date','array','object','buffer','string','function','boolean','number','bigint',
|
|
105
|
+
*/
|
|
106
|
+
export function isType(value, type: BaseTypes) { return isValid({ name: 'Is type check', value, type }) }
|
|
107
|
+
|
|
108
|
+
export function validatorReturnErrArray(...paramsToValidate: ValidatorObject[]): [string?, number?, object?] {
|
|
109
|
+
let paramsFormatted = []
|
|
110
|
+
|
|
111
|
+
// support for multiple names with multiple values for one rule. Eg: {name: [{startDate:'20180101'}, {endDate:'20180101'}], type: 'dateInt8'}
|
|
112
|
+
paramsToValidate.forEach(param => {
|
|
113
|
+
if (typeof param !== 'object' || Array.isArray(param))
|
|
114
|
+
throw new dataValidationUtilErrorHandler(`wrongTypeForDataValidatorArgument`, 500, { origin: 'Generic validator', expected: 'object', actualType: Array.isArray(param) ? 'array' : typeof param })
|
|
115
|
+
|
|
116
|
+
// parse => name: {myVar1: 'blah, myvar2: myvar2}
|
|
117
|
+
if (typeof param.name === 'object' && !Array.isArray(param.name))
|
|
118
|
+
Object.keys(param.name).forEach(name => paramsFormatted.push(Object.assign({}, param, { name: name, value: param.name[name] })))
|
|
119
|
+
else paramsFormatted.push(param)
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
for (const paramObj of paramsFormatted) {
|
|
123
|
+
let name = paramObj.name
|
|
124
|
+
let value = paramObj.value
|
|
125
|
+
let optional = paramObj.optional || false
|
|
126
|
+
let emptyAllowed = optional || paramObj.emptyAllowed || false
|
|
127
|
+
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 }]
|
|
128
|
+
|
|
129
|
+
// accept syntax { 'myVar.var2': myVar.var2, ... }
|
|
130
|
+
if (!isset(name)) {
|
|
131
|
+
name = Object.keys(paramObj).find(param => !['in', 'eq', 'lte', 'gte', 'name', 'value', 'type', 'regexp', 'minLength', 'maxLength', 'optional', 'emptyAllowed', 'mustNotBeSet', 'includes', 'length'].includes(param))
|
|
132
|
+
if (isset(name)) value = paramObj[name] // throw new dataValidationUtilErrorHandler('noNameProvidedForDataValidator', 500, { origin: 'Generic validator', });
|
|
133
|
+
}
|
|
134
|
+
// if nameString ends by $ sign it is optional
|
|
135
|
+
if (isset(name) && /.*\$$/.test(name)) {
|
|
136
|
+
name = name.substr(0, name.length - 1)
|
|
137
|
+
optional = true
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// DEFINED AND NOT EMPTY
|
|
141
|
+
if (!isset(value) && optional) continue
|
|
142
|
+
if (isset(value) && paramObj.mustNotBeSet) return errMess('variableMustNotBeSet')
|
|
143
|
+
if (paramObj.mustNotBeSet) continue // exit
|
|
144
|
+
if (!isset(value)) return errMess('requiredVariableEmptyOrNotSet')
|
|
145
|
+
if (!emptyAllowed && value === '') return errMess('requiredVariableEmpty')
|
|
146
|
+
|
|
147
|
+
const isArray = paramObj.isArray
|
|
148
|
+
if (isArray && !Array.isArray(value)) return errMess('wrongTypeForVar', { expectedTypes: 'array', gotType: typeof value })
|
|
149
|
+
|
|
150
|
+
// TYPE
|
|
151
|
+
if (isset(paramObj.type)) {
|
|
152
|
+
const types = asArray(paramObj.type) // support for multiple type
|
|
153
|
+
const areSomeTypeValid = types.some(type => {
|
|
154
|
+
if (type.endsWith('[]')) {
|
|
155
|
+
if (!Array.isArray(value)) errMess('wrongTypeForVar', { expectedTypes: 'array', gotType: typeof value })
|
|
156
|
+
type = type.replace('[]', '')
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const allTypes: Array<BaseTypes> = [
|
|
160
|
+
'objectId',
|
|
161
|
+
'dateInt6',
|
|
162
|
+
'dateInt', // alias for dateInt8
|
|
163
|
+
'dateInt8',
|
|
164
|
+
'dateInt12',
|
|
165
|
+
'time',
|
|
166
|
+
'humanReadableTimestamp',
|
|
167
|
+
'date',
|
|
168
|
+
'dateObject', // alias
|
|
169
|
+
'array',
|
|
170
|
+
'object',
|
|
171
|
+
'buffer',
|
|
172
|
+
'string',
|
|
173
|
+
'function',
|
|
174
|
+
'boolean',
|
|
175
|
+
'number',
|
|
176
|
+
'bigint',
|
|
177
|
+
'year',
|
|
178
|
+
'any',
|
|
179
|
+
'email',
|
|
180
|
+
//...Object.keys(configFn().customTypes)
|
|
181
|
+
]
|
|
182
|
+
|
|
183
|
+
if (!allTypes.includes(type)) throw new dataValidationUtilErrorHandler('typeDoNotExist', 500, { type })
|
|
184
|
+
|
|
185
|
+
const basicTypeCheck = {
|
|
186
|
+
objectId: val => /^[0-9a-fA-F-]{24,}$/.test(val), // "0c65940b-6b0c-4dd8-9c7a-7c5fe1ba907a"
|
|
187
|
+
dateInt6: val => isDateIntOrStringValid(parseInt(val + '01'), true, 8),
|
|
188
|
+
dateInt: val => isDateIntOrStringValid(val, true, 8),
|
|
189
|
+
dateInt8: val => isDateIntOrStringValid(val, true, 8),
|
|
190
|
+
dateInt12: val => isDateIntOrStringValid(val, true, 12),
|
|
191
|
+
time: val => /^\d\d:\d\d$/.test(val) && isTimeStringValid(val),
|
|
192
|
+
humanReadableTimestamp: val => (val + '').length === 17,
|
|
193
|
+
date: val => isDateIsoOrObjectValid(val, true),
|
|
194
|
+
dateObject: val => isDateIsoOrObjectValid(val, true),
|
|
195
|
+
array: val => Array.isArray(val),
|
|
196
|
+
object: val => !Array.isArray(val) && val !== null && typeof val === type,
|
|
197
|
+
buffer: val => Buffer.isBuffer(val),
|
|
198
|
+
year: val => /^\d\d\d\d$/.test(val),
|
|
199
|
+
email: val => /^[^\s@]+@([^\s@.,]+\.)+[^\s@.,]+$/.test(val),
|
|
200
|
+
any: () => true,
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return isset(basicTypeCheck[type]) && basicTypeCheck[type](value) ||
|
|
204
|
+
typeof value === type && type !== 'object' || // for string, number, boolean...
|
|
205
|
+
isset(configFn().customTypes[type]) && configFn().customTypes[type].test(value)
|
|
206
|
+
})
|
|
207
|
+
if (!areSomeTypeValid) return errMess(`wrongTypeForVar`, { expectedTypes: types.join(', '), gotType: Object.prototype.toString.call(value) })
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// GREATER / LESS
|
|
211
|
+
if (isset(paramObj.gte) && value < paramObj.gte) return errMess(`valueShouldBeSuperiorOrEqualForVar`, { shouldBeSupOrEqTo: paramObj.gte })
|
|
212
|
+
if (isset(paramObj.lte) && value > paramObj.lte) return errMess(`valueShouldBeInferiorOrEqualForVar`, { shouldBeInfOrEqTo: paramObj.lte })
|
|
213
|
+
if (isset(paramObj.gt) && value <= paramObj.gt) return errMess(`valueShouldBeSuperiorForVar`, { shouldBeSupOrEqTo: paramObj.gt })
|
|
214
|
+
if (isset(paramObj.lt) && value >= paramObj.lt) return errMess(`valueShouldBeInferiorForVar`, { shouldBeInfOrEqTo: paramObj.lt })
|
|
215
|
+
|
|
216
|
+
// IN VALUES
|
|
217
|
+
if (isset(paramObj.in)) {
|
|
218
|
+
const equals = Array.isArray(paramObj.in) ? paramObj.in : [paramObj.in]
|
|
219
|
+
if (!equals.some(equalVal => equalVal === value)) return errMess(`wrongValueForVar`, { supportedValues: JSON.stringify(equals) })
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// EQUAL (exact copy of .in)
|
|
223
|
+
if (paramObj.hasOwnProperty('eq')) {
|
|
224
|
+
const equals = Array.isArray(paramObj.eq) ? paramObj.eq : [paramObj.eq]
|
|
225
|
+
if (!equals.some(equalVal => equalVal === value)) return errMess(`wrongValueForVar`, { supportedValues: equals.join(', '), })
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// NOT EQUAL
|
|
229
|
+
if (paramObj.hasOwnProperty('neq')) {
|
|
230
|
+
const notEquals = Array.isArray(paramObj.neq) ? paramObj.neq : [paramObj.neq]
|
|
231
|
+
if (notEquals.some(equalVal => equalVal === value)) return errMess(`wrongValueForVar`, { NOTsupportedValues: notEquals.join(', ') })
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// INCLUDES
|
|
235
|
+
if (isset(paramObj.includes) && !value.includes(paramObj.includes))
|
|
236
|
+
return errMess(`wrongValueForVar`, { shouldIncludes: paramObj.includes })
|
|
237
|
+
|
|
238
|
+
// REGEXP
|
|
239
|
+
if (isset(paramObj.regexp) && !paramObj.regexp.test(value))
|
|
240
|
+
return errMess(`wrongValueForVar`, { shouldMatchRegexp: paramObj.regexp.toString() })
|
|
241
|
+
|
|
242
|
+
// MIN / MAX LENGTH works for number length. Eg: 20180101.length == 8
|
|
243
|
+
if (isset(paramObj.minLength) && paramObj.minLength > (typeof value == 'number' ? value + '' : value).length)
|
|
244
|
+
return errMess(`wrongLengthForVar`, { minLength: paramObj.minLength })
|
|
245
|
+
if (isset(paramObj.maxLength) && paramObj.maxLength < (typeof value == 'number' ? value + '' : value).length)
|
|
246
|
+
return errMess(`wrongLengthForVar`, { maxLength: paramObj.maxLength })
|
|
247
|
+
if (isset(paramObj.length) && paramObj.length !== (typeof value == 'number' ? value + '' : value).length)
|
|
248
|
+
return errMess(`wrongLengthForVar`, { length: paramObj.length })
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return []
|
|
252
|
+
}
|
|
253
|
+
|