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