cafe-utility 2.2.0 → 4.0.0
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/index.d.ts +480 -0
- package/index.js +706 -499
- package/module.mjs +1 -0
- package/package.json +3 -2
package/index.js
CHANGED
|
@@ -1,21 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
} catch {}
|
|
6
|
-
}
|
|
7
|
-
}
|
|
1
|
+
const ChildProcess = require('child_process')
|
|
2
|
+
const NodeCrypto = require('crypto')
|
|
3
|
+
const Fs = require('fs')
|
|
4
|
+
const Path = require('path')
|
|
8
5
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const Path = nodeModuleRequire('path')
|
|
13
|
-
|
|
14
|
-
const raceFulfilled = promises => invertPromise(Promise.all(promises.map(invertPromise)))
|
|
6
|
+
async function invertPromise(promise) {
|
|
7
|
+
return new Promise((resolve, reject) => promise.then(reject, resolve))
|
|
8
|
+
}
|
|
15
9
|
|
|
16
|
-
|
|
10
|
+
async function raceFulfilled(promises) {
|
|
11
|
+
invertPromise(Promise.all(promises.map(invertPromise)))
|
|
12
|
+
}
|
|
17
13
|
|
|
18
|
-
|
|
14
|
+
async function runInParallelBatches(promises, concurrency = 1) {
|
|
19
15
|
const batches = splitByCount(promises, concurrency)
|
|
20
16
|
const results = []
|
|
21
17
|
const jobs = batches.map(async batch => {
|
|
@@ -27,14 +23,15 @@ const runInParallelBatches = async (promises, concurrency = 1) => {
|
|
|
27
23
|
return results
|
|
28
24
|
}
|
|
29
25
|
|
|
30
|
-
|
|
31
|
-
new Promise(resolve =>
|
|
26
|
+
async function sleepMillis(millis) {
|
|
27
|
+
return new Promise(resolve =>
|
|
32
28
|
setTimeout(() => {
|
|
33
29
|
resolve(true)
|
|
34
30
|
}, millis)
|
|
35
31
|
)
|
|
32
|
+
}
|
|
36
33
|
|
|
37
|
-
|
|
34
|
+
function shuffle(array) {
|
|
38
35
|
for (let i = array.length - 1; i > 0; i--) {
|
|
39
36
|
const j = Math.floor(Math.random() * (i + 1))
|
|
40
37
|
const swap = array[i]
|
|
@@ -44,7 +41,7 @@ const shuffle = array => {
|
|
|
44
41
|
return array
|
|
45
42
|
}
|
|
46
43
|
|
|
47
|
-
|
|
44
|
+
function onlyOrThrow(array) {
|
|
48
45
|
if (array && array.length === 1) {
|
|
49
46
|
return array[0]
|
|
50
47
|
}
|
|
@@ -54,16 +51,18 @@ const onlyOrThrow = array => {
|
|
|
54
51
|
throw Error('Expected array, got: ' + array)
|
|
55
52
|
}
|
|
56
53
|
|
|
57
|
-
|
|
54
|
+
function onlyOrNull(array) {
|
|
58
55
|
if (array && array.length === 1) {
|
|
59
56
|
return array[0]
|
|
60
57
|
}
|
|
61
58
|
return null
|
|
62
59
|
}
|
|
63
60
|
|
|
64
|
-
|
|
61
|
+
function firstOrNull(array) {
|
|
62
|
+
return array.length > 0 ? array[0] : null
|
|
63
|
+
}
|
|
65
64
|
|
|
66
|
-
|
|
65
|
+
function initializeArray(count, initializer) {
|
|
67
66
|
const results = []
|
|
68
67
|
for (let i = 0; i < count; i++) {
|
|
69
68
|
results.push(initializer(i))
|
|
@@ -71,23 +70,39 @@ const initializeArray = (count, initializer) => {
|
|
|
71
70
|
return results
|
|
72
71
|
}
|
|
73
72
|
|
|
74
|
-
|
|
73
|
+
function takeRandomly(array, count) {
|
|
74
|
+
return shuffle(array).slice(0, count)
|
|
75
|
+
}
|
|
75
76
|
|
|
76
|
-
|
|
77
|
+
function pluck(array, key) {
|
|
78
|
+
return array.map(element => element[key])
|
|
79
|
+
}
|
|
77
80
|
|
|
78
|
-
|
|
81
|
+
function randomIntInclusive(min, max) {
|
|
82
|
+
return Math.floor(Math.random() * (max - min + 1)) + min
|
|
83
|
+
}
|
|
79
84
|
|
|
80
|
-
|
|
85
|
+
function randomBetween(min, max) {
|
|
86
|
+
return Math.random() * (max - min) + min
|
|
87
|
+
}
|
|
81
88
|
|
|
82
|
-
|
|
89
|
+
function signedRandom() {
|
|
90
|
+
return Math.random() * 2 - 1
|
|
91
|
+
}
|
|
83
92
|
|
|
84
|
-
|
|
93
|
+
function chance(threshold) {
|
|
94
|
+
return Math.random() < threshold
|
|
95
|
+
}
|
|
85
96
|
|
|
86
|
-
|
|
97
|
+
function pick(array) {
|
|
98
|
+
return array[Math.floor(array.length * Math.random())]
|
|
99
|
+
}
|
|
87
100
|
|
|
88
|
-
|
|
101
|
+
function last(array) {
|
|
102
|
+
return array[array.length - 1]
|
|
103
|
+
}
|
|
89
104
|
|
|
90
|
-
|
|
105
|
+
function pickWeighted(array, weights, randomNumber) {
|
|
91
106
|
if (array.length !== weights.length) {
|
|
92
107
|
throw new Error('Array length mismatch')
|
|
93
108
|
}
|
|
@@ -99,18 +114,19 @@ const pickWeighted = (array, weights, randomNumber) => {
|
|
|
99
114
|
return array[i]
|
|
100
115
|
}
|
|
101
116
|
}
|
|
117
|
+
throw new Error('Weight out of range')
|
|
102
118
|
}
|
|
103
119
|
|
|
104
|
-
|
|
120
|
+
function sortWeighted(array, weights) {
|
|
105
121
|
const rolls = weights.map(weight => Math.random() * weight)
|
|
106
122
|
const results = []
|
|
107
|
-
for (let i = 0; i < array; i++) {
|
|
123
|
+
for (let i = 0; i < array.length; i++) {
|
|
108
124
|
results.push([array[i], rolls[i]])
|
|
109
125
|
}
|
|
110
126
|
return results.sort((a, b) => a[1] - b[1]).map(a => a[0])
|
|
111
127
|
}
|
|
112
128
|
|
|
113
|
-
|
|
129
|
+
function getDeep(object, path) {
|
|
114
130
|
const parts = path.split('.')
|
|
115
131
|
let buffer = object
|
|
116
132
|
for (const part of parts) {
|
|
@@ -122,9 +138,11 @@ const getDeep = (object, path) => {
|
|
|
122
138
|
return buffer
|
|
123
139
|
}
|
|
124
140
|
|
|
125
|
-
|
|
141
|
+
function getDeepOrElse(object, path, fallback) {
|
|
142
|
+
return getDeep(object, path) || fallback
|
|
143
|
+
}
|
|
126
144
|
|
|
127
|
-
|
|
145
|
+
function setDeep(object, path, value) {
|
|
128
146
|
const parts = path.split(/\.|\[/)
|
|
129
147
|
let buffer = object
|
|
130
148
|
for (let i = 0; i < parts.length; i++) {
|
|
@@ -148,16 +166,18 @@ const setDeep = (object, path, value) => {
|
|
|
148
166
|
return value
|
|
149
167
|
}
|
|
150
168
|
|
|
151
|
-
|
|
169
|
+
function ensureDeep(object, path, value) {
|
|
170
|
+
return getDeep(object, path) || setDeep(object, path, value)
|
|
171
|
+
}
|
|
152
172
|
|
|
153
|
-
|
|
173
|
+
function deleteDeep(object, path) {
|
|
154
174
|
const location = beforeLast(path, '.')
|
|
155
175
|
const toDelete = afterLast(path, '.')
|
|
156
176
|
const segment = getDeep(object, location)
|
|
157
177
|
delete segment[toDelete]
|
|
158
178
|
}
|
|
159
179
|
|
|
160
|
-
|
|
180
|
+
function replaceDeep(object, path, value) {
|
|
161
181
|
const existing = getDeep(object, path)
|
|
162
182
|
if (!existing) {
|
|
163
183
|
throw new Error("Key '" + path + "' does not exist.")
|
|
@@ -166,7 +186,7 @@ const replaceDeep = (object, path, value) => {
|
|
|
166
186
|
return existing
|
|
167
187
|
}
|
|
168
188
|
|
|
169
|
-
|
|
189
|
+
function getFirstDeep(object, paths, fallbackToAnyKey) {
|
|
170
190
|
for (const path of paths) {
|
|
171
191
|
const value = getDeep(object, path)
|
|
172
192
|
if (value) {
|
|
@@ -182,7 +202,7 @@ const getFirstDeep = (object, paths, fallbackToAnyKey) => {
|
|
|
182
202
|
return null
|
|
183
203
|
}
|
|
184
204
|
|
|
185
|
-
|
|
205
|
+
async function forever(callable, millis) {
|
|
186
206
|
while (true) {
|
|
187
207
|
try {
|
|
188
208
|
await callable()
|
|
@@ -193,11 +213,15 @@ const forever = async (callable, millis) => {
|
|
|
193
213
|
}
|
|
194
214
|
}
|
|
195
215
|
|
|
196
|
-
|
|
216
|
+
async function readUtf8FileAsync(path) {
|
|
217
|
+
return Fs.promises.readFile(path, 'utf8')
|
|
218
|
+
}
|
|
197
219
|
|
|
198
|
-
|
|
220
|
+
async function readJsonAsync(path) {
|
|
221
|
+
return JSON.parse(await readUtf8FileAsync(path))
|
|
222
|
+
}
|
|
199
223
|
|
|
200
|
-
|
|
224
|
+
async function writeJsonAsync(path, object, prettify) {
|
|
201
225
|
if (prettify) {
|
|
202
226
|
await Fs.promises.writeFile(path, JSON.stringify(object, null, 4))
|
|
203
227
|
} else {
|
|
@@ -205,16 +229,23 @@ const writeJsonAsync = async (path, object, prettify) => {
|
|
|
205
229
|
}
|
|
206
230
|
}
|
|
207
231
|
|
|
208
|
-
|
|
232
|
+
async function readLinesAsync(path) {
|
|
233
|
+
return (await readUtf8FileAsync(path)).split(/\r?\n/)
|
|
234
|
+
}
|
|
209
235
|
|
|
210
|
-
|
|
236
|
+
async function readMatchingLines(path, filterFn) {
|
|
237
|
+
return (await readLinesAsync(path)).filter(filterFn)
|
|
238
|
+
}
|
|
211
239
|
|
|
212
|
-
|
|
240
|
+
async function readNonEmptyLines(path) {
|
|
241
|
+
return readMatchingLines(path, x => !!x)
|
|
242
|
+
}
|
|
213
243
|
|
|
214
|
-
|
|
215
|
-
(skip ? (await readNonEmptyLines(path)).slice(skip) : await readNonEmptyLines(path)).map(x =>
|
|
244
|
+
async function readCsv(path, skip = 0, delimiter = ',', quote = '"') {
|
|
245
|
+
return (skip ? (await readNonEmptyLines(path)).slice(skip) : await readNonEmptyLines(path)).map(x =>
|
|
216
246
|
parseCsv(x, delimiter, quote)
|
|
217
247
|
)
|
|
248
|
+
}
|
|
218
249
|
|
|
219
250
|
async function* walkTreeAsync(path) {
|
|
220
251
|
for await (const directory of await Fs.promises.opendir(path)) {
|
|
@@ -227,13 +258,13 @@ async function* walkTreeAsync(path) {
|
|
|
227
258
|
}
|
|
228
259
|
}
|
|
229
260
|
|
|
230
|
-
|
|
261
|
+
function removeLeadingDirectory(path, directory) {
|
|
231
262
|
directory = directory.startsWith('./') ? directory.slice(2) : directory
|
|
232
263
|
directory = directory.endsWith('/') ? directory : directory + '/'
|
|
233
264
|
return path.replace(directory, '')
|
|
234
265
|
}
|
|
235
266
|
|
|
236
|
-
|
|
267
|
+
async function readdirDeepAsync(path, cwd) {
|
|
237
268
|
const entries = []
|
|
238
269
|
for await (const entry of walkTreeAsync(path)) {
|
|
239
270
|
entries.push(cwd ? removeLeadingDirectory(entry, cwd) : entry)
|
|
@@ -241,7 +272,7 @@ const readdirDeepAsync = async (path, cwd) => {
|
|
|
241
272
|
return entries
|
|
242
273
|
}
|
|
243
274
|
|
|
244
|
-
|
|
275
|
+
async function existsAsync(path) {
|
|
245
276
|
try {
|
|
246
277
|
await Fs.promises.stat(path)
|
|
247
278
|
return true
|
|
@@ -250,14 +281,16 @@ const existsAsync = async path => {
|
|
|
250
281
|
}
|
|
251
282
|
}
|
|
252
283
|
|
|
253
|
-
|
|
284
|
+
async function getFileSize(path) {
|
|
254
285
|
const stats = await Fs.promises.stat(path)
|
|
255
286
|
return stats.size
|
|
256
287
|
}
|
|
257
288
|
|
|
258
|
-
|
|
289
|
+
function asMegabytes(number) {
|
|
290
|
+
return number / 1024 / 1024
|
|
291
|
+
}
|
|
259
292
|
|
|
260
|
-
|
|
293
|
+
async function getDirectorySize(path) {
|
|
261
294
|
let size = 0
|
|
262
295
|
for await (const file of walkTreeAsync(path)) {
|
|
263
296
|
size += await getFileSize(file)
|
|
@@ -265,52 +298,86 @@ const getDirectorySize = async path => {
|
|
|
265
298
|
return size
|
|
266
299
|
}
|
|
267
300
|
|
|
268
|
-
|
|
301
|
+
function convertBytes(bytes) {
|
|
269
302
|
if (bytes > 1000000) {
|
|
270
303
|
return (bytes / 1000000).toFixed(3) + 'MB'
|
|
271
304
|
}
|
|
272
305
|
if (bytes > 1000) {
|
|
273
306
|
return (bytes / 1000).toFixed(3) + 'KB'
|
|
274
307
|
}
|
|
275
|
-
return bytes
|
|
308
|
+
return bytes + ''
|
|
276
309
|
}
|
|
277
310
|
|
|
278
|
-
|
|
279
|
-
const hash =
|
|
311
|
+
function getChecksum(data) {
|
|
312
|
+
const hash = NodeCrypto.createHash('sha1')
|
|
280
313
|
hash.update(data)
|
|
281
314
|
return hash.digest('hex')
|
|
282
315
|
}
|
|
283
316
|
|
|
284
|
-
|
|
285
|
-
new Promise((resolve, reject) => {
|
|
286
|
-
const hash =
|
|
317
|
+
async function getChecksumOfFile(path) {
|
|
318
|
+
return new Promise((resolve, reject) => {
|
|
319
|
+
const hash = NodeCrypto.createHash('sha1')
|
|
287
320
|
const readStream = Fs.createReadStream(path)
|
|
288
321
|
readStream.on('error', reject)
|
|
289
322
|
readStream.on('data', chunk => hash.update(chunk))
|
|
290
323
|
readStream.on('end', () => resolve(hash.digest('hex')))
|
|
291
324
|
})
|
|
325
|
+
}
|
|
292
326
|
|
|
293
|
-
|
|
327
|
+
function isObject(value) {
|
|
328
|
+
return value !== null && typeof value === 'object'
|
|
329
|
+
}
|
|
294
330
|
|
|
295
|
-
|
|
331
|
+
function isStrictlyObject(value) {
|
|
332
|
+
return isObject(value) && !Array.isArray(value)
|
|
333
|
+
}
|
|
296
334
|
|
|
297
|
-
|
|
335
|
+
function isEmptyArray(value) {
|
|
336
|
+
return Array.isArray(value) && value.length === 0
|
|
337
|
+
}
|
|
298
338
|
|
|
299
|
-
|
|
339
|
+
function isUndefined(value) {
|
|
340
|
+
return typeof value === 'undefined'
|
|
341
|
+
}
|
|
300
342
|
|
|
301
|
-
|
|
343
|
+
function isFunction(value) {
|
|
344
|
+
return Object.prototype.toString.call(value) === '[object Function]'
|
|
345
|
+
}
|
|
302
346
|
|
|
303
|
-
|
|
347
|
+
function isString(value) {
|
|
348
|
+
return Object.prototype.toString.call(value) === '[object String]'
|
|
349
|
+
}
|
|
304
350
|
|
|
305
|
-
|
|
351
|
+
function isPromise(value) {
|
|
352
|
+
return value && typeof value.then === 'function'
|
|
353
|
+
}
|
|
306
354
|
|
|
307
|
-
|
|
355
|
+
function isNumber(value) {
|
|
356
|
+
return !isNaN(value) && String(value) === String(parseFloat(value))
|
|
357
|
+
}
|
|
308
358
|
|
|
309
|
-
|
|
359
|
+
function isDate(value) {
|
|
360
|
+
return Object.prototype.toString.call(value) === '[object Date]'
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function isBlank(value) {
|
|
364
|
+
return !isString(value) || value.trim().length === 0
|
|
365
|
+
}
|
|
310
366
|
|
|
367
|
+
const alphabet = 'abcdefghijklmnopqrstuvwxyz'
|
|
368
|
+
const alphanumericAlphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'
|
|
369
|
+
const richAsciiAlphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#$%^&*()_+-=[]{}|;:<>?,./'
|
|
370
|
+
const unicodeTestingAlphabet = ['—', '\\', '東', '京', '都', '𝖆', '𝖇', '𝖈', '👾', '🙇', '💁', '🙅']
|
|
311
371
|
const hexAlphabet = '0123456789abcdef'
|
|
372
|
+
function randomLetterString(length) {
|
|
373
|
+
let buffer = ''
|
|
374
|
+
for (let i = 0; i < length; i++) {
|
|
375
|
+
buffer += alphabet[Math.floor(Math.random() * alphabet.length)]
|
|
376
|
+
}
|
|
377
|
+
return buffer
|
|
378
|
+
}
|
|
312
379
|
|
|
313
|
-
|
|
380
|
+
function randomAlphanumericString(length) {
|
|
314
381
|
let buffer = ''
|
|
315
382
|
for (let i = 0; i < length; i++) {
|
|
316
383
|
buffer += alphanumericAlphabet[Math.floor(Math.random() * alphanumericAlphabet.length)]
|
|
@@ -318,7 +385,23 @@ const randomAlphanumericString = length => {
|
|
|
318
385
|
return buffer
|
|
319
386
|
}
|
|
320
387
|
|
|
321
|
-
|
|
388
|
+
function randomRichAsciiString(length) {
|
|
389
|
+
let buffer = ''
|
|
390
|
+
for (let i = 0; i < length; i++) {
|
|
391
|
+
buffer += richAsciiAlphabet[Math.floor(Math.random() * richAsciiAlphabet.length)]
|
|
392
|
+
}
|
|
393
|
+
return buffer
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function randomUnicodeString(length) {
|
|
397
|
+
let buffer = ''
|
|
398
|
+
for (let i = 0; i < length; i++) {
|
|
399
|
+
buffer += unicodeTestingAlphabet[Math.floor(Math.random() * unicodeTestingAlphabet.length)]
|
|
400
|
+
}
|
|
401
|
+
return buffer
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function randomHexString(length) {
|
|
322
405
|
let buffer = ''
|
|
323
406
|
for (let i = 0; i < length; i++) {
|
|
324
407
|
buffer += hexAlphabet[Math.floor(Math.random() * hexAlphabet.length)]
|
|
@@ -326,47 +409,35 @@ const randomHexString = length => {
|
|
|
326
409
|
return buffer
|
|
327
410
|
}
|
|
328
411
|
|
|
329
|
-
|
|
330
|
-
* @param {*} string
|
|
331
|
-
* @returns {string}
|
|
332
|
-
*/
|
|
333
|
-
const asString = string => {
|
|
412
|
+
function asString(string) {
|
|
334
413
|
if (isBlank(string)) {
|
|
335
414
|
throw new TypeError('Expected string, got: ' + string)
|
|
336
415
|
}
|
|
337
416
|
return string
|
|
338
417
|
}
|
|
339
418
|
|
|
340
|
-
|
|
341
|
-
* @param {*} number
|
|
342
|
-
* @returns {number}
|
|
343
|
-
*/
|
|
344
|
-
const asNumber = number => {
|
|
419
|
+
function asNumber(number) {
|
|
345
420
|
if (!isNumber(number)) {
|
|
346
421
|
throw new TypeError('Expected number, got: ' + number)
|
|
347
422
|
}
|
|
348
423
|
return number
|
|
349
424
|
}
|
|
350
425
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
*/
|
|
355
|
-
const asDate = date => {
|
|
356
|
-
if (Object.prototype.toString.call(date) === '[object Date]') {
|
|
357
|
-
return date
|
|
426
|
+
function asDate(date) {
|
|
427
|
+
if (!isDate(date)) {
|
|
428
|
+
throw new TypeError('Expected date, got: ' + date)
|
|
358
429
|
}
|
|
359
|
-
|
|
430
|
+
return date
|
|
360
431
|
}
|
|
361
432
|
|
|
362
|
-
|
|
433
|
+
function asNullableString(string) {
|
|
363
434
|
if (isBlank(string)) {
|
|
364
435
|
return null
|
|
365
436
|
}
|
|
366
437
|
return string
|
|
367
438
|
}
|
|
368
439
|
|
|
369
|
-
|
|
440
|
+
function represent(value) {
|
|
370
441
|
if (isObject(value)) {
|
|
371
442
|
return JSON.stringify(value, null, 4)
|
|
372
443
|
}
|
|
@@ -383,8 +454,8 @@ const loggerGlobalState = {
|
|
|
383
454
|
fileStream: null
|
|
384
455
|
}
|
|
385
456
|
|
|
386
|
-
|
|
387
|
-
const timestamp = new Date().toISOString().replace('T', ' ').
|
|
457
|
+
function log(level, module, pieces) {
|
|
458
|
+
const timestamp = new Date().toISOString().replace('T', ' ').slice(0, 19)
|
|
388
459
|
const message = `${timestamp} ${level} ${module} ${pieces.map(represent).join(' ')}\n`
|
|
389
460
|
process.stdout.write(message)
|
|
390
461
|
if (level === 'ERROR') {
|
|
@@ -395,7 +466,7 @@ const log = (level, module, pieces) => {
|
|
|
395
466
|
}
|
|
396
467
|
}
|
|
397
468
|
|
|
398
|
-
|
|
469
|
+
function createLogger(module) {
|
|
399
470
|
module = last(module.split(/\\|\//))
|
|
400
471
|
return {
|
|
401
472
|
trace: (...pieces) => {
|
|
@@ -416,11 +487,11 @@ const createLogger = module => {
|
|
|
416
487
|
}
|
|
417
488
|
}
|
|
418
489
|
|
|
419
|
-
|
|
490
|
+
function enableFileLogging(path) {
|
|
420
491
|
loggerGlobalState.fileStream = Fs.createWriteStream(path, { flags: 'a' })
|
|
421
492
|
}
|
|
422
493
|
|
|
423
|
-
|
|
494
|
+
function expandError(error, stackTrace) {
|
|
424
495
|
if (isString(error)) {
|
|
425
496
|
return error
|
|
426
497
|
}
|
|
@@ -432,7 +503,7 @@ const expandError = (error, stackTrace) => {
|
|
|
432
503
|
return stackTrace && error.stack ? joined + '\n' + error.stack : joined
|
|
433
504
|
}
|
|
434
505
|
|
|
435
|
-
|
|
506
|
+
function mergeDeep(target, source) {
|
|
436
507
|
if (isStrictlyObject(target) && isStrictlyObject(source)) {
|
|
437
508
|
for (const key in source) {
|
|
438
509
|
if (isStrictlyObject(source[key])) {
|
|
@@ -447,25 +518,28 @@ const mergeDeep = (target, source) => {
|
|
|
447
518
|
}
|
|
448
519
|
}
|
|
449
520
|
}
|
|
521
|
+
return target
|
|
450
522
|
}
|
|
451
523
|
|
|
452
|
-
|
|
453
|
-
const result = {
|
|
454
|
-
for (const
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
524
|
+
function zip(objects, reducer) {
|
|
525
|
+
const result = {}
|
|
526
|
+
for (const object of objects) {
|
|
527
|
+
for (const key of Object.keys(object)) {
|
|
528
|
+
if (result[key]) {
|
|
529
|
+
result[key] = reducer(result[key], object[key])
|
|
530
|
+
} else {
|
|
531
|
+
result[key] = object[key]
|
|
532
|
+
}
|
|
459
533
|
}
|
|
460
534
|
}
|
|
461
535
|
return result
|
|
462
536
|
}
|
|
463
537
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
538
|
+
function zipSum(objects) {
|
|
539
|
+
return zip(objects, (x, y) => x + y)
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function asPageNumber(value) {
|
|
469
543
|
let number
|
|
470
544
|
try {
|
|
471
545
|
number = parseInt(value, 10)
|
|
@@ -484,52 +558,37 @@ const asPageNumber = value => {
|
|
|
484
558
|
return number
|
|
485
559
|
}
|
|
486
560
|
|
|
487
|
-
|
|
561
|
+
function pushToBucket(object, bucket, item) {
|
|
488
562
|
if (!object[bucket]) {
|
|
489
563
|
object[bucket] = []
|
|
490
564
|
}
|
|
491
565
|
object[bucket].push(item)
|
|
492
566
|
}
|
|
493
567
|
|
|
494
|
-
|
|
568
|
+
function unshiftAndLimit(array, item, limit) {
|
|
495
569
|
array.unshift(item)
|
|
496
570
|
while (array.length > limit) {
|
|
497
571
|
array.pop()
|
|
498
572
|
}
|
|
499
573
|
}
|
|
500
574
|
|
|
501
|
-
|
|
502
|
-
|
|
575
|
+
function atRolling(array, index) {
|
|
576
|
+
let realIndex = index % array.length
|
|
577
|
+
if (realIndex < 0) {
|
|
578
|
+
realIndex += array.length
|
|
579
|
+
}
|
|
503
580
|
return array[realIndex]
|
|
504
581
|
}
|
|
505
582
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
const unshiftAll = (array, elements) => Array.prototype.unshift.apply(array, elements)
|
|
509
|
-
|
|
510
|
-
const mapWithIndex = (array, callback) => {
|
|
511
|
-
const results = new Array(array.length)
|
|
512
|
-
for (let i = 0; i < array.length; i++) {
|
|
513
|
-
results[i] = callback(array[i], i)
|
|
514
|
-
}
|
|
515
|
-
return results
|
|
583
|
+
function pushAll(array, elements) {
|
|
584
|
+
Array.prototype.push.apply(array, elements)
|
|
516
585
|
}
|
|
517
586
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
for (const key of keys) {
|
|
521
|
-
if (Array.isArray(key)) {
|
|
522
|
-
mapped[key[1]] = object.get(key[0])
|
|
523
|
-
} else {
|
|
524
|
-
mapped[key] = object.get(key)
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
return mapped
|
|
587
|
+
function unshiftAll(array, elements) {
|
|
588
|
+
Array.prototype.unshift.apply(array, elements)
|
|
528
589
|
}
|
|
529
590
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
const mapAllAsync = async (array, fn) => {
|
|
591
|
+
async function mapAllAsync(array, fn) {
|
|
533
592
|
const mapped = []
|
|
534
593
|
for (const object of array) {
|
|
535
594
|
mapped.push(await fn(object))
|
|
@@ -537,7 +596,7 @@ const mapAllAsync = async (array, fn) => {
|
|
|
537
596
|
return mapped
|
|
538
597
|
}
|
|
539
598
|
|
|
540
|
-
|
|
599
|
+
function glue(array, glueElement) {
|
|
541
600
|
const results = []
|
|
542
601
|
for (let i = 0; i < array.length; i++) {
|
|
543
602
|
results.push(array[i])
|
|
@@ -552,7 +611,7 @@ const glue = (array, glueElement) => {
|
|
|
552
611
|
return results
|
|
553
612
|
}
|
|
554
613
|
|
|
555
|
-
|
|
614
|
+
function pageify(data, totalElements, pageSize, currentPage) {
|
|
556
615
|
const totalPages = Math.floor(totalElements / pageSize) + 1
|
|
557
616
|
return {
|
|
558
617
|
data,
|
|
@@ -563,49 +622,49 @@ const pageify = (data, totalElements, pageSize, currentPage) => {
|
|
|
563
622
|
}
|
|
564
623
|
}
|
|
565
624
|
|
|
566
|
-
|
|
625
|
+
function asEqual(a, b) {
|
|
567
626
|
if (a !== b) {
|
|
568
627
|
throw Error(`Expected [${a}] to equal [${b}]`)
|
|
569
628
|
}
|
|
570
629
|
return [a, b]
|
|
571
630
|
}
|
|
572
631
|
|
|
573
|
-
|
|
632
|
+
function asTrue(data) {
|
|
574
633
|
if (data !== true) {
|
|
575
634
|
throw Error(`Expected [true], got [${data}]`)
|
|
576
635
|
}
|
|
577
636
|
return data
|
|
578
637
|
}
|
|
579
638
|
|
|
580
|
-
|
|
639
|
+
function asTruthy(data) {
|
|
581
640
|
if (!data) {
|
|
582
641
|
throw Error(`Expected truthy value, got [${data}]`)
|
|
583
642
|
}
|
|
584
643
|
return data
|
|
585
644
|
}
|
|
586
645
|
|
|
587
|
-
|
|
646
|
+
function asFalse(data) {
|
|
588
647
|
if (data !== false) {
|
|
589
648
|
throw Error(`Expected [false], got [${data}]`)
|
|
590
649
|
}
|
|
591
650
|
return data
|
|
592
651
|
}
|
|
593
652
|
|
|
594
|
-
|
|
653
|
+
function asFalsy(data) {
|
|
595
654
|
if (data) {
|
|
596
655
|
throw Error(`Expected falsy value, got [${data}]`)
|
|
597
656
|
}
|
|
598
657
|
return data
|
|
599
658
|
}
|
|
600
659
|
|
|
601
|
-
|
|
660
|
+
function asEither(data, values) {
|
|
602
661
|
if (!values.includes(data)) {
|
|
603
662
|
throw Error(`Expected any of [${values.join(', ')}], got [${data}]`)
|
|
604
663
|
}
|
|
605
664
|
return data
|
|
606
665
|
}
|
|
607
666
|
|
|
608
|
-
|
|
667
|
+
function scheduleMany(handlers, dates) {
|
|
609
668
|
for (let i = 0; i < handlers.length; i++) {
|
|
610
669
|
const handler = handlers[i]
|
|
611
670
|
const date = dates[i]
|
|
@@ -614,13 +673,19 @@ const scheduleMany = (handlers, dates) => {
|
|
|
614
673
|
}
|
|
615
674
|
}
|
|
616
675
|
|
|
617
|
-
|
|
676
|
+
function interpolate(a, b, t) {
|
|
677
|
+
return a + (b - a) * t
|
|
678
|
+
}
|
|
618
679
|
|
|
619
|
-
|
|
680
|
+
function sum(array) {
|
|
681
|
+
return array.reduce((a, b) => a + b, 0)
|
|
682
|
+
}
|
|
620
683
|
|
|
621
|
-
|
|
684
|
+
function average(array) {
|
|
685
|
+
return array.reduce((a, b) => a + b, 0) / array.length
|
|
686
|
+
}
|
|
622
687
|
|
|
623
|
-
|
|
688
|
+
function range(start, end) {
|
|
624
689
|
const array = []
|
|
625
690
|
for (let i = start; i <= end; i++) {
|
|
626
691
|
array.push(i)
|
|
@@ -628,7 +693,7 @@ const range = (start, end) => {
|
|
|
628
693
|
return array
|
|
629
694
|
}
|
|
630
695
|
|
|
631
|
-
|
|
696
|
+
function includesAny(string, substrings) {
|
|
632
697
|
for (const substring of substrings) {
|
|
633
698
|
if (string.includes(substring)) {
|
|
634
699
|
return true
|
|
@@ -637,8 +702,8 @@ const includesAny = (string, substrings) => {
|
|
|
637
702
|
return false
|
|
638
703
|
}
|
|
639
704
|
|
|
640
|
-
|
|
641
|
-
string
|
|
705
|
+
function slugify(string) {
|
|
706
|
+
return string
|
|
642
707
|
.toLowerCase()
|
|
643
708
|
.normalize('NFD')
|
|
644
709
|
.replace(/[\u0300-\u036f]/g, '')
|
|
@@ -647,12 +712,21 @@ const slugify = string =>
|
|
|
647
712
|
.join('')
|
|
648
713
|
.replace(/-+/g, '-')
|
|
649
714
|
.replace(/^-|-$/g, '')
|
|
715
|
+
}
|
|
650
716
|
|
|
651
|
-
|
|
717
|
+
function camelToTitle(string) {
|
|
718
|
+
return capitalize(string.replace(/([A-Z])/g, ' $1'))
|
|
719
|
+
}
|
|
652
720
|
|
|
653
|
-
|
|
721
|
+
function slugToTitle(string) {
|
|
722
|
+
return string.split('-').map(capitalize).join(' ')
|
|
723
|
+
}
|
|
654
724
|
|
|
655
|
-
|
|
725
|
+
function slugToCamel(string) {
|
|
726
|
+
return decapitalize(string.split('-').map(capitalize).join(''))
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
function joinHumanly(parts, separator = ', ', lastSeparator = ' and ') {
|
|
656
730
|
if (!parts || !parts.length) {
|
|
657
731
|
return null
|
|
658
732
|
}
|
|
@@ -665,16 +739,23 @@ const joinHumanly = (parts, separator = ', ', lastSeparator = ' and ') => {
|
|
|
665
739
|
return `${parts.slice(0, parts.length - 1).join(separator)}${lastSeparator}${parts[parts.length - 1]}`
|
|
666
740
|
}
|
|
667
741
|
|
|
668
|
-
|
|
742
|
+
function surroundInOut(string, filler) {
|
|
743
|
+
return filler + string.split('').join(filler) + filler
|
|
744
|
+
}
|
|
669
745
|
|
|
670
|
-
|
|
746
|
+
function enumify(string) {
|
|
747
|
+
return slugify(string).replace(/-/g, '_').toUpperCase()
|
|
748
|
+
}
|
|
671
749
|
|
|
672
|
-
|
|
750
|
+
function getFuzzyMatchScore(string, input) {
|
|
673
751
|
if (input.length === 0) {
|
|
674
752
|
return 0
|
|
675
753
|
}
|
|
676
754
|
const lowercaseString = string.toLowerCase()
|
|
677
755
|
const lowercaseInput = input.toLowerCase()
|
|
756
|
+
if (string === input) {
|
|
757
|
+
return 10000
|
|
758
|
+
}
|
|
678
759
|
if (lowercaseString.startsWith(lowercaseInput)) {
|
|
679
760
|
return 10000 - string.length
|
|
680
761
|
}
|
|
@@ -685,12 +766,15 @@ const getFuzzyMatchScore = (string, input) => {
|
|
|
685
766
|
return regex.test(lowercaseString) ? 1000 - string.length : 0
|
|
686
767
|
}
|
|
687
768
|
|
|
688
|
-
|
|
689
|
-
strings
|
|
769
|
+
function sortByFuzzyScore(strings, input) {
|
|
770
|
+
return strings
|
|
690
771
|
.filter(a => getFuzzyMatchScore(a, input))
|
|
691
772
|
.sort((a, b) => getFuzzyMatchScore(b, input) - getFuzzyMatchScore(a, input))
|
|
773
|
+
}
|
|
692
774
|
|
|
693
|
-
|
|
775
|
+
function escapeHtml(string) {
|
|
776
|
+
return string.replace(/\</g, '<').replace('/>/g', '>')
|
|
777
|
+
}
|
|
694
778
|
|
|
695
779
|
const htmlEntityMap = {
|
|
696
780
|
'"': '"',
|
|
@@ -698,80 +782,104 @@ const htmlEntityMap = {
|
|
|
698
782
|
'<': '<'
|
|
699
783
|
}
|
|
700
784
|
|
|
701
|
-
|
|
702
|
-
let buffer = string.replace(/&#(\d+);/g, (
|
|
785
|
+
function decodeHtmlEntities(string) {
|
|
786
|
+
let buffer = string.replace(/&#(\d+);/g, (_, dec) => String.fromCharCode(dec))
|
|
703
787
|
for (const key of Object.keys(htmlEntityMap)) {
|
|
704
788
|
buffer = buffer.split(key).join(htmlEntityMap[key])
|
|
705
789
|
}
|
|
706
790
|
return buffer
|
|
707
791
|
}
|
|
708
792
|
|
|
709
|
-
|
|
793
|
+
function before(string, searchString) {
|
|
710
794
|
const position = string.indexOf(searchString)
|
|
711
795
|
return position === -1 ? string : string.slice(0, position)
|
|
712
796
|
}
|
|
713
797
|
|
|
714
|
-
|
|
798
|
+
function after(string, searchString) {
|
|
715
799
|
const position = string.indexOf(searchString)
|
|
716
800
|
return position === -1 ? string : string.slice(position + searchString.length)
|
|
717
801
|
}
|
|
718
802
|
|
|
719
|
-
|
|
803
|
+
function beforeLast(string, searchString) {
|
|
720
804
|
const position = string.lastIndexOf(searchString)
|
|
721
805
|
return position === -1 ? string : string.slice(0, position)
|
|
722
806
|
}
|
|
723
807
|
|
|
724
|
-
|
|
808
|
+
function afterLast(string, searchString) {
|
|
725
809
|
const position = string.lastIndexOf(searchString)
|
|
726
810
|
return position === -1 ? string : string.slice(position + searchString.length)
|
|
727
811
|
}
|
|
728
812
|
|
|
729
|
-
|
|
813
|
+
function between(string, start, end) {
|
|
814
|
+
return before(after(string, start), end)
|
|
815
|
+
}
|
|
730
816
|
|
|
731
|
-
|
|
817
|
+
function betweenWide(string, start, end) {
|
|
818
|
+
return after(beforeLast(string, end), start)
|
|
819
|
+
}
|
|
732
820
|
|
|
733
|
-
|
|
821
|
+
function betweenNarrow(string, start, end) {
|
|
822
|
+
return before(after(string, start), end)
|
|
823
|
+
}
|
|
734
824
|
|
|
735
|
-
|
|
736
|
-
string.includes(separator) ? [before(string, separator), after(string, separator)] : [string, '']
|
|
825
|
+
function splitOnce(string, separator) {
|
|
826
|
+
return string.includes(separator) ? [before(string, separator), after(string, separator)] : [string, '']
|
|
827
|
+
}
|
|
737
828
|
|
|
738
|
-
|
|
739
|
-
const name = last(
|
|
740
|
-
const lastIndex = name.lastIndexOf('.')
|
|
829
|
+
function getExtension(path) {
|
|
830
|
+
const name = last(path.split(/\\|\//g))
|
|
831
|
+
const lastIndex = name.lastIndexOf('.', name.length - 1)
|
|
741
832
|
return lastIndex <= 0 ? '' : name.slice(lastIndex + 1)
|
|
742
833
|
}
|
|
743
834
|
|
|
744
|
-
|
|
745
|
-
const name = last(
|
|
746
|
-
const
|
|
747
|
-
return
|
|
835
|
+
function getBasename(path) {
|
|
836
|
+
const name = last(path.split(/\\|\//g))
|
|
837
|
+
const lastIndex = name.lastIndexOf('.', name.length - 1)
|
|
838
|
+
return lastIndex <= 0 ? name : name.slice(0, lastIndex)
|
|
748
839
|
}
|
|
749
840
|
|
|
750
|
-
|
|
841
|
+
function normalizeFilename(path) {
|
|
842
|
+
const basename = getBasename(path)
|
|
843
|
+
const extension = getExtension(path)
|
|
844
|
+
return extension ? `${basename}.${extension}` : basename
|
|
845
|
+
}
|
|
751
846
|
|
|
752
|
-
|
|
847
|
+
function parseFilename(string) {
|
|
753
848
|
const basename = getBasename(string)
|
|
754
849
|
const extension = getExtension(string)
|
|
755
850
|
return {
|
|
756
851
|
basename,
|
|
757
852
|
extension,
|
|
758
|
-
filename: `${basename}.${extension}`
|
|
853
|
+
filename: extension ? `${basename}.${extension}` : basename
|
|
759
854
|
}
|
|
760
855
|
}
|
|
761
856
|
|
|
762
|
-
|
|
857
|
+
function randomize(string) {
|
|
858
|
+
return string.replace(/\{(.+?)\}/g, (_, group) => pick(group.split('|')))
|
|
859
|
+
}
|
|
763
860
|
|
|
764
|
-
|
|
861
|
+
function shrinkTrim(string) {
|
|
862
|
+
return string.replace(/\s+/g, ' ').replace(/\s$|^\s/g, '')
|
|
863
|
+
}
|
|
765
864
|
|
|
766
|
-
|
|
865
|
+
function capitalize(string) {
|
|
866
|
+
return string.charAt(0).toUpperCase() + string.slice(1)
|
|
867
|
+
}
|
|
767
868
|
|
|
768
|
-
|
|
869
|
+
function decapitalize(string) {
|
|
870
|
+
return string.charAt(0).toLowerCase() + string.slice(1)
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
function csvEscape(string) {
|
|
874
|
+
return string.match(/"|,/) ? `"${string.replace(/"/g, '""')}"` : string
|
|
875
|
+
}
|
|
769
876
|
|
|
770
|
-
|
|
877
|
+
function parseCsv(string, delimiter = ',', quote = '"') {
|
|
771
878
|
const items = []
|
|
772
879
|
let buffer = ''
|
|
773
880
|
let escaped = false
|
|
774
|
-
|
|
881
|
+
const characters = string.split('')
|
|
882
|
+
for (const character of characters) {
|
|
775
883
|
if (character === delimiter && !escaped) {
|
|
776
884
|
items.push(buffer)
|
|
777
885
|
buffer = ''
|
|
@@ -785,23 +893,13 @@ const parseCsv = (string, delimiter = ',', quote = '"') => {
|
|
|
785
893
|
return items
|
|
786
894
|
}
|
|
787
895
|
|
|
788
|
-
|
|
789
|
-
`[${Math.floor(state.progress * 100)}%] ${humanizeTime(state.deltaMs)} out of ${humanizeTime(
|
|
896
|
+
function humanizeProgress(state) {
|
|
897
|
+
return `[${Math.floor(state.progress * 100)}%] ${humanizeTime(state.deltaMs)} out of ${humanizeTime(
|
|
790
898
|
state.totalTimeMs
|
|
791
899
|
)} (${humanizeTime(state.remainingTimeMs)} left) [${Math.round(state.baseTimeMs)} ms each]`
|
|
792
|
-
|
|
793
|
-
const expectThrow = async (name, message, callable) => {
|
|
794
|
-
try {
|
|
795
|
-
await callable()
|
|
796
|
-
throw new Error('Expected ' + name + ' ' + message + ' but everything went fine')
|
|
797
|
-
} catch (error) {
|
|
798
|
-
if (error.name !== name || error.message !== message) {
|
|
799
|
-
throw new Error('Expected ' + name + ' ' + message + ' but caught ' + error.name + ' ' + error.message)
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
900
|
}
|
|
803
901
|
|
|
804
|
-
|
|
902
|
+
async function waitFor(predicate, waitLength, maxWaits) {
|
|
805
903
|
for (let i = 0; i < maxWaits; i++) {
|
|
806
904
|
try {
|
|
807
905
|
if (await predicate()) {
|
|
@@ -815,7 +913,7 @@ const waitFor = async (predicate, waitLength, maxWaits) => {
|
|
|
815
913
|
return false
|
|
816
914
|
}
|
|
817
915
|
|
|
818
|
-
|
|
916
|
+
async function mkdirp(path) {
|
|
819
917
|
const segments = path.split('/')
|
|
820
918
|
let buffer = ''
|
|
821
919
|
for (const segment of segments) {
|
|
@@ -832,7 +930,7 @@ const mkdirp = async path => {
|
|
|
832
930
|
}
|
|
833
931
|
}
|
|
834
932
|
|
|
835
|
-
|
|
933
|
+
function filterAndRemove(array, predicate) {
|
|
836
934
|
const results = []
|
|
837
935
|
for (let i = array.length - 1; i >= 0; i--) {
|
|
838
936
|
if (predicate(array[i])) {
|
|
@@ -842,8 +940,8 @@ const filterAndRemove = (array, predicate) => {
|
|
|
842
940
|
return results
|
|
843
941
|
}
|
|
844
942
|
|
|
845
|
-
|
|
846
|
-
new Promise((resolve, reject) => {
|
|
943
|
+
async function execAsync(command, resolveWithErrors, inherit, options) {
|
|
944
|
+
return new Promise((resolve, reject) => {
|
|
847
945
|
const childProcess = ChildProcess.exec(command, options, (error, stdout, stderr) => {
|
|
848
946
|
if (error) {
|
|
849
947
|
if (resolveWithErrors) {
|
|
@@ -856,16 +954,17 @@ const execAsync = async (command, resolveWithErrors, inherit, options) =>
|
|
|
856
954
|
}
|
|
857
955
|
})
|
|
858
956
|
if (inherit) {
|
|
859
|
-
childProcess.stdout.pipe(process.stdout)
|
|
860
|
-
childProcess.stderr.pipe(process.stderr)
|
|
957
|
+
childProcess.stdout && childProcess.stdout.pipe(process.stdout)
|
|
958
|
+
childProcess.stderr && childProcess.stderr.pipe(process.stderr)
|
|
861
959
|
}
|
|
862
960
|
})
|
|
961
|
+
}
|
|
863
962
|
|
|
864
|
-
|
|
865
|
-
new Promise((resolve, reject) => {
|
|
866
|
-
const subprocess = ChildProcess.spawn(command, args)
|
|
867
|
-
subprocess
|
|
868
|
-
subprocess
|
|
963
|
+
async function runProcess(command, args, options, onStdout, onStderr) {
|
|
964
|
+
return new Promise((resolve, reject) => {
|
|
965
|
+
const subprocess = ChildProcess.spawn(command, args, options)
|
|
966
|
+
subprocess?.stdout?.on('data', onStdout)
|
|
967
|
+
subprocess?.stderr?.on('data', onStderr)
|
|
869
968
|
subprocess.on('close', code => {
|
|
870
969
|
if (code === 0) {
|
|
871
970
|
resolve(code)
|
|
@@ -874,23 +973,34 @@ const runProcess = async (command, args, onStdout, onStderr) =>
|
|
|
874
973
|
}
|
|
875
974
|
})
|
|
876
975
|
})
|
|
976
|
+
}
|
|
877
977
|
|
|
878
|
-
|
|
978
|
+
function cloneWithJson(a) {
|
|
979
|
+
return JSON.parse(JSON.stringify(a))
|
|
980
|
+
}
|
|
879
981
|
|
|
880
|
-
|
|
982
|
+
function unixTimestamp(optionalTimestamp) {
|
|
983
|
+
return Math.ceil((optionalTimestamp || Date.now()) / 1000)
|
|
984
|
+
}
|
|
881
985
|
|
|
882
|
-
|
|
986
|
+
function isoDate(optionalDate) {
|
|
987
|
+
return (optionalDate || new Date()).toISOString().slice(0, 10)
|
|
988
|
+
}
|
|
883
989
|
|
|
884
|
-
|
|
990
|
+
function dateTimeSlug(optionalDate) {
|
|
991
|
+
return (optionalDate || new Date()).toISOString().slice(0, 19).replace(/T|:/g, '-')
|
|
992
|
+
}
|
|
885
993
|
|
|
886
|
-
|
|
994
|
+
function fromUtcString(string) {
|
|
887
995
|
const date = new Date(string)
|
|
888
996
|
return new Date(date.getTime() - date.getTimezoneOffset() * 60000)
|
|
889
997
|
}
|
|
890
998
|
|
|
891
|
-
|
|
999
|
+
function createTimeDigits(value) {
|
|
1000
|
+
return String(Math.floor(value)).padStart(2, '0')
|
|
1001
|
+
}
|
|
892
1002
|
|
|
893
|
-
|
|
1003
|
+
function humanizeTime(millis) {
|
|
894
1004
|
let seconds = Math.floor(millis / 1000)
|
|
895
1005
|
if (seconds < 60) {
|
|
896
1006
|
return `${seconds}s`
|
|
@@ -905,8 +1015,10 @@ const humanizeTime = millis => {
|
|
|
905
1015
|
return `${createTimeDigits(hours)}:${createTimeDigits(minutes)}:${createTimeDigits(seconds)}`
|
|
906
1016
|
}
|
|
907
1017
|
|
|
908
|
-
|
|
909
|
-
|
|
1018
|
+
function getAgo(date, now) {
|
|
1019
|
+
if (!now) {
|
|
1020
|
+
now = Date.now()
|
|
1021
|
+
}
|
|
910
1022
|
const then = date.getTime()
|
|
911
1023
|
let delta = (now - then) / 1000
|
|
912
1024
|
if (delta < 10) {
|
|
@@ -927,7 +1039,7 @@ const getAgo = date => {
|
|
|
927
1039
|
return delta.toFixed(0) + ' days ago'
|
|
928
1040
|
}
|
|
929
1041
|
|
|
930
|
-
|
|
1042
|
+
function debounce(longWrapper, millis) {
|
|
931
1043
|
if (Date.now() > longWrapper.value) {
|
|
932
1044
|
longWrapper.value = Date.now() + millis
|
|
933
1045
|
return true
|
|
@@ -942,15 +1054,18 @@ const timeUnits = {
|
|
|
942
1054
|
d: 86400000
|
|
943
1055
|
}
|
|
944
1056
|
|
|
945
|
-
|
|
1057
|
+
function timeSince(unit, a, optionalB) {
|
|
946
1058
|
a = isDate(a) ? a.getTime() : a
|
|
947
1059
|
optionalB = optionalB ? (isDate(optionalB) ? optionalB.getTime() : optionalB) : Date.now()
|
|
948
1060
|
return (optionalB - a) / timeUnits[unit]
|
|
949
1061
|
}
|
|
950
1062
|
|
|
951
|
-
|
|
1063
|
+
function getProgress(startedAt, current, total, now) {
|
|
1064
|
+
if (!now) {
|
|
1065
|
+
now = Date.now()
|
|
1066
|
+
}
|
|
952
1067
|
const progress = current / total
|
|
953
|
-
const deltaMs =
|
|
1068
|
+
const deltaMs = now - startedAt
|
|
954
1069
|
const baseTimeMs = deltaMs / current
|
|
955
1070
|
const totalTimeMs = baseTimeMs * total
|
|
956
1071
|
const remainingTimeMs = totalTimeMs - deltaMs
|
|
@@ -973,23 +1088,31 @@ const dayNumberIndex = {
|
|
|
973
1088
|
6: 'saturday'
|
|
974
1089
|
}
|
|
975
1090
|
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
1091
|
+
function mapDayNumber(zeroBasedIndex) {
|
|
1092
|
+
return {
|
|
1093
|
+
zeroBasedIndex,
|
|
1094
|
+
day: dayNumberIndex[zeroBasedIndex]
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
980
1097
|
|
|
981
|
-
|
|
1098
|
+
function getDayInfoFromDate(date) {
|
|
1099
|
+
return mapDayNumber(date.getDay())
|
|
1100
|
+
}
|
|
982
1101
|
|
|
983
|
-
|
|
1102
|
+
function getDayInfoFromDateTimeString(dateTimeString) {
|
|
1103
|
+
return getDayInfoFromDate(new Date(dateTimeString))
|
|
1104
|
+
}
|
|
984
1105
|
|
|
985
|
-
|
|
1106
|
+
function getPreLine(string) {
|
|
1107
|
+
return string.replace(/ +/g, ' ').replace(/^ /gm, '')
|
|
1108
|
+
}
|
|
986
1109
|
|
|
987
|
-
|
|
1110
|
+
function containsWord(string, word) {
|
|
988
1111
|
const slug = slugify(string)
|
|
989
1112
|
return slug.startsWith(word + '-') || slug.endsWith('-' + word) || slug.includes('-' + word + '-') || slug === word
|
|
990
1113
|
}
|
|
991
1114
|
|
|
992
|
-
|
|
1115
|
+
function containsWords(string, words) {
|
|
993
1116
|
for (const word of words) {
|
|
994
1117
|
if (containsWord(string, word)) {
|
|
995
1118
|
return true
|
|
@@ -999,8 +1122,7 @@ const containsWords = (string, words) => {
|
|
|
999
1122
|
}
|
|
1000
1123
|
|
|
1001
1124
|
const tinyCache = {}
|
|
1002
|
-
|
|
1003
|
-
const getCached = async (key, ttlMillis, handler) => {
|
|
1125
|
+
async function getCached(key, ttlMillis, handler) {
|
|
1004
1126
|
const now = Date.now()
|
|
1005
1127
|
const existing = tinyCache[key]
|
|
1006
1128
|
if (existing && existing.validUntil > now) {
|
|
@@ -1015,7 +1137,7 @@ const getCached = async (key, ttlMillis, handler) => {
|
|
|
1015
1137
|
return value
|
|
1016
1138
|
}
|
|
1017
1139
|
|
|
1018
|
-
|
|
1140
|
+
function joinUrl(...parts) {
|
|
1019
1141
|
let url = parts[0][parts[0].length - 1] === '/' ? parts[0].slice(0, -1) : parts[0]
|
|
1020
1142
|
for (let i = 1; i < parts.length; i++) {
|
|
1021
1143
|
const part = parts[i]
|
|
@@ -1029,7 +1151,7 @@ const joinUrl = (...parts) => {
|
|
|
1029
1151
|
return url
|
|
1030
1152
|
}
|
|
1031
1153
|
|
|
1032
|
-
|
|
1154
|
+
function sortObject(object) {
|
|
1033
1155
|
const keys = Object.keys(object)
|
|
1034
1156
|
const orderedKeys = keys.sort((a, b) => a.localeCompare(b))
|
|
1035
1157
|
const sorted = {}
|
|
@@ -1039,7 +1161,7 @@ const sortObject = object => {
|
|
|
1039
1161
|
return sorted
|
|
1040
1162
|
}
|
|
1041
1163
|
|
|
1042
|
-
|
|
1164
|
+
function sortArray(array) {
|
|
1043
1165
|
const result = []
|
|
1044
1166
|
array
|
|
1045
1167
|
.sort((a, b) => JSON.stringify(sortAny(a)).localeCompare(JSON.stringify(sortAny(b))))
|
|
@@ -1047,7 +1169,7 @@ const sortArray = array => {
|
|
|
1047
1169
|
return result
|
|
1048
1170
|
}
|
|
1049
1171
|
|
|
1050
|
-
|
|
1172
|
+
function sortAny(any) {
|
|
1051
1173
|
if (Array.isArray(any)) {
|
|
1052
1174
|
return sortArray(any)
|
|
1053
1175
|
}
|
|
@@ -1057,9 +1179,11 @@ const sortAny = any => {
|
|
|
1057
1179
|
return any
|
|
1058
1180
|
}
|
|
1059
1181
|
|
|
1060
|
-
|
|
1182
|
+
function deepEquals(a, b) {
|
|
1183
|
+
return JSON.stringify(sortAny(a)) === JSON.stringify(sortAny(b))
|
|
1184
|
+
}
|
|
1061
1185
|
|
|
1062
|
-
|
|
1186
|
+
function safeParse(stringable) {
|
|
1063
1187
|
try {
|
|
1064
1188
|
return JSON.parse(stringable)
|
|
1065
1189
|
} catch (error) {
|
|
@@ -1087,23 +1211,25 @@ const longNumberUnits = [
|
|
|
1087
1211
|
'decillion'
|
|
1088
1212
|
]
|
|
1089
1213
|
const shortNumberUnits = ['K', 'M', 'B', 't', 'q', 'Q', 's', 'S', 'o', 'n', 'd']
|
|
1090
|
-
|
|
1091
|
-
const
|
|
1092
|
-
const
|
|
1214
|
+
function formatNumber(number, options) {
|
|
1215
|
+
const longFormat = options?.longForm ?? false
|
|
1216
|
+
const unitString = options?.unit ? ` ${options.unit}` : ''
|
|
1217
|
+
const table = longFormat ? longNumberUnits : shortNumberUnits
|
|
1218
|
+
const precision = options?.precision ?? 1
|
|
1093
1219
|
if (number < thresholds[0]) {
|
|
1094
|
-
return number
|
|
1220
|
+
return `${number}${unitString}`
|
|
1095
1221
|
}
|
|
1096
1222
|
for (let i = 0; i < thresholds.length - 1; i++) {
|
|
1097
1223
|
if (number < thresholds[i + 1]) {
|
|
1098
|
-
return `${(number / thresholds[i]).toFixed(precision)}${
|
|
1224
|
+
return `${(number / thresholds[i]).toFixed(precision)}${longFormat ? ' ' : ''}${table[i]}${unitString}`
|
|
1099
1225
|
}
|
|
1100
1226
|
}
|
|
1101
|
-
return `${(number / thresholds[thresholds.length - 1]).toFixed(precision)}${
|
|
1227
|
+
return `${(number / thresholds[thresholds.length - 1]).toFixed(precision)}${longFormat ? ' ' : ''}${
|
|
1102
1228
|
table[thresholds.length - 1]
|
|
1103
|
-
}`
|
|
1229
|
+
}${unitString}`
|
|
1104
1230
|
}
|
|
1105
1231
|
|
|
1106
|
-
|
|
1232
|
+
function parseIntOrThrow(numberOrString) {
|
|
1107
1233
|
if (typeof numberOrString === 'number') {
|
|
1108
1234
|
if (isNaN(numberOrString)) {
|
|
1109
1235
|
throw Error('parseIntOrThrow got NaN for input')
|
|
@@ -1123,19 +1249,21 @@ const parseIntOrThrow = numberOrString => {
|
|
|
1123
1249
|
throw Error('parseIntOrThrow got unsupported input type: ' + typeof numberOrString)
|
|
1124
1250
|
}
|
|
1125
1251
|
|
|
1126
|
-
|
|
1252
|
+
function clamp(value, lower, upper) {
|
|
1253
|
+
return value < lower ? lower : value > upper ? upper : value
|
|
1254
|
+
}
|
|
1127
1255
|
|
|
1128
|
-
|
|
1256
|
+
function increment(value, change, maximum) {
|
|
1129
1257
|
const result = value + change
|
|
1130
1258
|
return result > maximum ? maximum : result
|
|
1131
1259
|
}
|
|
1132
1260
|
|
|
1133
|
-
|
|
1261
|
+
function decrement(value, change, minimum) {
|
|
1134
1262
|
const result = value - change
|
|
1135
1263
|
return result < minimum ? minimum : result
|
|
1136
1264
|
}
|
|
1137
1265
|
|
|
1138
|
-
|
|
1266
|
+
function getHeapMegabytes() {
|
|
1139
1267
|
const memory = process.memoryUsage()
|
|
1140
1268
|
return {
|
|
1141
1269
|
used: (memory.heapUsed / 1024 / 1024).toFixed(3),
|
|
@@ -1144,18 +1272,18 @@ const getHeapMegabytes = () => {
|
|
|
1144
1272
|
}
|
|
1145
1273
|
}
|
|
1146
1274
|
|
|
1147
|
-
|
|
1275
|
+
function runOn(object, callable) {
|
|
1148
1276
|
callable(object)
|
|
1149
1277
|
return object
|
|
1150
1278
|
}
|
|
1151
1279
|
|
|
1152
|
-
|
|
1280
|
+
function ifPresent(object, callable) {
|
|
1153
1281
|
if (object) {
|
|
1154
1282
|
callable(object)
|
|
1155
1283
|
}
|
|
1156
1284
|
}
|
|
1157
1285
|
|
|
1158
|
-
|
|
1286
|
+
function mergeArrays(target, source) {
|
|
1159
1287
|
const keys = Object.keys(source)
|
|
1160
1288
|
for (const key of keys) {
|
|
1161
1289
|
if (Array.isArray(source[key]) && Array.isArray(target[key])) {
|
|
@@ -1164,28 +1292,30 @@ const mergeArrays = (target, source) => {
|
|
|
1164
1292
|
}
|
|
1165
1293
|
}
|
|
1166
1294
|
|
|
1167
|
-
|
|
1295
|
+
function empty(array) {
|
|
1168
1296
|
array.splice(0, array.length)
|
|
1169
1297
|
return array
|
|
1170
1298
|
}
|
|
1171
1299
|
|
|
1172
|
-
|
|
1300
|
+
function removeEmptyArrays(object) {
|
|
1173
1301
|
for (const key of Object.keys(object)) {
|
|
1174
|
-
if (
|
|
1302
|
+
if (isEmptyArray(object[key])) {
|
|
1175
1303
|
delete object[key]
|
|
1176
1304
|
}
|
|
1177
1305
|
}
|
|
1306
|
+
return object
|
|
1178
1307
|
}
|
|
1179
1308
|
|
|
1180
|
-
|
|
1309
|
+
function removeEmptyValues(object) {
|
|
1181
1310
|
for (const entry of Object.entries(object)) {
|
|
1182
1311
|
if (isUndefined(entry[1]) || entry[1] === null || (isString(entry[1]) && isBlank(entry[1]))) {
|
|
1183
1312
|
delete object[entry[0]]
|
|
1184
1313
|
}
|
|
1185
1314
|
}
|
|
1315
|
+
return object
|
|
1186
1316
|
}
|
|
1187
1317
|
|
|
1188
|
-
|
|
1318
|
+
function mapObject(object, mapper) {
|
|
1189
1319
|
const output = {}
|
|
1190
1320
|
for (const entry of Object.entries(object)) {
|
|
1191
1321
|
output[entry[0]] = mapper(entry[1])
|
|
@@ -1193,7 +1323,7 @@ const mapObject = (object, mapper) => {
|
|
|
1193
1323
|
return output
|
|
1194
1324
|
}
|
|
1195
1325
|
|
|
1196
|
-
|
|
1326
|
+
async function rethrow(asyncFn, throwable) {
|
|
1197
1327
|
try {
|
|
1198
1328
|
const returnValue = await asyncFn()
|
|
1199
1329
|
return returnValue
|
|
@@ -1202,13 +1332,13 @@ const rethrow = async (asyncFn, throwable) => {
|
|
|
1202
1332
|
}
|
|
1203
1333
|
}
|
|
1204
1334
|
|
|
1205
|
-
|
|
1335
|
+
function setSomeOnObject(object, key, value) {
|
|
1206
1336
|
if (typeof value !== 'undefined' && value !== null) {
|
|
1207
1337
|
object[key] = value
|
|
1208
1338
|
}
|
|
1209
1339
|
}
|
|
1210
1340
|
|
|
1211
|
-
|
|
1341
|
+
function flip(object) {
|
|
1212
1342
|
const result = {}
|
|
1213
1343
|
for (const [key, value] of Object.entries(object)) {
|
|
1214
1344
|
result[value] = key
|
|
@@ -1216,7 +1346,7 @@ const flip = object => {
|
|
|
1216
1346
|
return result
|
|
1217
1347
|
}
|
|
1218
1348
|
|
|
1219
|
-
|
|
1349
|
+
function getAllPermutations(object) {
|
|
1220
1350
|
const keys = Object.keys(object)
|
|
1221
1351
|
const lengths = keys.map(key => object[key].length)
|
|
1222
1352
|
const count = lengths.reduce((previous, current) => (previous *= current))
|
|
@@ -1239,14 +1369,15 @@ const crossJoin = object => {
|
|
|
1239
1369
|
return results
|
|
1240
1370
|
}
|
|
1241
1371
|
|
|
1242
|
-
|
|
1372
|
+
function countTruthyValues(object) {
|
|
1243
1373
|
return Object.values(object).filter(x => x).length
|
|
1244
1374
|
}
|
|
1245
1375
|
|
|
1246
|
-
|
|
1247
|
-
prefix + (bracket ? '[' + key + ']' : (prefix.length ? '.' : '') + key)
|
|
1376
|
+
function getFlatNotation(prefix, key, bracket) {
|
|
1377
|
+
return prefix + (bracket ? '[' + key + ']' : (prefix.length ? '.' : '') + key)
|
|
1378
|
+
}
|
|
1248
1379
|
|
|
1249
|
-
|
|
1380
|
+
function flattenInner(target, object, prefix, bracket, arrays) {
|
|
1250
1381
|
if (!isObject(object)) {
|
|
1251
1382
|
return object
|
|
1252
1383
|
}
|
|
@@ -1269,11 +1400,11 @@ const flattenInner = (target, object, prefix, bracket, arrays) => {
|
|
|
1269
1400
|
return target
|
|
1270
1401
|
}
|
|
1271
1402
|
|
|
1272
|
-
|
|
1403
|
+
function flatten(object, arrays = false, prefix) {
|
|
1273
1404
|
return flattenInner({}, object, prefix || '', false, arrays)
|
|
1274
1405
|
}
|
|
1275
1406
|
|
|
1276
|
-
|
|
1407
|
+
function unflatten(object) {
|
|
1277
1408
|
if (!isObject(object)) {
|
|
1278
1409
|
return object
|
|
1279
1410
|
}
|
|
@@ -1292,9 +1423,11 @@ const unflatten = object => {
|
|
|
1292
1423
|
return target
|
|
1293
1424
|
}
|
|
1294
1425
|
|
|
1295
|
-
|
|
1426
|
+
function match(value, options, fallback) {
|
|
1427
|
+
return options[value] ? options[value] : fallback
|
|
1428
|
+
}
|
|
1296
1429
|
|
|
1297
|
-
|
|
1430
|
+
function indexArray(array, keyFn, useArrays) {
|
|
1298
1431
|
const target = {}
|
|
1299
1432
|
for (const element of array) {
|
|
1300
1433
|
const key = keyFn(element)
|
|
@@ -1310,7 +1443,7 @@ const indexArray = (array, keyFn, useArrays) => {
|
|
|
1310
1443
|
return target
|
|
1311
1444
|
}
|
|
1312
1445
|
|
|
1313
|
-
|
|
1446
|
+
function splitBySize(array, size) {
|
|
1314
1447
|
const batches = []
|
|
1315
1448
|
for (let i = 0; i < array.length; i += size) {
|
|
1316
1449
|
batches.push(array.slice(i, i + size))
|
|
@@ -1318,7 +1451,7 @@ const splitBySize = (array, size) => {
|
|
|
1318
1451
|
return batches
|
|
1319
1452
|
}
|
|
1320
1453
|
|
|
1321
|
-
|
|
1454
|
+
function splitByCount(array, count) {
|
|
1322
1455
|
const size = Math.ceil(array.length / count)
|
|
1323
1456
|
const batches = []
|
|
1324
1457
|
for (let i = 0; i < array.length; i += size) {
|
|
@@ -1327,7 +1460,7 @@ const splitByCount = (array, count) => {
|
|
|
1327
1460
|
return batches
|
|
1328
1461
|
}
|
|
1329
1462
|
|
|
1330
|
-
|
|
1463
|
+
function tokenizeByLength(string, length) {
|
|
1331
1464
|
const parts = []
|
|
1332
1465
|
const count = Math.ceil(string.length / length)
|
|
1333
1466
|
for (let i = 0; i < count; i++) {
|
|
@@ -1336,14 +1469,16 @@ const tokenizeByLength = (string, length) => {
|
|
|
1336
1469
|
return parts
|
|
1337
1470
|
}
|
|
1338
1471
|
|
|
1339
|
-
|
|
1472
|
+
function tokenizeByCount(string, count) {
|
|
1340
1473
|
const length = Math.ceil(string.length / count)
|
|
1341
1474
|
return tokenizeByLength(string, length)
|
|
1342
1475
|
}
|
|
1343
1476
|
|
|
1344
|
-
|
|
1477
|
+
function makeUnique(array, fn) {
|
|
1478
|
+
return Object.values(indexArray(array, fn, false))
|
|
1479
|
+
}
|
|
1345
1480
|
|
|
1346
|
-
|
|
1481
|
+
function countUnique(array, mapper, plain, sort, reverse) {
|
|
1347
1482
|
const mapped = mapper ? array.map(mapper) : array
|
|
1348
1483
|
const object = {}
|
|
1349
1484
|
for (const item of mapped) {
|
|
@@ -1356,9 +1491,11 @@ const countUnique = (array, mapper, plain, sort, reverse) => {
|
|
|
1356
1491
|
return sorted
|
|
1357
1492
|
}
|
|
1358
1493
|
|
|
1359
|
-
|
|
1494
|
+
function sortObjectValues(object, compareFn) {
|
|
1495
|
+
return Object.fromEntries(Object.entries(object).sort(compareFn))
|
|
1496
|
+
}
|
|
1360
1497
|
|
|
1361
|
-
|
|
1498
|
+
function transformToArray(objectOfArrays) {
|
|
1362
1499
|
const array = []
|
|
1363
1500
|
const keys = Object.keys(objectOfArrays)
|
|
1364
1501
|
const length = objectOfArrays[keys[0]].length
|
|
@@ -1372,21 +1509,23 @@ const transformToArray = objectOfArrays => {
|
|
|
1372
1509
|
return array
|
|
1373
1510
|
}
|
|
1374
1511
|
|
|
1375
|
-
|
|
1512
|
+
function incrementMulti(objects, key, step = 1) {
|
|
1376
1513
|
for (const object of objects) {
|
|
1377
1514
|
object[key] += step
|
|
1378
1515
|
}
|
|
1379
1516
|
}
|
|
1380
1517
|
|
|
1381
|
-
|
|
1518
|
+
function setMulti(objects, key, value) {
|
|
1382
1519
|
for (const object of objects) {
|
|
1383
1520
|
object[key] = value
|
|
1384
1521
|
}
|
|
1385
1522
|
}
|
|
1386
1523
|
|
|
1387
|
-
|
|
1524
|
+
function createFastIndex() {
|
|
1525
|
+
return { index: {}, keys: [] }
|
|
1526
|
+
}
|
|
1388
1527
|
|
|
1389
|
-
|
|
1528
|
+
function pushToFastIndex(object, key, item, limit = 100) {
|
|
1390
1529
|
if (object.index[key]) {
|
|
1391
1530
|
const index = object.keys.indexOf(key)
|
|
1392
1531
|
object.keys.splice(index, 1)
|
|
@@ -1395,15 +1534,15 @@ const pushToFastIndex = (object, key, item, limit = 100) => {
|
|
|
1395
1534
|
object.keys.push(key)
|
|
1396
1535
|
if (object.keys.length > limit) {
|
|
1397
1536
|
const oldKey = object.keys.shift()
|
|
1398
|
-
delete object.index[oldKey]
|
|
1537
|
+
oldKey && delete object.index[oldKey]
|
|
1399
1538
|
}
|
|
1400
1539
|
}
|
|
1401
1540
|
|
|
1402
|
-
|
|
1541
|
+
function pushToFastIndexWithExpiracy(object, key, item, expiration, limit = 100) {
|
|
1403
1542
|
pushToFastIndex(object, key, { validUntil: Date.now() + expiration, data: item }, limit)
|
|
1404
1543
|
}
|
|
1405
1544
|
|
|
1406
|
-
|
|
1545
|
+
function getFromFastIndexWithExpiracy(object, key) {
|
|
1407
1546
|
const item = object.index[key]
|
|
1408
1547
|
if (item && item.validUntil > Date.now()) {
|
|
1409
1548
|
return item.data
|
|
@@ -1411,213 +1550,281 @@ const getFromFastIndexWithExpiracy = (object, key) => {
|
|
|
1411
1550
|
return null
|
|
1412
1551
|
}
|
|
1413
1552
|
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
pushAll,
|
|
1441
|
-
unshiftAll,
|
|
1442
|
-
filterAndRemove,
|
|
1443
|
-
merge: mergeArrays,
|
|
1444
|
-
empty,
|
|
1445
|
-
pushToBucket,
|
|
1446
|
-
unshiftAndLimit,
|
|
1447
|
-
atRolling
|
|
1448
|
-
},
|
|
1449
|
-
System: {
|
|
1450
|
-
sleepMillis,
|
|
1451
|
-
forever,
|
|
1452
|
-
scheduleMany,
|
|
1453
|
-
waitFor,
|
|
1454
|
-
execAsync,
|
|
1455
|
-
getHeapMegabytes,
|
|
1456
|
-
expandError,
|
|
1457
|
-
runProcess
|
|
1458
|
-
},
|
|
1459
|
-
Numbers: {
|
|
1460
|
-
sum,
|
|
1461
|
-
average,
|
|
1462
|
-
clamp,
|
|
1463
|
-
range,
|
|
1464
|
-
interpolate,
|
|
1465
|
-
createSequence,
|
|
1466
|
-
increment,
|
|
1467
|
-
decrement,
|
|
1468
|
-
prettify: prettifyNumber,
|
|
1469
|
-
parseIntOrThrow
|
|
1470
|
-
},
|
|
1471
|
-
Promises: {
|
|
1472
|
-
raceFulfilled,
|
|
1473
|
-
invert: invertPromise,
|
|
1474
|
-
runInParallelBatches
|
|
1475
|
-
},
|
|
1476
|
-
Dates: {
|
|
1477
|
-
getAgo,
|
|
1478
|
-
isoDate,
|
|
1479
|
-
debounce,
|
|
1480
|
-
timeSince,
|
|
1481
|
-
dateTimeSlug,
|
|
1482
|
-
unixTimestamp,
|
|
1483
|
-
fromUtcString,
|
|
1484
|
-
getProgress,
|
|
1485
|
-
humanizeTime,
|
|
1486
|
-
humanizeProgress,
|
|
1487
|
-
createTimeDigits,
|
|
1488
|
-
mapDayNumber,
|
|
1489
|
-
getDayInfoFromDate,
|
|
1490
|
-
getDayInfoFromDateTimeString
|
|
1491
|
-
},
|
|
1492
|
-
Objects: {
|
|
1493
|
-
safeParse,
|
|
1494
|
-
deleteDeep,
|
|
1495
|
-
getDeep,
|
|
1496
|
-
getDeepOrElse,
|
|
1497
|
-
setDeep,
|
|
1498
|
-
ensureDeep,
|
|
1499
|
-
replaceDeep,
|
|
1500
|
-
getFirstDeep,
|
|
1501
|
-
mergeDeep,
|
|
1502
|
-
mapWithGetters,
|
|
1503
|
-
mapAllWithGetters,
|
|
1504
|
-
mapAllAsync,
|
|
1505
|
-
cloneWithJson,
|
|
1506
|
-
sortObject,
|
|
1507
|
-
sortArray,
|
|
1508
|
-
sortAny,
|
|
1509
|
-
deepEquals,
|
|
1510
|
-
runOn,
|
|
1511
|
-
ifPresent,
|
|
1512
|
-
zip,
|
|
1513
|
-
removeEmptyArrays,
|
|
1514
|
-
removeEmptyValues,
|
|
1515
|
-
flatten,
|
|
1516
|
-
unflatten,
|
|
1517
|
-
match,
|
|
1518
|
-
sort: sortObjectValues,
|
|
1519
|
-
map: mapObject,
|
|
1520
|
-
rethrow,
|
|
1521
|
-
setSomeOnObject,
|
|
1522
|
-
flip,
|
|
1523
|
-
crossJoin,
|
|
1524
|
-
countTruthyValues,
|
|
1525
|
-
transformToArray,
|
|
1526
|
-
setMulti,
|
|
1527
|
-
incrementMulti,
|
|
1528
|
-
createFastIndex,
|
|
1529
|
-
pushToFastIndex,
|
|
1530
|
-
pushToFastIndexWithExpiracy,
|
|
1531
|
-
getFromFastIndexWithExpiracy
|
|
1532
|
-
},
|
|
1533
|
-
Pagination: {
|
|
1534
|
-
asPageNumber,
|
|
1535
|
-
pageify
|
|
1536
|
-
},
|
|
1537
|
-
Files: {
|
|
1538
|
-
existsAsync,
|
|
1539
|
-
writeJsonAsync,
|
|
1540
|
-
readdirDeepAsync,
|
|
1541
|
-
readUtf8FileAsync,
|
|
1542
|
-
readJsonAsync,
|
|
1543
|
-
readLinesAsync,
|
|
1544
|
-
readMatchingLines,
|
|
1545
|
-
readNonEmptyLines,
|
|
1546
|
-
readCsv,
|
|
1547
|
-
walkTreeAsync,
|
|
1548
|
-
getFileSize,
|
|
1549
|
-
asMegabytes,
|
|
1550
|
-
getDirectorySize,
|
|
1551
|
-
convertBytes,
|
|
1552
|
-
getChecksum: getChecksumOfFile,
|
|
1553
|
-
mkdirp
|
|
1554
|
-
},
|
|
1555
|
-
Types: {
|
|
1556
|
-
isFunction,
|
|
1557
|
-
isObject,
|
|
1558
|
-
isStrictlyObject,
|
|
1559
|
-
isUndefined,
|
|
1560
|
-
isString,
|
|
1561
|
-
isNumber,
|
|
1562
|
-
isDate,
|
|
1563
|
-
isBlank,
|
|
1564
|
-
asString,
|
|
1565
|
-
asNumber,
|
|
1566
|
-
asDate,
|
|
1567
|
-
asNullableString
|
|
1568
|
-
},
|
|
1569
|
-
Strings: {
|
|
1570
|
-
tokenizeByCount,
|
|
1571
|
-
tokenizeByLength,
|
|
1572
|
-
randomAlphanumeric: randomAlphanumericString,
|
|
1573
|
-
randomHex: randomHexString,
|
|
1574
|
-
includesAny,
|
|
1575
|
-
slugify,
|
|
1576
|
-
enumify,
|
|
1577
|
-
escapeHtml,
|
|
1578
|
-
decodeHtmlEntities,
|
|
1579
|
-
after,
|
|
1580
|
-
afterLast,
|
|
1581
|
-
before,
|
|
1582
|
-
beforeLast,
|
|
1583
|
-
between,
|
|
1584
|
-
betweenWide,
|
|
1585
|
-
betweenNarrow,
|
|
1586
|
-
getPreLine,
|
|
1587
|
-
containsWord,
|
|
1588
|
-
containsWords,
|
|
1589
|
-
joinUrl,
|
|
1590
|
-
getFuzzyMatchScore,
|
|
1591
|
-
sortByFuzzyScore,
|
|
1592
|
-
getChecksum,
|
|
1593
|
-
splitOnce,
|
|
1594
|
-
randomize,
|
|
1595
|
-
shrinkTrim,
|
|
1596
|
-
capitalize,
|
|
1597
|
-
csvEscape,
|
|
1598
|
-
parseCsv,
|
|
1599
|
-
surroundInOut,
|
|
1600
|
-
getExtension,
|
|
1601
|
-
getBasename,
|
|
1602
|
-
normalizeFilename,
|
|
1603
|
-
parseFilename,
|
|
1604
|
-
camelToTitle,
|
|
1605
|
-
slugToTitle,
|
|
1606
|
-
joinHumanly
|
|
1607
|
-
},
|
|
1608
|
-
Assertions: {
|
|
1609
|
-
asEqual,
|
|
1610
|
-
asTrue,
|
|
1611
|
-
asTruthy,
|
|
1612
|
-
asFalse,
|
|
1613
|
-
asFalsy,
|
|
1614
|
-
asEither
|
|
1615
|
-
},
|
|
1616
|
-
Cache: {
|
|
1617
|
-
get: getCached
|
|
1618
|
-
},
|
|
1619
|
-
Logger: {
|
|
1620
|
-
create: createLogger,
|
|
1621
|
-
enableFileLogging
|
|
1553
|
+
function makeAsyncQueue(concurrency = 1) {
|
|
1554
|
+
const queue = []
|
|
1555
|
+
let running = 0
|
|
1556
|
+
async function runOneTask() {
|
|
1557
|
+
if (queue.length > 0 && running < concurrency) {
|
|
1558
|
+
running++
|
|
1559
|
+
const task = queue.shift()
|
|
1560
|
+
try {
|
|
1561
|
+
task && (await task())
|
|
1562
|
+
} finally {
|
|
1563
|
+
running--
|
|
1564
|
+
runOneTask()
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
return {
|
|
1569
|
+
enqueue(fn) {
|
|
1570
|
+
queue.push(fn)
|
|
1571
|
+
runOneTask()
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
class Maybe {
|
|
1577
|
+
constructor(value) {
|
|
1578
|
+
this.value = value
|
|
1622
1579
|
}
|
|
1580
|
+
bind(fn) {
|
|
1581
|
+
if (this.value === null || this.value === undefined) {
|
|
1582
|
+
return new Maybe(null)
|
|
1583
|
+
}
|
|
1584
|
+
if (isPromise(this.value)) {
|
|
1585
|
+
return new Maybe(this.value.then(x => (x !== null && x !== undefined ? fn(x) : null)).catch(() => null))
|
|
1586
|
+
}
|
|
1587
|
+
try {
|
|
1588
|
+
const result = fn(this.value)
|
|
1589
|
+
return new Maybe(result)
|
|
1590
|
+
} catch (error) {
|
|
1591
|
+
return new Maybe(null)
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
async valueOf() {
|
|
1595
|
+
try {
|
|
1596
|
+
return await this.value
|
|
1597
|
+
} catch {
|
|
1598
|
+
return null
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
exports.Maybe = Maybe
|
|
1604
|
+
|
|
1605
|
+
exports.Random = {
|
|
1606
|
+
inclusiveInt: randomIntInclusive,
|
|
1607
|
+
between: randomBetween,
|
|
1608
|
+
chance,
|
|
1609
|
+
signed: signedRandom
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
exports.Arrays = {
|
|
1613
|
+
countUnique,
|
|
1614
|
+
makeUnique,
|
|
1615
|
+
splitBySize,
|
|
1616
|
+
splitByCount,
|
|
1617
|
+
index: indexArray,
|
|
1618
|
+
onlyOrThrow,
|
|
1619
|
+
onlyOrNull,
|
|
1620
|
+
firstOrNull,
|
|
1621
|
+
shuffle,
|
|
1622
|
+
takeRandomly,
|
|
1623
|
+
initialize: initializeArray,
|
|
1624
|
+
glue,
|
|
1625
|
+
pluck,
|
|
1626
|
+
pick,
|
|
1627
|
+
last,
|
|
1628
|
+
pickWeighted,
|
|
1629
|
+
sortWeighted,
|
|
1630
|
+
pushAll,
|
|
1631
|
+
unshiftAll,
|
|
1632
|
+
filterAndRemove,
|
|
1633
|
+
merge: mergeArrays,
|
|
1634
|
+
empty,
|
|
1635
|
+
pushToBucket,
|
|
1636
|
+
unshiftAndLimit,
|
|
1637
|
+
atRolling
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
exports.System = {
|
|
1641
|
+
sleepMillis,
|
|
1642
|
+
forever,
|
|
1643
|
+
scheduleMany,
|
|
1644
|
+
waitFor,
|
|
1645
|
+
execAsync,
|
|
1646
|
+
getHeapMegabytes,
|
|
1647
|
+
expandError,
|
|
1648
|
+
runProcess
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
exports.Numbers = {
|
|
1652
|
+
sum,
|
|
1653
|
+
average,
|
|
1654
|
+
clamp,
|
|
1655
|
+
range,
|
|
1656
|
+
interpolate,
|
|
1657
|
+
createSequence,
|
|
1658
|
+
increment,
|
|
1659
|
+
decrement,
|
|
1660
|
+
format: formatNumber,
|
|
1661
|
+
parseIntOrThrow
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1664
|
+
exports.Promises = {
|
|
1665
|
+
raceFulfilled,
|
|
1666
|
+
invert: invertPromise,
|
|
1667
|
+
runInParallelBatches,
|
|
1668
|
+
makeAsyncQueue
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
exports.Dates = {
|
|
1672
|
+
getAgo,
|
|
1673
|
+
isoDate,
|
|
1674
|
+
debounce,
|
|
1675
|
+
timeSince,
|
|
1676
|
+
dateTimeSlug,
|
|
1677
|
+
unixTimestamp,
|
|
1678
|
+
fromUtcString,
|
|
1679
|
+
getProgress,
|
|
1680
|
+
humanizeTime,
|
|
1681
|
+
humanizeProgress,
|
|
1682
|
+
createTimeDigits,
|
|
1683
|
+
mapDayNumber,
|
|
1684
|
+
getDayInfoFromDate,
|
|
1685
|
+
getDayInfoFromDateTimeString
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
exports.Objects = {
|
|
1689
|
+
safeParse,
|
|
1690
|
+
deleteDeep,
|
|
1691
|
+
getDeep,
|
|
1692
|
+
getDeepOrElse,
|
|
1693
|
+
setDeep,
|
|
1694
|
+
ensureDeep,
|
|
1695
|
+
replaceDeep,
|
|
1696
|
+
getFirstDeep,
|
|
1697
|
+
mergeDeep,
|
|
1698
|
+
mapAllAsync,
|
|
1699
|
+
cloneWithJson,
|
|
1700
|
+
sortObject,
|
|
1701
|
+
sortArray,
|
|
1702
|
+
sortAny,
|
|
1703
|
+
deepEquals,
|
|
1704
|
+
runOn,
|
|
1705
|
+
ifPresent,
|
|
1706
|
+
zip,
|
|
1707
|
+
zipSum,
|
|
1708
|
+
removeEmptyArrays,
|
|
1709
|
+
removeEmptyValues,
|
|
1710
|
+
flatten,
|
|
1711
|
+
unflatten,
|
|
1712
|
+
match,
|
|
1713
|
+
sort: sortObjectValues,
|
|
1714
|
+
map: mapObject,
|
|
1715
|
+
rethrow,
|
|
1716
|
+
setSomeOnObject,
|
|
1717
|
+
flip,
|
|
1718
|
+
getAllPermutations,
|
|
1719
|
+
countTruthyValues,
|
|
1720
|
+
transformToArray,
|
|
1721
|
+
setMulti,
|
|
1722
|
+
incrementMulti,
|
|
1723
|
+
createFastIndex,
|
|
1724
|
+
pushToFastIndex,
|
|
1725
|
+
pushToFastIndexWithExpiracy,
|
|
1726
|
+
getFromFastIndexWithExpiracy
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
exports.Pagination = {
|
|
1730
|
+
asPageNumber,
|
|
1731
|
+
pageify
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
exports.Files = {
|
|
1735
|
+
existsAsync,
|
|
1736
|
+
writeJsonAsync,
|
|
1737
|
+
readdirDeepAsync,
|
|
1738
|
+
readUtf8FileAsync,
|
|
1739
|
+
readJsonAsync,
|
|
1740
|
+
readLinesAsync,
|
|
1741
|
+
readMatchingLines,
|
|
1742
|
+
readNonEmptyLines,
|
|
1743
|
+
readCsv,
|
|
1744
|
+
walkTreeAsync,
|
|
1745
|
+
getFileSize,
|
|
1746
|
+
asMegabytes,
|
|
1747
|
+
getDirectorySize,
|
|
1748
|
+
convertBytes,
|
|
1749
|
+
getChecksum: getChecksumOfFile,
|
|
1750
|
+
mkdirp
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
exports.Types = {
|
|
1754
|
+
isFunction,
|
|
1755
|
+
isObject,
|
|
1756
|
+
isStrictlyObject,
|
|
1757
|
+
isEmptyArray,
|
|
1758
|
+
isUndefined,
|
|
1759
|
+
isString,
|
|
1760
|
+
isNumber,
|
|
1761
|
+
isDate,
|
|
1762
|
+
isBlank,
|
|
1763
|
+
asString,
|
|
1764
|
+
asNumber,
|
|
1765
|
+
asDate,
|
|
1766
|
+
asNullableString
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
exports.Strings = {
|
|
1770
|
+
tokenizeByCount,
|
|
1771
|
+
tokenizeByLength,
|
|
1772
|
+
randomHex: randomHexString,
|
|
1773
|
+
randomLetter: randomLetterString,
|
|
1774
|
+
randomAlphanumeric: randomAlphanumericString,
|
|
1775
|
+
randomRichAscii: randomRichAsciiString,
|
|
1776
|
+
randomUnicode: randomUnicodeString,
|
|
1777
|
+
includesAny,
|
|
1778
|
+
slugify,
|
|
1779
|
+
enumify,
|
|
1780
|
+
escapeHtml,
|
|
1781
|
+
decodeHtmlEntities,
|
|
1782
|
+
after,
|
|
1783
|
+
afterLast,
|
|
1784
|
+
before,
|
|
1785
|
+
beforeLast,
|
|
1786
|
+
between,
|
|
1787
|
+
betweenWide,
|
|
1788
|
+
betweenNarrow,
|
|
1789
|
+
getPreLine,
|
|
1790
|
+
containsWord,
|
|
1791
|
+
containsWords,
|
|
1792
|
+
joinUrl,
|
|
1793
|
+
getFuzzyMatchScore,
|
|
1794
|
+
sortByFuzzyScore,
|
|
1795
|
+
getChecksum,
|
|
1796
|
+
splitOnce,
|
|
1797
|
+
randomize,
|
|
1798
|
+
shrinkTrim,
|
|
1799
|
+
capitalize,
|
|
1800
|
+
decapitalize,
|
|
1801
|
+
csvEscape,
|
|
1802
|
+
parseCsv,
|
|
1803
|
+
surroundInOut,
|
|
1804
|
+
getExtension,
|
|
1805
|
+
getBasename,
|
|
1806
|
+
normalizeFilename,
|
|
1807
|
+
parseFilename,
|
|
1808
|
+
camelToTitle,
|
|
1809
|
+
slugToTitle,
|
|
1810
|
+
slugToCamel,
|
|
1811
|
+
joinHumanly
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
exports.Assertions = {
|
|
1815
|
+
asEqual,
|
|
1816
|
+
asTrue,
|
|
1817
|
+
asTruthy,
|
|
1818
|
+
asFalse,
|
|
1819
|
+
asFalsy,
|
|
1820
|
+
asEither
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
exports.Cache = {
|
|
1824
|
+
get: getCached
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
exports.Logger = {
|
|
1828
|
+
create: createLogger,
|
|
1829
|
+
enableFileLogging
|
|
1623
1830
|
}
|