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,356 @@
|
|
|
1
|
+
//----------------------------------------
|
|
2
|
+
// OBJECT UTILS
|
|
3
|
+
//----------------------------------------
|
|
4
|
+
import { ObjectGeneric } from "./private/types"
|
|
5
|
+
import { err500IfNotSet } from "./error-utils"
|
|
6
|
+
import { recursiveGenericFunctionSync } from "./loop-utils"
|
|
7
|
+
import { isset } from "./isset"
|
|
8
|
+
import { isObject } from "./is-object"
|
|
9
|
+
import { dataValidationUtilErrorHandler } from "./private/error-handler"
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
*
|
|
13
|
+
* @param {Object} object main object
|
|
14
|
+
* @param {String[]} maskedOrSelectedFields array of fields
|
|
15
|
+
* @param {Boolean} isMask default: true; determine the behavior of the function. If is mask, selected fields will not appear in the resulting object. If it's a select, only selected fields will appear.
|
|
16
|
+
* @param {Boolean} deleteKeysInsteadOfReturningAnewObject default:false; modify the existing object instead of creating a new instance
|
|
17
|
+
*/
|
|
18
|
+
export function simpleObjectMaskOrSelect(object: ObjectGeneric, maskedOrSelectedFields: string[], isMask = true, deleteKeysInsteadOfReturningAnewObject = false) {
|
|
19
|
+
const allKeys = Object.keys(object)
|
|
20
|
+
const keysToMask = allKeys.filter(keyName => {
|
|
21
|
+
if (isMask) return maskedOrSelectedFields.includes(keyName)
|
|
22
|
+
else return !maskedOrSelectedFields.includes(keyName)
|
|
23
|
+
})
|
|
24
|
+
if (deleteKeysInsteadOfReturningAnewObject) {
|
|
25
|
+
keysToMask.forEach(keyNameToDelete => delete object[keyNameToDelete])
|
|
26
|
+
return object
|
|
27
|
+
} else {
|
|
28
|
+
return allKeys.reduce((newObject, key) => {
|
|
29
|
+
if (!keysToMask.includes(key)) newObject[key] = object[key]
|
|
30
|
+
return newObject
|
|
31
|
+
}, {})
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* check if **object OR array** has property Safely (avoid cannot read property x of null and such)
|
|
37
|
+
* @param {Object} obj object to test against
|
|
38
|
+
* @param {string} addr `a.b.c.0.1` will test if myObject has props a that has prop b. Work wit arrays as well (like `arr.0`)
|
|
39
|
+
*/
|
|
40
|
+
export function has(obj: ObjectGeneric, addr: string) {
|
|
41
|
+
if (!isset(obj) || typeof obj !== 'object') return
|
|
42
|
+
let propsArr = addr.replace(/\.?\[(\d+)\]/g, '.$1').split('.') // replace a[3] => a.3;
|
|
43
|
+
let objChain = obj
|
|
44
|
+
return propsArr.every(prop => {
|
|
45
|
+
objChain = objChain[prop]
|
|
46
|
+
return isset(objChain)
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Find address in an object "a.b.c" IN { a : { b : {c : 'blah' }}} RETURNS 'blah'
|
|
51
|
+
* @param obj
|
|
52
|
+
* @param addr accept syntax like "obj.subItem.[0].sub2" OR "obj.subItem.0.sub2" OR "obj.subItem[0].sub2"
|
|
53
|
+
* @returns the last item of the chain OR undefined if not found
|
|
54
|
+
*/
|
|
55
|
+
export function findByAddress(obj: ObjectGeneric, addr: string): any | undefined {
|
|
56
|
+
if (addr === '') return obj
|
|
57
|
+
if (!isset(obj) || typeof obj !== 'object') return console.warn('Main object in `findByAddress` function is undefined or has the wrong type')
|
|
58
|
+
const propsArr = addr.replace(/\.?\[(\d+)\]/g, '.$1').split('.') // replace .[4] AND [4] TO .4
|
|
59
|
+
const objRef = propsArr.reduce((objChain, prop) => {
|
|
60
|
+
if (!isset(objChain) || typeof objChain !== 'object' || !isset(objChain[prop])) return
|
|
61
|
+
else return objChain[prop]
|
|
62
|
+
}, obj)
|
|
63
|
+
return objRef
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
/** Will return all objects matching that path. Eg: user.*.myVar */
|
|
68
|
+
export function findByAddressAll(obj, addr, returnAddresses?: true): Array<[string, any]>
|
|
69
|
+
export function findByAddressAll(obj, addr, returnAddresses?: false): Array<any>
|
|
70
|
+
export function findByAddressAll(obj, addr, returnAddresses = false) {
|
|
71
|
+
err500IfNotSet({ obj, addr })
|
|
72
|
+
if (addr === '') return obj
|
|
73
|
+
const addrRegexp = new RegExp('^' + addr
|
|
74
|
+
.replace(/\.?\[(\d+)\]/g, '.$1') // replace .[4] AND [4] TO .4
|
|
75
|
+
.replace(/\./g, '\\.')
|
|
76
|
+
.replace(/\.\*/g, '.[^.]+') // replace * by [^. (all but a point)]
|
|
77
|
+
+ '$')
|
|
78
|
+
|
|
79
|
+
const matchingItems = []
|
|
80
|
+
|
|
81
|
+
recursiveGenericFunctionSync(obj, (item, address) => {
|
|
82
|
+
if (addrRegexp.test(address)) matchingItems.push(returnAddresses ? [address, item] : item)
|
|
83
|
+
})
|
|
84
|
+
return matchingItems
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Enforce writing subItems. Eg: user.name.blah will ensure all are set until the writing of the last item
|
|
88
|
+
* NOTE: doesn't work with arrays
|
|
89
|
+
*/
|
|
90
|
+
export function objForceWrite(obj: ObjectGeneric, addr: string, item) {
|
|
91
|
+
const chunks = addr.replace(/\.?\[(\d+)\]/g, '.[$1').split('.')
|
|
92
|
+
let lastItem = obj
|
|
93
|
+
chunks.forEach((chunkRaw, i) => {
|
|
94
|
+
const chunk = chunkRaw.replace(/^\[/, '')
|
|
95
|
+
if (i === chunks.length - 1) lastItem[chunk] = item
|
|
96
|
+
else if (!isset(lastItem[chunk])) {
|
|
97
|
+
const nextChunk = chunks[i + 1]
|
|
98
|
+
if (isset(nextChunk) && nextChunk.startsWith('[')) lastItem[chunk] = []
|
|
99
|
+
else lastItem[chunk] = {}
|
|
100
|
+
} else if (typeof lastItem[chunk] !== 'object') throw new dataValidationUtilErrorHandler(`itemNotTypeObjectOrArrayInAddrChainForObjForceWrite`, 500, { origin: 'Validator', chunks, actualValueOfItem: lastItem[chunk], actualChunk: chunk })
|
|
101
|
+
lastItem = lastItem[chunk]
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Enforce writing subItems, only if obj.addr is empty.
|
|
106
|
+
* Eg: user.name.blah will ensure all are set until the writing of the last item
|
|
107
|
+
* if user.name.blah has a value it will not change it.
|
|
108
|
+
* NOTE: doesn't work with arrays
|
|
109
|
+
*/
|
|
110
|
+
export function objForceWriteIfNotSet(obj: ObjectGeneric, addr: string, item) {
|
|
111
|
+
if (!isset(findByAddress(obj, addr))) return objForceWrite(obj, addr, item)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Merge mixins into class. Use it in the constructor like: mergeMixins(this, {myMixin: true}) */
|
|
115
|
+
export function mergeMixins(that, ...mixins) {
|
|
116
|
+
mixins.forEach(mixin => {
|
|
117
|
+
for (const method in mixin) {
|
|
118
|
+
that[method] = mixin[method]
|
|
119
|
+
}
|
|
120
|
+
})
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function cloneObject(o) {
|
|
124
|
+
return JSON.parse(JSON.stringify(o))
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Deep clone. WILL REMOVE circular references */
|
|
128
|
+
export function deepClone<T>(obj: T, cache = []): T {
|
|
129
|
+
|
|
130
|
+
let copy
|
|
131
|
+
// usefull to not modify 1st level objet by lower levels
|
|
132
|
+
// this is required for the same object to be referenced not in a redundant way
|
|
133
|
+
const newCache = [...cache]
|
|
134
|
+
if (obj instanceof Date) return new Date(obj) as any
|
|
135
|
+
|
|
136
|
+
// Handle Array
|
|
137
|
+
if (Array.isArray(obj)) {
|
|
138
|
+
if (newCache.includes(obj)) return [] as any
|
|
139
|
+
newCache.push(obj)
|
|
140
|
+
copy = []
|
|
141
|
+
for (var i = 0, len = obj.length; i < len; i++) {
|
|
142
|
+
copy[i] = deepClone(obj[i], newCache)
|
|
143
|
+
}
|
|
144
|
+
return copy
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (typeof obj === 'object' && obj !== null && Object.getPrototypeOf(obj) === Object.prototype) {
|
|
148
|
+
if (newCache.includes(obj)) return {} as any
|
|
149
|
+
newCache.push(obj)
|
|
150
|
+
copy = {}
|
|
151
|
+
for (var key in obj) {
|
|
152
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
153
|
+
copy[key] = deepClone(obj[key], newCache)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return copy
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return obj // number, string...
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* @param {Object} obj the object on which we want to filter the keys
|
|
165
|
+
* @param {function} filterFunc function that returns true if the key match the wanted criteria
|
|
166
|
+
*/
|
|
167
|
+
export function filterKeys(obj: object, filter) {
|
|
168
|
+
const clone = cloneObject(obj)
|
|
169
|
+
recursiveGenericFunctionSync(obj, (item, addr, lastElementKey) => {
|
|
170
|
+
if (!filter(lastElementKey)) deleteByAddress(clone, addr.split('.'))
|
|
171
|
+
})
|
|
172
|
+
return clone
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* @param {Object} obj the object on which we want to delete a property
|
|
176
|
+
* @param {Array} addr addressArray on which to delete the property
|
|
177
|
+
*/
|
|
178
|
+
export function deleteByAddress(obj: object, addr: string[]) {
|
|
179
|
+
let current = obj
|
|
180
|
+
for (let i = 0; i < addr.length - 2; i++) current = current[addr[i]]
|
|
181
|
+
delete current[addr[addr.length - 1]]
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
/** Remove all key/values pair if value is undefined */
|
|
187
|
+
export function objFilterUndefined(o) {
|
|
188
|
+
Object.keys(o).forEach(k => !isset(o[k]) && delete o[k])
|
|
189
|
+
return o
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** Lock all 1st level props of an object to read only */
|
|
193
|
+
export function readOnly(o) {
|
|
194
|
+
const throwErr = () => { throw new dataValidationUtilErrorHandler('Cannot modify object that is read only', 500) }
|
|
195
|
+
return new Proxy(o, {
|
|
196
|
+
set: throwErr,
|
|
197
|
+
defineProperty: throwErr,
|
|
198
|
+
deleteProperty: throwErr,
|
|
199
|
+
})
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** Fields of the object can be created BUT NOT reassignated */
|
|
203
|
+
export function reassignForbidden(o) {
|
|
204
|
+
return new Proxy(o, {
|
|
205
|
+
defineProperty: function (that, key, value) {
|
|
206
|
+
if (key in that) throw new dataValidationUtilErrorHandler(`Cannot reassign the property ${key.toString()} of this object`, 500)
|
|
207
|
+
else {
|
|
208
|
+
that[key] = value
|
|
209
|
+
return true
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
deleteProperty: function (_, key) {
|
|
213
|
+
throw new dataValidationUtilErrorHandler(`Cannot delete the property ${key.toString()} of this object`, 500)
|
|
214
|
+
}
|
|
215
|
+
})
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** All fileds and subFields of the object will become readOnly */
|
|
219
|
+
export function readOnlyForAll(object) {
|
|
220
|
+
recursiveGenericFunctionSync(object, (item, _, lastElementKey, parent) => {
|
|
221
|
+
if (typeof item === 'object') parent[lastElementKey] = readOnly(item)
|
|
222
|
+
})
|
|
223
|
+
return object
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export function objFilterUndefinedRecursive(obj) {
|
|
227
|
+
if (obj) {
|
|
228
|
+
const flattenedObj = flattenObject(obj)
|
|
229
|
+
Object.keys(flattenedObj).forEach(key => {
|
|
230
|
+
if (!isset(flattenedObj[key])) {
|
|
231
|
+
delete flattenedObj[key]
|
|
232
|
+
}
|
|
233
|
+
})
|
|
234
|
+
return unflattenObject(flattenedObj)
|
|
235
|
+
} else return obj
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export function sortObjKeyAccordingToValue(unorderedObj, ascending = true) {
|
|
239
|
+
const orderedObj = {}
|
|
240
|
+
const sortingConst = ascending ? 1 : -1
|
|
241
|
+
Object.keys(unorderedObj)
|
|
242
|
+
.sort((keyA, keyB) => unorderedObj[keyA] < unorderedObj[keyB] ? sortingConst : -sortingConst)
|
|
243
|
+
.forEach(key => { orderedObj[key] = unorderedObj[key] })
|
|
244
|
+
return orderedObj
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Make default value if object key do not exist
|
|
249
|
+
* @param {object} obj
|
|
250
|
+
* @param {string} addr
|
|
251
|
+
* @param {any} defaultValue
|
|
252
|
+
* @param {function} callback (obj[addr]) => processValue. Eg: myObjAddr => myObjAddr.push('bikou')
|
|
253
|
+
* @return obj[addr] eventually processed by the callback
|
|
254
|
+
*/
|
|
255
|
+
export function ensureObjectProp(obj: object, addr: string, defaultValue, callback = o => o) {
|
|
256
|
+
err500IfNotSet({ obj, addr, defaultValue, callback })
|
|
257
|
+
if (!isset(obj[addr])) obj[addr] = defaultValue
|
|
258
|
+
callback(obj[addr])
|
|
259
|
+
return obj[addr]
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
/** object and array merge
|
|
264
|
+
* @warn /!\ Array will be merged and duplicate values will be deleted /!\
|
|
265
|
+
* @return {Object} new object result from merge
|
|
266
|
+
* NOTE: objects in params will NOT be modified*/
|
|
267
|
+
export function mergeDeep(...objects) {
|
|
268
|
+
return mergeDeepConfigurable(
|
|
269
|
+
(previousVal, currentVal) => [...previousVal, ...currentVal].filter((elm, i, arr) => arr.indexOf(elm) === i),
|
|
270
|
+
(previousVal, currentVal) => mergeDeep(previousVal, currentVal),
|
|
271
|
+
undefined,
|
|
272
|
+
...objects
|
|
273
|
+
)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** object and array merge
|
|
277
|
+
* @warn /!\ Array will be replaced by the latest object /!\
|
|
278
|
+
* @return {Object} new object result from merge
|
|
279
|
+
* NOTE: objects in params will NOT be modified */
|
|
280
|
+
export function mergeDeepOverrideArrays(...objects) {
|
|
281
|
+
return mergeDeepConfigurable(
|
|
282
|
+
undefined,
|
|
283
|
+
(previousVal, currentVal) => mergeDeepOverrideArrays(previousVal, currentVal),
|
|
284
|
+
undefined,
|
|
285
|
+
...objects
|
|
286
|
+
)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/** object and array merge
|
|
290
|
+
* @param {Function} replacerForArrays item[key] = (prevValue, currentVal) => () When 2 values are arrays,
|
|
291
|
+
* @param {Function} replacerForObjects item[key] = (prevValue, currentVal) => () When 2 values are objects,
|
|
292
|
+
* @param {Function} replacerDefault item[key] = (prevValue, currentVal) => () For all other values
|
|
293
|
+
* @param {...Object} objects
|
|
294
|
+
* @return {Object} new object result from merge
|
|
295
|
+
* NOTE: objects in params will NOT be modified
|
|
296
|
+
*/
|
|
297
|
+
export function mergeDeepConfigurable(replacerForArrays = (prev, curr) => curr, replacerForObjects, replacerDefault = (prev, curr) => curr, ...objects) {
|
|
298
|
+
return objects.reduce((actuallyMerged, obj) => {
|
|
299
|
+
Object.keys(obj).forEach(key => {
|
|
300
|
+
const previousVal = actuallyMerged[key]
|
|
301
|
+
const currentVal = obj[key]
|
|
302
|
+
|
|
303
|
+
if (Array.isArray(previousVal) && Array.isArray(currentVal)) {
|
|
304
|
+
actuallyMerged[key] = replacerForArrays(previousVal, currentVal)
|
|
305
|
+
} else if (isObject(previousVal) && isObject(currentVal)) {
|
|
306
|
+
actuallyMerged[key] = replacerForObjects(previousVal, currentVal)
|
|
307
|
+
} else {
|
|
308
|
+
actuallyMerged[key] = replacerDefault(previousVal, currentVal)
|
|
309
|
+
}
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
return actuallyMerged
|
|
313
|
+
}, {})
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/** { a: {b:2}} => {'a.b':2} useful for translations
|
|
317
|
+
* NOTE: will remove circular references
|
|
318
|
+
*/
|
|
319
|
+
export function flattenObject(data, config: { withoutArraySyntax?: boolean, withArraySyntaxMinified?: boolean } = {}) {
|
|
320
|
+
const { withoutArraySyntax = false, withArraySyntaxMinified = false } = config
|
|
321
|
+
const result = {}
|
|
322
|
+
const seenObjects = [] // avoidCircular reference to infinite loop
|
|
323
|
+
const recurse = (cur, prop) => {
|
|
324
|
+
if (Array.isArray(cur)) {
|
|
325
|
+
let l = cur.length
|
|
326
|
+
let i = 0
|
|
327
|
+
if (withoutArraySyntax) recurse(cur[0], prop)
|
|
328
|
+
else {
|
|
329
|
+
for (; i < l; i++) recurse(cur[i], prop + (withArraySyntaxMinified ? `.${i}` : `[${i}]`))
|
|
330
|
+
if (l == 0) result[prop] = []
|
|
331
|
+
}
|
|
332
|
+
} else if (isObject(cur)) { // is object
|
|
333
|
+
try {
|
|
334
|
+
if (seenObjects.includes(cur)) cur = deepClone(cur) // avoid circular ref but allow duplicate objects
|
|
335
|
+
else seenObjects.push(cur)
|
|
336
|
+
|
|
337
|
+
const isEmpty = Object.keys(cur).length === 0
|
|
338
|
+
|
|
339
|
+
for (const p in cur) recurse(cur[p], (prop ? prop + '.' : '') + p.replace(/\./g, '%')) // allow prop to contain special chars like points);
|
|
340
|
+
|
|
341
|
+
if (isEmpty && prop) result[prop] = {}
|
|
342
|
+
} catch (error) {
|
|
343
|
+
console.warn('Circular reference in flattenObject, impossible to parse')
|
|
344
|
+
}
|
|
345
|
+
} else result[prop] = cur
|
|
346
|
+
}
|
|
347
|
+
recurse(data, '')
|
|
348
|
+
return result
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/** {'a.b':2} => { a: {b:2}} */
|
|
352
|
+
export function unflattenObject(data) {
|
|
353
|
+
const newO = {}
|
|
354
|
+
for (const [addr, value] of Object.entries(data)) objForceWrite(newO, addr, value)
|
|
355
|
+
return newO
|
|
356
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { Color } from './types'
|
|
2
|
+
import { isset } from '../isset'
|
|
3
|
+
|
|
4
|
+
export type Config = {
|
|
5
|
+
env: string
|
|
6
|
+
isProd: boolean
|
|
7
|
+
nbOfLogsToKeep: number
|
|
8
|
+
customTypes: object,
|
|
9
|
+
preprocessLog?: Function,
|
|
10
|
+
terminal: {
|
|
11
|
+
noColor: boolean
|
|
12
|
+
theme: {
|
|
13
|
+
primary: Color, // blue theme
|
|
14
|
+
shade1: Color,
|
|
15
|
+
shade2: Color,
|
|
16
|
+
bgColor?: Color
|
|
17
|
+
paddingX: number
|
|
18
|
+
paddingY: number
|
|
19
|
+
fontColor?: Color
|
|
20
|
+
pageWidth: number
|
|
21
|
+
debugModeColor: Color,
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let config: Config = {
|
|
27
|
+
env: 'development',
|
|
28
|
+
isProd: false,
|
|
29
|
+
nbOfLogsToKeep: 25,
|
|
30
|
+
customTypes: {},
|
|
31
|
+
terminal: {
|
|
32
|
+
noColor: false,
|
|
33
|
+
theme: {
|
|
34
|
+
primary: [0, 149, 250], // blue theme
|
|
35
|
+
shade1: [0, 90, 250],
|
|
36
|
+
shade2: [0, 208, 245],
|
|
37
|
+
paddingX: 2,
|
|
38
|
+
paddingY: 2,
|
|
39
|
+
pageWidth: 53,
|
|
40
|
+
debugModeColor: [201, 27, 169],
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Allow dynamic changing of config */
|
|
46
|
+
export function configFn() { return config }
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
/** Register custom config
|
|
50
|
+
* @param {object} customConfig { 'email': email => /.+@.+/.test(email), type2 : myTestFunction() }
|
|
51
|
+
* * env: 'development',
|
|
52
|
+
* * customTypes: {},
|
|
53
|
+
* * terminal: {
|
|
54
|
+
* * noColor: false, // disable colored escape sequences like /mOO35...etc
|
|
55
|
+
* * theme: {
|
|
56
|
+
* * primary: [61, 167, 32], // main color (title font)
|
|
57
|
+
* * shade1: [127, 192, 39], // gradient shade 1
|
|
58
|
+
* * shade2: [194, 218, 47], // gradient shade 2
|
|
59
|
+
* * bgColor: false, // background color
|
|
60
|
+
* * paddingX: 2, // nb spaces added before an outputted str
|
|
61
|
+
* * paddingY: 2, //
|
|
62
|
+
* * fontColor: false, // default font color
|
|
63
|
+
* * pageWidth: 53, // page size in character
|
|
64
|
+
* * debugModeColor: [147, 212, 6], // usually orange
|
|
65
|
+
* * }
|
|
66
|
+
* * },
|
|
67
|
+
*/
|
|
68
|
+
export function registerConfig(customConfig) {
|
|
69
|
+
if (!isset(customConfig.terminal)) customConfig.terminal = {}
|
|
70
|
+
const newconfig = {
|
|
71
|
+
...config,
|
|
72
|
+
...customConfig
|
|
73
|
+
}
|
|
74
|
+
newconfig.terminal = {
|
|
75
|
+
...config.terminal,
|
|
76
|
+
...customConfig.terminal
|
|
77
|
+
}
|
|
78
|
+
newconfig.terminal.theme = {
|
|
79
|
+
...config.terminal.theme,
|
|
80
|
+
...(customConfig.terminal.theme || {})
|
|
81
|
+
}
|
|
82
|
+
config = newconfig
|
|
83
|
+
|
|
84
|
+
config.isProd = config.env.includes('prod')
|
|
85
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export class dataValidationUtilErrorHandler extends Error {
|
|
2
|
+
fromDataValidation: boolean
|
|
3
|
+
code: number
|
|
4
|
+
extraInfos: object
|
|
5
|
+
msg: string
|
|
6
|
+
errorDescription: { [k: string]: any }
|
|
7
|
+
constructor(msg, code, extraInfos?) {
|
|
8
|
+
super(msg)
|
|
9
|
+
this.message = msg
|
|
10
|
+
this.msg = msg
|
|
11
|
+
this.name = `${code} ${msg}`
|
|
12
|
+
this.fromDataValidation = true // will be catched by express error handler
|
|
13
|
+
this.code = code
|
|
14
|
+
this.extraInfos = extraInfos
|
|
15
|
+
this.errorDescription = {
|
|
16
|
+
msg,
|
|
17
|
+
code,
|
|
18
|
+
...extraInfos,
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
//----------------------------------------
|
|
2
|
+
// REGEXP UTILS
|
|
3
|
+
//----------------------------------------
|
|
4
|
+
import { C } from "./logger-utils"
|
|
5
|
+
|
|
6
|
+
export function escapeRegexp(str: string, config: { parseStarChar?: boolean } = {}): string {
|
|
7
|
+
const { parseStarChar = false } = config
|
|
8
|
+
if (parseStarChar) return str.replace(/[-[\]{}()+?.,\\^$|#\s]/g, '\\$&').replace(/\*/g, '.*')
|
|
9
|
+
else return str.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&')
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Get first match of the first capturing group of regexp
|
|
13
|
+
* Eg: const basePath = firstMatch(apiFile, /basePath = '(.*?)'/); will get what is inside quotes
|
|
14
|
+
*/
|
|
15
|
+
export function firstMatch(str: string, regExp: RegExp): string | undefined { return (str.match(regExp) || [])[1] }
|
|
16
|
+
|
|
17
|
+
/** Get all matches from regexp with g flag
|
|
18
|
+
* Eg: [ [full, match1, m2], [f, m1, m2]... ]
|
|
19
|
+
* NOTE: the G flag will be appended to regexp
|
|
20
|
+
*/
|
|
21
|
+
export function allMatches(str: string, reg: RegExp): string[] {
|
|
22
|
+
let i = 0
|
|
23
|
+
let matches
|
|
24
|
+
const arr = []
|
|
25
|
+
if (typeof str !== 'string') C.error('Not a string provided as first argument for allMatches()')
|
|
26
|
+
else {
|
|
27
|
+
reg = new RegExp(reg, 'g')
|
|
28
|
+
while ((matches = reg.exec(str))) {
|
|
29
|
+
arr.push(matches)
|
|
30
|
+
if (i++ > 99) {
|
|
31
|
+
C.error('error', 'Please provide the G flag in regexp for allMatches')
|
|
32
|
+
break
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return arr
|
|
37
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
|
|
2
|
+
export function removeCircularJSONstringify(object, indent = 2) {
|
|
3
|
+
const getCircularReplacer = () => {
|
|
4
|
+
const seen = new WeakSet()
|
|
5
|
+
return (key, value) => {
|
|
6
|
+
if (typeof value === 'object' && value !== null) {
|
|
7
|
+
if (seen.has(value)) {
|
|
8
|
+
return
|
|
9
|
+
}
|
|
10
|
+
seen.add(value)
|
|
11
|
+
}
|
|
12
|
+
return value
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return JSON.stringify(object, getCircularReplacer(), indent)
|
|
17
|
+
}
|