@naturalcycles/js-lib 14.134.0 → 14.136.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 (54) hide show
  1. package/dist/decorators/logMethod.decorator.js +2 -2
  2. package/dist/env.d.ts +14 -0
  3. package/dist/env.js +23 -0
  4. package/dist/error/error.util.d.ts +1 -1
  5. package/dist/error/error.util.js +2 -0
  6. package/dist/error/tryCatch.js +1 -3
  7. package/dist/http/fetcher.d.ts +2 -0
  8. package/dist/http/fetcher.js +104 -92
  9. package/dist/http/fetcher.model.d.ts +14 -3
  10. package/dist/index.d.ts +2 -0
  11. package/dist/index.js +2 -0
  12. package/dist/promise/abortable.d.ts +20 -0
  13. package/dist/promise/abortable.js +36 -0
  14. package/dist/promise/pDefer.d.ts +14 -1
  15. package/dist/promise/pDefer.js +2 -0
  16. package/dist/promise/pDelay.d.ts +18 -0
  17. package/dist/promise/pDelay.js +37 -2
  18. package/dist/promise/pRetry.d.ts +0 -8
  19. package/dist/promise/pRetry.js +37 -63
  20. package/dist/promise/pTimeout.d.ts +4 -6
  21. package/dist/promise/pTimeout.js +8 -10
  22. package/dist/string/stringifyAny.d.ts +0 -6
  23. package/dist/string/stringifyAny.js +0 -5
  24. package/dist/types.d.ts +3 -0
  25. package/dist/vendor/is.d.ts +2 -2
  26. package/dist-esm/decorators/logMethod.decorator.js +2 -2
  27. package/dist-esm/env.js +18 -0
  28. package/dist-esm/error/error.util.js +2 -0
  29. package/dist-esm/error/tryCatch.js +2 -4
  30. package/dist-esm/http/fetcher.js +111 -98
  31. package/dist-esm/index.js +2 -0
  32. package/dist-esm/promise/abortable.js +32 -0
  33. package/dist-esm/promise/pDefer.js +2 -0
  34. package/dist-esm/promise/pDelay.js +35 -1
  35. package/dist-esm/promise/pRetry.js +38 -61
  36. package/dist-esm/promise/pTimeout.js +8 -7
  37. package/dist-esm/string/stringifyAny.js +0 -5
  38. package/package.json +1 -1
  39. package/src/decorators/logMethod.decorator.ts +2 -2
  40. package/src/env.ts +19 -0
  41. package/src/error/error.util.ts +3 -1
  42. package/src/error/tryCatch.ts +2 -6
  43. package/src/http/fetcher.model.ts +14 -3
  44. package/src/http/fetcher.ts +117 -95
  45. package/src/index.ts +2 -0
  46. package/src/promise/abortable.ts +34 -0
  47. package/src/promise/pDefer.ts +19 -1
  48. package/src/promise/pDelay.ts +44 -2
  49. package/src/promise/pRetry.ts +41 -89
  50. package/src/promise/pState.ts +1 -1
  51. package/src/promise/pTimeout.ts +12 -14
  52. package/src/string/stringifyAny.ts +0 -13
  53. package/src/types.ts +3 -0
  54. package/src/vendor/is.ts +3 -3
@@ -3,7 +3,22 @@
3
3
  */
4
4
  export interface DeferredPromise<T = void> extends Promise<T> {
5
5
  resolve: (a?: T) => void
6
- reject: (e?: Error) => void
6
+ reject: (err?: Error) => void
7
+
8
+ /**
9
+ * Can be overridden.
10
+ * Otherwise will reject with "Aborted" or "Aborted: $reason" on abort().
11
+ *
12
+ * @experimental
13
+ */
14
+ abort: (reason?: string) => void
15
+
16
+ /**
17
+ * Rejects the promise with `new Error('Aborted: $reason')`.
18
+ *
19
+ * @experimental
20
+ */
21
+ rejectAborted: (reason?: string) => void
7
22
  }
8
23
 
9
24
  /* eslint-disable @typescript-eslint/promise-function-async */
@@ -22,6 +37,9 @@ export function pDefer<T = void>(): DeferredPromise<T> {
22
37
 
23
38
  promise.resolve = resolve
24
39
  promise.reject = reject
40
+ promise.rejectAborted = reason =>
41
+ reject(new Error(['Aborted', reason].filter(Boolean).join(': ')))
42
+ promise.abort = reason => promise.rejectAborted(reason)
25
43
 
26
44
  return promise
27
45
  }
