topkat-utils 1.0.60 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/index.d.ts +34 -0
  3. package/dist/index.js +56 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/src/array-utils.d.ts +56 -0
  6. package/dist/src/array-utils.js +138 -0
  7. package/dist/src/array-utils.js.map +1 -0
  8. package/dist/src/date-utils.d.ts +100 -0
  9. package/dist/src/date-utils.js +357 -0
  10. package/dist/src/date-utils.js.map +1 -0
  11. package/dist/src/env-utils.d.ts +8 -0
  12. package/dist/src/env-utils.js +38 -0
  13. package/dist/src/env-utils.js.map +1 -0
  14. package/dist/src/error-utils.d.ts +8 -0
  15. package/dist/src/error-utils.js +99 -0
  16. package/dist/src/error-utils.js.map +1 -0
  17. package/dist/src/is-empty.d.ts +1 -0
  18. package/dist/src/is-empty.js +13 -0
  19. package/dist/src/is-empty.js.map +1 -0
  20. package/dist/src/is-object.d.ts +2 -0
  21. package/dist/src/is-object.js +7 -0
  22. package/dist/src/is-object.js.map +1 -0
  23. package/dist/src/isset.d.ts +1 -0
  24. package/dist/src/isset.js +8 -0
  25. package/dist/src/isset.js.map +1 -0
  26. package/dist/src/logger-utils.d.ts +76 -0
  27. package/dist/src/logger-utils.js +355 -0
  28. package/dist/src/logger-utils.js.map +1 -0
  29. package/dist/src/loop-utils.d.ts +37 -0
  30. package/dist/src/loop-utils.js +105 -0
  31. package/dist/src/loop-utils.js.map +1 -0
  32. package/dist/src/math-utils.d.ts +23 -0
  33. package/dist/src/math-utils.js +43 -0
  34. package/dist/src/math-utils.js.map +1 -0
  35. package/dist/src/mongo-utils.d.ts +11 -0
  36. package/dist/src/mongo-utils.js +49 -0
  37. package/dist/src/mongo-utils.js.map +1 -0
  38. package/dist/src/object-utils.d.ts +96 -0
  39. package/dist/src/object-utils.js +369 -0
  40. package/dist/src/object-utils.js.map +1 -0
  41. package/dist/src/private/config.d.ts +44 -0
  42. package/dist/src/private/config.js +55 -0
  43. package/dist/src/private/config.js.map +1 -0
  44. package/dist/src/private/error-handler.d.ts +10 -0
  45. package/dist/src/private/error-handler.js +18 -0
  46. package/dist/src/private/error-handler.js.map +1 -0
  47. package/dist/src/private/types.d.ts +4 -0
  48. package/dist/src/private/types.js +3 -0
  49. package/dist/src/private/types.js.map +1 -0
  50. package/dist/src/regexp-utils.d.ts +12 -0
  51. package/dist/src/regexp-utils.js +44 -0
  52. package/dist/src/regexp-utils.js.map +1 -0
  53. package/dist/src/remove-circular-json-stringify.d.ts +1 -0
  54. package/dist/src/remove-circular-json-stringify.js +20 -0
  55. package/dist/src/remove-circular-json-stringify.js.map +1 -0
  56. package/dist/src/string-utils.d.ts +77 -0
  57. package/dist/src/string-utils.js +209 -0
  58. package/dist/src/string-utils.js.map +1 -0
  59. package/dist/src/tests-utils.js +77 -0
  60. package/dist/src/tests-utils.js.map +1 -0
  61. package/dist/src/timer-utils.d.ts +16 -0
  62. package/dist/src/timer-utils.js +79 -0
  63. package/dist/src/timer-utils.js.map +1 -0
  64. package/dist/src/transaction-utils.d.ts +14 -0
  65. package/dist/src/transaction-utils.js +87 -0
  66. package/dist/src/transaction-utils.js.map +1 -0
  67. package/dist/src/validation-utils.d.ts +89 -0
  68. package/dist/src/validation-utils.js +192 -0
  69. package/dist/src/validation-utils.js.map +1 -0
  70. package/dist/src/wtf-utils.d.ts +7 -0
  71. package/dist/src/wtf-utils.js +83 -0
  72. package/dist/src/wtf-utils.js.map +1 -0
  73. package/index.ts +38 -0
  74. package/package.json +2 -2
  75. package/src/array-utils.ts +128 -0
  76. package/src/date-utils.ts +377 -0
  77. package/src/env-utils.ts +29 -0
  78. package/src/error-utils.ts +77 -0
  79. package/src/is-empty.ts +5 -0
  80. package/src/is-object.ts +3 -0
  81. package/src/isset.ts +3 -0
  82. package/src/logger-utils.ts +349 -0
  83. package/src/loop-utils.ts +101 -0
  84. package/src/math-utils.ts +38 -0
  85. package/src/mongo-utils.ts +38 -0
  86. package/src/object-utils.ts +356 -0
  87. package/src/private/config.ts +85 -0
  88. package/src/private/error-handler.ts +21 -0
  89. package/src/private/types.ts +6 -0
  90. package/src/regexp-utils.ts +37 -0
  91. package/src/remove-circular-json-stringify.ts +17 -0
  92. package/src/string-utils.ts +212 -0
  93. package/src/tests-utils.ts +70 -0
  94. package/src/timer-utils.ts +58 -0
  95. package/src/transaction-utils.ts +63 -0
  96. package/src/validation-utils.ts +253 -0
  97. package/src/wtf-utils.ts +88 -0
  98. package/tsconfig.json +11 -4
  99. package/utils.d.ts +0 -694
  100. package/utils.js +0 -2227
  101. package/utils.js.map +0 -1
  102. package/utils.ts +0 -2304
