@naturalcycles/js-lib 14.75.0 → 14.78.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.
@@ -1,6 +1,12 @@
1
- import { _since, _stringifyAny, CommonLogger } from '..'
1
+ import { _since, _stringifyAny, AnyFunction, CommonLogger } from '..'
2
2
 
3
3
  export interface PRetryOptions {
4
+ /**
5
+ * If set - will be included in the error message.
6
+ * Can be used to identify the place in the code that failed.
7
+ */
8
+ name?: string
9
+
4
10
  /**
5
11
  * How many attempts to try.
6
12
  * First attempt is not a retry, but "initial try". It still counts.
@@ -75,13 +81,14 @@ export interface PRetryOptions {
75
81
  * Implements "Exponential back-off strategy" by multiplying the delay by `delayMultiplier` with each try.
76
82
  */
77
83
  // eslint-disable-next-line @typescript-eslint/ban-types
78
- export function pRetry<T extends Function>(fn: T, opt: PRetryOptions = {}): T {
84
+ export function pRetry<T extends AnyFunction>(fn: T, opt: PRetryOptions = {}): T {
79
85
  const {
80
86
  maxAttempts = 4,
81
87
  delay: initialDelay = 1000,
82
88
  delayMultiplier = 2,
83
89
  predicate,
84
90
  logger = console,
91
+ name = fn.name,
85
92
  } = opt
86
93
 
87
94
  let { logFirstAttempt = false, logRetries = true, logFailures = false, logSuccess = false } = opt
@@ -93,7 +100,7 @@ export function pRetry<T extends Function>(fn: T, opt: PRetryOptions = {}): T {
93
100
  logSuccess = logFirstAttempt = logRetries = logFailures = false
94
101
  }
95
102
 
96
- const fname = ['pRetry', fn.name].filter(Boolean).join('.')
103
+ const fname = ['pRetry', name].filter(Boolean).join('.')
97
104
 
98
105
  return async function (this: any, ...args: any[]) {
99
106
  let delay = initialDelay
@@ -118,9 +125,10 @@ export function pRetry<T extends Function>(fn: T, opt: PRetryOptions = {}): T {
118
125
  } catch (err) {
119
126
  if (logFailures) {
120
127
  logger.warn(
121
- `${fname} attempt #${attempt} error in ${_since(started)}:\n${_stringifyAny(err, {
128
+ `${fname} attempt #${attempt} error in ${_since(started)}:`,
129
+ _stringifyAny(err, {
122
130
  includeErrorData: true,
123
- })}`,
131
+ }),
124
132
  )
125
133
  }
126
134
 
@@ -1,5 +1,8 @@
1
+ import { AppError } from '../error/app.error'
1
2
  import { AnyFunction } from '../types'
2
3
 
4
+ export class TimeoutError extends AppError {}
5
+
3
6
  export interface PTimeoutOptions {
4
7
  /**
5
8
  * Timeout in milliseconds.
@@ -24,37 +27,46 @@ export interface PTimeoutOptions {
24
27
  * Throws an Error if the Function is not resolved in a certain time.
25
28
  * If the Function rejects - passes this rejection further.
26
29
  */
27
- export function pTimeout<T extends AnyFunction>(fn: T, opt: PTimeoutOptions): T {
28
- // const fname = fn.name || 'function'
30
+ export function pTimeoutFn<T extends AnyFunction>(fn: T, opt: PTimeoutOptions): T {
31
+ opt.name ||= fn.name
32
+
33
+ return async function pTimeoutInternalFn(this: any, ...args: any[]) {
34
+ return await pTimeout(fn.apply(this, args), opt)
35
+ } as any
36
+ }
37
+
38
+ /**
39
+ * Decorates a Function with a timeout and immediately calls it.
40
+ * Throws an Error if the Function is not resolved in a certain time.
41
+ * If the Function rejects - passes this rejection further.
42
+ */
43
+ export async function pTimeout<T>(promise: Promise<T>, opt: PTimeoutOptions): Promise<T> {
44
+ // todo: check how we can automatically infer function name (only applicable to named functions)
29
45
  const { timeout, name, onTimeout } = opt
30
46
 
31
- return async function (this: any, ...args: any[]) {
32
- // eslint-disable-next-line no-async-promise-executor
33
- return await new Promise(async (resolve, reject) => {
34
- // Prepare the timeout timer
35
- const timer = setTimeout(() => {
36
- if (onTimeout) {
37
- try {
38
- resolve(onTimeout())
39
- } catch (err) {
40
- reject(err)
41
- }
42
- return
47
+ // eslint-disable-next-line no-async-promise-executor
48
+ return await new Promise(async (resolve, reject) => {
49
+ // Prepare the timeout timer
50
+ const timer = setTimeout(() => {
51
+ if (onTimeout) {
52
+ try {
53
+ resolve(onTimeout())
54
+ } catch (err) {
55
+ reject(err)
43
56
  }
44
-
45
- reject(
46
- new Error(`"${name || fn.name || 'pTimeout function'}" timed out after ${timeout} ms`),
47
- )
48
- }, timeout)
49
-
50
- // Execute the Function
51
- try {
52
- resolve(await fn.apply(this, args))
53
- } catch (err) {
54
- reject(err)
55
- } finally {
56
- clearTimeout(timer)
57
+ return
57
58
  }
58
- })
59
- } as any
59
+
60
+ reject(new TimeoutError(`"${name || 'pTimeout function'}" timed out after ${timeout} ms`))
61
+ }, timeout)
62
+
63
+ // Execute the Function
64
+ try {
65
+ resolve(await promise)
66
+ } catch (err) {
67
+ reject(err)
68
+ } finally {
69
+ clearTimeout(timer)
70
+ }
71
+ })
60
72
  }
package/src/seq/seq.ts CHANGED
@@ -140,6 +140,16 @@ export class Sequence<T> implements Iterable<T> {
140
140
  a.push(v)
141
141
  }
142
142
  }
143
+
144
+ forEach(fn: (v: T, i: number) => void): void {
145
+ let i = -1
146
+ // eslint-disable-next-line no-constant-condition
147
+ while (true) {
148
+ const v = this.next()
149
+ if (v === END) return
150
+ fn(v, ++i)
151
+ }
152
+ }
143
153
  }
144
154
 
145
155
  /**
@@ -76,16 +76,20 @@ export function _stringifyAny(obj: any, opt: StringifyAnyOptions = {}): string {
76
76
  // Error or ErrorObject
77
77
  //
78
78
 
79
+ // Omit "default" error name as non-informative
80
+ // UPD: no, it's still important to understand that we're dealing with Error and not just some string
81
+ // if (obj?.name === 'Error') {
82
+ // s = obj.message
83
+ // }
84
+ s = [obj.name, obj.message].join(': ')
85
+
79
86
  if (opt.includeErrorStack && obj.stack) {
80
- // Stack includes message
81
- s = obj.stack
82
- } else {
83
- // Omit "default" error name as non-informative
84
- // UPD: no, it's still important to understand that we're dealing with Error and not just some string
85
- // if (obj?.name === 'Error') {
86
- // s = obj.message
87
- // }
88
- s = [obj.name, obj.message].join(': ')
87
+ // Here we're using the previously-generated "title line" (e.g "Error: some_message"),
88
+ // concatenating it with the Stack (but without the title line of the Stack)
89
+ // This is to fix the rare error (happened with Got) where `err.message` was changed,
90
+ // but err.stack had "old" err.message
91
+ // This should "fix" that
92
+ s = [s, ...obj.stack.split('\n').slice(1)].join('\n')
89
93
  }
90
94
 
91
95
  if (_isErrorObject(obj)) {
package/src/types.ts CHANGED
@@ -122,9 +122,18 @@ export type ValueOf<T> = T[keyof T]
122
122
 
123
123
  export type KeyValueTuple<K, V> = [key: K, value: V]
124
124
 
125
- export type ObjectMapper<OBJ, OUT> = (key: string, value: OBJ[keyof OBJ], obj: OBJ) => OUT
126
-
127
- export type ObjectPredicate<OBJ> = (key: keyof OBJ, value: OBJ[keyof OBJ], obj: OBJ) => boolean
125
+ // Exclude<something, undefined> is used here to support StringMap<OBJ> (because values of StringMap add `undefined`)
126
+ export type ObjectMapper<OBJ, OUT> = (
127
+ key: string,
128
+ value: Exclude<OBJ[keyof OBJ], undefined>,
129
+ obj: OBJ,
130
+ ) => OUT
131
+
132
+ export type ObjectPredicate<OBJ> = (
133
+ key: keyof OBJ,
134
+ value: Exclude<OBJ[keyof OBJ], undefined>,
135
+ obj: OBJ,
136
+ ) => boolean
128
137
 
129
138
  /**
130
139
  * Allows to identify instance of Class by `instanceId`.
@@ -1,9 +1,9 @@
1
1
  export function _gb(b: number): number {
2
- return Math.round(b / (1024 * 1024 * 1024))
2
+ return Math.round(b / 1024 ** 3)
3
3
  }
4
4
 
5
5
  export function _mb(b: number): number {
6
- return Math.round(b / (1024 * 1024))
6
+ return Math.round(b / 1024 ** 2)
7
7
  }
8
8
 
9
9
  export function _kb(b: number): number {
@@ -14,8 +14,24 @@ export function _kb(b: number): number {
14
14
  * Byte size to Human byte size string
15
15
  */
16
16
  export function _hb(b = 0): string {
17
- if (b < 800) return `${Math.round(b)} byte(s)`
18
- if (b < 800 * 1024) return `${Math.round(b / 1024)} Kb`
19
- if (b < 800 * 1024 * 1024) return `${Math.round(b / 1024 / 1024)} Mb`
20
- return `${Math.round(b / 1024 / 1024 / 1024)} Gb`
17
+ if (b < 1024) return `${Math.round(b)} byte`
18
+ if (b < 1024 ** 2) return `${(b / 1024).toPrecision(3)} Kb`
19
+ if (b < 1024 ** 3) return `${(b / 1024 ** 2).toPrecision(3)} Mb`
20
+ if (b < 1024 ** 4) return `${(b / 1024 ** 3).toPrecision(3)} Gb`
21
+ if (b < 1024 ** 5) return `${(b / 1024 ** 4).toPrecision(3)} Tb`
22
+ return `${Math.round(b / 1024 ** 4)} Tb`
23
+ }
24
+
25
+ /**
26
+ * hc stands for "human count", similar to "human bytes" `_hb` function.
27
+ * Helpful to print big numbers, as it adds `K` (kilo), `M` (mega), etc to make
28
+ * them more readable.
29
+ */
30
+ export function _hc(c = 0): string {
31
+ if (c < 10 ** 4) return String(c)
32
+ if (c < 10 ** 6) return (c / 10 ** 3).toPrecision(3) + ' K'
33
+ if (c < 10 ** 9) return (c / 10 ** 6).toPrecision(3) + ' M' // million
34
+ if (c < 10 ** 12) return (c / 10 ** 9).toPrecision(3) + ' B' // billion
35
+ if (c < 10 ** 15) return (c / 10 ** 12).toPrecision(3) + ' T' // trillion
36
+ return Math.round(c / 10 ** 12) + ' T'
21
37
  }