@@ -1,3 +1,45 @@
1
- export async function pDelay<T>(ms: number = 0, value?: T): Promise<T> {
2
- return await new Promise<T>(resolve => setTimeout(() => resolve(value as T), ms))
1
+ import type { PromisableFunction } from '../types'
2
+ import { DeferredPromise, pDefer } from './pDefer'
3
+
4
+ /**
5
+ * Promisified version of setTimeout.
6
+ *
7
+ * Can return a value.
8
+ * If value is instanceof Error - rejects the Promise instead of resolving.
9
+ */
10
+ export async function pDelay<T>(ms = 0, value?: T): Promise<T> {
11
+ return await new Promise<T>((resolve, reject) =>
12
+ setTimeout(value instanceof Error ? reject : resolve, ms, value),
13
+ )
14
+ }
15
+
16
+ /* eslint-disable @typescript-eslint/promise-function-async */
17
+
18
+ /**
19
+ * Promisified version of setTimeout.
20
+ *
21
+ * Wraps the passed function with try/catch,
22
+ * catch will propagate to pDelayFn rejection,
23
+ * otherwise pDelayFn will resolve with returned value.
24
+ *
25
+ * On abort() - clears the Timeout and immediately resolves the Promise with void.
26
+ */
27
+ export function pDelayFn<T>(ms = 0, fn: PromisableFunction<T>): DeferredPromise<T> {
28
+ const p = pDefer<T>()
29
+
30
+ const timer = setTimeout(async () => {
31
+ try {
32
+ p.resolve(await fn())
33
+ } catch (err) {
34
+ p.reject(err as Error)
35
+ }
36
+ }, ms)
37
+
38
+ p.abort = () => {
39
+ clearTimeout(timer)
40
+ // p.rejectAborted(reason) // nope
41
+ p.resolve()
42
+ }
43
+
44
+ return p
3
45
  }
@@ -1,6 +1,5 @@
1
- import type { AnyFunction, AppError, CommonLogger, ErrorData } from '..'
2
- import { _since, _stringifyAny } from '..'
3
- import { TimeoutError } from './pTimeout'
1
+ import type { AnyFunction, CommonLogger, ErrorData } from '..'
2
+ import { _errorDataAppend, _since, pDelay, pTimeout } from '..'
4
3
 
5
4
  export interface PRetryOptions {
6
5
  /**
@@ -84,15 +83,6 @@ export interface PRetryOptions {
84
83
  */
85
84
  logger?: CommonLogger
86
85
 
87
- /**
88
- * Defaults to true.
89
- * If true - preserves the stack trace in case of a Timeout (usually - very useful!).
90
- * It has a certain perf cost.
91
- *
92
- * @experimental
93
- */
94
- keepStackTrace?: boolean
95
-
96
86
  /**
97
87
  * Will be merged with `err.data` object.
98
88
  */
@@ -120,12 +110,10 @@ export async function pRetry<T>(
120
110
  predicate,
121
111
  logger = console,
122
112
  name,
123
- keepStackTrace = true,
124
113
  timeout,
125
114
  } = opt
126
115
 
127
- const fakeError = keepStackTrace ? new Error('RetryError') : undefined
128
-
116
+ const fakeError = timeout ? new Error('TimeoutError') : undefined
129
117
  let { logFirstAttempt = false, logRetries = true, logFailures = false, logSuccess = false } = opt
130
118
 
131
119
  if (opt.logAll) {
@@ -139,86 +127,50 @@ export async function pRetry<T>(
139
127
 
140
128
  let delay = initialDelay
141
129
  let attempt = 0
142
- let timer: NodeJS.Timeout | undefined
143
- let timedOut = false
144
-
145
- return await new Promise((resolve, reject) => {
146
- const rejectWithTimeout = () => {
147
- timedOut = true // to prevent more tries
148
- const err = new TimeoutError(`"${fname}" timed out after ${timeout} ms`, opt.errorData)
149
- if (fakeError) {
150
- // keep original stack
151
- err.stack = fakeError.stack!.replace('Error: RetryError', 'TimeoutError')
130
+
131
+ /* eslint-disable no-await-in-loop, no-constant-condition */
132
+ while (true) {
133
+ const started = Date.now()
134
+
135
+ try {
136
+ attempt++
137
+ if ((attempt === 1 && logFirstAttempt) || (attempt > 1 && logRetries)) {
138
+ logger.log(`${fname} attempt #${attempt}...`)
152
139
  }
153
- reject(err)
154
- }
155
140
 
156
- const next = async () => {
157
- if (timedOut) return
141
+ let result: any
158
142
 
159
143
  if (timeout) {
160
- timer = setTimeout(rejectWithTimeout, timeout)
144
+ await pTimeout(async () => await fn(attempt), {
145
+ timeout,
146
+ name: fname,
147
+ errorData: opt.errorData,
148
+ fakeError,
149
+ })
150
+ } else {
151
+ result = await fn(attempt)
161
152
  }
162
153
 
163
- const started = Date.now()
164
-
165
- try {
166
- attempt++
167
- if ((attempt === 1 && logFirstAttempt) || (attempt > 1 && logRetries)) {
168
- logger.log(`${fname} attempt #${attempt}...`)
169
- }
170
-
171
- const r = await fn(attempt)
172
-
173
- clearTimeout(timer)
174
-
175
- if (logSuccess) {
176
- logger.log(`${fname} attempt #${attempt} succeeded in ${_since(started)}`)
177
- }
178
-
179
- resolve(r)
180
- } catch (err) {
181
- clearTimeout(timer)
182
-
183
- if (logFailures) {
184
- logger.warn(
185
- `${fname} attempt #${attempt} error in ${_since(started)}:`,
186
- _stringifyAny(err, {
187
- includeErrorData: true,
188
- }),
189
- )
190
- }
191
-
192
- if (
193
- attempt >= maxAttempts ||
194
- (predicate && !predicate(err as Error, attempt, maxAttempts))
195
- ) {
196
- // Give up
197
-
198
- if (fakeError) {
199
- // Preserve the original call stack
200
- Object.defineProperty(err, 'stack', {
201
- value:
202
- (err as Error).stack +
203
- '\n --' +
204
- fakeError.stack!.replace('Error: RetryError', ''),
205
- })
206
- }
207
-
208
- ;(err as AppError).data = {
209
- ...(err as AppError).data,
210
- ...opt.errorData,
211
- }
212
-
213
- reject(err)
214
- } else {
215
- // Retry after delay
216
- delay *= delayMultiplier
217
- setTimeout(next, delay)
218
- }
154
+ if (logSuccess) {
155
+ logger.log(`${fname} attempt #${attempt} succeeded in ${_since(started)}`)
156
+ }
157
+
158
+ return result
159
+ } catch (err) {
160
+ if (logFailures) {
161
+ logger.warn(`${fname} attempt #${attempt} error in ${_since(started)}:`, err)
219
162
  }
220
- }
221
163
 
222
- void next()
223
- })
164
+ if (attempt >= maxAttempts || (predicate && !predicate(err as Error, attempt, maxAttempts))) {
165
+ // Give up
166
+ _errorDataAppend(err, opt.errorData)
167
+ throw err
168
+ }
169
+
170
+ // Retry after delay
171
+ delay *= delayMultiplier
172
+ await pDelay(delay)
173
+ // back to while(true) loop
174
+ }
175
+ }
224
176
  }
@@ -13,6 +13,6 @@ export async function pState(p: Promise<any>): Promise<'resolved' | 'rejected' |
13
13
  v => {
14
14
  return v === UNIQUE_VALUE ? 'pending' : 'resolved'
15
15
  },
16
- () => 'rejected' as const,
16
+ () => 'rejected',
17
17
  )
18
18
  }
@@ -1,5 +1,6 @@
1
1
  import { AppError } from '../error/app.error'
2
2
  import type { ErrorData } from '../error/error.model'
3
+ import { _errorDataAppend } from '../error/error.util'
3
4
  import type { AnyAsyncFunction } from '../types'
4
5
 
5
6
  export class TimeoutError extends AppError {
@@ -30,13 +31,11 @@ export interface PTimeoutOptions {
30
31
  onTimeout?: (err: TimeoutError) => any
31
32
 
32
33
  /**
33
- * Defaults to true.
34
- * If true - preserves the stack trace in case of a Timeout (usually - very useful!).
35
- * It has a certain perf cost.
36
- *
37
- * @experimental
34
+ * If passed - fakeError.stack will be used as a stacktrace.
35
+ * This is to "keep stacktrace" when pTimeout is called from another
36
+ * function (like pRetry).
38
37
  */
39
- keepStackTrace?: boolean
38
+ fakeError?: Error
40
39
 
41
40
  /**
42
41
  * Will be merged with `err.data` object.
@@ -66,25 +65,24 @@ export function pTimeoutFn<T extends AnyAsyncFunction>(fn: T, opt: PTimeoutOptio
66
65
  * If the Function rejects - passes this rejection further.
67
66
  */
68
67
  export async function pTimeout<T>(fn: AnyAsyncFunction<T>, opt: PTimeoutOptions): Promise<T> {
69
- const { timeout, name = fn.name || 'pTimeout function', onTimeout, keepStackTrace = true } = opt
70
- const fakeError = keepStackTrace ? new Error('TimeoutError') : undefined
68
+ const { timeout, name = fn.name || 'pTimeout function', onTimeout } = opt
69
+ const fakeError = opt.fakeError || new Error('TimeoutError')
71
70
 
72
71
  // eslint-disable-next-line no-async-promise-executor
73
72
  return await new Promise(async (resolve, reject) => {
74
73
  // Prepare the timeout timer
75
74
  const timer = setTimeout(() => {
76
75
  const err = new TimeoutError(`"${name}" timed out after ${timeout} ms`, opt.errorData)
77
- if (fakeError) err.stack = fakeError.stack // keep original stack
76
+ // keep original stack
77
+ err.stack = fakeError.stack!.replace('Error: TimeoutError', 'TimeoutError: ' + err.message)
78
78
 
79
79
  if (onTimeout) {
80
80
  try {
81
81
  resolve(onTimeout(err))
82
82
  } catch (err: any) {
83
- if (fakeError) err.stack = fakeError.stack // keep original stack
84
- err.data = {
85
- ...err.data,
86
- ...opt.errorData,
87
- }
83
+ // keep original stack
84
+ err.stack = fakeError.stack!.replace('Error: TimeoutError', err.name + ': ' + err.message)
85
+ _errorDataAppend(err, opt.errorData)
88
86
  reject(err)
89
87
  }
90
88
  return
@@ -33,13 +33,6 @@ export interface StringifyAnyOptions {
33
33
  */
34
34
  maxLen?: number
35
35
 
36
- /**
37
- * Pass true to include "stringified" `error.data` in the output.
38
- *
39
- * @default false
40
- */
41
- includeErrorData?: boolean
42
-
43
36
  /**
44
37
  * Set to true to print Error.stack instead of just Error.message.
45
38
  *
@@ -130,12 +123,6 @@ export function _stringifyAny(obj: any, opt: StringifyAnyOptions = {}): string {
130
123
  // `replace` here works ONCE, exactly as we need it
131
124
  s = s.replace('HttpError', `HttpError(${obj.data.httpStatusCode})`)
132
125
  }
133
-
134
- // Here we ensure it has `data`
135
- const { data } = obj
136
- if (opt.includeErrorData && Object.keys(data).length > 0) {
137
- s = [s, _stringifyAny(data, opt)].join('\n')
138
- }
139
126
  } else if (typeof (obj as any).code === 'string') {
140
127
  // Error that has no `data`, but has `code` property
141
128
  s = [s, `code: ${(obj as any).code}`].join('\n')
package/src/types.ts CHANGED
@@ -80,6 +80,9 @@ export type UnsavedId<T extends Partial<ObjectWithId>> = Omit<T, 'id'> & {
80
80
  */
81
81
  export type AnyFunction<T = any> = (...args: any[]) => T
82
82
  export type AnyAsyncFunction<T = any> = (...args: any[]) => Promise<T>
83
+ export type AsyncFunction<T = any> = () => Promise<T>
84
+ export type AnyPromisableFunction<T = any> = (...args: any[]) => Promisable<T>
85
+ export type PromisableFunction<T = any> = () => Promisable<T>
83
86
 
84
87
  /**
85
88
  * Symbol to indicate END of Sequence.
package/src/vendor/is.ts CHANGED
@@ -25,7 +25,7 @@ const typedArrayTypeNames = [
25
25
  'BigUint64Array',
26
26
  ] as const
27
27
 
28
- type TypedArrayTypeName = typeof typedArrayTypeNames[number]
28
+ type TypedArrayTypeName = (typeof typedArrayTypeNames)[number]
29
29
 
30
30
  function isTypedArrayName(name: unknown): name is TypedArrayTypeName {
31
31
  return typedArrayTypeNames.includes(name as TypedArrayTypeName)
@@ -60,7 +60,7 @@ const objectTypeNames = [
60
60
  ...typedArrayTypeNames,
61
61
  ] as const
62
62
 
63
- type ObjectTypeName = typeof objectTypeNames[number]
63
+ type ObjectTypeName = (typeof objectTypeNames)[number]
64
64
 
65
65
  function isObjectTypeName(name: unknown): name is ObjectTypeName {
66
66
  return objectTypeNames.includes(name as ObjectTypeName)
@@ -76,7 +76,7 @@ const primitiveTypeNames = [
76
76
  'symbol',
77
77
  ] as const
78
78
 
79
- type PrimitiveTypeName = typeof primitiveTypeNames[number]
79
+ type PrimitiveTypeName = (typeof primitiveTypeNames)[number]
80
80
 
81
81
  function isPrimitiveTypeName(name: unknown): name is PrimitiveTypeName {
82
82
  return primitiveTypeNames.includes(name as PrimitiveTypeName)