package/src/isset.ts ADDED
@@ -0,0 +1,3 @@
1
+ export function isset(...elms) {
2
+ return elms.every(elm => typeof elm !== 'undefined' && elm !== null)
3
+ }
@@ -0,0 +1,349 @@
1
+ //----------------------------------------
2
+ // LOGGER
3
+ //----------------------------------------
4
+ import { configFn } from "./private/config"
5
+ import { isset } from "./isset"
6
+ import { Color } from "./private/types"
7
+ import { removeCircularJSONstringify } from "./remove-circular-json-stringify"
8
+ import { cleanStackTrace } from "./error-utils"
9
+
10
+ export const logger = {
11
+ log(str, type = 'log') {
12
+ const { preprocessLog } = configFn()
13
+ if (typeof preprocessLog === 'function') str = preprocessLog(str) || str
14
+ if (isset(console[type])) console[type](str)
15
+ else if (type === 'appError') console.warn(str)
16
+ else console.log(str)
17
+
18
+ logger.raw.push(str + `\n`)
19
+ logger.raw = logger.raw.slice(logger.raw.length - configFn().nbOfLogsToKeep, logger.raw.length)
20
+ },
21
+ /**
22
+ * @param {String[]|String} inputLogs
23
+ */
24
+ toHtml(inputLogs = [...logger.raw]) {
25
+ if (!Array.isArray(inputLogs)) inputLogs = [inputLogs]
26
+ const code2css = {
27
+ 2: `opacity:.5`, // dim
28
+ 32: `color:#679933`, // green
29
+ 31: `color:#A8383B`, // red
30
+ 33: `color:#D7941C`, // yellow
31
+ 35: `color:#C21949`, // magenta
32
+ 36: `color:#128C6D`, // cyan
33
+ 34: `color:#1B568B`, // blue
34
+ }
35
+
36
+ let htmlLogs = ''
37
+ inputLogs.join('<br/>').split('\x1b[').forEach((bit, i) => {
38
+ // the first doesn't have a preceding (may be undef)
39
+ if (bit) {
40
+ if (i !== 0) {
41
+ const [, code, R, G, B, content] = bit.match(/(^\d\d?)(?:;|m)(?:\d;(\d+);(\d+);(\d+)m)?(.*$)/) || []
42
+ if (content) {
43
+ const style = !isset(R) ? isset(code) ? code2css[code] : undefined : `color:rgb(${R},${G},${B})`
44
+ htmlLogs += isset(style) ? `<span style='${style}'>${content.replace(/\n/g, '<br>')}</span>` : content.replace(/\n/g, '<br>')
45
+ }
46
+ } else htmlLogs += bit.replace(/\n/g, '<br>')
47
+ }
48
+ })
49
+ return `<div style='color:#ccc'>${htmlLogs}<br></div>`
50
+ },
51
+ toText(inputLogs = [...logger.raw]) {
52
+ const str = Array.isArray(inputLogs) ? inputLogs.join('\n') : inputLogs
53
+ return str.replace(/\x1b\[.*?m/g, '')
54
+ },
55
+ raw: [],
56
+ json: [],
57
+ }
58
+
59
+ /**
60
+ // console colored output
61
+ // * console.log(C.green(C.dim('Hey bro !')))
62
+ // * or C.log() // will use padding and color defined by themes
63
+ // * or C.line('MY TITLE', 53)
64
+ // * or C.gradientize(myLongString)
65
+ */
66
+ export const C = {
67
+ dim: str => C.output(2, str), // opacity 0.5
68
+ green: str => C.output(32, str),
69
+ red: str => C.output(31, str),
70
+ yellow: str => C.output(33, str),
71
+ grey: str => C.output(2, str),
72
+ magenta: str => C.output(35, str),
73
+ cyan: str => C.output(36, str),
74
+ blue: str => C.output(34, str),
75
+ primary: str => {
76
+ const primary: Color = configFn().terminal.theme.primary
77
+ return C.rgb(...primary) + str + C.reset
78
+ },
79
+ reset: '\x1b[0m',
80
+ output: (code, str = '') => configFn().terminal.noColor ? str : `\x1b[${code}m${str.toString().split('\n').join('\x1b[0m\n\x1b[2m')}\x1b[0m`,
81
+ // true RGB colors B-*
82
+ rgb: (r, g = 0, b = 0) => configFn().terminal.noColor || !isset(r, g, b) ? '' : `\x1b[38;2;${r};${g};${b}m`,
83
+ bg: (r?, g?, b?) => configFn().terminal.noColor || !isset(r, g, b) ? '' : `${'\x1b['}48;2;${r};${g};${b}m`,
84
+ /** Output a line of title */
85
+ line(title = '', length = configFn().terminal.theme.pageWidth, clr = configFn().terminal.theme.primary, char = '=', paddingX = configFn().terminal.theme.paddingX) {
86
+ const padX = ' '.repeat(paddingX)
87
+ this.log('\u00A0\n' + padX + this.rgb(...clr) + (title + ' ').padEnd(length, char) + this.reset + padX + '\u00A0\n')
88
+ },
89
+ /** Eg: ['cell1', 'cell2', 'cell3'], [25, 15] will start cell2 at 25 and cell 3 at 25 + 15
90
+ * @param {Array} limits default divide the viewport
91
+ */
92
+ cols(strings, limits = [], clr = configFn().terminal.theme.fontColor, paddingX = configFn().terminal.theme.paddingX) {
93
+
94
+ if (!limits.length) {
95
+ const colWidth = Math.round(configFn().terminal.theme.pageWidth / strings.length)
96
+ limits = Array(strings.length - 1).fill(2).map((itm, i) => colWidth * i + 1)
97
+ }
98
+ const str = strings.reduce((glob, str = '', i) => {
99
+ const realCharLength = str.toString().replace(/\x1b\[.*?m/, '').length
100
+ const charLength = str.toString().length
101
+ const realLimit = limits[i] + charLength - realCharLength
102
+ return glob + str.toString().substring(0, realLimit || 999).padEnd(realLimit || 0, ' ')
103
+ }, '')
104
+ this.logClr(str, clr, paddingX)
105
+ },
106
+ /** Console log alias */
107
+ log(...stringsCtxMayBeFirstParam) {
108
+ stringsCtxMayBeFirstParam.forEach(str => this.logClr(str, undefined, undefined))
109
+ },
110
+ logClr(str, clr = configFn().terminal.theme.fontColor, paddingX = configFn().terminal.theme.paddingX) {
111
+ if (!isset(str)) return
112
+ const padX = ' '.repeat(paddingX)
113
+ str = padX + (isset(clr) ? this.rgb(...clr) : '') + str.toString().replace(/\n/g, '\n' + padX + (isset(clr) ? this.rgb(...clr) : ''))
114
+ logger.log(str + this.reset, 'log')
115
+ },
116
+ info(...str) {
117
+ str.forEach((s, i) => {
118
+ if (i === 0) this.logClr('ⓘ ' + s, configFn().terminal.theme.primary)
119
+ else this.log(this.dimStrSplit(s))
120
+ })
121
+ this.log(' ')
122
+ },
123
+ success(...str) {
124
+ str.forEach((s, i) => {
125
+ if (i === 0) this.log(this.green('✓ ' + s))
126
+ else this.log(this.dimStrSplit(s))
127
+ })
128
+ },
129
+ /** First param **false** to avoid logging stack trace */
130
+ error: (...errors) => logErrPrivate('error', [255, 0, 0], ...errors),
131
+ /** First param **false** to avoid logging stack trace */
132
+ warning: (...str) => logErrPrivate('warn', [255, 122, 0], ...str),
133
+ customError: (color, ...str) => logErrPrivate('error', color, ...str),
134
+ customWarning: (color, ...str) => logErrPrivate('warn', color, ...str),
135
+ applicationError: (color, ...str) => logErrPrivate('appError', color, ...str),
136
+ warningLight: (color, ...str) => logErrPrivate('warn', [196, 120, 52], ...str),
137
+ dimStrSplit(...logs) {
138
+ let logsStr = []
139
+ logs.filter(isset).forEach(log => log.toString().split('\n').forEach(line => line && logsStr.push(this.dim(` ${line}`))))
140
+ return logsStr.join('\n')
141
+ },
142
+ notifShow() {
143
+ console.log('\n\u00A0')
144
+ this.notifications.forEach(fn => fn())
145
+ this.notifications = []
146
+ console.log('\n\u00A0')
147
+ },
148
+ /** Keep in memory the logs to show when needed with C.notifShow()
149
+ * Ex: C.notification('info', str); */
150
+ notification(type, ...messages) {
151
+ this.notifications.push(() => {
152
+ if (isset(this[type])) {
153
+ this[type](...messages)
154
+ } else {
155
+ this.warning('Wrong param for C.notification')
156
+ }
157
+ })
158
+ },
159
+ notifications: [],
160
+ /** Gratientize lines of text (separated by \n) */
161
+ 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) {
162
+
163
+ const lines = str.split('\n')
164
+ const largestLine = lines.reduce((sum, line) => sum < line.length ? line.length : sum, 0)
165
+ const rgbParts = rgb1.map((val, i) => (val - rgb2[i]) / (lines.length))
166
+
167
+ const bg = bgRgb ? this.bg(bgRgb[0], bgRgb[1], bgRgb[2]) : ''
168
+ const padX = ' '.repeat(paddingX)
169
+ const padLine = bg + padX + ' '.padEnd(largestLine, ' ') + padX + '\x1b[0m\n'
170
+
171
+ console.log(padLine.repeat(paddingY) +
172
+ lines.reduce((s, line, i) => {
173
+ return s + bg + padX + this.rgb(...rgb1.map((val, i2) => Math.round(val - i * rgbParts[i2]))) + line.padEnd(largestLine, ' ') + padX + '\x1b[0m\n'
174
+ }, '') +
175
+ padLine.repeat(paddingY))
176
+ },
177
+ debugModeLog(title, ...string) {
178
+ this.logClr('🐞 ' + title, configFn().terminal.theme.debugModeColor, 0)
179
+ this.log(this.dimStrSplit(...string))
180
+ },
181
+ // DEPRECATED
182
+ useTheme() { },
183
+ }
184
+
185
+ export function logErrPrivate(type, color: Color, ...errors) {
186
+ const { isProd } = configFn()
187
+
188
+ if (errors.length === 1 && typeof errors[0].log === 'function') return errors[0].log()
189
+
190
+ let stackTrace = (new Error('')).stack || ''
191
+ const displayStack = errors[0] === false ? errors.shift() : true
192
+ const symbol = type === 'error' ? '✘ ' : '⚠ '
193
+ if (errors.length > 1 && !isset(errors[0])) errors.shift()
194
+
195
+ const getStringFromErr = (err, i) => {
196
+ if (!isset(err)) return ''
197
+ else if (typeof err === 'string') {
198
+ if (i === 0) return C.rgb(...color) + symbol + err + C.reset
199
+ else return err.split('\n').map(val => C.dim(val)).join('\n')
200
+ } else if (err instanceof Error) {
201
+ const { str, stackTrace: stkTrc } = stringifyInstanceOfError(err, type, color)
202
+ if (stkTrc) stackTrace = stkTrc
203
+ return str
204
+ } else if (typeof err === 'object') {
205
+ let msg = ''
206
+ msg += removeCircularJSONstringify(err, 2).split('\n').map(val => C.dim(val)).join('\n') + '\n'
207
+ const { str, stackTrace: stkTrc } = stringifyExtraInfos(err.extraInfo || err, type, color)
208
+ if (stkTrc) stackTrace = stkTrc
209
+ msg += str
210
+ return msg
211
+ } else return ''
212
+ }
213
+
214
+ if (errors.length && errors[0]) {
215
+ const messages = errors.map((e, i) => {
216
+ if (typeof e.log === 'function') {
217
+ e.log()
218
+ return ''
219
+ } else return getStringFromErr(e, i)
220
+ })
221
+ // Stack
222
+ if (displayStack) {
223
+ messages.push(isProd ? stackTrace : cleanStackTrace(stackTrace) + '\n')
224
+ }
225
+ logger.log(messages.join(''), type)
226
+ }
227
+ }
228
+
229
+ export function stringifyInstanceOfError(err, type = 'error', color: Color = [255, 0, 0], level = 0) { // level = keep track of recursions
230
+ if (level > 5) return { str: '' }
231
+ let str = ''
232
+ let stackTrace
233
+ const symbol = type === 'error' ? '✘ ' : '⚠ '
234
+ const title = err.msg || err.message || err.id || (err.stack ? err.stack.split('\n')[0] : 'Error')
235
+ // Err mess
236
+ str += C.rgb(...color) + symbol + title + C.reset + '\n'
237
+ if (err.stack) stackTrace = err.stack // more relevant
238
+ // ExtraInfos
239
+ if (isset(err.extraInfo)) {
240
+ const { str: str2, stackTrace: stkTrc } = stringifyExtraInfos(err.extraInfo, type, color, level++)
241
+ if (stkTrc) stackTrace = stkTrc
242
+ str += str2
243
+ }
244
+ return { str, stackTrace }
245
+ }
246
+
247
+ export function stringifyExtraInfos(extraInfoOriginal, type, color, level = 0) {
248
+ let stackTrace
249
+ const originalError = [C.dim(`ORIGINAL ERROR ${'-'.repeat(39)}\n`)]
250
+ if (extraInfoOriginal instanceof Error) { // case where error is passed directly to extraInfos
251
+ return stringifyInstanceOfError(extraInfoOriginal, type, color)
252
+ } else {
253
+ const extraInfo = { ...extraInfoOriginal }
254
+ const extraInfos = [C.dim(`EXTRA INFOS ${'-'.repeat(41)}\n`)]
255
+ if (typeof extraInfo === 'object' && Object.keys(extraInfo).length) {
256
+ for (const itemName in extraInfo) {
257
+ if (extraInfo[itemName] instanceof Error) {
258
+ const { str, stackTrace: stkTrc } = stringifyInstanceOfError(extraInfo[itemName], type, color, level++)
259
+ originalError.push(str)
260
+ stackTrace = stkTrc
261
+ delete extraInfo[itemName]
262
+ }
263
+ }
264
+ }
265
+ if (Object.keys(extraInfo).length) {
266
+ extraInfos.push(
267
+ removeCircularJSONstringify(extraInfo, 2)
268
+ .replace(/(?:^\s*{(?:\n {2})?|}\s*$)/g, '')
269
+ .replace(/\n {2}/g, '\n')
270
+ .split('\n')
271
+ .map(val => C.dim(val)).join('\n') + '\n')
272
+ }
273
+ return {
274
+ str: (extraInfos.length > 1 ? extraInfos.join('') : '') + (originalError.length > 1 ? originalError.join('') + '\n' : ''),
275
+ stackTrace
276
+ }
277
+ }
278
+ }
279
+
280
+ /**
281
+ * Call this at each steps of your progress and change the step value
282
+ * @param {Number} step Number of "char" to output
283
+ * @param {String} char Default: '.'
284
+ * @param {String} msg String before char. Final output will be `${str}${char.repeat(step)}`
285
+ */
286
+ export function cliProgressBar(step, char = '.', msg = `\x1b[2mⓘ Waiting response`) {
287
+ if (isset(process) && isset(process.stdout) && isset(process.stdout.clearLine)) {
288
+ process.stdout.clearLine(0)
289
+ process.stdout.cursorTo(0)
290
+ process.stdout.write(`${msg}${char.repeat(step)}\x1b[0m`) // \x1b[0m == reset color
291
+ }
292
+ }
293
+
294
+ /** This allow an intuitive inline loading spinner with a check mark when loading as finished or a red cross for errors */
295
+ class cliLoadingSpinner {
296
+ /** Please use it like spinner.start('myStuff') then spinner.end()
297
+ * @param {String} type in: ['arrow', 'dots']
298
+ */
299
+ frameRate: number
300
+ animFrames: string[]
301
+ activeProcess: any
302
+ frameNb: number
303
+ progressMessage: string
304
+ interval: any
305
+
306
+ constructor(type = 'dots', activeProcess = process) {
307
+ const anims = {
308
+ arrow: {
309
+ interval: 120,
310
+ frames: ['▹▹▹▹▹', '▸▹▹▹▹', '▹▸▹▹▹', '▹▹▸▹▹', '▹▹▹▸▹', '▹▹▹▹▸']
311
+ },
312
+ dots: {
313
+ interval: 80,
314
+ frames: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
315
+ }
316
+ }
317
+ this.frameRate = anims[type].interval
318
+ this.animFrames = anims[type].frames
319
+ this.activeProcess = activeProcess
320
+ }
321
+ start(msg) {
322
+ this.frameNb = 0
323
+ this.progressMessage = msg
324
+ this.interval = setInterval(() => {
325
+ this.activeProcess.stdout.clearLine()
326
+ this.activeProcess.stdout.cursorTo(0)
327
+ const symbol = this.animFrames[this.frameNb++ % this.animFrames.length]
328
+ this.activeProcess.stdout.write(C.primary(symbol) + ' ' + this.progressMessage)
329
+ }, this.frameRate)
330
+ }
331
+ end(error = false) {
332
+ clearInterval(this.interval)
333
+ this.activeProcess.stdout.clearLine()
334
+ this.activeProcess.stdout.cursorTo(0)
335
+ this.activeProcess.stdout.write(
336
+ error ? C.red('✘ ' + this.progressMessage + '\n\n')
337
+ : '\x1b[32m✓ ' + this.progressMessage + '\n\n')
338
+ this.progressMessage = ''
339
+ }
340
+ error() {
341
+ return this.end(true)
342
+ }
343
+ }
344
+
345
+
346
+ export function dim(str = '') {
347
+ return configFn().terminal.noColor ? str : `\x1b[2m${str.toString().split('\n').join('\x1b[0m\n\x1b[2m')}\x1b[0m`
348
+ }
349
+
@@ -0,0 +1,101 @@
1
+ //----------------------------------------
2
+ // LOOP UTILS
3
+ //----------------------------------------
4
+ import { ObjectGeneric } from "./private/types"
5
+ import { err500IfNotSet } from "./error-utils"
6
+ import { isObject } from "./is-object"
7
+
8
+ export function forI<T extends any[] | any>(nbIterations: number, callback: (number: number, previousValue, arrayOfPreviousValues: any[]) => T): T[] {
9
+ const results = []
10
+ for (let i = 0; i < nbIterations; i++) {
11
+ const prevValue = results[results.length - 1]
12
+ results.push(callback(i, prevValue, results))
13
+ }
14
+ return results
15
+ }
16
+
17
+ export async function forIasync<T extends any[] | any>(nbIterations: number, callback: (number) => T): Promise<T[]> {
18
+ const results = []
19
+ for (let i = 0; i < nbIterations; i++) {
20
+ results.push(await callback(i))
21
+ }
22
+ return results
23
+ }
24
+
25
+
26
+
27
+ export type RecursiveCallback = (item: any, addr: string, lastElementKey: string | number, parent: ObjectGeneric | any[]) => false | any
28
+ /**
29
+ * @param {any} item the first array or object or whatever you want to recursively browse
30
+ * @param {function} callback the callback you want to apply on items including the main one
31
+ * * this callback has 2 arguments: (item, address) =>
32
+ * * `item` => the actual item
33
+ * * `addr` => the address of the item, not including root (Eg: subItem1.sub2.[3].[2]) array indexes are juste written as numbers
34
+ * * `lastElementKey` => the key of last item. May be a number if last item is an array
35
+ * * `parent` => reference the parent object as this is the only way of reassigning a value for the item. Eg: parent[lastElementKey] = myNewItem
36
+ * * **NOTE** => if a key of an item contains dots, they will be replaced by '%' in `addr`
37
+ * * **NOTE2** => if false is returned by the callback it will stop all other iterations (but not in an array)
38
+ * @param {string} addr$ optional, the base address for the callback function
39
+ * @param lastElementKey technical field
40
+ * NOTE: will remove circular references
41
+ * /!\ check return values
42
+ */
43
+ export async function recursiveGenericFunction(item: ObjectGeneric | any[], callback: RecursiveCallback, addr$ = '', lastElementKey: string | number = '', parent?, techFieldToAvoidCircularDependency = []) {
44
+ err500IfNotSet({ callback })
45
+
46
+ if (!techFieldToAvoidCircularDependency.includes(item)) {
47
+ const result = addr$ === '' ? true : await callback(item, addr$, lastElementKey, parent)
48
+
49
+ if (result !== false) {
50
+ const addr = addr$ ? addr$ + '.' : ''
51
+ if (Array.isArray(item)) {
52
+ techFieldToAvoidCircularDependency.push(item)
53
+ await Promise.all(item.map(
54
+ (e, i) => recursiveGenericFunction(e, callback, addr + '[' + i + ']', i, item, techFieldToAvoidCircularDependency)
55
+ ))
56
+ } else if (isObject(item)) {
57
+ techFieldToAvoidCircularDependency.push(item)
58
+ await Promise.all(Object.entries(item).map(
59
+ ([key, val]) => recursiveGenericFunction(val, callback, addr + key.replace(/\./g, '%'), key, item, techFieldToAvoidCircularDependency)
60
+ ))
61
+ }
62
+ }
63
+ }
64
+ return item
65
+ }
66
+
67
+ /**
68
+ * @param {any} item the first array or object or whatever you want to recursively browse
69
+ * @param {function} callback the callback you want to apply on items including the main one
70
+ * * this callback has 2 arguments: (item, address) =>
71
+ * * `item` => the actual item
72
+ * * `addr` => the address of the item, not including root (Eg: subItem1.sub2.[3].[2]) array indexes are juste written as numbers
73
+ * * `lastElementKey` => the key of last item. May be a number if last item is an array
74
+ * * `parent` => reference the parent object as this is the only way of reassigning a value for the item. Eg: parent[lastElementKey] = myNewItem
75
+ * * **NOTE** => if a key of an item contains dots, they will be replaced by '%' in `addr`
76
+ * * **NOTE2** => if false is returned by the callback it will stop all other iterations (but not in an array)
77
+ * * **NOTE3** => to reassign a key use => parent[lastElementKey] = myNewItem
78
+ * @param {string} addr$ optional, the base address for the callback function
79
+ * @param lastElementKey technical field
80
+ * NOTE: will remove circular references
81
+ * /!\ check return values
82
+ */
83
+ export function recursiveGenericFunctionSync(item: ObjectGeneric | any[], callback: RecursiveCallback, addr$ = '', lastElementKey: string | number = '', parent?, techFieldToAvoidCircularDependency = []) {
84
+ err500IfNotSet({ callback })
85
+
86
+ if (!techFieldToAvoidCircularDependency.includes(item)) {
87
+ const result = addr$ === '' ? true : callback(item, addr$, lastElementKey, parent)
88
+
89
+ if (result !== false) {
90
+ const addr = addr$ ? addr$ + '.' : ''
91
+ if (Array.isArray(item)) {
92
+ techFieldToAvoidCircularDependency.push(item) // do not up one level
93
+ item.forEach((e, i) => recursiveGenericFunctionSync(e, callback, addr + '[' + i + ']', i, item, techFieldToAvoidCircularDependency))
94
+ } else if (isObject(item)) {
95
+ techFieldToAvoidCircularDependency.push(item)
96
+ Object.entries(item).forEach(([key, val]) => recursiveGenericFunctionSync(val, callback, addr + key.replace(/\./g, '%'), key, item, techFieldToAvoidCircularDependency))
97
+ }
98
+ }
99
+ }
100
+ return item
101
+ }
@@ -0,0 +1,38 @@
1
+ //----------------------------------------
2
+ // MATH UTILS
3
+ //----------------------------------------
4
+ import { isset } from "./isset"
5
+
6
+ /** Round with custom number of decimals (default:0) */
7
+ 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) }
8
+ /** Round with custom number of decimals (default:2) */
9
+ export function round2(number: number | string, decimals = 2) { return round(number, decimals) }
10
+
11
+ /** Is number between two numbers (including those numbers) */
12
+ export function isBetween(number: number, min: number, max: number, inclusive = true) { return inclusive ? number <= max && number >= min : number < max && number > min }
13
+
14
+ /** Random number between two values with 0 decimals by default */
15
+ export function random(nb1: number, nb2: number, nbOfDecimals = 0) { return round(Math.random() * (nb2 - nb1) + nb1, nbOfDecimals) }
16
+
17
+ /** Sum all values of an array, all values MUST be numbers */
18
+ export function sumArray(array: number[]) {
19
+ return array.filter(item => typeof item === 'number').reduce((sum, val) => isset(val) ? val + sum : sum, 0)
20
+ }
21
+
22
+ /** Moyenne / average between array of values
23
+ * @param {Number} round number of decimals to keep. Default:2
24
+ */
25
+ export function moyenne(array: number[], nbOfDecimals = 2) {
26
+ return round(sumArray(array) / array.length, nbOfDecimals)
27
+ }
28
+
29
+ /** length default 2, shortcut for 1 to 01 */
30
+ export function pad(numberOrStr: number | string, length = 2) { return ('' + numberOrStr).padStart(length, '0') }
31
+
32
+ /** return the number or the closest number of the range
33
+ * * nb min max => returns
34
+ * * 7 5 10 => 7 // in the range
35
+ * * 2 5 10 => 5 // below the min value
36
+ * * 99 5 10 => 10// above the max value
37
+ */
38
+ export function minMax(nb: number, min: number, max: number) { return Math.max(min, Math.min(nb, max)) }
@@ -0,0 +1,38 @@
1
+ //----------------------------------------
2
+ // MONGO UTILS
3
+ //----------------------------------------
4
+ import { isType } from "./validation-utils"
5
+ import { isset } from "./isset"
6
+
7
+ /** @return undefined if cannot find _id */
8
+ export function getId(obj: any = {}): string {
9
+ if (!obj) return // null case
10
+ if (obj._id) return obj._id.toString()
11
+ else if (isType(obj, 'objectId')) return obj.toString()
12
+ }
13
+
14
+ /** Merge filter with correct handling of OR and AND
15
+ * @param {Object} filterA
16
+ * @param {Object} filterB
17
+ * @param {Boolean} assignToFilterA defualt false: if true, it will modify filterA, else it will return merged filters as a new object
18
+ */
19
+ export function mongoFilterMerger(filterA, filterB, assignToFilterA = false) {
20
+ if (isset(filterA.$and) && isset(filterB.$and)) {
21
+ filterA.$and.push(...filterB.$and)
22
+ delete filterB.$and
23
+ }
24
+ if (isset(filterA.$or) && isset(filterB.$or)) {
25
+ if (!isset(filterA.$and)) filterA.$and = []
26
+ filterA.$and.push({ $or: filterA.$or }, { $or: filterA.$or })
27
+ delete filterB.$or
28
+ }
29
+ if (assignToFilterA) {
30
+ Object.assign(filterA, filterB)
31
+ return filterA
32
+ } else return { ...filterA, ...filterB }
33
+ }
34
+
35
+ export function mongoPush(field: string, value: any, fields: { [k: string]: any }) {
36
+ if (!isset(fields.$push)) fields.$push = {}
37
+ fields.$push[field] = value
38
+ }