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.
Files changed (4) hide show
  1. package/index.d.ts +480 -0
  2. package/index.js +706 -499
  3. package/module.mjs +1 -0
  4. package/package.json +3 -2
package/index.js CHANGED
@@ -1,21 +1,17 @@
1
- function nodeModuleRequire(module) {
2
- if (require) {
3
- try {
4
- return require(module)
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
- const ChildProcess = nodeModuleRequire('child_process')
10
- const Crypto = nodeModuleRequire('crypto')
11
- const Fs = nodeModuleRequire('fs')
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
- const invertPromise = promise => new Promise((resolve, reject) => promise.then(reject, resolve))
10
+ async function raceFulfilled(promises) {
11
+ invertPromise(Promise.all(promises.map(invertPromise)))
12
+ }
17
13
 
18
- const runInParallelBatches = async (promises, concurrency = 1) => {
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
- const sleepMillis = millis =>
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
- const shuffle = array => {
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
- const onlyOrThrow = array => {
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
- const onlyOrNull = array => {
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
- const firstOrNull = array => (array.length > 0 ? array[0] : null)
61
+ function firstOrNull(array) {
62
+ return array.length > 0 ? array[0] : null
63
+ }
65
64
 
66
- const initializeArray = (count, initializer) => {
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
- const takeRandomly = (array, count) => shuffle(array).slice(0, count)
73
+ function takeRandomly(array, count) {
74
+ return shuffle(array).slice(0, count)
75
+ }
75
76
 
76
- const pluck = (array, key) => array.map(element => element[key])
77
+ function pluck(array, key) {
78
+ return array.map(element => element[key])
79
+ }
77
80
 
78
- const randomIntInclusive = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min
81
+ function randomIntInclusive(min, max) {
82
+ return Math.floor(Math.random() * (max - min + 1)) + min
83
+ }
79
84
 
80
- const randomBetween = (min, max) => Math.random() * (max - min) + min
85
+ function randomBetween(min, max) {
86
+ return Math.random() * (max - min) + min
87
+ }
81
88
 
82
- const signedRandom = () => Math.random() * 2 - 1
89
+ function signedRandom() {
90
+ return Math.random() * 2 - 1
91
+ }
83
92
 
84
- const chance = threshold => Math.random() < threshold
93
+ function chance(threshold) {
94
+ return Math.random() < threshold
95
+ }
85
96
 
86
- const pick = array => array[Math.floor(array.length * Math.random())]
97
+ function pick(array) {
98
+ return array[Math.floor(array.length * Math.random())]
99
+ }
87
100
 
88
- const last = array => array[array.length - 1]
101
+ function last(array) {
102
+ return array[array.length - 1]
103
+ }
89
104
 
90
- const pickWeighted = (array, weights, randomNumber) => {
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
- const sortWeighted = (array, weights) => {
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
- const getDeep = (object, path) => {
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
- const getDeepOrElse = (object, path, fallback) => getDeep(object, path) || fallback
141
+ function getDeepOrElse(object, path, fallback) {
142
+ return getDeep(object, path) || fallback
143
+ }
126
144
 
127
- const setDeep = (object, path, value) => {
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
- const ensureDeep = (object, path, value) => getDeep(object, path) || setDeep(object, path, value)
169
+ function ensureDeep(object, path, value) {
170
+ return getDeep(object, path) || setDeep(object, path, value)
171
+ }
152
172
 
153
- const deleteDeep = (object, path) => {
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
- const replaceDeep = (object, path, value) => {
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
- const getFirstDeep = (object, paths, fallbackToAnyKey) => {
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
- const forever = async (callable, millis) => {
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
- const readUtf8FileAsync = async path => Fs.promises.readFile(path, 'utf8')
216
+ async function readUtf8FileAsync(path) {
217
+ return Fs.promises.readFile(path, 'utf8')
218
+ }
197
219
 
198
- const readJsonAsync = async path => JSON.parse(await readUtf8FileAsync(path))
220
+ async function readJsonAsync(path) {
221
+ return JSON.parse(await readUtf8FileAsync(path))
222
+ }
199
223
 
200
- const writeJsonAsync = async (path, object, prettify) => {
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
- const readLinesAsync = async path => (await readUtf8FileAsync(path)).split(/\r?\n/)
232
+ async function readLinesAsync(path) {
233
+ return (await readUtf8FileAsync(path)).split(/\r?\n/)
234
+ }
209
235
 
210
- const readMatchingLines = async (path, filterFn) => (await readLinesAsync(path)).filter(filterFn)
236
+ async function readMatchingLines(path, filterFn) {
237
+ return (await readLinesAsync(path)).filter(filterFn)
238
+ }
211
239
 
212
- const readNonEmptyLines = async path => readMatchingLines(path, x => x)
240
+ async function readNonEmptyLines(path) {
241
+ return readMatchingLines(path, x => !!x)
242
+ }
213
243
 
214
- const readCsv = async (path, skip = 0, delimiter = ',', quote = '"') =>
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
- const removeLeadingDirectory = (path, directory) => {
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
- const readdirDeepAsync = async (path, cwd) => {
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
- const existsAsync = async path => {
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
- const getFileSize = async path => {
284
+ async function getFileSize(path) {
254
285
  const stats = await Fs.promises.stat(path)
255
286
  return stats.size
256
287
  }
257
288
 
258
- const asMegabytes = number => number / 1024 / 1024
289
+ function asMegabytes(number) {
290
+ return number / 1024 / 1024
291
+ }
259
292
 
260
- const getDirectorySize = async path => {
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
- const convertBytes = bytes => {
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
- const getChecksum = data => {
279
- const hash = Crypto.createHash('sha1')
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
- const getChecksumOfFile = async path =>
285
- new Promise((resolve, reject) => {
286
- const hash = Crypto.createHash('sha1')
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
- const isObject = value => value !== null && typeof value === 'object'
327
+ function isObject(value) {
328
+ return value !== null && typeof value === 'object'
329
+ }
294
330
 
295
- const isStrictlyObject = value => isObject(value) && !Array.isArray(value)
331
+ function isStrictlyObject(value) {
332
+ return isObject(value) && !Array.isArray(value)
333
+ }
296
334
 
297
- const isUndefined = value => typeof value === 'undefined'
335
+ function isEmptyArray(value) {
336
+ return Array.isArray(value) && value.length === 0
337
+ }
298
338
 
299
- const isFunction = value => Object.prototype.toString.call(value) === '[object Function]'
339
+ function isUndefined(value) {
340
+ return typeof value === 'undefined'
341
+ }
300
342
 
301
- const isString = value => Object.prototype.toString.call(value) === '[object String]'
343
+ function isFunction(value) {
344
+ return Object.prototype.toString.call(value) === '[object Function]'
345
+ }
302
346
 
303
- const isNumber = value => !isNaN(value) && String(value) === String(parseFloat(value))
347
+ function isString(value) {
348
+ return Object.prototype.toString.call(value) === '[object String]'
349
+ }
304
350
 
305
- const isDate = value => Object.prototype.toString.call(value) === '[object Date]'
351
+ function isPromise(value) {
352
+ return value && typeof value.then === 'function'
353
+ }
306
354
 
307
- const isBlank = value => !isString(value) || value.trim().length === 0
355
+ function isNumber(value) {
356
+ return !isNaN(value) && String(value) === String(parseFloat(value))
357
+ }
308
358
 
309
- const alphanumericAlphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'
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
- const randomAlphanumericString = length => {
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
- const randomHexString = length => {
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
- * @param {*} date
353
- * @returns {Date}
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
- throw new TypeError('Expected date, got: ' + date)
430
+ return date
360
431
  }
361
432
 
362
- const asNullableString = string => {
433
+ function asNullableString(string) {
363
434
  if (isBlank(string)) {
364
435
  return null
365
436
  }
366
437
  return string
367
438
  }
368
439
 
369
- const represent = value => {
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
- const log = (level, module, pieces) => {
387
- const timestamp = new Date().toISOString().replace('T', ' ').substr(0, 19)
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
- const createLogger = module => {
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
- const enableFileLogging = path => {
490
+ function enableFileLogging(path) {
420
491
  loggerGlobalState.fileStream = Fs.createWriteStream(path, { flags: 'a' })
421
492
  }
422
493
 
423
- const expandError = (error, stackTrace) => {
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
- const mergeDeep = (target, source) => {
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
- const zip = (first, second, reducer) => {
453
- const result = { ...first }
454
- for (const entry of Object.entries(second)) {
455
- if (result[entry[0]]) {
456
- result[entry[0]] = reducer(result[entry[0]], entry[1])
457
- } else {
458
- result[entry[0]] = entry[1]
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
- * @param {*} value
466
- * @returns {number}
467
- */
468
- const asPageNumber = value => {
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
- const pushToBucket = (object, bucket, item) => {
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
- const unshiftAndLimit = (array, item, limit) => {
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
- const atRolling = (array, index) => {
502
- const realIndex = index % array.length
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
- const pushAll = (array, elements) => Array.prototype.push.apply(array, elements)
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
- const mapWithGetters = (object, keys) => {
519
- const mapped = {}
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
- const mapAllWithGetters = (array, keys) => array.map(object => mapWithGetters(object, keys))
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
- const glue = (array, glueElement) => {
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
- const pageify = (data, totalElements, pageSize, currentPage) => {
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
- const asEqual = (a, b) => {
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
- const asTrue = data => {
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
- const asTruthy = data => {
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
- const asFalse = data => {
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
- const asFalsy = data => {
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
- const asEither = (data, values) => {
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
- const scheduleMany = (handlers, dates) => {
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
- const interpolate = (a, b, t) => a + (b - a) * t
676
+ function interpolate(a, b, t) {
677
+ return a + (b - a) * t
678
+ }
618
679
 
619
- const sum = array => array.reduce((a, b) => a + b, 0)
680
+ function sum(array) {
681
+ return array.reduce((a, b) => a + b, 0)
682
+ }
620
683
 
621
- const average = array => array.reduce((a, b) => a + b, 0) / array.length
684
+ function average(array) {
685
+ return array.reduce((a, b) => a + b, 0) / array.length
686
+ }
622
687
 
623
- const range = (start, end) => {
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
- const includesAny = (string, substrings) => {
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
- const slugify = string =>
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
- const camelToTitle = string => capitalize(string.replace(/([A-Z])/g, ' $1'))
717
+ function camelToTitle(string) {
718
+ return capitalize(string.replace(/([A-Z])/g, ' $1'))
719
+ }
652
720
 
653
- const slugToTitle = string => string.split('-').map(capitalize).join(' ')
721
+ function slugToTitle(string) {
722
+ return string.split('-').map(capitalize).join(' ')
723
+ }
654
724
 
655
- const joinHumanly = (parts, separator = ', ', lastSeparator = ' and ') => {
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
- const surroundInOut = (string, filler) => filler + string.split('').join(filler) + filler
742
+ function surroundInOut(string, filler) {
743
+ return filler + string.split('').join(filler) + filler
744
+ }
669
745
 
670
- const enumify = string => slugify(string).replace(/-/g, '_').toUpperCase()
746
+ function enumify(string) {
747
+ return slugify(string).replace(/-/g, '_').toUpperCase()
748
+ }
671
749
 
672
- const getFuzzyMatchScore = (string, input) => {
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
- const sortByFuzzyScore = (strings, input) =>
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
- const escapeHtml = string => string.replace(/\</g, '&lt;').replace('/>/g', '&gt;')
775
+ function escapeHtml(string) {
776
+ return string.replace(/\</g, '&lt;').replace('/>/g', '&gt;')
777
+ }
694
778
 
695
779
  const htmlEntityMap = {
696
780
  '&quot;': '"',
@@ -698,80 +782,104 @@ const htmlEntityMap = {
698
782
  '&lt': '<'
699
783
  }
700
784
 
701
- const decodeHtmlEntities = string => {
702
- let buffer = string.replace(/&#(\d+);/g, (match, dec) => String.fromCharCode(dec))
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
- const before = (string, searchString) => {
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
- const after = (string, searchString) => {
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
- const beforeLast = (string, searchString) => {
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
- const afterLast = (string, searchString) => {
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
- const between = (string, start, end) => before(after(string, start), end)
813
+ function between(string, start, end) {
814
+ return before(after(string, start), end)
815
+ }
730
816
 
731
- const betweenWide = (string, start, end) => beforeLast(after(string, start), end)
817
+ function betweenWide(string, start, end) {
818
+ return after(beforeLast(string, end), start)
819
+ }
732
820
 
733
- const betweenNarrow = (string, start, end) => before(afterLast(string, start), end)
821
+ function betweenNarrow(string, start, end) {
822
+ return before(after(string, start), end)
823
+ }
734
824
 
735
- const splitOnce = (string, separator) =>
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
- const getExtension = string => {
739
- const name = last(string.split(/\\|\//g))
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
- const getBasename = string => {
745
- const name = last(string.split(/\\|\//g))
746
- const index = name.indexOf('.', 1)
747
- return index <= 0 ? string : name.slice(0, index)
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
- const normalizeFilename = string => `${getBasename(string)}.${getExtension(string)}`
841
+ function normalizeFilename(path) {
842
+ const basename = getBasename(path)
843
+ const extension = getExtension(path)
844
+ return extension ? `${basename}.${extension}` : basename
845
+ }
751
846
 
752
- const parseFilename = string => {
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
- const randomize = string => string.replace(/\{(.+?)\}/g, (_, group) => pick(group.split('|')))
857
+ function randomize(string) {
858
+ return string.replace(/\{(.+?)\}/g, (_, group) => pick(group.split('|')))
859
+ }
763
860
 
764
- const shrinkTrim = string => string.replace(/\s+/g, ' ').replace(/\s$|^\s/g, '')
861
+ function shrinkTrim(string) {
862
+ return string.replace(/\s+/g, ' ').replace(/\s$|^\s/g, '')
863
+ }
765
864
 
766
- const capitalize = string => string.charAt(0).toUpperCase() + string.slice(1)
865
+ function capitalize(string) {
866
+ return string.charAt(0).toUpperCase() + string.slice(1)
867
+ }
767
868
 
768
- const csvEscape = string => (string.match(/"|,/) ? `"${string.replace(/"/g, '""')}"` : string)
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
- const parseCsv = (string, delimiter = ',', quote = '"') => {
877
+ function parseCsv(string, delimiter = ',', quote = '"') {
771
878
  const items = []
772
879
  let buffer = ''
773
880
  let escaped = false
774
- for (const character of string) {
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
- const humanizeProgress = state =>
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
- const waitFor = async (predicate, waitLength, maxWaits) => {
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
- const mkdirp = async path => {
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
- const filterAndRemove = (array, predicate) => {
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
- const execAsync = async (command, resolveWithErrors, inherit, options) =>
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
- const runProcess = async (command, args, onStdout, onStderr) =>
865
- new Promise((resolve, reject) => {
866
- const subprocess = ChildProcess.spawn(command, args)
867
- subprocess.stdout.on('data', onStdout)
868
- subprocess.stderr.on('data', onStderr)
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
- const cloneWithJson = a => JSON.parse(JSON.stringify(a))
978
+ function cloneWithJson(a) {
979
+ return JSON.parse(JSON.stringify(a))
980
+ }
879
981
 
880
- const unixTimestamp = optionalTimestamp => Math.ceil((optionalTimestamp || Date.now()) / 1000)
982
+ function unixTimestamp(optionalTimestamp) {
983
+ return Math.ceil((optionalTimestamp || Date.now()) / 1000)
984
+ }
881
985
 
882
- const isoDate = optionalDate => (optionalDate || new Date()).toISOString().slice(0, 10)
986
+ function isoDate(optionalDate) {
987
+ return (optionalDate || new Date()).toISOString().slice(0, 10)
988
+ }
883
989
 
884
- const dateTimeSlug = optionalDate => (optionalDate || new Date()).toISOString().slice(0, 19).replace(/T|:/g, '-')
990
+ function dateTimeSlug(optionalDate) {
991
+ return (optionalDate || new Date()).toISOString().slice(0, 19).replace(/T|:/g, '-')
992
+ }
885
993
 
886
- const fromUtcString = string => {
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
- const createTimeDigits = value => String(Math.floor(value)).padStart(2, '0')
999
+ function createTimeDigits(value) {
1000
+ return String(Math.floor(value)).padStart(2, '0')
1001
+ }
892
1002
 
893
- const humanizeTime = millis => {
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
- const getAgo = date => {
909
- const now = Date.now()
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
- const debounce = (longWrapper, millis) => {
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
- const timeSince = (unit, a, optionalB) => {
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
- const getProgress = (startedAt, current, total) => {
1063
+ function getProgress(startedAt, current, total, now) {
1064
+ if (!now) {
1065
+ now = Date.now()
1066
+ }
952
1067
  const progress = current / total
953
- const deltaMs = Date.now() - startedAt
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
- const mapDayNumber = zeroBasedIndex => ({
977
- zeroBasedIndex,
978
- day: dayNumberIndex[zeroBasedIndex]
979
- })
1091
+ function mapDayNumber(zeroBasedIndex) {
1092
+ return {
1093
+ zeroBasedIndex,
1094
+ day: dayNumberIndex[zeroBasedIndex]
1095
+ }
1096
+ }
980
1097
 
981
- const getDayInfoFromDate = date => mapDayNumber(date.getDay())
1098
+ function getDayInfoFromDate(date) {
1099
+ return mapDayNumber(date.getDay())
1100
+ }
982
1101
 
983
- const getDayInfoFromDateTimeString = dateTimeString => getDayInfoFromDate(new Date(dateTimeString))
1102
+ function getDayInfoFromDateTimeString(dateTimeString) {
1103
+ return getDayInfoFromDate(new Date(dateTimeString))
1104
+ }
984
1105
 
985
- const getPreLine = string => string.replace(/ +/g, ' ').replace(/^ /gm, '')
1106
+ function getPreLine(string) {
1107
+ return string.replace(/ +/g, ' ').replace(/^ /gm, '')
1108
+ }
986
1109
 
987
- const containsWord = (string, word) => {
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
- const containsWords = (string, words) => {
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
- const joinUrl = (...parts) => {
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
- const sortObject = object => {
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
- const sortArray = array => {
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
- const sortAny = any => {
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
- const deepEquals = (a, b) => JSON.stringify(sortAny(a)) === JSON.stringify(sortAny(b))
1182
+ function deepEquals(a, b) {
1183
+ return JSON.stringify(sortAny(a)) === JSON.stringify(sortAny(b))
1184
+ }
1061
1185
 
1062
- const safeParse = stringable => {
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 prettifyNumber = (number, precision = 1, long = false) => {
1092
- const table = long ? longNumberUnits : shortNumberUnits
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)}${long ? ' ' : ''}${table[i]}`
1224
+ return `${(number / thresholds[i]).toFixed(precision)}${longFormat ? ' ' : ''}${table[i]}${unitString}`
1099
1225
  }
1100
1226
  }
1101
- return `${(number / thresholds[thresholds.length - 1]).toFixed(precision)}${long ? ' ' : ''}${
1227
+ return `${(number / thresholds[thresholds.length - 1]).toFixed(precision)}${longFormat ? ' ' : ''}${
1102
1228
  table[thresholds.length - 1]
1103
- }`
1229
+ }${unitString}`
1104
1230
  }
1105
1231
 
1106
- const parseIntOrThrow = numberOrString => {
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
- const clamp = (value, lower, upper) => (value < lower ? lower : value > upper ? upper : value)
1252
+ function clamp(value, lower, upper) {
1253
+ return value < lower ? lower : value > upper ? upper : value
1254
+ }
1127
1255
 
1128
- const increment = (value, change, maximum) => {
1256
+ function increment(value, change, maximum) {
1129
1257
  const result = value + change
1130
1258
  return result > maximum ? maximum : result
1131
1259
  }
1132
1260
 
1133
- const decrement = (value, change, minimum) => {
1261
+ function decrement(value, change, minimum) {
1134
1262
  const result = value - change
1135
1263
  return result < minimum ? minimum : result
1136
1264
  }
1137
1265
 
1138
- const getHeapMegabytes = () => {
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
- const runOn = (object, callable) => {
1275
+ function runOn(object, callable) {
1148
1276
  callable(object)
1149
1277
  return object
1150
1278
  }
1151
1279
 
1152
- const ifPresent = (object, callable) => {
1280
+ function ifPresent(object, callable) {
1153
1281
  if (object) {
1154
1282
  callable(object)
1155
1283
  }
1156
1284
  }
1157
1285
 
1158
- const mergeArrays = (target, source) => {
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
- const empty = array => {
1295
+ function empty(array) {
1168
1296
  array.splice(0, array.length)
1169
1297
  return array
1170
1298
  }
1171
1299
 
1172
- const removeEmptyArrays = object => {
1300
+ function removeEmptyArrays(object) {
1173
1301
  for (const key of Object.keys(object)) {
1174
- if (Array.isArray(object[key]) && object[key].length === 0) {
1302
+ if (isEmptyArray(object[key])) {
1175
1303
  delete object[key]
1176
1304
  }
1177
1305
  }
1306
+ return object
1178
1307
  }
1179
1308
 
1180
- const removeEmptyValues = object => {
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
- const mapObject = (object, mapper) => {
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
- const rethrow = async (asyncFn, throwable) => {
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
- const setSomeOnObject = (object, key, value) => {
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
- const flip = object => {
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
- const crossJoin = object => {
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
- const countTruthyValues = object => {
1372
+ function countTruthyValues(object) {
1243
1373
  return Object.values(object).filter(x => x).length
1244
1374
  }
1245
1375
 
1246
- const getFlatNotation = (prefix, key, bracket) =>
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
- const flattenInner = (target, object, prefix, bracket, arrays) => {
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
- const flatten = (object, arrays, prefix) => {
1403
+ function flatten(object, arrays = false, prefix) {
1273
1404
  return flattenInner({}, object, prefix || '', false, arrays)
1274
1405
  }
1275
1406
 
1276
- const unflatten = object => {
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
- const match = (value, options, fallback) => (options[value] ? options[value] : fallback)
1426
+ function match(value, options, fallback) {
1427
+ return options[value] ? options[value] : fallback
1428
+ }
1296
1429
 
1297
- const indexArray = (array, keyFn, useArrays) => {
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
- const splitBySize = (array, size) => {
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
- const splitByCount = (array, count) => {
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
- const tokenizeByLength = (string, length) => {
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
- const tokenizeByCount = (string, count) => {
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
- const makeUnique = (array, fn) => Object.values(indexArray(array, fn, false))
1477
+ function makeUnique(array, fn) {
1478
+ return Object.values(indexArray(array, fn, false))
1479
+ }
1345
1480
 
1346
- const countUnique = (array, mapper, plain, sort, reverse) => {
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
- const sortObjectValues = (object, compareFn) => Object.fromEntries(Object.entries(object).sort(compareFn))
1494
+ function sortObjectValues(object, compareFn) {
1495
+ return Object.fromEntries(Object.entries(object).sort(compareFn))
1496
+ }
1360
1497
 
1361
- const transformToArray = objectOfArrays => {
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
- const incrementMulti = (objects, key, step = 1) => {
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
- const setMulti = (objects, key, value) => {
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
- const createFastIndex = () => ({ index: {}, keys: [] })
1524
+ function createFastIndex() {
1525
+ return { index: {}, keys: [] }
1526
+ }
1388
1527
 
1389
- const pushToFastIndex = (object, key, item, limit = 100) => {
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
- const pushToFastIndexWithExpiracy = (object, key, item, expiration, limit = 100) => {
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
- const getFromFastIndexWithExpiracy = (object, key) => {
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
- module.exports = {
1415
- Random: {
1416
- inclusiveInt: randomIntInclusive,
1417
- between: randomBetween,
1418
- chance,
1419
- signed: signedRandom
1420
- },
1421
- Arrays: {
1422
- countUnique,
1423
- makeUnique,
1424
- splitBySize,
1425
- splitByCount,
1426
- index: indexArray,
1427
- onlyOrThrow,
1428
- onlyOrNull,
1429
- firstOrNull,
1430
- shuffle,
1431
- takeRandomly,
1432
- initialize: initializeArray,
1433
- mapWithIndex,
1434
- glue,
1435
- pluck,
1436
- pick,
1437
- last,
1438
- pickWeighted,
1439
- sortWeighted,
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
  }