topkat-utils 1.0.60 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/index.d.ts +34 -0
  3. package/dist/index.js +56 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/src/array-utils.d.ts +56 -0
  6. package/dist/src/array-utils.js +138 -0
  7. package/dist/src/array-utils.js.map +1 -0
  8. package/dist/src/date-utils.d.ts +100 -0
  9. package/dist/src/date-utils.js +357 -0
  10. package/dist/src/date-utils.js.map +1 -0
  11. package/dist/src/env-utils.d.ts +8 -0
  12. package/dist/src/env-utils.js +38 -0
  13. package/dist/src/env-utils.js.map +1 -0
  14. package/dist/src/error-utils.d.ts +8 -0
  15. package/dist/src/error-utils.js +99 -0
  16. package/dist/src/error-utils.js.map +1 -0
  17. package/dist/src/is-empty.d.ts +1 -0
  18. package/dist/src/is-empty.js +13 -0
  19. package/dist/src/is-empty.js.map +1 -0
  20. package/dist/src/is-object.d.ts +2 -0
  21. package/dist/src/is-object.js +7 -0
  22. package/dist/src/is-object.js.map +1 -0
  23. package/dist/src/isset.d.ts +1 -0
  24. package/dist/src/isset.js +8 -0
  25. package/dist/src/isset.js.map +1 -0
  26. package/dist/src/logger-utils.d.ts +76 -0
  27. package/dist/src/logger-utils.js +355 -0
  28. package/dist/src/logger-utils.js.map +1 -0
  29. package/dist/src/loop-utils.d.ts +37 -0
  30. package/dist/src/loop-utils.js +105 -0
  31. package/dist/src/loop-utils.js.map +1 -0
  32. package/dist/src/math-utils.d.ts +23 -0
  33. package/dist/src/math-utils.js +43 -0
  34. package/dist/src/math-utils.js.map +1 -0
  35. package/dist/src/mongo-utils.d.ts +11 -0
  36. package/dist/src/mongo-utils.js +49 -0
  37. package/dist/src/mongo-utils.js.map +1 -0
  38. package/dist/src/object-utils.d.ts +96 -0
  39. package/dist/src/object-utils.js +369 -0
  40. package/dist/src/object-utils.js.map +1 -0
  41. package/dist/src/private/config.d.ts +44 -0
  42. package/dist/src/private/config.js +55 -0
  43. package/dist/src/private/config.js.map +1 -0
  44. package/dist/src/private/error-handler.d.ts +10 -0
  45. package/dist/src/private/error-handler.js +18 -0
  46. package/dist/src/private/error-handler.js.map +1 -0
  47. package/dist/src/private/types.d.ts +4 -0
  48. package/dist/src/private/types.js +3 -0
  49. package/dist/src/private/types.js.map +1 -0
  50. package/dist/src/regexp-utils.d.ts +12 -0
  51. package/dist/src/regexp-utils.js +44 -0
  52. package/dist/src/regexp-utils.js.map +1 -0
  53. package/dist/src/remove-circular-json-stringify.d.ts +1 -0
  54. package/dist/src/remove-circular-json-stringify.js +20 -0
  55. package/dist/src/remove-circular-json-stringify.js.map +1 -0
  56. package/dist/src/string-utils.d.ts +77 -0
  57. package/dist/src/string-utils.js +209 -0
  58. package/dist/src/string-utils.js.map +1 -0
  59. package/dist/src/tests-utils.js +77 -0
  60. package/dist/src/tests-utils.js.map +1 -0
  61. package/dist/src/timer-utils.d.ts +16 -0
  62. package/dist/src/timer-utils.js +79 -0
  63. package/dist/src/timer-utils.js.map +1 -0
  64. package/dist/src/transaction-utils.d.ts +14 -0
  65. package/dist/src/transaction-utils.js +87 -0
  66. package/dist/src/transaction-utils.js.map +1 -0
  67. package/dist/src/validation-utils.d.ts +89 -0
  68. package/dist/src/validation-utils.js +192 -0
  69. package/dist/src/validation-utils.js.map +1 -0
  70. package/dist/src/wtf-utils.d.ts +7 -0
  71. package/dist/src/wtf-utils.js +83 -0
  72. package/dist/src/wtf-utils.js.map +1 -0
  73. package/index.ts +38 -0
  74. package/package.json +2 -2
  75. package/src/array-utils.ts +128 -0
  76. package/src/date-utils.ts +377 -0
  77. package/src/env-utils.ts +29 -0
  78. package/src/error-utils.ts +77 -0
  79. package/src/is-empty.ts +5 -0
  80. package/src/is-object.ts +3 -0
  81. package/src/isset.ts +3 -0
  82. package/src/logger-utils.ts +349 -0
  83. package/src/loop-utils.ts +101 -0
  84. package/src/math-utils.ts +38 -0
  85. package/src/mongo-utils.ts +38 -0
  86. package/src/object-utils.ts +356 -0
  87. package/src/private/config.ts +85 -0
  88. package/src/private/error-handler.ts +21 -0
  89. package/src/private/types.ts +6 -0
  90. package/src/regexp-utils.ts +37 -0
  91. package/src/remove-circular-json-stringify.ts +17 -0
  92. package/src/string-utils.ts +212 -0
  93. package/src/tests-utils.ts +70 -0
  94. package/src/timer-utils.ts +58 -0
  95. package/src/transaction-utils.ts +63 -0
  96. package/src/validation-utils.ts +253 -0
  97. package/src/wtf-utils.ts +88 -0
  98. package/tsconfig.json +11 -4
  99. package/utils.d.ts +0 -694
  100. package/utils.js +0 -2227
  101. package/utils.js.map +0 -1
  102. package/utils.ts +0 -2304
@@ -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
+